华为云GaussDB(for Redis)揭秘第15期:为什么需要强一致

网友投稿 425 2022-05-29

有人说,开源Redis的最终一致性已经能满足大部分应用场景,也有人说,多副本的强一致代价太大,没有必要实现。要笔者说,其实弱一致性已经不满足很多应用场景的诉求。怎么,不信?请听笔者娓娓道来。

1. 不一致带来的困扰

1.1 秒杀变秒崩

分享一个电商秒杀活动中限流器的例子,在电商的秒杀活动中,为了扛住前端对数据库的超大流量冲击,一般使用两种方案来保护系统,一个是缓存,另一个则是限流。缓存这个容易实现,只需要在数据库前加一层缓存服务器,而对于限流来说,最简单的可以使用Redis的计数器来实现限流功能。

具体来说,假设我们需要对某个接口限定流量为5000qps,即每秒钟访问的次数不能超过5000。那么我们可以这么做:在一开始的时候设置一个计数器counter为5000,并且过期时间为1s,即1s后计数器失效。每当一个请求过来的时候,counter的值减1,判断当前counter的值是否等于0,如果等于,则说明请求次数过多,直接拒绝请求。如果counter计数器不存在,则重置计数器为5000,开始新一秒的接口限流,注意并发情况下计数器需要加锁。

正常情况下,这种方案不会出现问题,但是针对这种秒杀活动,不怕一万,就怕万一,万一Redis突然宕机怎么办,那岂不是限流器形同虚设,所有流量全部涌向后端的数据库,瞬间系统崩溃。此时聪明的你肯定会想到,给Redis搞一个备用服务器不就解决了,主服务器如果宕机,备用服务器顶上。没错,这种方案是对的,但是只正确了一半。为什么呢,如下图所示。

当给Redis配置从服务器之后,如果主服务器出现宕机,可以立刻切换到从服务器,但是由于开源Redis主从服务器之间的数据是异步复制的,如果网络不畅,经常发生主从数据不一致,如果此时主服务器发生宕机,切换到从服务器之后,因为限流器的判断出错,流量压力很容易超出阈值,一下子涌向数据库服务器,同样会造成系统崩溃。

仔细探究这个问题产生,根因是在于开源Redis的一致性机制为弱一致性,在某些时间内,主从副本数据不一致。而要彻底解决这个问题,只有真正的强一致才能解决。

1.2 难以维护的MySQL组件

其实不止Redis,就连大名鼎鼎的MySQL也逃不过弱一致的坑。MySQL的部署中,为了保证高可用性,主从热备份是MySQL常用的部署方式。但是如果发生故障时,仅仅靠MySQL自身的同步机制,是无法保证主库和从库之前的数据一致的,于是出现了重要的辅助组件MHA(Master High Availability),它的部署方式如下:

MHA由管理服务和Node服务组成,Node服务部署在每个MySQL节点上,MHA组件负责让MySQL的从库尽可能的追平主库,提供主从一致的状态。发生故障进行主从切换时,Manager首先为从库补充落后的数据,然后再将用户访问切换到从库,这个过程可能长达数十秒。

MHA的部署和维护都相当复杂,如未能顺利执行故障切换或发生数据丢失,运维面临的场面都将很棘手。其实运维同学何尝不希望手中的系统稳定运行呢?要是数据库自身能提供强一致保障,何苦再依赖复杂的辅助组件!

2. 什么是强一致

上一节中笔者介绍了弱一致带来各种问题,接下来这一节具体介绍下什么是强一致。在“分布式系统”和“数据库”这两个领域中,一致性都是重要概念,但它表达的内容却并不相同。对于分布式系统而言,一致性是在探讨当系统内的一份逻辑数据存在多个物理的数据副本时,对其执行读写操作会产生什么样的结果,这也符合 CAP 理论对一致性的表述。而在数据库领域,“一致性”与事务密切相关,又进一步细化到 ACID 四个方面。因此,当我们谈论分布式数据库的一致性时,实质上是在谈论事务一致性和数据一致性两个方面。

2.1 事务一致性

事务的一致性主要是指的事务的ACID,分别是原子性、一致性、隔离性和持久性,如下图所示:

