本书从分布式一致性的理论出发,向读者简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。全书共8章,分为五部分:第一部分(第1章)主要介绍了计算机系统从集中式向分布式系统演变过程中面临的挑战,并简要介绍了ACID、CAP和BASE等经典分布式理论;第二部分(第2~4章)介绍了2PC、3PC和Paxos三种分布式一致性协议,并着重讲解了ZooKeeper中使用的一致性协议——ZAB协议;第三部分(第5~6章)介绍了ZooKeeper的使用方法,包括客户端API的使用以及对ZooKeeper服务的部署与运行,并结合真实的分布式应用场景,总结了ZooKeeper使用的最佳实践;第四部分(第7章)对ZooKeeper的架构设计和实现原理进行了深入分析,包含系统模型、Leader选举、客户端与服务端的工作原理、请求处理,以及服务器角色的工作流程和数据存储等;第五部分(第8章)介绍了ZooKeeper的运维实践,包括配置详解和监控管理等,重点讲解了如何构建一个高可用的ZooKeeper服务。
问题的提出
在计算机科学领域,分布式一致性问题是一个相当重要,且被广泛探索与论证的问题,通常存在于诸如分布式文件系统、缓存系统和数据库等大型分布式存储系统中。
什么是分布式一致性?分布式一致性分为哪些类型?分布式系统达到一致性后将会是一个什么样的状态?如果失去了一致性约束,分布式系统是否还可以依赖?如果一味地追求一致性,对系统的整体架构和性能又有多大影响?这一系列的问题,似乎都没有一个严格意义上准确的定义和答案。
终端用户
IT技术的发展,让我们受益无穷,从日常生活的超市收银,到高端精细的火箭发射,现代社会中几乎所有行业,都离不开计算机技术的支持。
尽管计算机工程师们创造出了很多高科技的计算机产品来解决我们日常碰到的问题,但用户只会倾向于选择一些易用、好用的产品,那些难以使用的计算机产品最终都会被淘汰——这种易用性,其实就是用户体验的一部分。
计算机产品的用户体验,可以分为便捷性、安全性和稳定性等方面。在本书中,我们主要讨论的是用户在使用计算机产品过程中遇到的那些和一致性有关的问题。在此之前,我们首先来看一下计算机产品的终端用户是谁,他们的需求又是什么。
火车站售票
假如说我们的终端用户是一位经常做火车的旅行家,通常他是去车站的售票处购买车票,然后拿着车票去检票口,再坐上火车,开始一段美好的旅行——一切似乎都是那么和谐。想象一下,如果他选择的目的地是杭州,而某一趟开往杭州的火车只剩下最后一张车票了,可能在同一时刻,不同售票窗口的另一位乘客也购买了同一张车票。假如说售票系统没有进行一致性保障,两人都购票成功了。而在检票口检票的时候,其中一位乘客会被告知他的车票无效——当然,现代的中国铁路售票系统已经很少出现这样的问题了,但在这个例子中,我们可以看出,终端用户对于我们的系统的需求非常简单:
“请售票给我,如果没有余票了,请在售票的时候就告诉我票是无效票的。”
这就对购票系统提出了严格的一致性要求——系统的数据(在本例中指的就是那趟开往杭州的火车的余票数),无论在哪个售票窗口,每时每刻都必须是准确无误的!
银行转账
假如说我们的终端用户是一名刚毕业的大学生,通常在拿到第一个月工资之后,都会选择向家里汇款。当他来到银行柜台,完成转账操作后,银行的柜台服务员会友善地提醒他:“您的转账将在N个工作日后到账!”此时这名毕业生有一些沮丧,会对那名柜台服务员叮嘱:“好吧,多久没关系,钱不要少就行了!”——这也成为了几乎所有的用户对于现代银行系统最基本的需求。
网上购物
假如说我们的终端用户是一名网上购物狂,当他看到一件库存量为5的心仪商品,会迅速地确认购买,写下收货地址,然后下单——然而,在下单的那个瞬间,系统可能会告知该用户:“库存量不足!”此时,绝大部分的消费者往往都会抱怨自己动作太慢,使得心爱的商品被其他人抢走了!
但其实有过网购系统开发经验的工程师一定明白,在商品详情页面上显示的那个库存量,通常不是该商品的真实库存量,只有在真正下单购买的时候,系统才会检查该商品的真实库存量。但是,谁在意呢?
在上面三个例子中,相信读者一定已经看出来了,我们的终端用户在使用不同的计算机产品时对于数据一致性的需求是不一样的:
有些系统,既要快速地响应用户,同时还要保证系统的数据对于任意客户端都是真实可靠的,就像火车站的售票系统。
还有些系统,需要为用户保证绝对可靠的数据安全,虽然在数据一致性上存在延时,但最终务必保证严格的一致,就像银行的转账系统。
另外的一些系统,虽然向用户展示了一些可以说是“错误”的数据,但是在整个系统使用过程中,一定会在某一个流程上对系统数据进行准确无误的检查,从而避免用户发生不必要的损失,就像网购系统。
更新的并发性
在计算机发展的早期阶段,受到底层硬件技术的制约,同时也是由于人们对于计算机系统的实际使用需求比较简单,因此很多上层的应用程序架构都是单线程模型的。以C语言为例,其诞生于上世纪70年代,当时几乎所有使用C语言开发的应用程序都是单线程的。从现在来看,单线程应用程序虽然在运行效率上无法和后来的多线程应用程序相比,但是在编程模型上相对简单,因此能够避免多线程程序中出现的不少并发问题。
随着计算机底层硬件技术和现代操作系统的不断发展,多线程技术开始被越来越多地引入到计算机编程模型之中,并对现代计算机应用程序的整体架构起到了至关重要的作用。
多线程的引入,为应用程序带来性能上的卓越提升,同时也带来了一个最大的副作用,那就是并发。《深入理解计算机系统》注 一书对并发进行了如下定义:如果逻辑控制流在时间上重叠,那么它们就是并发的。这里提到的逻辑控制流,通俗地讲,就是一次程序操作,比如读取或更新内存中变量的值。
在本书后面的讨论中,我们提到的“并发”都特指更新操作的并发,即有多个线程同时更新内存中变量的值——我们将这一现象称为更新的并发性。
分布式一致性问题
在分布式系统中另一个需要解决的重要问题就是数据的复制。在我们日常的开发经验中,相信很多开发人员都碰到过这样的问题:假设客户端C1将系统中的一个值K由V1更新为V2,但客户端C2无法立即读取到K的最新值,需要在一段时间之后才能读取到。读者可能也已经猜到了,上面这个例子就是常见的数据库之间复制的延时问题。
分布式系统对于数据的复制需求一般都来自于以下两个原因。
为了增加系统的可用性,以防止单点故障引起的系统不可用。
提高系统的整体性能,通过负载均衡技术,能够让分布在不同地方的数据副本都能够为用户提供服务。
数据复制在可用性和性能方面给分布式系统带来的巨大好处是不言而喻的,然而数据复制所带来的一致性挑战,也是每一个系统研发人员不得不面对的。
所谓的分布式一致性问题,是指在分布式环境中引入数据复制机制后,不同数据节点间可能出现的,并无法依靠计算机应用程序自身解决的数据不一致情况。简单地讲,数据一致性就是指在对一个副本数据进行更新的同时,必须确保也能够更新其他的副本,否则不同副本之间的数据将不再一致。
那怎么来解决这个问题呢?顺着上面提到的复制延时问题,很快就有人想到了一种解决办法,那就是:
“既然是由于延时引起的问题,那我可以将写入的动作阻塞,直到数据复制完成后,才完成写入动作。”
没错,这似乎能解决问题,而且有一些系统的架构也确实直接使用了这个思路。但这个思路在解决一致性问题的同时,又带来了新的问题:写入的性能。如果你的应用场景有非常多的写请求,那么使用这个思路之后,后续的写请求都将会阻塞在前一个请求的写操作上,导致系统整理性能急剧下降。
总的来讲,我们无法找到一种能够满足分布式系统所有系统属性的分布式一致性解决方案。因此,如何既保证数据的一致性,同时又不影响系统运行的性能,是每一个分布式系统都需要重点考虑和权衡的。于是,一致性级别由此诞生。
强一致性
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响比较大。
弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不具体承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。弱一致性还可以再进行细分:
会话一致性:该一致性级别只保证对于写入的值,在同一个客户端会话中可以读到一致的值,但其他的会话不能保证。
用户一致性:该一致性级别只保证对于写入的值,在同一个用户中可以读到一致的值,但其他用户不能保证。
最终一致性
最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常重要的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。
本书将会从分布式一致性的理论出发,向读者讲解几种典型的分布式一致性协议是如何解决分布式一致性问题的。之后,本书则会深入介绍分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现以及运维技巧。
致谢
首先要感谢现在的部门老大蒋江伟先生。第一次接触蒋江伟是在2011年,当时参加了他的一个讲座“淘宝前台系统优化实践——吞吐量优化”,对其中关于“编写GC友好代码”的内容有不解之处,于是私下请教。他耐心的讲解令我至今记忆犹新。两年前,他全面负责中间件团队之后,给予了我更大的帮助和鼓励,使我得到了极大的进步,真的非常感谢。本书的问世,离不开他的推荐。也正是这一份写作的责任感,让我有决心和毅力来对整个ZooKeeper内容进行了一次全面的整理。在这里,衷心祝福蒋江伟先生带领中间件团队走向新的高度。
其次,本书的写作,离不开各位小伙伴们的支持和帮助,他们是各领域的资深专家,我向他们征集了很多有营养的内容。在这里,按照章节顺序,依次表示感谢:许泽彬参与了“问题提出”的写作;侯前明对Paxos算法的前世今生进行的整理;段培乐对晦涩的Paxos协议进行了细致的讲解;姜宇向我提供了他对于分布式事务的见解;徐伟辰参与了分布式锁服务Chubby相关的写作;叶成旭提供了他在上家公司时对Hypertable的学习和研究成果;高伟细致地向我展示了Curator这一ZooKeeper客户端的使用;陈杰提供了他在“自动化的DNS服务”场景中的经验总结;曹龙参与了Hadoop相关内容的写作;邓明鉴则贡献了他对HBase的深刻见解;作为产品的开源负责人,庄晓丹和王强提供了对消息中间件Metamorphosis技术架构的讲解;李鼎则向我全面展示了RPC服务框架Dubbo的技术细节;楼江航向我提供了Canal和Otter这两个分布式产品中的ZooKeeper应用场景;李雨前、柳明和温朝凯则一起写了终搜在产品演进过程中对ZooKeeper的使用和改进;封仲淹参与了对其自主产品JStorm的技术剖析……是你们一遍又一遍地对内容进行修改,才使得本书内容更为丰满。
另外,也要感谢温文鎏、王林、许泽彬、高伟和段培乐等人对全书的审阅,正是你们提出的宝贵建议,对完善本书提供了非常大的帮助。
感谢现在的同事陆学慧先生,从2013年下半年开始,他全面接手对ZooKeeper的开发和运维,在他身上感受到的专业和创新精神让我备受鼓舞。
另外,感谢我的第一个主管马震先生,是他的帮助为我指引了方向,让我有机会进入ZooKeeper的世界,并负责这个产品在公司的发展。尽管由于业务调整,马震先生已经转岗到其他部门,但依然由衷祝福他工作顺利。
还要感谢我的同事,阿里巴巴店铺平台的侯前明先生。本来该书作者应该是我们两个人,但是由于期间他的家庭又增加了一个小生命,导致其不得不中途退出。从本书的选题到写作大纲的制定,他都倾注了不少心血,相信如果有他一起创作,本书内容会更加丰满、深刻。这里表达遗憾的同时,也向这位两个孩子的父亲送去祝福,祝愿他生活美满。
感谢本书的责任编辑刘芸女士,是她反复审稿和编排,才能让本书的内容趋于完美。
感谢本书的封面设计吴海燕女士,她的努力已经无需言表,在技术书上的这一前卫、极富视觉冲击力的封面设计,深深震撼到了我,也希望读者朋友们能够喜欢。
尤其感谢本书的策划编辑张春雨先生。作为一个南方人,我很少有机会和那些有着一口北方腔的朋友交谈,第一次接到张春雨先生电话的时候,我才真正领略了北京腔,也正是他的邀请,才能让我有机会进行本书的撰写,同时在前后将近1年半的漫长写作过程中,也是他的帮助和鼓励,才让我坚持完成并不断完善本书的内容。在这里,也衷心祝愿张春雨先生事业更上一层楼。
最后,还有我的父母,在过去的1年时间里,多次放假没有回家,尽管父母一直鼓励我专注工作,专注于自己的事业,但我深知他们内心对儿子的牵挂,在这里也深深地向他们道一声:“谢谢”,也谨以此书献给我最亲爱的爸爸妈妈。
倪 超
2014年12月于杭州淘宝城
“执行更能操作后”
应为
“执行更新操作后”
5.2.2 读取
与读取相关的命令包含ls命令和set命令。
应为
与读取相关的命令包含ls命令和get命令。