为了保证数据的高可用,分布式系统需要保存多份数据。随之而来的问题是如何在不同副本之间同步数据?不同的同步机制有不同的效果和代价,本文试图对常见的分布式组件的同步机制做一个总结。常用机制常用的同步机制有一些,对它们的评价维度也有很多。来看看大师的经典总结:上图是常用的同步方式(个人理解,请批评指正):备份,也就是定期备份,对于现有系统的性能基本不受影响,但是当节点宕机时,它可以勉强恢复每条指令的Master-Slave、主从复制、异步复制,可以看作是粒度更细的常规备份。Multi-Muster,multi-master,又叫“MasterMaster”,MS的增强版,可以写在多个节点上,然后想办法同步2Phase-Commit,两阶段提交,同步保证写入前通知所有节点,性能容易卡在master节点上的Paxos类似于2PC,多个节点可以同时写入,只需要通知大部分节点即可。有两种吞吐量较高的同步方法,异步性能好但可能会丢失数据,同步可以保证不丢失数据但性能较差,同样的算法也可以改进(比如Paxosfor2PC),但是实现难度非常高,实现只能在这几个点上做取舍,在考虑同步算法的时候,需要考虑节点宕机、网络中断等故障情况,下面我们来看看一些分布式组件的数据同步机制,主要考虑数据写请求是如何处理的,这期间可能会涉及到如何读取数据。RedisRedis3.0开始引入RedisCluster来支持集群模式。我个人认为它的设计非常漂亮。你可以看看官方文档。采用主从复制,消息异步同步。极端情况下,数据会丢失,只能从主节点读写数据。从节点只会拒绝并让客户端重定向,不会转发请求。如果主节点宕机一段时间,从节点会自动选主。Ifthereisanydatainconsistencyduringtheperiod,thedataofthenewlyelectedmasternodeshallprevail.一些设计细节:HASH_SLOT=CRC16(Key)mod16384MEETWAITKafkaKafka的分片粒度是Partition,每个Partition可以有多个副本。副本同步设计参考官方文档类似2PC,节点分master和slave,消息同步更新。除非所有节点都宕机,否则消息不会丢失。消息发送给主节点,主节点写入后等待“所有”从节点拉取消息,然后通知客户端写入完成。“所有”节点是指同步副本(ISR)。响应太慢或宕机的从节点将被踢出。主节点宕机后,从节点将被选举为新的主节点,在主节点宕机时继续提供服务,不保证修改被提交(可能提交的消息没有ACK)。一些设计细节:目前消费者只能从master节点读取数据,未来可能会改变master-slave的粒度。Partition,每个broker对于某些Partition是master节点,对于另外一些是slave节点。当Partition被创建时,Kafka会尝试让首选副本均匀分布在各个broker中。master选举是controller与zookeeper交互后的“默认”,然后通过RPC通知具体的master节点,可以防止partition过多,同时选举master会造成zk过载。ElasticSearchElasticSearch的数据存储需求和Kafka非常相似,设计上也非常相似。详见官方文档。ES中有一个主节点的概念,它的实际作用是管理集群状态,与数据请求无关。为了上下文一致性,我们称其为管理节点,主分片为“主节点”,副本分片为从节点。ES的设计:与2PC类似,节点分为master和slave,消息同步更新。除非节点全部宕机,否则消息不会丢失。消息被发送到主节点。录入成功后,通知客户端写入完成。管理节点会维护一个需要写入每个分片的从节点列表,称为同步副本。master节点宕机后,slave节点会被选举为新的master节点,继续提供服务,如果stageslave节点不可用,master节点会要求管理节点将slave节点从in-sync中移除副本。一些设计细节:写入只能通过主节点完成,读取可以从任何从节点提供服务,它们会将请求转发到数据分片所在的节点,但建议循环遍历每个分片的负载均衡节点:shard=hash(routing)%number_of_primary_shards创建索引时需要确定主分片的个数,确定的主从粒度为shard。对于某些分片,每个节点都是主节点,而对于其他分片,它是从节点。master选择算法使用ES自带的ZenDiscoveryHadoopHadoop使用链式复制,参考ReplicationPipelining数据的多个副本写入多个datanode。只要有一个幸存的数据,它就不会丢失。数据被分成多个块。每个块由数据写入的数据节点的名称节点决定。链式复制需要将数据发送到A节点,该节点发送到下一个节点,下一个节点返回且本地写入成功后返回,以此类推形成写链。写入过程中宕机的节点会从管道中移除,不一致的数据会交由namenode处理。实现细节:实现上对链复制进行了优化:区块被拆分成多个数据包,节点1收到数据包,写入本地同时发送给节点2,等待节点2完成后返回ACK本地完成。节点2在本地写入数据包,以此类推发送给节点3……TiKVTiKV在写入数据时使用Raft协议实现一致性。参考三??篇文章,了解TiDB技术的内幕。使用Raft存储时,需要超过半数的节点写入成功才能返回。如果一半以上的节点宕机,数据不会丢失。TiKV将数据的key按范围划分成Region,写入时以Region的粒度进行同步。写入和读取都通过领导者。每个区域组成自己的RaftGroup并有自己的领导者。ZookeeperZookeeper使用Zookeeper自带的Zab算法(Paxos的变种?),参考ZookeeperInternals数据只能由master节点写入(请求会转发到master节点),master节点之后的任何节点都可以读取写入数据会广播给所有节点,超过半数的节点写入后会返回给客户端。Zookeeper不保证读取的数据是最新的,而是通过“单一视图”保证读取的数据版本不“回滚”。总结如果系统对性能要求很高,以至于可以容忍数据丢失的话(Redis),异步同步的方式显然是一个不错的选择。而当系统要保证数据不丢失时,几乎只能使用同步复制的机制。看到Kafka和Elasticsearch一致使用了PacificA算法(个人认为可以看作是2PC的变种)。当然,这种方式的响应是受最慢副本限制的,所以Kafka和Elasticsearch都有相关机制去除最慢副本。当然,Paxos、Raft、Zab等新算法似乎比2PC好:一致性保证更强,只要一半节点写入成功就可以返回,而且Paxos也支持多点写入。只是这些算法也很难正确实现和优化。