原子性:事务中的所有变更要么全部发生,要么一个也不发生,通过日志技术实现;

一致性:事务要保持数据的完整性,它是应用程序的属性,依赖原子性和隔离属性来实现;

隔离性:多事务并行执行所得到的结果,与串行执行(一个接一个)完全相同,通过并发控制技术来实现;

持久性:一旦事务提交,它对数据的改变将被永久保留,不应受到任何系统故障的影响,通过日志技术实现。

2.2 数据一致性

在分布式系统中,为了避免网络不可靠带来的问题,通常会存储多个数据副本,逻辑上的一份数据存储在多个物理副本上,自然带来了数据一致性问题。

(1)状态视角

从状态的视角来看,任何变更操作后,数据只有两种状态,所有副本一致或者不一致。在某些条件下,不一致的状态是暂时,还会转换到一致的状态,而那些永远不一致的情况几乎不会去讨论,所以习惯上大家会把不一致称为“弱一致”。相对的,一致就叫做“强一致”了。以一个一主两备的MySQL集群为例,“强一致”的交互过程如下:

在该模式下,主库与备库同步 binlog 时,主库只有在收到两个备库的成功响应后,才能够向客户端反馈提交成功。显然,用户获得响应时,主库和备库的数据副本已经达成一致,所以后续的读操作肯定是没有问题的,这就是状态视角的”强一致”的模型。

但是状态视角的这种强一致副作用很大:第一个是性能很差,主库必须要等备库1和备库2成功返回后,主库才返回;第二个是可用性问题,如果主备节点很多,出现故障的概率非常高。因此状态视角的强一致代价非常大,因此很少使用。

(2)操作视角

状态视角的强一致降低了系统的可用性,因此很多系统选择状态视角的弱一致性模型,通过额外的算法(如Raft、Paxos)在不保证所有节点状态的一致的情况下,来保证操作视角的一致性,同时提高了系统的可用性。通过加入一些限定条件,衍生出了若干种一致性模型:

线性一致性:操作视角实现真正的强一致

顺序一致性:一致性强度弱于线性一致性

因果一致性:一致性强度弱于顺序一致性

写后读一致性:一致行强度相当,弱于因果一致性

这些一致性模型的介绍参考《高斯Redis与强一致》这篇文章。

3. 强一致的刚需场景

上一节我们介绍了什么是强一致,这一节中我们介绍介绍下我们强一致的典型应用场景。

在常见的互联网应用中,如果数据库服务器只部署在单个节点上,那么应用程序所有的读和写都只会访问单个节点,一份逻辑数据在物理上也只有一份,这种场景下就谈不上强一致的问题。

但是随着系统中业务访问量的增加,如果是单机部署数据库,就会导致I/O访问频率过高,数据库就会成为系统的瓶颈。此时,为了降低单机磁盘的I/O访问频率,提高单个机器的I/O性能,通常会增加多个数据存储节点,形成一主一从或者一主多重的架构,

此时,我们可以将负载分布在多个从节点上,一方面可以实现读写分离,写请求访问主库,读请求访问备库。另一方面,还可以在主库如果出现宕机的情况下进行主备切换,增强系统的稳定性。在以上两个场景中,由于一份逻辑数据在物理上有多个副本,那么如何保证多个副本之间的数据一致呢,这就是强一致需要解决的问题。

3.1 读写分离场景

以关系型数据库MySQL为例,典型的部署方案为一主两从三节点方案,主节点负责处理写操作,两个从节处理读操作,分担主库的压力,如下图所示:

此时,如果系统没有实现强一致,就有可能会遇到执行完写操作后,立刻去读发现读不到或者读到旧状态的尴尬场景,比如操作顺序为以下操作:

客户端首先通过代理向主节点 Master 进行了写入操作,此时由于没有实现强一致,写操作写完后立即返回;

紧接着第二步去从节点 Slave A 执行读操作,此时Master和Slave A之间的同步还未完成,系统处于非强一致的状态,所以第二步的读操作读取到了旧状态。

可以看出,在一主多备读写分离的场景下,如果想要保证写入和读取操作的准确无误,系统实现强一致是非常重要的。

