分布式技术浅见之复制
- 前言
- 一、何为复制?
- 1.1 主从复制和无主从复制
- 1.2 同步复制与异步复制
- 1.3 来点栗子
- 1.3.1 Redis
- 1.3.2 Mysql
- 1.3.3 Mongo
- 二、复制是如何实现的?
- 2.1 复制的过程
- 2.2 来点栗子
- 2.2.1 Redis
- 2.2.2 Mysql
- 2.2.3 Mongo
- 2.2 复制的实现方式
- 2.4 举个例子:
- 2.4.1 Redis
- 2.4.2 Mysql
- 2.4.3 Mongo
- 三、复制所带来的一些问题?
- 3.1 节点故障和切换
- 3.2 来点栗子
- 3.2.1 Redis
- 3.2.2 Mysql
- 3.2.3 Mongo
- 3.2 复制滞后问题
- 3.2.1 读自己的写(Read own writes)
- 3.2.5 读后写(Writes follow reads)
- 3.2.2 单调读(Monotonic reads)
- 3.2.4 单调写(Monotonic writes)
- 3.3 来点栗子
- 3.3.1 Redis
- 3.3.2 Mysql
- 3.3.3 Mongo
- 后记
- 参考
前言
最一段时间又较为详细的看了DDIA的几个重点章节,对分布式相关技术有了一些新的理解和感悟,所以想以博客的形式系统的梳理、总结下来,希望可以便人便己。
当前的互联网业务中大都用到了分布式系统。从上层的微服务业务,到下层的分布式数据库,应该这么说,稍微有点量的互联网业务都会使用分布式的相关技术来提升系统整体的性能和可用性。 尤其是分布式数据库,作为业务中几乎必不可少的一个组件,为了达到高的扩展性和可用性, 都会或多或少的采用一些分布式的数据库系统。学习理解这些技术,一是可以更好的使用这些组件,二是理解分布式的思维方式,以分布式的相关思路来解决高并发、高可用的问题。
分布式是一个大系统,涉及到包括复制、分片、一致性等诸多内容,本篇主要总结讨论分布式(主要是分布式数据库中)中复制里涉及到的一些内容,并简要介绍redis、mongo和mysql这几种具体的数据库系统是如何处理相应细节的。
废话不多说了,开整。
一、何为复制?
我们现实中的复制主要是为了保存多个相同一致的事物以达到共享或者冗余的目的。分布式中的复制也很类似,它主要是通过网络将相同的数据内容复制到多个节点(一般就是不同的机器上)上,我们把这些相同的数据叫做一个一个的副本(承载的机器叫做副本节点,下文也简称为副本)。这么做直观上有两个好处:
- 一是达到冗余的目的,当一个副本损坏了(包括承载的机器挂了,或者数据损坏了)其他的副本可以顶上来。
- 二是多个副本可以同时提供服务,理论上可以把性能提升N倍(当然理想丰满,现实骨感。各种各样的限制使之成为不可能)
还有一个不那么明显的引申好处是:可以把多个副本放在不同的区域,放在离用户近的区域,这样就会降低不同地域的访问延时。
ok,复制那就复制吧,拿个U盘把相同的数据拷贝一份行不?
如果数据已经完全不变了,这种方式或许可行。但实际情况是,由于在不断的提供服务,数据库中的数据总是在不断的变化,停机拷贝的方式虽然在某些备份系统中也略有耳闻,但在实际的分布式副本复制用处不大。
因此现在的分布式数据库大都是在接受客户端请求的同时,将数据的变化通过网络实时传到其他的副本中,以达到各个副本中数据一致。如下图所示。
这里又涉及到2个问题。
- 以哪个副本节点为主导,接收客户端的请求呢?还是所有的副本都可以接受客户端的请求呢?
- 何时给客户端返回结果呢?是所有的副本都达到一致了再给客户端返回,还是只要一个节点接收到请求就给客户端返回,后续再慢慢复制数据呢?
这涉及到两类复制的方式。
1.1 主从复制和无主从复制
第一个问题引申出了单主从复制、多主从复制和无主复制。
所谓单主从复制指的是由一个主节点对(对于多分区情况则可能是每个分区一个主节点)多个从节点。主节点接收客户端的写请求,从节点不断从主节点复制数据。写入是只能从主节点写入,但读取时可以从多个从节点读取,达到扩展读性能的要。如下图所示:
而多主复制指的是接受客户端请求的有多个主节点,然后这多个主节点将数据同步至其他的副本节点。下图所示:
最后一种放的更开,允许所有的副本节点接受客户端的请求,所有的节点都是主节点,接收到写入请求之后需要同步至其他所有的节点,这种方式也叫做去中心化复制。
后两种方式有一个最大的问题就是多个主节点如果需要处理同一个数据或者有关联的数据,需要处理数据冲突的问题。而第一种方式在实际中应用的最多,本文也主要讲的就是这种单主从方式,下文中主从复制也主要讲的就是单主从复制。
1.2 同步复制与异步复制
第二个问题引申出了同步复制和异步复制。
前者表示从节点向主节点返回复制成功时,主节点才向客户端返回成功,这保持了更高程度的一致性,但是可能会由于网络或者从节点异常等情况造成整体的写服务异常;
如下图所示。
异步复制就是主节点不管从节点的复制,直接向客户端返回。但是,可能会有一定程度上的数据不一致,如果主节点宕机,数据可能会造成一定程度的丢失。
显而易见,在客户端看来,同步复制的响应时间最长,但是数据更稳定,可靠性更高。而异步复制,客户端得到的响应时间最短,但如果后续的异步复制过程出了问题,数据会丢失或者各种奇奇怪怪的问题(下文会讨论)。
除了完全的同步复制和异步复制之外,还有一种介于他两之间的半同步复制,即数据写入到一部分节点之后将结果返回给客户端,算是一种折中的平衡。一般来说,写到大多数副本节点中就给客户端返回结果在实际中应用的比较多(和选主算法结合)。
1.3 来点栗子
1.3.1 Redis
无论是单纯的主从复制复制、哨兵模式还是集群模式,redis的复制模式使用的是单节点的主从复制。而且作为一个缓存系统,为了提高性能,在实际中使用到的redis大都是完全异步模式。毕竟redis本身的定位就是一个缓存,性能是其首要的关注点,可靠性和一致性的要求相对来说没那么高。
1.3.2 Mysql
对于mysql来说,其内建的复制很简单,仅仅算是提供了一个备库follow主库的功能,让备库可以从主库中同步数据。所以默认情况下它的主从模式是一种异步复制的系统,依靠从节点follow主节点的方式来完成,各个从节点之间没有啥联系(也不会知道其他从节点存在),如下图所示。下文对mysql的举例主要指的就是这种方式。
当然在MySQL 5.5 引入了一种半同步复制模式。该模式可以确保至少一个从库接收完主库发送的binlog日志文件并写入到自己的中继日志里(可以简单理解为至少有一个从节点接收到了数据),然后会给主库一个反馈,告诉主库已经接收完毕。然后主库才会将结果反馈给客户端。
现在各大厂商也有提供各种MySQL cluster 集群方式, 有些已经支持完全同步复制,当所有的从库都接收到了主库的复制日志,主库才会向客户端返回。
1.3.3 Mongo
mongo的副本复制方式算是一种用户可控的同步。它提供了一个参数writeConcern,让上层的使用者来决定在返回给客户端之前,要把数据同步到哪些机器上(0,1,majority等,甚至可以给机器打tag,让mongo把数据同步到具有某些tag的节点上)。
这种writeConcern其实是属于一种Quorum副本管理机制,通过控制读写副本的个数来改变整个系统的一致性和高可用性,算是一种可调一致性。mongo直接把这个设置交给了上层使用者,这便是让使用者来决定业务是需要一致性更高些,还是可用性更高些。
所以,如果非要说mongo的副本复制是属于哪种方式,我觉得算是一种可调整的半同步复制方式。
二、复制是如何实现的?
2.1 复制的过程
如果主节点和从节点是同时启用的,ok, 好,那么主节点接收到的数据会通过变更的方式不断传给从节点,从节点和主节点的数据保持相对同步。但如果从节点是“半路”加入的(比如说想要扩充副本数量),那么就必须先把加入之前数据先获取到。
总结起来大概是这样:
一般来说,当添加一个从节点时,主节点会向从节点传输数据。这主要分为两个过程,首先发送T0(从节点加入的时刻)时刻的一个快照; 当从节点将快照中的数据apply完成之后,再发送从T0时刻到当前时刻T1所有的数据更改日志(一般来说是命令序列)。如下图所示。
当然这个过程有些数据库系统是全自动的,有些则需要人工介入。
2.2 来点栗子
2.2.1 Redis
redis主从同步中的异步复制就是像上述那样玩的,它把这两个过程叫做同步(Sync)和命令传播(Command Propagate)。 当从节点slaveof 主节点之后,主节点发送一个RDB(可以理解为T0时刻的快照),然后从节点apply这些RDB文件,同步过程结束; 之后命令传播过程开始,主节点源源不断的向从节点传递之后的命令序列。
2.2.2 Mysql
对于mysql内建的主从复制来说,添加一个从节点并不向Redis和Mongo那样自动化。添加一个从节点, 默认情况下主节点只会将新的数据变更同步到从节点,而不会自动同步主节点已有的那些数据(也就是说没有初始化数据的过程)。需要人工通过快照或者备份的方式将数据导入到从节点中。
2.2.3 Mongo
mong的复制也分为这两个阶段。当新的节点加入的时候会启动Initial Sync,这个过程会扫描源节点(不一定是主节点)的所有collection(除了源节点的local database),然后插入到目标节点(注意这里用了插入,说明这种复制方式是属于逻辑上复制,即scan and insert);这里要说明的是,除了同步具体的数据之外,在这个过程还会把数据库的索引也同步到目标节点。
当第一阶段同步完成之后,节点会进入SECONDAY阶段。在这个阶段通过复制其他节点的oplog(操作日志)来同步最近的客户端操作。
这里额外说明两点,
第一,mongo的第一阶段除了可以使用逻辑上的复制之外,还可以使用File Copy Based的方式,这种方式是直接复制数据库文件,有点类似于快照的形式,效率更高,但是有一些限制。当然,这种高级的复制方式,按照官网的说法,需要企业版的。
第二点,与redis不同的是,在这两个阶段中,虽然大部分情况下源节点都是主节点。但是mongo提供了一定的配置选项,可以选择其他的副本节点作为源节点,当然选择这种副本节点要有一定的规则,总的原则就是数据更新、网络更好,具体的规则细节这里就不讨论了。
2.2 复制的实现方式
这里所说的实现方式指的是上述数据同步的过程中,传递的到底是什么?
从节点和主节点的数据保持一致,笼统一点说,大都是通过把日志发送给从节点,然后从节点来apply日志达到和主节点一致的状态。但是这种“日志”具体实现是通过什么方式呢?也就是说这个“日志”是怎样的?目前主要有以下几种方式:
- 基于磁盘同步的方式进行传输。将主节点执行命令之后,要写入磁盘的数据打成日志传输给从节点。这种日志携带了类似于传输“在磁盘的某个地址写入某些字 节序列”的相关信息。 显而易见,这种方式和底层的存储联系比较紧密,如果底层存储格式发生了变更,很有可能会有不兼容的情况发生。 当然,好处是这种方式执行的较快。
比如说DRBD这种块级同步复制技术,它是一个以 Linux模块方式实现的块级别同步复制技术。它通过网卡将主服务器上每个的块设备上复制到另一个服务器的块设备(备用设备),并在主设备提交块之前记录下来。
- 复制执行语句。这种情况简单来说,就是主节点执行的命令,让从节点按照顺序执行一遍。很好理解。 但是这里面有一个问题,对于某些非确定性的命令,比如说一些获取随机数,获取当前时间这类命令,从节点和主节点执行会得到不同的值。对于这个问题,有一种解决方式是让主节点把这种非确定的数据转换成执行后确定的数据 a在再发送给从节点。
- 基于逻辑日志进行传输。 这种方式将复制的日志和具体的存储引擎解耦。传输的大概是执行命令后的值(感觉和第一种方式中主节点对待非确定数据那样)。在关系数据库中,它的逻辑日志一般是描述数据表行级别的写请求。
- 基于应用层的复制。上述几种方式都是数据库来底层实现的。还有一种方式是应用层自己来决定复制哪些数据,复制到哪个地方。这种方式利用数据库提供的接口(触发器或者存储过程等)来注入应用层面的执行逻辑。 当然,还有一种更高层、更灵活的的方式,应用层接收到请求后自己对不同的数据库地址进行写入,比如说双写等。
2.4 举个例子:
2.4.1 Redis
对于redis来说,其复制是是通过同步和命令传播两个过程实现的。前者是传递一个RDB快照,类似于上述的第3种方式;而后者是通过传递命令,也就是复制执行语句来实现的。如上所述,在命令传播时redis也会遇到非确定命令的情况,redis 如何处理的呢?我从网上没查到具体的资料,但是chatGPT告诉我非确定函数的结果是在主节点确定之后,再传播给从节点的。
需要注意的是,如果是在一个lua脚本中使用了非确定函数,我试验过对于redis4.0版本中要打开script effects replication(开启单命令复制模式模式),否则会报错,而redis5.0版本中lua脚本的默认模式就是script effects replication。详情请参见。所以,为了更高的兼容性,建议不要在lua脚本中执行非确定性函数,如获取time()等,尤其是在使用分布式redis的情况下。
2.4.2 Mysql
Mysql主从复制主要是采用基于语句的复制和基于行的复制。
前者是前面所述的复制执行语句的方式。这种方式简单紧凑,但是有一些缺陷和限制,如对于非确定性操作的限制、有些存储过程和触发器在使用这种复制方式时会有问题,还有些存储引擎不支持这种方式。
还有一种方式是基于行的复制,这相当于前面所述的第3种,基于逻辑日志的复制方式。这种方式会将实际的复制数据保存在日志中,最大的好处就是避免了非确定操作的问题,可以正确的复制每一行。
2.4.3 Mongo
前面说到,mongo的复制过程也分为两个阶段。第一个同步阶段复制目前系统已有的数据。mongo提供了两种方式,
- 通过scan and insert 的方式,这算是属于上面说的基于应用层的复制,使用数据库本身提供的的api接口来进行复制。
- 使用File Copy Based的方式,这种方式大概类似于上面说的 基于WAL(预写日志)进行传输,属于比较底层的复制方式。
第二个阶段的增量同步,使用的是oplog操作日志。
如下图所示是一条解析出来的oplog日志。
简要解释下上述日志格式,Oplog 的日志由 key value 组成。主要字段如下所示:
ts 的值: 表示该日志的时间戳
op 的值: i 表示 insert ,u 表示 update, d 表示 delete, c 表示的是 db cmd, db 表示声明当前数据库 (其中ns 被设置成为=>数据库名称+ '.'), n 表示 noop,,即空操作,其会定期执行以确保时效性
ns 的值: 表示操作所在的数据库和集合。
ui 的值: 表示当前登录用户的会话 id 值。
wall 的值: 表示该操作的执行时间,utc时间。
o 的值: 表示操作的内容,如果是插入,就会将插入的数据放到该位置。如过时更新,则是设置的值,如上图所示是在pvtcim.c2c_msg489的collection中更新unique_key的值。
这其实就是和redis的同步过程类似,通过传递客户端的写入日志来达到同步数据的目的。
三、复制所带来的一些问题?
确切的说,这里说的是副本机制所带来的一些问题。单个节点的数据库系统,独立行事。但是采用了副本机制之后,相当于各个副本之间有了一定的联系,这个时候就会有了一些问题。
3.1 节点故障和切换
节点故障与恢复是属于高可用性的问题,对于主从复制系统来说,可以分为从节点故障和主节点故障。从节点故障失效比较简单,重启从节点或者建立新的从节点,让其按照上面的复制方式同步主节点的数据即可。
但是如果主节点发生了故障,问题就复杂些,因为我们需要切换新的主节点,而且要保证新的主节点上已有的数据尽量是最新的。这个过程简要来说一般包括如下几个步骤:
- 确认旧主节点A失效。这一般通过超时机制来完成。(长时间不联系,我就认为你挂了)
- 选举(指定)新的主节点B。 这主要是使用协调节点或者各个节点间的共识方式来选举。 我们一般会使B副本的数据尽可能的新。(大家共同选举一个新的leader,有一个标准就是新的leader对整个"业务线"最熟悉)
- 系统配置,使新主节点B生效。(告诉系统其他所有的人,旧的leader已经下岗了,以后新的leader是B,有事情要先找B)
这种涉及到主节点的切换影响的范围比较广,在切换新的主节点过程中可能会出现一些棘手的问题。比如说:
- 如果采用了异步复制的方式,当A中的数据并未完全同步至B节点,A挂了之后,B选举为新节点,那么可能存在一定丢数据的可能,在客户端看来像是发生了数据的rollback。有些情况下,丢数据是不可容忍的,可能会造成系统问题。
- 有些故障情况,可能会出现“脑裂”,整个系统出现了两个或多个自认为是主节点的情况,那么系统可能会出现各种莫名其妙的问题。比如说,Pold继续喝Pnew都向外提供服务,最终的数据可能会错乱。这造成的影响比较大,因此要尽量避免脑裂这个问题。
- 还有上面提到的通过超时机制来判断节点失效,取长了,可能主节点出现故障了,系统不可用的时间就长;取短了,可能会发生误判造成不必要的主从节点切换。
东西有点多是吧? 还好,上述选举过程中出现的问题,大多数分布式数据库通过选举协议都帮我处理好了。
3.2 来点栗子
3.2.1 Redis
对于redis来说,节点之间是通过定时的ping-pong来判断对方是否存活的。当主节点A发送给另一个主节点B的ping消息没有在规定的时间内收到回复时,A则把B的状态认为是“疑似下线”,当半数以上的主节点都认为B是“疑似下线”, 则整个集群将B置为下线状态。然后通过广播将B下线的消息传给集群中其他节点。
当主节点B对应的副本节点获取到B下线的消息之后,各个副本节点会通过Raft共识算法选举一个新的主节点代替B. 以此来完成故障的切换。
3.2.2 Mysql
默认的mysql主从架构不支持自动的故障切换与恢复,主从复制机制只给故障切换提供了一个基础。当mysql主节点挂了之后,需要手动进行故障切换。当然也有一些监控mysql主从节点的管理程序和中间件,如MMM(Master-Master replication manager for MySQL)、Master High Availability(Master High Availability)可以提供故障自动切换功能。
3.2.3 Mongo
对于mongo,从大的方面来说,它的Failover 过程和其他复制系统来说是很类似的。节点之间也是通过心跳协议来判断存活的状态,当超过了某个设置的阈值之后,则会将某个节点判断为unavailable。
如果从节点失效, 只要整个复制集群的有效节点数目大于一半,则可以正常进行读写。否则整个系统进入不可用状态。 当节点从失效状态恢复之后,可以重新加入集群,成为正常的副本节点。
如果主节点失效, 则会立即发起投票,选举出新的主节点。毕竟当家的不在,需要另选一个当家人。 在MongoDB r3.2.0以前选举协议是基于Bully算法,从r3.2.0开始默认使用基于Raft算法的选举策略。 具体的算法协议这里就不赘述了。
从小的方面来说,mongo在这个过程中提供了许多灵活的配置。比如说:
- 判断阶段失效的时间
- 哪些节点参与投票,哪些节点不参与
- 哪些节点的权限更重,更容易被选做主节点
- …
由于mongo是采用类似某种半同步的复制方式,因此当primary节点发生故障,准备进行切换的时候,mongo的一些机制可以防止(或者说降低数据丢失带来的影响)这个过程中的数据丢失(由于新的primary节点未同步到所有数据而造成的数据丢失)。比如说,
- 在写入操作时时候配置writeConcern 为majority,这样写操作之后同步到大多数节点才会返回给客户端,切换的新的primary节点一定具有最新的数据;
- 如果切换的新primary节点还未同步原来primary的所有数据(比如说未采用writeConcern为majority),从客户端看来好像是数据发生了rollback(“怎么新写入的数据不见了”), mongo有一种机制可以将rollback 的数据持久化到文件中。这样让用户来判断是否手动恢复这些rollback的数据。
3.2 复制滞后问题
由于复制系统中的从节点需要一定的时间从主节点同步最新的数据,在这段时间间隔内,如果发生了数据读写,可能会出现一些意想不到的情况。 这些意想不到的事儿主要两种:一种是复制过程本身的问题,比如说复制超时:
对于mongo这种不是完全异步复制的系统来说,如果客户端发送写入命令,却接收到返回error, 并不代表此次操作完全失败,有可能primary在复制到其他节点的时候延时比较高,导致整个操作超时,超过了用户配置的时间,最终返回给客户端失败的信息。客户的写入操作有可能会在接下来的时间内顺利完成,因此客户端在写入时要考虑到这种情况。
这可能需要上层的业务逻辑多加注意,问题还不是很大。
另外一种就是数据的一致性问题。这里以客户端为视角讨论几个常见的数据一致性问题。
3.2.1 读自己的写(Read own writes)
对于主从复制系统来说,我们知道不同的客户端有可能在读写时出现不一致的状况。对于同一个客户端呢? 答案是,也会出现略显迷惑的不一致。比如说客户端A写入的时候写到了主节点M,但是读取的时候是从 从节点S中读取的,这个过程中S还没有同步到A写入的数据,就会出现迷惑行为:“我刚才明明写成功了,怎么刷新一下就没了”? 当然,过一会A再刷新,一般就会出现新的数据。
所谓的读写一致性,就是对于单个客户端来说,要至少保证它能读到自己刚才写入的数据(其他人写入的数据,可能会有延时)。这种不一致对大部分业务场景可能没啥问题,顶多就是多刷新几次。但是对于某些苛刻的场景来说(“我刚存入的500块钱,一刷新,我去,没了。我就很恼火”),是需要保证的。
可以考虑使用下面的方案来达到读写一致性:
- 对于某些要求读写一致性的场景,直接从主节点读取。这样读到的数据就一定是新的。
- 客户端请求的时候,携带上一次更新的时间戳(或者可以记录先后顺序的其他数据),服务端在接收到请求时,判断上一次更新的时间戳与当前时间是否过小,如果比较小的话,比如说一分钟之内,都把请求路由到主节点中去。
- 如果操作的数据库是持久性数据库(如mysql、mongo等),在写入主节点之后,返回结果之前可以向redis等缓存数据库中写入一份数据(一般比异步复制要快);查询时先从redis中查,这样一般可以读取到最新的数据。当redis中的数据过期之后,master节点中的数据一般也已经同步到slave节点了。
3.2.5 读后写(Writes follow reads)
读后写指的是客户端读到版本n数据后(可能是其他客户端写入的),随后的写操作必须要在版本号大于等于n的副本上执行。
怎么理解这句话呢?简单来说,就是一个写入操作是根据先前的读取结果来完成的,不同的结果可能会执行不同的操作。用程序代码来表示如下:
d = read();
if d == condition{write1()
}else{write2()
}
举个例子:去银行账户里取钱,只有银行账户的余额balance>0 (read操作) ,才可以取出money来(write)。所以这个操作要保证上面的读后写一致性。
比如说上图所示,假设账户余额D1初始值是0,Client1充值100块(Write1: D1=100)。 过年了,Client2想要提取100元 买年货。 他查看账户发现有余额(从Slave1中Read1:D1 = 100),所以执行了提取操作操作(W2: D1 = D1-100)。但是对于Slave2来说,W2的写入请求先到达,如果没有其他措施的话,W2的请求先执行,就会有余款D1为负的尴尬情况 (W2:D1 = 0-100)。
3.2.2 单调读(Monotonic reads)
还有一种有意思的现象发生在客户端多次读取的场景。 假设主节点有两次或者多次的写入操作,比如说第一次写入了Write1(T = t1), 第二次又写入了Write2(T=t2); 然后客户端A在读取的时候可能从不同的副本先读到了T=t2时刻写入的数据,然后又读到了T=t1时刻写入的数据, 疑惑再次袭来,“好好的D1怎么变了呢,是莫名其妙的消失了?”
上述这种先读到旧数据,再读到新数据的状况就是单调读不一致。 针对这种状况要保证的一致性就是单调读一致性,也就是说,要保证客户端读取的数据不会发生“回滚”。
从上面的描述和图可以看出,发生这种“回滚”最主要的原因就是客户端的请求被路由到了不同的副本节点,所以,最简单也是最常用的一种方式就是把客户端的请求路由到同一个副本节点,当然这需要将客户端的请求以某种方式进行标识。
3.2.4 单调写(Monotonic writes)
单调写指的是对于同一个客户端的两个不同写操作,在所有副本上都以他们到达存储系统的相同的顺序执行。如下图所示就不是一个单调写的复制系统。对于副本Slave2来说,Write2操作的结果在Write1之前到达,如果Slave2闭着眼去apply接收到的数据,就会发送单调写不一致的情况。
一致性问题在分布式系统中是个大问题,涉及到的内容也相当的多。上述的几种一致性主要是主要是从客户端的角度来观察的,因此也叫做以客户端中心(Client-centric) 一致性模型。除此之外还有以数据为中心(Data-centric)的一致性模型。基本的一致性模型有如下图所示。
这里就暂且不论述了,以后有机会可以单独做一篇总结。
3.3 来点栗子
3.3.1 Redis
对于Redis这种缓存系统来说,其为了更高的性能对数据一致性没做怎么处理(Redis作为缓存的定位也不需要它对一致性做更高的保证,谁会把需要重要的数据,需要保持一致性的数据放在Redis中呢 ?),所以,它保证的只能算是个基本的最终一致性。
3.3.2 Mysql
简单的主从架构的mysql,它提供的基本上就是一个可以从主节点同步数据至从节点的复制机制。同步复制、故障切换等功能需要使用其他的模式或者外部的工具。对于它的一致性,不能奢求太多,上面讨论的一致性问题,mysql的主从架构都存在。如果需要响应程度的一致性保证,就要靠上层应用自己来实现了。
3.3.3 Mongo
对于Mongo来说,它利用可调一致性中的内容(ReadConcern 、WriteConcern)和客户端的Causal Consistency Session(维护 Server 端返回的一些操作执行的元信息(主要是关于操作定序的信息))提供了一套因果一致性 (Causal Consistency)机制。 通过调节ReadConcern和WriteConcern来保证上述不同级别的一致性问题。如下:
后记
当有了自动化的复制机制,各个副本节点组成一个副本集群来共同维护整个系统的数据,这提高了数据库系统的性能(读性能)和可用性。同时由于副本之间建立了联系,也就有了一定的牵绊,维护副本间的数据同步和故障时的切换成为了复制系统中两个重要的问题。 对于上层应用来说,使用副本机制的数据库可以提高整体的性能,但是也同时带来了一致性问题,如果下层的数据系统搞不定,就需要上层的应用多做琢磨了。
参考
- 《DDIA》
- 《高性能Mysql》
- 《Redis的设计与实现》
- Modify Execution of CRUD Operations
- Replica Set Data Synchronization
- 分布式系统开发实战:数据一致性,以客户为中心的一致性模型
- 分布式系统中的一致性模型,以及事务
- MongoDB 一致性模型设计与实现
- MongoDB · 内核特性 · 一致性模型设计与实现
- Session Guarantees for Weakly Consistent Replicated Data
- 分布式系统中的一致性模型
- Consistency model
- 分布式相关技术 && 《DDIA读书笔记》