初识分布式协调器
分布式协调器的“协调”二字让人摸不到头脑,怎么就协调了,用的着协调吗?实际上这个东西在之前就是为了提供分布式锁服务而设计的,伟大的google公司发明了chubby,雅虎随后也推出了chubby的开源实现zookeeper。由于其高可用高容错的特性逐渐的衍生出了非常丰富的功能。目前来说最重要的三个功能是分布式锁、选主节点、命名服务。
比如选主时,为了让集群的所有节点达成一致,必须要通过选举算法来实现,可能有人会问,我直接配置好不就行了,已启动大家都知道谁是主节点。但是master挂了咋办?那你的HA就不能保证了,这叫single point of failure(单点故障)。那有人可能又说了,轮流来当master不就行了?但是动态增加删除节点你的集群怎么做到Scalable(可伸缩)。总之别问了,人择原理,存在即是合理的,所以请听我细细道来…
关于cocklebur的开发目的
我闲暇时间开发了一个叫做cocklebur的东西,已经开源到github。其实当初在看了一些关于paxos的文章之后觉得很困惑,于是就去看zookeeper的源码,以及一些源码剖析的文章,但依然觉得不得要领。所以就打算自己去搞一下,因为在开发过程中,你能够正面的遇到问题,然后去解决问题,而不是旁观别人提出问题,然后直接去看答案。
开发cocklebur,完全是为了学习,而并没有期望把它打造成向zookeeper那样的项目,本人水平有限,接触分布式领域也不过1年半,所以有写的不对不好的地方,还望各路高手指点一二。
Cocklebur概述
首先cocklebur可以部署成为一个集群,每个节点都维护着所有节点的地址信息;Cocklebur集群刚刚启动时,每个节点都会提议自己是master(因为它们不知道彼此的情况),经过一个选举过程,最终达成一致。选出master之后,整个集群就开始同步数据,每个节点都保存了一份类似Unix文件系统的数据结构,里面存放着目录、文件。同步的目的就是保证集群每个节点都持有最新且一致的数据,之后集群就可以对外服务了。外界的客户端可以向集群提交一些对文件系统修改的一些操作(创建目录,删除目录,上传文件等等),而要求就是节点的每一个节点的数据在任何时候都要保证最终一致(可以不马上一致,但逻辑上最终数据都会一致,所以client的操作顺序肯定需要保证)。服务的方式不光是可读写,而且还提供了订阅机制,也就是说,client告诉集群(实际上是注册到某个节点,一般是Follower)我关注了那个文件(或目录),它只要发生了变化,集群(接受注册的那个Follower)就能立马(其实有一定的时间窗口)通知client发生了什么样的变化。所以从这方面看他更像信号量的机制,所以称之为分布式“锁”。
也就是说最终每个节点都会维护了一份最新而且一致的数据,无论集群发生宕机(整体宕机也算)还是个别机器坏掉,只要client向集群的任意一个节点提交了修改(create,delete,upload file)请求,那么一定不会保证数据丢失,当然你整个机房都被炸掉了那就没办法了...补充一句,可能已经向集群订阅的client接受通知与某client提交修改有一定的时间窗口,延迟嘛,你懂的。
一些可能存在的疑问
选举都考虑那些因素?Leader是不是永远都是拥有最新数据的那个?
要考虑的就是每个节点保存数据的版本,原则上最新的要当选Leader。但是出于容错和性能考虑,可以不必是最新的,只是在某个多数派中是最新就可以了(这尼玛也太随便了吧,随便一个多数派就行么?听我把话说完~)。说到这,有人就搞不懂了,数据不是最新就能当选Leader?没错!因为你启动集群后不能保证拥有最新数据的节点启动成功了;那如果成功了呢?那也没这个必要!如果它启动的慢了影响大家的进度怎么办,而且如果在集群服务之前,拥有最新数据的节点只要被Leader发现,那么Leader也会善解人意的从这个节点上同步最新数据,然后分发给每个Follower。这就是为什么Zookeeper在提出当选Leader前要等待200ms。其实等多长时间要看你的需求,这没有办法太绝对。正如有些人为了爱情可以苦等一辈子,有些人牵牵手就像旅游~麻痹的又不正经了,原谅我吧~
为啥能够实现分布式的观察者模式?(那个订阅机制是怎么实现的)
其实这个是通过客户端异步实现的。大家肯定很熟悉本地的观察者模式,观察者向发布者注册后,发布者只要更新了观察者所关注的信息就会直接调用观察者的引用,从而产生一个观察者方法的回调。其实分布式的观察者模式实现的关键在客户端(观察者)这里。传统的方式就是轮询,服务端的数据变化了,就可以做其他事情了。但是现在需要自动的通知,所谓通知就是回调client端的一个函数去处理返回的结果即可。大家可以参考我写的这篇文章:利用thrift实现一个非阻塞带有回调机制的客户端。
为何无论怎么宕机,出故障都能保证数据最终一致切完整?
其实这并不是绝对的,只能尽可能的把丢失数据的概率减少到最少,并且尽可能的让集群对外服务。
理论上来讲,只要有一个节点有最新数据,并且已经持久化到磁盘,磁盘还没坏,那么就一定能让整个集群恢复到最新的状态,只是时间长短的问题。所以本着这样的思路,我们就采用了Paxos的理念。另外,只要集群总数存活的节点数量超过一半,就能构成一个多数派,所以集群就能够对外提供服务。
文件系统数据内存和磁盘是如何交换的?
这个问题比较实在。其实现机制很简单,定期的把内存数据序列化到磁盘上形成快照,而且每执行一个操作之前就先写日志。这样我们就能以快照作为时间节点并利用日志去恢复数据了。有人可能会说“尼玛,这么粗暴!直接把文件系统全部序列化到磁盘了?”。是的!因为所谓“文件系统”并不是你想象中的那种服务器上的文件系统,它只是利用文件系统这种数据结构去管理少量的数据,比如配置文件,命名空间(就是一些目录结构),或者写个ip在某个目录下这种。定位不同,所以设计越简单越好。
更有思维缜密的同学问道,如果Leader接受完收据,还没同步到Follower却挂了,那么剩下的Follower组成新的集群,后续一来新的操作,不就漏掉了之前的操作了吗?
别忘了我们有数据版本这一说,数据的操作版本号必须是连续的,如果不连续说明中间漏掉了操作,所以,这就需要客户端可以缓存操作以便重试。如果有人问,如果客户端也挂了呢?缓存的操作不就没了吗?答客户端没接到成功它自己知道,只要重新读取任务就行了,所以多虑啦!另外补充一句,发生任何灾难性的情况都有可能,只不过是概率问题。我们能做的就是尽最大可能把能避免的问题避免,正如前面所说的,所有节点都坏了,那肯定完了。你咋不说世界末日呢,别说机房数据了,自己本本上备份的种子也没了呢!