3.2 主备切换场景

主备切换的场景也需要强一致来保证,以目前业内使用最广泛的内存数据库Redis为例,Redis的主从同步如下图所示:

从上图可以看出,当Redis客户端向Master服务器发送一条命令时,Master服务器立即回复客户端命令的执行结果,并不等待命令同步到从服务器再回复,也就是说Redis的主从同步其实是异步的。

由于Master节点存在宕机的可能,在这种情况下,如果在Master收到命令但是还没同步到Slave服务器时发生了宕机,Redis就会发生主备切换,然后此时Master服务器和Slave服务器的数据还没有同步,就导致了数据丢失的情况。可见,开源Redis弱一致性本身的缺陷和不足,而要解决这个问题,必须实现强一致性才能解决。

4. 高斯Redis强一致

由于开源的Redis不具备强一致的特性,导致开源Redis的应用也受到了诸多限制,为了解决开源Redis弱一致的问题,GaussDB(for Redis)应运而生。GaussDB(for Redis) 是华为云数据库团队自主研发的兼容Redis协议的云原生数据库,彻底解决了开源Redis一致性问题带来的痛点。

4.1 高斯Redis架构

相比于开源Redis,高斯Redis采用存算分离的设计思想,计算层负责计算和协议等的处理,聚焦服务。而存储层负责副本管理、扩缩容等处理,聚焦数据本身。高斯Redis的优势如下:

数据强一致:存储层使用分布式存储DFV,轻松实现了3副本强一致;

超可用:N个节点的集群最多可以挂掉N – 1个节点;

低成本:数据采用磁盘存储并且进行压缩,每GB的成本不到开源Redis的十分之一;

秒扩容:计算层仅需修改路由映射,无需数据搬迁,实现秒级扩容;

自动备份:高斯Redis可以实现MVCC快照备份和定期自动备份。

4.2 高斯Redis强一致的实现

开源Redis和高斯Redis的架构如下图所示:

开源Redis或者传统的主从结构如左图所示,如果在读写分离的场景或者主节点出现宕机发生主从切换的时候,都会导致数据不一致的情况。

高斯Redis采用存算分离的架构,如右图所示,在存储层DFV的副本管理中采用分布式共识算法实现了3副本的强一致。计算层调用存储层的接口时,如果返回OK,那么即表示存储层已经实现副本强一致的复制。

5. 结语

我们在做架构设计的时候,其实很多场景都隐藏着强一致的诉求。如朋友圈这类应用,如果没有实现强一致,朋友圈的评论很容易乱序。再比如限流器的场景,如果没有强一致的保证,也极容易造成数据库的崩溃。因此,必须在系统设计之初就认识到强一致的重要性,才能设计出更加稳定和可靠的系统。

6. 附录

更多技术文章,关注高斯Redis官方博客:https://bbs.huaweicloud.com/community/usersnew/id_1614151726110813

扩展阅读:

《华为云企业级Redis揭秘第七期:高斯Redis与强一致》

https://bbs.huaweicloud.com/blogs/256888

2.《华为云PB级数据库GaussDB(for Redis)揭秘第一期:Redis与存算分离》

https://bbs.huaweicloud.com/blogs/245622

3.《Strong consistency models》

https://aphyr.com/posts/313-strong-consistency-models

华为云GaussDB(for Redis)揭秘第15期:为什么需要强一致

4.《Redis数据一致性分析》

http://baobing.github.io/2017/12/23/Redis/Redis数据一致性分析

5.《如何解决Redis 主从数据不一致问题》

https://segmentfault.com/a/1190000013144617

6.《Don't Settle for Eventual Consistency》

https://queue.acm.org/detail.cfm?id=2610533

7.《微服务的灾难-最终一致》

https://xargin.com/disaster-of-microservice-evconst/

8.《ETCD应用场景》

https://tonydeng.github.io/2015/10/19/etcd-application-scenarios/

MySQL Redis 云数据库 GaussDB(for Redis) 数据库 缓存

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:万字直通面试:Flink双流JOIN
下一篇:进程间通信
相关文章