一 前言
上节说到分布式系统是为了应对大数据,将数据分散存储在不同的主机上,这些主机一般都是普通的 PC 主机,那么这种主机稳定性一般都不太好,机器出现故障导致数据丢失的问题,很常见。我曾经使用过几百台机器的分布式系统,磁盘就经常坏,一是本身大数据的写入和读取的量很大,对磁盘损耗很大,另一方面普通机器的磁盘质量本身一般,为数据的安全,必须进行备份,不然丢了关键数据库哭都来不及!
数据备份除了数据安全之外,还可以提升分布式系统的性能。多个客户端可以同时通过并行读取分布式机器上多份副本数据,提升整体的性能。
当然既然产生有多份同样的数据,就存在着数据不一致的问题,这里就涉及到大名鼎鼎的 CAP 原理。CAP 原理适用于具有互联的数据共享的系统中,系统的三个特性:Consistency(一致性),Availability(可用性),Partition tolerance(分区容错性)三者只能选其二。一致性是指多个副本,在同一时刻,看到的数据是一样的,读操作都可以看到最新的结果;可用性是指系统可以在合理时间返回合理的结果(超时和出错不算);分区容错性,就是当网络出问题后,一个互连的分布式系统,由于网络故障(也有可能是丢数据),系统仍然能够履行职责。
由于是分布式系统,所以网络出现故障是难以避免的,所以一般选择 CP 或 AP,比如采用 CP 的 Hbase,MongoDB,Redis,Zookeeper; 采用 AP 的比如有 Cassandra,DynamoDB 。
Kafka 比较特殊是属于选择 CA,这是因为 Kafka 基本处于同一个数据中心,所以网络一般比较好,另外 Kafka 的 leader 中维护一组被称为In-sync-replica
的节点,每次写入的数据的时候,需要等In-sync-replica
的节点数据同步之后才返回给客户端,所以任何一个节点掉了,In-sync-replica
的节点都可以选为 leader(kafka 还可以配置不在这个列表里面节点选为 leader,不过这就会造成数据的丢失)。这相当于如果副本所在的机器慢了(kafka 中是配置延迟多少毫秒:replica.lag.time.max.ms 或 replica.lag.max.messages 延迟多少消息)后直接被剔出 ISR 列表,缩小了集群,所以就不存在网络分区的情况(比较难以理解).
更好理解的 CA 系统是单机数据库这类,但是这种又不属于真正的 CAP 要讨论的数据共享的分布式系统的范围.
特别需要注意的是,CAP 理论上虽然无法同时满足,但是工程实践中,不是说选择了 AP 就完全放弃了 C, 一致性也分为强一致性和弱一致性,最终一致性,AP 系统也可以通过其他方法保证最终一致性.
二 分身术
数据复制 像极了分身术,是将自己复制多份,如何能保证每个分身和本体是相同的,这就涉及到分身技术了. 世间万事都包含着妥协与折中,分身复制技术也一样。为保障最大程序的性能,希望复制尽可能快;为保障分身完全一致,又必须耐心等待复制完毕;这是一对矛盾,根据可用性和一致性的权衡,复制按照同步和异步划分主要分为以下三种:
-
主从之间的同步复制。
-
主从之间的异步复制。
-
主从之间的部分同步复制。
2.1 同步复制
同步复制流程如下:
-
一个节点被选定为主节点。
-
所有的写入操作必须发送到主节点。
-
主节点将数据写入到本地后,还需要将数据主动同步到其他从节点,等待从节点的返回,返回成功后再返回给客户端。
-
客户端可以从任意的节点读取到最新的数据。
特点:
由于主节点需要等待从节点,所以写入性能比较差,依赖于性能最差的从节点。
保障了数据的一致性,适合于一致性要求比较高的场景。
客户端可以从任意节点读取数据,读取性能好。
采用同步复制技术的分布式系统还挺多,比如上面提到的 Kafka,也是将数据同步复制到 ISR 中,只是这里面的 ISR 列表是动态变化的;还比如 Mysql 的全复制技术也采用同步复制方法;还有就是 Solr Cloud 模式部署下,也采用同步复制。
也许你觉得需要同步等待这样太慢了吧,工程上其实也有做优化,比如写数据只是写内存配合 WAL 日志的方式,既保证了性能,也保障数据的可靠性。
2.2 异步复制
异步复制的思路也比较简单,就是主节点在写入后,不等待从节点的操作就直接返回,保障了系统的高可用,同时也牺牲了数据的一致性,客户端连上从节点获取的数据不一定是最新的数据,适用于对数据一致性要求不是很严格的场景。Mysql 集群默认的复制模式,就是采用异步复制模式。
2.3 半同步复制
半同步复制技术可以说是同步复制,异步复制之间的折中,也就是主不必等待所有的从节点返回,比如严格来讲 kafka 的副本复制算是半同步复制技术,因为在 ISR 节点的副本采用同步复制,而不在 ISR 的节点采用异步复制模式,即保障了数据的一致性,还提升了集群的写性能,性能比较差的节点已经被 T 出 ISR 节点列表了。
PostgreSQL 高可用方案 manatee 也是采用这种半复制的方案,若干从节点采用同步复制,若干个采用异步复制,当主节点挂了之后,从同步复制节点中再次选主。
Zookeeper 系统中也是采用半同步复制,写入数据到 zk 系统中,必须等待半数以上节点同步复制后,才可以给客户端返回成功。
这种半同步复制后续的异步复制节点可以采用多种算法来保证数据的最终一致性,比如 Raft 算法。简单理解起来就是以 Leader 数据为准,强制复制数据到从节点上。
三 Quorum
刚才所说半复制器是一种折中,实际应用中更灵活的实现是支持 Quorum NWR.
3.1 N 表示副本数即复制因子
如图所示,三个节点组成的集群中,数据 A 有三个副本(这个概念让人误会,副本包括自身),B 数据有两个副本,C 数据只有一个副本。
一般为了数据安全期间,一个节点只能有同一个数据的一个副本,多个保存在同一个节点上的话,那么这个节点挂掉之后,所有的上面的数据丢会丢失。
3.2 W 写一致级别
表示完成 W 个节点的写入操作才算写入成功,我理解的是 W 可以看作同步写入的节点个数,如下图:
如果集群的写入级别 W 为 2 的情况下,数据 A 虽然有三个副本,但是只是同步写入到 2 个。
3.3 R 读一致性级别
客户端在读取一个数据的时候,需要同时读取多个节点 R 个副本数据,然后判断下哪个节点的副本数据最新就用哪个。
这里面就有意思了,刚才我们在写入数据的时候写入了节点 A 和节点 B,读取的时候,我们读取的是节点 B 和节点 C,由于节点 B 上的数据更新,所以最终系统会返回节点 B 的数据。
这里有个公式当 W(2)+R(2)>N(4)的时候,整个系统可以保证强一致性。在使用的时候就可以选择不同的配置,让系统适用不同的场景:
-
W+R >N 适用于要求强一致性的场合。
-
如果设置 W = N ,则是完全同步,这样的话系统的读性能更好,写性能比较差。
-
如果设置 R = N,写性能比较好,我只要写入一个节点就可以了,读的话性能比较差,需要读 N 个节点判断哪个数据最新。
-
如果设置 W = (N+1)/2 R = (N+1)/2 这种情况下,W+R = N+1 ,满足强一致性,而且可以容忍(N-1)/2 的节点的故障。
InfluxDB 企业版就可以对 R 和 W 灵活配置,以达到不同的目的。
四 诗词欣赏
将进酒·君不见黄河之水天上来
[唐] [李白]
君不见黄河之水天上来,奔流到海不复回。
君不见高堂明镜悲白发,朝如青丝暮成雪。
人生得意须尽欢,莫使金樽空对月。
天生我材必有用,千金散尽还复来。
烹羊宰牛且为乐,会须一饮三百杯。
岑夫子,丹丘生,将进酒,杯莫停。
与君歌一曲,请君为我倾耳听。
钟鼓馔玉不足贵,但愿长醉不复醒。
古来圣贤皆寂寞,惟有饮者留其名。
陈王昔时宴平乐,斗酒十千恣欢谑。
主人何为言少钱,径须沽取对君酌。
五花马,千金裘,
呼儿将出换美酒,与尔同销万古愁。