一个系统如果说是分布式系统,意味着:
1. 运行在多个服务器上面;
2. 它们需要同步数据,因此是有状态的;
分布式系统同步数据需要面临的问题:
1. 系统本身的不可靠性,比如宕机、奔物理磁盘损坏等;
2. 网络的不可靠性;
解决模式针对上面的问题,有固定的解决思路。
1. 进程crash进程可能在任何时间,因为软件或者硬件的原因奔溃,比如:
(1)运维正部署过程的正常启停
(2) IO *** 作时,由于磁盘已满未正常处理异常,导致进程被 killed
对于进程crash,你需要保证已经回复确认成功的 client 你需要保证数据成功持久化。这就需要保证在是同步持久化的。
实际上很多系统在设计的时候并不会保证每一次插入或更新都会被立即保存到磁盘,因为 IO 是非常耗时的 *** 作,通常都会有一个 in-memory 的结构,用来保存用户的 *** 作,然后周期性地进行刷盘。这就带来一个问题,如果进程突然 crash,就会丢失上次刷盘后到当前时间的数据。
=> write-ahead log为了解决这个问题,引入了 “预写日志”, “write-ahead log”,,服务器将每一个写 *** 作的命令都保存到一个追加文件里。文件追加 *** 作时顺序写,通常会比较快,没有性能影响,服务器恢复后可以重放日志,恢复数据。
但是,服务器恢复之前是无法继续为客户端提供服务的,因此就引入多节点部署,提高可用性,但是又会带来数据同步的问题。
2. 网络延迟1. 一个服务器同步数据给其他服务器时,不应该无限等待;
2. 一个集群不应该出现这种情况: 不应该有两组服务器,每一组都认为另一组出现故障,因此继续为不同的客户端提供服务。这就是所谓的脑裂。脑裂恢复后,会带来数据被覆盖的问题。
第一个问题的解决模式是 heartbeat, 通过周期性的心跳包来检测其他服务器的存活情况,第二个问题的解决方式就是 quorum 机制,保证大多数的服务器同意当前服务器采取的任何 *** 作。
quorum 机制能解决脑裂问题,但是无法解决客户端看到的数据的不一致问题,quorum 服务器中,leader 接收了更新 *** 作,但是 follower 还没来得及同步,客户端如果是读取 *** 作就有可能读到的事旧值。
High-Water Mark
为了保证一致性,需要处理 quorum 中所有服务器达成一致, 保证发送给客户端的数据是所有服务器可用的数据。高水位线就是解决这个问题, 高水位线用来跟踪 write-ahead log 中已经成功复制到 quorum 中所有 follower 的条目。leader 也会同时将高水位线复制过去,因此如果 leader 挂了,follower 重新选举为新的 leader,client 看到的数据仍然能保证是一致的。
3. 进程暂停leader 由于gc 导致暂停,无法机选发送 heartbeat 到 follower, 超过timeout, follwer 会选举一个新的 leader 并接收新的请求,如果在此期间旧的 leader 接受了客户端请求,就有可能导致数据被覆盖的情况。
=> Generation Clock
一个单调递增的数字,用来检测和标记旧 leader 接收的请求。比如 《分布式—一致性协议 raft》提到的 term 就是基于这个思想。
分布式一致性至此,大家发现分布式系统中要解决的终极问题就是数据一致性问题。简单来说,一致性指的是一组服务器,它们对存储的数据、存储数据的顺序以及何时让客户可以看到这些数据达成一致。
=> replicated log
为了实现多台服务数据的一致,需要将 leader 的 write-ahead log 复制到所有的 follower 上,这项技术叫做 replicated log 。
比如Paxos、zab、raft 等, 这些协议的实现基本都利用了上面提到的模式。
=> 两阶段提交
上面提到的都是,所有的节点存储相同的数据达成一致的问题。有时在分布式系统中还有多个节点存储不同的数据,也就是数据分区,这个时候如果想达成一致(实际上是指,要么都成功要么都回滚)就会使用另一个模式:两阶段提交。
两阶段提交通常会锁住数据,严重影响系统吞吐量,因此在实现的时候通常还会借助一个叫 Versioned Value 的模式,就是为一个 key 打上版本,每次有更新都会有一个新的版本,这样做的好处是:一可以查看历史版本数据,二更新的时候不用修改,直接追加一个条目即可。
gossip协议在避免不必要的状态交换就使用了 Versioned Value 模式,数据库 MVVC 实现事务隔离性也使用了 Versioned Value 模式。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)