﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>语源科技BlogJava-jinfeng_wang</title><link>http://www.blogjava.net/jinfeng_wang/</link><description>G-G-S,D-D-U!</description><language>zh-cn</language><lastBuildDate>Fri, 17 Apr 2026 21:35:49 GMT</lastBuildDate><pubDate>Fri, 17 Apr 2026 21:35:49 GMT</pubDate><ttl>60</ttl><item><title>Raft一致性算法</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 08:34:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432287.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432287.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432287.html</trackback:ping><description><![CDATA[<div>https://taozj.org/201612/learn-note-of-distributed-system-%284%29-raft-consensus.html<br /><br /><br /><h1>一、前言</h1><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　Paxos在分布式系统中地位是不容置疑的，现代分布式系统的实现基本都直接或者间接依赖于Paxos算法。不过Paxos算法有着固有的缺陷：原始的BasicPaxos原理还是比较容易理解的，整个算法无非被分为Prepare和Accept两个阶段，但是要把这种算法工程化实现，各个节点的角色是对等的，系统的效率(可用性)将会非常的低，所以常用的也就是MultiPaxos变体版本以提高性能，这也就隐含的包含了leader的概念了，然后MultiPaxos还允许一个提交窗口，窗口中允许发起多个提案，但是这种情况下考虑到服务器随时可能崩溃的情况，算法将会变得极端复杂。Lamport爷爷就此也是简明说了几句不清不楚的优化，没有具体的实现细节，算是挖了一个巨坑吧。所以说了解BasicPaxos只是一个皮毛，将其推送到工程化可不是件容易的事情。<br />　　Raft算法是斯坦福两位博士生提出的分布式一致性系统，从其论文的题目《<a href="https://raft.github.io/raft.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">In Search of an Understandable Consensus Algorithm</a>》可以看出，作者以Paxos过于复杂为出发点，力求得到一个正常智商的大学生都能看懂，且工程上也容易实现的分布式系统一致性算法为目标。Raft算法借鉴了Paxos的原理，但最大不同是显式强化了Leader这个角色(虽然很多Paxos算法的实现也会产生Leader角色，但这不是Paxos算法本身所必须的)，并添加了各种约束条件(比如日志的连续性)，让算法更加容易理解和实现。虽然吾等草根屁民尚且不能从理论上审视这个算法的正确性，不过短短时间内美国很多著名大学分布式教学都将Raft算法列入教学课程，且基于Raft协议的项目也越来越多，这些事实已经足以证明一切了。<br />　　学习Raft算法，一篇<a href="https://raft.github.io/raft.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">普通论文</a>和一篇<a href="https://ramcloud.stanford.edu/~ongaro/thesis.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">博士学位论文</a>算是不得不读的经典，而前者网上发现了翻译好的中文版本，简单对比了一下原文，发现翻译的质量还是挺高的，很值得参考，但有些原理中文理解困难的话可以对比英文方便理解。作为正规论文，按照论文八股需求难免有些啰嗦拖沓，本文就是按照前面论文阅读摘抄一些重点要点出来，而后面那篇两百多页的博士论文，后面可以慢慢评鉴一下作为补充吧，当然最好的方式还是&#8212;&#8212;一言不合就读<a href="https://github.com/logcabin/liblogcabin" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">代码</a>，毕竟作者说就两千多行！<br />　　还有，论文对状态机的描述比较的好，算是归纳了分布式系统的本质&#8212;&#8212;复制状态机的基础是通过复制日志来实现的：当每个副本的初始状态相同，只要保证各个副本得到的日志都是顺序且一致的，那么按照相同的顺序执行这些日志中的指令，所有副本必然可以进行相同的状态转移得到相同的结果。所以分布式系统解决的根本问题，就是一致性问题，具体就是日志一致性问题，在这个基础上，上层就可以方便实现分布式日志、分布式数据库(bin log)、分布式存储等具体业务了。<br /><a href="https://taozj.org/post_images/images/201612/b0c484d0.jpg" title="raft-stat" rel="fancy-group"  fancybox"="" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;"><img src="https://taozj.org/post_images/images/201612/b0c484d0.jpg" width="undefined" height="undefined" title="raft-stat" alt="raft-stat" style="margin: 0.5em auto; padding: 0px; border: none; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; max-width: 100%;" /></a></p><h1>二、Raft算法</h1><h2>2.1 Raft算法基础</h2><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　Raft算法保证和Paxos算法具有相同的安全性，当集群中绝大多数服务器是正常的，则集群即可以正常工作，比如5个节点的集群，允许2个节点的失效。Raft算共有三种角色、需要处理三个问题，三个角色分别是Leader、Candidate、Follower，三个问题是Leader选举、日志复制和安全性，三个问题将会在后面详细阐述。<br />　　任何服务器只能处于上述三种角色中的一种，正常工作的集群具有一个Leader，剩余的节点都是Fellower，系统保证任何时候至多只有一个Leader，也有可能在选举的过程中尚未产生Leader，而在选举新Leader的时候会产生Candidate这个角色。<br /><a id="more" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; color: #258fb8;"></a><br />　　a.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">Leader</strong><br />　　Raft算法强化了一个领导者(Leader)的角色。一旦被选举成为Leader，就会发送空的AppendEntries RPC作为心跳给所有其他服务器，并且这个心跳会在一定空余时间后不停的发送，这样可以阻止选取超时而阻止其他服务器重新发起选举操作；<br />　　Leader负责接收客户端的请求(如果客户端和Fellower联系，那么这个请求会被重新定向给Leader)，然后附加entry到本地日志存储中，并负责把日志<em style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-family: inherit; vertical-align: baseline;">安全地</em>复制到其他服务器上面，entry被用于Leader本地状态机后给客户端发送响应；<br />　　对于任何一个Fellower，如果已存储的日志比较旧，还会向Leader不断减小nextIndex要求发送之前的日志条目；<br />　　如果日志被安全地复制到了大多数节点上面，则增加提交索引号commitIndex，表明日志已经被安全复制；<br />　　b.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">Follower</strong><br />　　通常的服务器角色状态，只响应来自Candidate和Leader的请求；<br />　　如果在选举超时到达后还没收到Leader的心跳消息，或者是候选人发起投票请求，则自己变成Candidate；<br />　　c.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">Candidate</strong><br />　　当转变成Candidate后就立即开始选举过程&#8212;&#8212;增加当前term、给自己投票、重置选举超时定时器、发送RequestVote给所有的服务器；<br />　　如果在超时之前收到绝大多数服务器的投票，就成为Leader；当一个节点收到RequestVote RPC的时候，如果请求投票节点的term不比自己旧，其日志也不比自己少，则投票给他；<br />　　如果收到来自新Leader的AppendEntries RPC，则退变成Fellower；<br />　　如果选举过程超时，则再次发起新一轮选举；</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　Raft中使用term表示Leader的任期，使用连续递增的整数来标记区分，在Raft中充当了逻辑时钟的作用，很多操作和记录会打上这个&#8220;逻辑时间戳&#8221;；每个服务器存储自己的term，服务器之间通信也会交换各自当前term，通过比较term可以确定哪些信息已经过期；当Candidate或Leader发现自己的term过期之后，会立即退变成Fellower的角色；如果服务器收到包含过期term的请求，就会直接拒绝这个请求返回false。<br />　　Raft算法中节点之间的通信使用RPC的方式，主要包括RequestVote、AppendEntries以及InstallSnapshot三种，RPC可以并行的被发起，而当没有及时收到响应的时候会进行重试，同时RPC只需要在绝大多数快速的节点上完成就可以了，少部分比较慢的节点不会影响到系统的整体性能。 　　</p><h2>2.2 Leader选举</h2><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　很显然以Leader为中心可以大大简化系统的设计和实现，但是分布式系统中任何一个服务器都可能随时崩溃不可用，那么必须使用选举机制来解决Leader出错导致的单点故障。<br />　　Raft中Leader地位的维持和心跳机制密切相关。集群中所有服务器默认都是Fellower的角色，如果Fellower在一段时间中没有收到任何消息(无论是正常消息还是心跳消息)，就会触发选举超时，从而认为系统中没有可用的领导者，进而开始进行选举。选举开始的时候，Fellower会增加当前term并切换为Candidate角色，然后并行向集群中所有其他服务器节点发送RequestVote RPC来给自己投票，候选人等待直到三种情况之一发生：<br />　　a.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">该Candidate赢得该次选举成为新Leader</strong><br />　　当某个Candidate从集群中绝大多数服务器获得针对该term的选票，则其赢得该次选举成为Leader，因为按照先来先得的原则，每个服务器最多会对一个term投出一张选票，获得绝大多数选票确保了至多只有一个候选人赢得该次选举。<br />　　一旦Candidate成为Leader，就会向其他服务器发送心跳消息维护自己Leader的地位，并且阻止产生新的Leader。<br />　　b.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">其他服务器成为新Leader</strong><br />　　Candidate在收集选票的时候，很可能收到其他服务器发送的声明自己是Leader的AppendEntries RPC消息，此时如果收到的AppendEntries RPC的term不小于该Candidate当前的term，则Candidate承认发送该AppendEntries RPC的服务器的Leader合法地位，自己退化为Fellower角色。否则会拒绝这个RPC并维持自己Candidate的角色。<br />　　c.&nbsp;<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">一段时间后没有任何Candidate胜出</strong><br />　　一般出现在多个Fellower变成Candidate，然后同时发出RequestVote RPC请求，导致选票被瓜分后，没有任何一个Candidate获得绝大多数选票。此时Candidate会增加term并开始新一轮的Leader选举。<br />　　通常情况下这种选票瓜分的恶性竞争会持续下去。Raft采用随机选举超时的机制来减少这种情况的发生，选举超时设置为某个固定时间区域中的随机值(比如150-300ms)，这样当现有Leader挂掉之后，通常也只有一个Fellower会首先超时，然后迅速切换为Candidate、发出RequestVote RPC并赢得选举，然后快速发送心跳包；同时在发生选票瓜分的情况下，每次在Candidate开始新一轮的选举时候，会重置一个随机的选举超时时间。通过这机制，发生选票瓜分的几率被大大降低了。</p><h2>2.3 日志复制</h2><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　一旦Leader的角色被确定，就可以接受客户端的请求并提供服务了。客户端的每一个请求都包含状态机的执行指令，Leader负责刚其作为一个entry追加到自己本地日志中，然后并行的向其他服务器发送附含执行指令的AppendEntries RPC，使其他服务器都复制这个日志entry。当该条日志被安全的复制后(即通过AppendEntries RPC调用的结果得知，该日志被安全地复制到绝大多数服务器上面了)，Leader会应用这条日志到自己的状态机中，并将执行的结果返回给客户端。如果(少部分)Fellower崩溃或者运行缓慢，或者因为网络等问题，即使Leader已经回复了客户端，Leader仍然会不断的重试AppendEntries RPC直到所有的Fellower都最终存储了所有的日志条目。<br />　　在Raft算法中，日志是有序序号标记的entry组成的，每个entry包含该创建时候的term、状态机需要执行的指令、在所有日志中的位置索引组成。Raft保证当日志条目被复制到绝大多数服务器上面的时候，该日志entry就会被提交，即更新commitIndex，Raft算法同时也保证所有已提交的日志都是持久化的并且最终会被所有的可用状态机执行。<br /><a href="https://taozj.org/post_images/images/201612/978287ea.jpg" title="raft-log" rel="fancy-group"  fancybox"="" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;"><img src="https://taozj.org/post_images/images/201612/978287ea.jpg" width="undefined" height="undefined" title="raft-log" alt="raft-log" style="margin: 0.5em auto; padding: 0px; border: none; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; max-width: 100%;" /></a><br />　　Leader跟踪了最大将会被提交日志entry的索引，并且该索引值会被包含在未来所有的AppendEntries RPC中(包含附加日志内容为空的心跳包)，这可以方便的让其他服务器知道Leader的提交位置，一旦Fellower知道一条日志已经被提交，就会按照日志的顺序将其应用到本地的状态机中去执行。<br />　　和Paxos不同的是，Raft的日志约束为连续的、中间不允许有空洞，而且相同索引的日志在各个服务器上面指令完全相同，这样的设计可以减少很多不必要的麻烦，具体来说：<br />　　(1) 如果在不同服务器的日志中拥有相同的索引和Term，那么他们存储了相同的指令；<br />　　(2) 如果不同服务器中两个日志具有相同的索引和Term，那么他们之前所有的日志条目也都全部相同。<br />　　对于上面第二点，通过每次RPC简单一致性检查就可以确保。每当Leader发送AppendEntries RPC的时候，会包含新entry紧接之前条目的索引位置和term附加在里面，当Fellower接收到的时候会进行一致性检查，如果发现索引及term指定的日志找不到，就会拒绝接收该AppendEntries RPC请求。这种一致性通过强制Fellower复制Leader日志的方式来解决，领导者对每一个Fellower都维护了一个nextIndex，表示下一个需要发送给Fellower日志条目的索引位置，Leader通过不断减少nextIndex后退的方式尝试最终会找到和Fellower日志不一致位置开始的地方，然后从这个地方开始Leader将自己的日志同步给Fellower就可以了。不过这种大规模的日志不一致性通常在服务器接连崩溃的情况下更容易产生，当新选出了Leader之后，该Leader也会强制所有服务器的nextIndex为自己本地最后一条日志索引值+1，这样在后续正常发送AppendEntries RPC请求后，所有的Fellower就会自动进行本地日志一致性检查并在必要情况下进行同步操作了。<br />　　通过这种方式，只需Leader向Fellower单方面同步日志就可以完成，而且只需要Leader正常发送AppendEntries RPC请求，Fellower自己进行一致性检查，Leader得到失败的反馈信息后，再同Fellower进行不断交互以使得两者日志最终趋于一致，而且这种操作不会影响到其他的Fellower，因而也不会影响到系统整体的性能。</p><h2>2.4 安全性</h2><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　上面的内容都比较的理想化，但是在现实环境中Leader、Fellower各个服务器随时都可能崩溃不可用，如果没有额外的约束整个系统的工作是不安全的，比如当只有少量日志的Fellower被选取成了新Leader的情况。<br />　　简单来说，此处就是要实现即使发生了Leader重新选取，也要让任何新Leader对于给定的term，都必须有前任Leader所有已经被提交的日志条目，那么上面的机制就仍然可以正常工作。</p><h3>2.4.1 增加选举限制</h3><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　如果任何的Fellower都可以成为新人Leader，那么这个新Leader很有可能缺失前Leader的部分提交日志，很多一致性算法确实是这样的，然后通过某些方法识别出新Leader缺失日志，并在选举阶段或者成为新Leader之后快速的将这些缺失日志补齐，这些方法实现起来比较复杂。Raft算法通过很简单的限制解决了这个问题：<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;"><em style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-family: inherit; vertical-align: baseline;">在RequestVote RPC 中包含了Candidate的日志信息，然后投票人会拒绝掉那些日志没有自己新的请求</em></strong>。<br />　　上面的这条约束肯定是有效的：对于一个被提交的日志，那么这个日志肯定是被大多数服务器所见而被存储的，而一个Candidate要想成为Leader就必须和集群中的大多数服务器所通信，这样一来新Leader肯定会遇到至少一个记录着最新提交日志的服务器。再加上上面的限制条件，那么一个新当选的Leader至少会和大多数的服务器节点一样新，其肯定持有了所有已经提交了的日志条目。</p><h3>2.4.2 提交前term内的日志条目</h3><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　在Raft算法中，当一个日志被安全的复制到绝大多数的机器上面，即AppendEntries RPC在绝大多数服务器正确返回了，那么这个日志就是被提交了，然后Leader会更新commitIndex。<br />　　这里书中描述的比较复杂，其实本质就是通过上面的选举机制和提交限制，让Raft算法是安全的，即使是针对前term的日志：如果日志被复制到绝大多数服务器上面，那么含有较少日志的S5服务器就不会被选举成Leader，也就不会发生描述的entry-2日志即使被复制到绝大多数服务器上面，也最终被覆盖的情况；而当含有被复制的绝大多数日志entry-2的服务器被选为新节点的时候，提交entry-4也会让前面的entry-2被隐式提交。</p><h3>2.4.3 Candidate和Fellower崩溃</h3><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　Candidate和Fellower崩溃的情况处理要简单的多。如果这类角色崩溃了，那么后续发送给他们的 RequestVote和AppendEntries的所有RCP都会失败，Raft算法中处理这类失败就是简单的无限重试的方式。<br />　　如果这些服务器重新可用，那么这些RPC就会成功返回。如果一个服务器完成了一个RPC，但是在响应Leader前崩溃了，那么当他再次可用的时候还会收到相同的RPC请求，此时接收服务器负责检查，比如如果收到了已经包含该条日志的RPC请求，可以直接忽略这个请求，确保对系统是无害的。</p><h1>三、集群成员变更</h1><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　集群成员的变更和成员的宕机与重启不同，因为前者会修改成员个数进而影响到Leader的选取和决议过程，因为在分布式系统这对于majority这个集群中成员大多数的概念是极为重要的。<br />　　在集群中成员变更的NEW配置不可能立即在所有成员中生效，所以必须采用两阶段的方式来保证安全性，传统方式是将集群暂停工作，让其不再接受新客户的请求，更新配置完成后在让集群继续正常运行。<br />　　Raft中集群成员的变更是全自动的，通过产生一个&#8220;共同一致状态&#8221;来过渡传播NEW配置，并且做到在集群成员变更过程中仍然支持客户端请求，依靠在临界状态下达成一致的操作(针对选取和提交)需要分别在新旧两种配置上都获得绝大多数服务器投票才可以。成员变更请求在日志中以特殊的configuration entry来存储和通信，主要通过C-old,new和C-new这两个特殊日志entry来实现。<br />　　在Leader接收到成员变更请求后，会创建C-old,new日志entry，然后Leader会首先将其添加到自己本地日志中，并通过之前描述的普通日志复制提交流程，确保在OLD、NEW两个配置下的在绝大多数服务器上复制成功并提交；接下来Leader会创建一个C-new并在NEW配置下的绝大多数机器上复制成功并提交，创建C-new之后NEW配置就可以单独做出决议了。此外，这里还有一个关键性的约定&#8212;&#8212;所有的服务器一旦存储了C-old,new日志条目后(实际就是见到NEW配置后)，就总是会用NEW配置而无论这个配置日志条目是否已经被提交，通过这种方式实现NEW配置在集群成员中快速传播。一旦C-old,new被提交后，OLD和NEW配置都不能单方面的产生决议(只能在新旧两个配置下都完成绝大多数投票)以保证安全，直到NEW配置下的C-new日志条目被产生，NEW配置就可以单独决议了。<br /><a href="https://taozj.org/post_images/images/201612/d255f8d6.jpg" title="raft-member" rel="fancy-group"  fancybox"="" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;"><img src="https://taozj.org/post_images/images/201612/d255f8d6.jpg" width="undefined" height="undefined" title="raft-member" alt="raft-member" style="margin: 0.5em auto; padding: 0px; border: none; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; max-width: 100%;" /></a><br />　　至于在C-old,new提交和C-new创建之间为什么会有间隙，原文没有说清楚。其实也很容易理解：在C-old,new日志entry的创建和提交之间，Leader还可能有其他新的决议发起(比如客户端请求)，按照日志顺序一旦C-old,new被提交，那么集群中绝大多数主机都更新成NEW配置了，但是在NEW配置传播的过程中，为了保证安全在这个期间产生的所有日志都必须在新老配置中都得到绝大多数投票才允许真正被提交。至于C-new的产生，是为了表明Leader承诺从这个时候起所有的日志都不再会发给OLD配置主机，所以这个点之后NEW配置就可以独立工作了，由于Raft序列化日志的特性，一旦这个C-new日志条目被提交，集群配置中被删除的服务器就可以安全下线了。<br />　　新加入的机器日志都是空白的，起始阶段都在进行日志追赶(catch up)，Raft算法为了减少可能的性能损耗，对新加入的机器都是以旁观者的状态一直追赶旧日志而不会追加新日志参与投票，只有到了追赶日志和Leader对齐了，再参与新日志追加投票以行使正常集群成员的职能。还有NEW配置可能会把现任Leader删除掉，那么当C-new被提交后，该Leader将会卸任并退化成Fellower的角色，此时在NEW配置下会发生新Leader的选举，选举得到的新Leader一定是NEW配置下的主机，而在这之前由于一致性状态的约束，如果发生Leader选举那么选出来只可能是OLD配置中的服务器，因为一致性状态选举操作必须在新旧配置中都得到绝大多数选票才行。</p><h1>四、日志压缩</h1><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　日志会随着系统的不断运行会无限制的增长，这会给存储带来压力，几乎所有的分布式系统(Chubby、ZooKeeper)都采用快照的方式进行日志压缩，做完快照之后快照会在稳定持久存储中保存，而快照之前的日志和快照就可以丢弃掉。<br />　　Raft算法中快照所需保存的数据有：快照点时候状态机的状态信息；最后被快照所取代的日志条目在日志中的索引值；上面被取代条目所属的任期term，此外为了支持成员更新，快照还会将当前最新的成员配置写入上面描述的那个日志索引中。<br />　　Raft采用让服务器独立创建快照，而不是只让Leader负责创建快照，主要考虑到在所有服务器本地已经具有了创建快照所需的全部信息，而且本地创建快照代替Leader创建快照，就免除了Leader要向各个节点传输快照的额外任务、带宽和性能损耗，而且在Leader负责客户端响应、发送RPC的任务外如果还需维护快照的任务，其角色就会更加复杂。<br />　　在Raft中快照都是各个服务器独立创建的，但是有时候需要Leader向其他服务器发送快照，比如某些服务器跟随的速度缓慢，或者新加入集群的服务器，此时需要向Leader同步日志的时候，如果Leader创建了快照并将之前的日志都删除掉了，那么此时就必须通过快照的方式发送了。<br />　　Raft中采用一个额外的InstallSpanshot RPC的调用来实现日志传输，虽然名曰快照，其实也就算是一个特殊的日志entry。当接收到快照的时候，通常情况下快照会包含接受者中没有的信息，即快照代表的日志entry会比接受者当前本地含有的日志要新，此时接收者会丢弃掉自己所有的日志，并在指定位置写入该快照作为最新状态；如果因为网络或其他因素，接收者含有比快照更新的日志，那么接收者负责把快照位置之前的数据全部删除，同时比快照新的数据需要保留下来。</p><h1>五、Client交互</h1><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　Client只向Leader发送请求，当Client开始的时候会随机向集群中的任何一个服务器发送请求，如果Client挑中的恰巧不是Leader，那么该服务器会拒绝Client的请求，并将其最近获得的Leader信息(包括通信用的IP:Port)返回给Client，接下来Client根据这个信息直接向Leader重新发送请求；如果此时Leader恰巧崩溃了，那么Client的请求就会超时出错，Client会再次重新随机挑选服务器再次发送请求。<br />　　Raft算法要求Client的请求是线性化语义的，即每次请求会被立即执行，在请求和响应中只会被执行一次(也就是RESTful中的等幂性，同一个请求发起一次或者多次，最终的效果是相同的)，而要确保这个效果的方式是客户端负责对每条指令都赋予一个唯一的序列号，然后状态机跟踪每条指令最新序列号和其响应结果，如果后面收到一条指令具有相同的序列号但是该序列号已经被执行了，那么就直接返回结果而不重新执行该指令。<br />　　对于只读操作可以直接处理而不需要记录日志，但是会有读取脏数据的风险。在Leader稳定运行的时态，Leader肯定知道当前已经提交的entry，但是在新Leader选取刚上任的时候，虽然肯定是含有最完整的日志信息的，但是还不知道提交到哪条entry了(可以参看上面提交前term日志条目的情况)，所以在新Leader上任的时候会发起一个no-op的entry来刷新得到最新commit信息。然后，Leader在响应只读请求的时候，需要向绝大多数服务器发送一个心跳信息，确保当前自己还是合法的Leader。</p><h1>总结</h1><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　　如果在工程化的水平上考虑，Raft算法的确比MultiPaxos要简单容易的多，而且对比PhxPaxos中做出的诸如Master选举与心跳、Master负责所有客户端请求(允许普通节点响应脏数据除外)、日志压缩与快照等等操作，在这里看来也是那么的熟悉，只不过Raft对于整个分布式的设计和实现要更清晰、更系统，而不会让人感觉是在MultiPaxos的基础上缝缝补补拼凑出来的一个怪物吧。<br />　　眼观这么多论文，大家对Paxos的工程化实现，感觉都是相互借鉴啊，哈哈！</p><h1>参考</h1><ul style="margin: 10px 0px; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; list-style: none; color: #333333; line-height: 26.4px; background-color: rgba(255, 255, 255, 0.45098);"><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://raft.github.io/raft.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">In Search of an Understandable Consensus Algorithm</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc; background: rgba(158, 188, 226, 0.207843);"><a href="https://raft.github.io/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">raft.github.io</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">找一种易于理解的一致性算法（扩展版）</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://ramcloud.stanford.edu/~ongaro/thesis.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">CONSENSUS: BRIDGING THEORY AND PRACTICE</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://raft.github.io/slides/raftuserstudy2013.pdf" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">Raft: A Consensus Algorithm for Replicated Logs</a></li></ul></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432287.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 16:34 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432287.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于ZooKeeper的分布式系统的应用场景</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432286.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 08:33:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432286.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432286.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432286.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432286.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432286.html</trackback:ping><description><![CDATA[<div>https://taozj.org/201701/learn-note-of-distributed-system-%286%29-application.html<br /><br /><br /><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">　至此，Paxos、Raft、ZAB代表着分布式系统中最常见的一致性协议都有所了解，但是除了PaxSql之外，对于分布式系统一致性原理的实际应用还处于一脸懵逼的状态中。此处于是主要借着<a href="https://book.douban.com/subject/26292004/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">《从Paxos到Zookeeper:分布式一致性原理与实践》</a>中的案例，依靠ZooKeeper分布式系统组建，对分布式系统的使用情境做一个简单的了解。毕竟嘛，我又不是做学术的，了解原理后不知道现实有什么用处又有啥意义&#8230;<br />　　ZooKeeper扮演者分布式系统的管理和协调框架的作用，通过前面对ZAB和ZooKeeper的了解，ZooKeeper通过ZNode数据模型和序列号、持久和临时节点分类，以及Watcher事件通知机制，可以快速构建分布式应用的核心功能，看看Apache旗下那么多基于ZooKeeper的分布式项目就可晓而知了。ZooKeeper的集群达到3台就可以正常工作了，但是工业上通常认为至少达到5台才是合适的，一方面机器增多集群的可靠性增加了，再次集群工作过程中常常会有机器下线维护的情况，这样在一台机器下线的时候集群还可以容易一台机器宕机而不停止服务。<br /><a href="https://taozj.org/post_images/images/201701/3ac72ee6.jpg" title="zookeeper" rel="fancy-group"  fancybox"="" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;"><img src="https://taozj.org/post_images/images/201701/3ac72ee6.jpg" width="undefined" height="undefined" title="zookeeper" alt="zookeeper" style="margin: 0.5em auto; padding: 0px; border: none; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; max-width: 100%;" /></a><br />　　再次说明，个人没什么分布式系统的经验，这些都是查阅资料摘抄得来，如果错误尽请指正，被我误导概不负责！</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">一、数据发布/订阅(配置中心)</strong>&nbsp;- Publish/Subscribe<br />　　发布/订阅模式不仅仅会出现在这里，通常消息队列的设计中会更加常用到这种模式。发布/订阅模式功能可以分为这几种模式：<br />　　(1). Push模式是服务器主动将数据更新发送给所有订阅的客户端，优点是实时性好，但是服务端的工作比较繁重，常常需要记录各个客户端的状态信息，甚至要考虑消费者的消费能力实现流量控制工作；<br />　　(2). Pull模式则是由客户端主动发起请求来获取最新数据，通常采用定时机制轮训拉取的方式进行，所以实时性较差，但好处是实现简单；<br />　　(3). 还有就是结合两者进行推拉相结合的方式，客户端向服务端注册自己关心的主题，一旦主题数据有所更新，服务端会向对应订阅的客户端发送事件通知，客户端接收到事件通知后主动向服务器拉取最新的数据。<a id="more" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; color: #258fb8;"></a><br />　　在系统开发中通常会遇到机器列表信息、运行时开关配置、数据库配置等信息，这些数据的特点是<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">数据规模比较小</strong>、<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">数据内容在运行时候常常发生动态变更</strong>、<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">相同的数据在多个机器中共享</strong>。在单机的情况下，可以通过共享内存，或者统一配置文件(可以使用文件系统的特性监测文件变更iNotify)来满足这个需求，但是如果配置需要跨主机，尤其机器规模大且成员会发生变动的情况下共享这些动态信息就较为困难。<br />　　基于ZooKeeper的配置中心方案可以这样设计：首先运行一个ZooKeeper服务器集群，然后在其上面创建周知路径的ZNode；专用的配置管理程序可以修改ZNode上的配置信息，作为用户更新配置的操作接口；关心这些配置的分布式应用启动时候主动获取配置信息一次，然后对ZNode注册Watcher监听，那么当ZNode的数据内容发生变化后，ZooKeeper就可以将这个变更通知发送给所有的客户端，客户端得知这个变更通知后就可以请求获取最新数据。通过这种手段，就可以实现配置信息的集中式管理和动态更新部署的功能了。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">二、负载均衡</strong>&nbsp;- Load Balance<br />　　负载均衡在前面的Nginx中也介绍过了，Nginx支持3层和7层软件负载均衡，对用户看来这实际上是实现的基于服务端的负载均衡操作。其实负载均衡还可以在客户端实现，之前介绍的memcached的负载均衡基本都是在客户端通过一致性hash原理实现的。<br />　　通过ZooKeeper实现的负载均衡也是通过客户端来实现的：ZooKeeper创建一个周知路径的ZNode，其数据内容包含了可以提供服务的服务器地址信息，接收服务的客户端在该ZNode上注册Watcher以侦听它的改变。在工作的时候客户端获取提供服务的服务器列表，在接收到修改事前之前可以缓存该列表提高性能，然后服务调用者可以采用自己某种特定负载均衡算法(比如Round Robin、HASH、响应时间、最少连接等算法)选取机器获取服务。<br />　　为了使用方便，服务机器列表的配置可以采用全自动的方式，这也涉及到机器健康度检测问题。可以设计一个健康度探测服务端，并负责更新ZNode中的机器列表：健康度探测服务端可以同机器中的列表建立TCP长连接，健康度探测服务端采用Ping-Pong心跳监测的方式进行健康度监测；也可以机器每隔一定的时间向这个健康度探测服务端发送数据包，如果健康度探测在某个超时时间后仍未收到某机器的数据包，就认定其不可用而将其从机器列表中进行删除。(呃，后面想想，每个服务端机器把自己作为临时ZNode创建在某个路径下，这样的侦测操作不是更加的方便？和集群管理重了)</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">三、命名服务</strong>&nbsp;- Name Service<br />　　命名服务其实是指明了一种映射关系，在分布式开发中有很多的资源需要命名，比如主机地址，RPC服务列表等，客户端根据名字可以获取资源的实体、服务地址和提供者等信息。<br />　　ZooKeeper提供了方便的API，可以轻松的创建全局唯一的path，这个path就可以作为名称使用，所以ZooKeeper的Name Service是开箱即用的。而且ZNode支持SEQUENTIAL属性，通过创建顺序节点的手法就可以创建具有相同名字但带顺序后缀的这样很具有规则性的名字，这样的命名服务显然在保证命名唯一性的同时更具有解释意义。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">四、分布式协调/通知</strong>&nbsp;- Coordinator<br />　　得益于ZooKeeper特有的Watcher注册和异步通知的机制，可以实现分布式环境下不同机器，甚至是不同系统之间的通知和协调工作，以应对于数据变更的实时快速处理，这是和观察者类似的工作模式。而且通过ZooKeepr作为一个事件的中介者，也起到了业务之间解除耦合的效果。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">五、集群管理</strong>&nbsp;- Management<br />　　用于对集群中的机器进行监控的情况，主要包括对集群运行状态的收集，以及对集群和集群成员进行操作和控制。<br />　　传统的运维都是基于Agent的分布式集群管理模式，通过在集群成员上部署该类服务软件，该软件负责主动向集群控制中心汇报机器的状态信息。这种方式虽然直接，但是具有着固有的缺陷：很难实现跟业务相关的细化监控，通常都是对CPU、负载等通用信息进行监测；如果整个集群环境一致还可以，否则就必须面对集群中的异构成员进行兼容和适配的问题。<br />　　如果运行一个ZooKeeper集群，不仅通过临时节点在会话结束后会自动消失的特性，可以快速侦测机器的上下线，而且可以通过创建特定的ZNode，集群中的机器就可以向控制中心报告更多主机相关甚至业务相关的信息，而且这一切操作都极为便利，只需要在业务端和控制中心进行适配就可以了。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">六、Master选举</strong>&nbsp;- Election<br />　　可以应用于那些只需要一个主节点做特殊的工作，其他节点做普通的工作或者候命作为冗余节点的情况，比如数据库的读写分离可以让Master处理所有的写任务，而剩余的读请求由其他机器负载均衡完成，借此提供整个数据库性能；类似的Master可以单独处理一些复杂的逻辑或者计算，而将计算结果同步共享给集群中的其他主机使用，以此减少重复劳动提高资源利用率。<br />　　传统情况下的选主可以使用数据库唯一性索引简单实现，在此约束下所有的机器创建同一条记录时候只能有一个机器成功，那么可以让这个独一无二的机器作为Master，但是这种方法只是解决了竞争冲突问题，无法解决Master单点故障后整个系统不可用的问题，即不能实现高效快速的动态Master选举功能。<br />　　对此，ZooKeeper的强一致性可以天然解决Master选举的问题(注意这里的选主是客户端的Master，和ZAB协议中的Leader没有关系)：首先ZooKeeper保证在多个客户端请求创建同一路径描述的ZNode的情况下，只会有一个客户端的请求成功；其次创建ZNode可以是临时ZNode，那么一旦创建这个临时ZNode的Master挂掉后会导致会话结束，这个临时ZNode就会自动消失；在之前竞争Master失败的客户端，可以注册该ZNode的Watcher侦听，一旦接收到节点的变更事件，就表示Master不可用了，此时大家就可以即刻再次发起Master选举操作了，以实现了一种高可用的automatic fail-over机制，满足了机器在线率有较高要求的应用场景。<br />　　除了上面的方式，各个主机还可以通过创建临时顺序ZNode的方式，每个主机会具有不同的后缀，一旦当前的Master宕机之后自动轮训下一个可用机器，而下线的机器也可以随时再次上线创建新序列号的临时顺序节点。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">七、分布式锁</strong>&nbsp;- Lock<br />　　主要用来进行分布式系统之间的访问资源的同步手段。在使用中分布式锁可以支持这些服务：保持独占、共享使用和时序访问。虽然关系数据库在更新的时候，数据库系统会根据隔离等级自动使用行锁、表锁机制保证数据的完整性，但是数据库通常都是大型系统的性能瓶颈之所在，所以如果使用分布式锁可以起到一定的协调作用，那么可以期待增加系统的运行效率。<br />　　<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">保持独占</strong>，就是当多个客户端试图获取这把锁的时候，只能有一个可以成功获得该锁，跟上面的Master选举比较类似，当多个竞争者同时尝试创建某个path(例如&#8221;_locknode_/guid-lock-&#8220;)的ZNode时候，ZooKeeper的一致性能够保证只有一个客户端成功，创建成功的客户端也就拥有了这把互斥锁，此时其他客户端可以在这个ZNode上面注册Watcher侦听，以便得到锁变更(如持锁客户端宕机、主动解锁)的情况采取接下来的操作。<br />　　<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">共享使用</strong>类似于读写锁的功能，多个读锁可以同时对一个事务可见，但是读锁和写锁以及写锁和写锁之间是互斥的。锁的名字按照类似&#8221;/share_lock/[Host]-R/W-SN&#8221;的形式创建临时顺序节点，在创建锁的同时读取/share_lock节点下的所有子节点，并注册对/share_lock/节点的Watcher侦听，然后依据读写所的兼容法则检查比自己序号小的节点看是否可以满足当前操作请求，如果不满足就执行等待。当持有锁的节点崩溃或者释放锁之后，所有处于等待状态的节点就都得到了通知，实际中这会产生一个&#8220;惊群效应&#8221;，所以可以在上面注册/share_lock/的Watcher事件进行细化，只注册比自己小的那个子节点的Watcher侦听就可以了，以避免不必要的唤醒。<br />　　<strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">时序访问</strong>的方式，则是在这个时候每个请求锁的客户端都可以创建临时顺序ZNode的子节点，他们维系着一个带有序列号的后缀，同时添加对锁节点(或者像上面类似优化，只注册序列号比自己小的那个子节点)的Watcher侦听，这样前面的客户端释放锁之后，后面的客户端会得到事件通知，然后按照一定顺序接下来的一个客户端获得锁。该模式能够保证每个客户端都具有访问的机会，但是其是按照创建临时顺序子节点的顺序按次序依次访问的。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">八、分布式队列</strong>&nbsp;- Queue<br />　　说到分布式队列，目前已有相当多的成熟消息中间件了。在ZooKeeper的基础上，可以方便地创建先进先出队列，以及对数据进行聚集之后再统一安排处理的Barrier的工作模式。<br />　　先入先出之FIFO队列算是最常见使用的数据模型了，类似于一个生产者-消费者的工作模型。生产者会创建顺序ZNode，这些顺序ZNode的后缀表明了创建的顺序，消费者获得这些顺序ZNode后挑出序列号最小的进行消费，就简单的实现了FIFO的数据类型。<br />　　对于另外一种Barrier(叫做屏障)工作模式，是需要把数据集聚之后再做统一处理，通常在大规模并行计算的场景上会使用到。这种队列实现也很简单，就是在创建顺序ZNode的时候记录队列当前已经拥有的事务数目，如果达到了Barrier的数目，就表示条件就绪了于是创建类似&#8220;/synchronizing/start&#8221;的ZNode，而等待处理的消费者之前就侦听该ZNode是否被创建，如果侦测到一旦创建了就表明事务的数目满足需求了，于是可以启动处理工作。</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);"><strong style="margin: 0px; padding: 0px; border: 0px; outline: 0px; font-style: inherit; font-family: inherit; vertical-align: baseline;">小结</strong><br />　　其实，通过上面的查看，基于ZooKeeper实现特定需要的分布式应用是比较方便的，而且更可贵的是，上面的应用都是反反复复基于ZooKeeper的那几条性质实现的！ZooKeeper真是个好东西！</p><p style="margin-top: 0.85em; margin-bottom: 0.85em; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; line-height: 1.7; color: #333333; background-color: rgba(255, 255, 255, 0.45098);">本文完！</p><h1>参考</h1><ul style="margin: 10px 0px; padding: 0px; border: 0px; outline: 0px; font-family: 'Helvetica Neue', 'Luxi Sans', 'DejaVu Sans', Tahoma, 'Hiragino Sans GB', NSimsun, 'Lucida Grande', Verdana, 'Helvetica Neue', Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; font-size: 16px; vertical-align: baseline; list-style: none; color: #333333; line-height: 26.4px; background-color: rgba(255, 255, 255, 0.45098);"><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://book.douban.com/subject/26292004/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">从Paxos到Zookeeper:分布式一致性原理与实践</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="http://www.ibm.com/developerworks/library/bd-zookeeper/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">ZooKeeper fundamentals, deployment, and applications</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="http://zookeeper.apache.org/doc/trunk/recipes.html" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">ZooKeeper Recipes and Solutions</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://my.oschina.net/galenz/blog/315240" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">ZooKeeper典型应用场</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; color: #99cc99;">分布式服务框架 Zookeeper &#8211; 管理分布式环境中的数据</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="https://www.nginx.com/blog/nginx-and-zookeeper-dynamic-load-balancing-and-deployments/" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">NGINX and ZooKeeper, Dynamic Load Balancing and Deployments</a></li><li style="margin: 0.1em 0.1em 0.1em 1.1em; padding: 0px; border: 0px; outline: 0px; font-weight: inherit; font-style: inherit; font-family: inherit; font-size: 1rem; vertical-align: baseline; text-rendering: auto; -webkit-font-smoothing: antialiased; list-style-type: disc;"><a href="http://www.kuqin.com/system-analysis/20111120/315148.html" target="_blank" rel="external" style="margin: 0px; padding: 0px; border: 0px; outline: none; font-weight: inherit; font-style: inherit; font-family: inherit; vertical-align: baseline; text-decoration: none; color: #258fb8;">ZooKeeper典型使用场景一览</a></li></ul></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432286.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 16:33 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432286.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统理论基础 - 时间、时钟和事件顺序</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:44:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432285.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432285.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432285.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5448766.html<br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><blockquote style="padding: 0px 0px 0px 45px; margin: 0px auto; color: #666666; font-size: 13px; width: 827.094px; background-image: url(&quot;images/bq.gif&quot;); background-color: #ffffff; background-position: 0% 0%; background-repeat: no-repeat;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">十六号&#8230;&#8230; 四月十六号。一九六零年四月十六号下午三点之前的一分钟你和我在一起，因为你我会记住这一分钟。从现在开始我们就是一分钟的朋友，这是事实，你改变不了，因为已经过去了。我明天会再来。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp; &nbsp; &#8212;&#8212; 《阿飞正传》</p></blockquote><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">现实生活中时间是很重要的概念，时间可以记录事情发生的时刻、比较事情发生的先后顺序。分布式系统的一些场景也需要记录和比较不同节点间事件发生的顺序，但不同于日常生活使用物理时钟记录时间，分布式系统使用逻辑时钟记录事件顺序关系，下面我们来看分布式系统中几种常见的逻辑时钟。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>物理时钟 vs 逻辑时钟</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">可能有人会问，为什么分布式系统不使用物理时钟(physical clock)记录事件？每个事件对应打上一个时间戳，当需要比较顺序的时候比较相应时间戳就好了。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">这是因为现实生活中物理时间有统一的标准，而分布式系统中每个节点记录的时间并不一样，即使设置了&nbsp;<a href="http://www.zhihu.com/question/24960940" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">NTP</a>&nbsp;时间同步节点间也存在毫秒级别的偏差<sup>[1][2]</sup>。因而分布式系统需要有另外的方法记录事件顺序关系，这就是逻辑时钟(logical clock)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160501132311347-349996615.jpg" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Lamport timestamps</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><a href="https://en.wikipedia.org/wiki/Leslie_Cheung" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leslie</a>&nbsp;<a href="https://en.wikipedia.org/wiki/Leslie_Lamport" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Lamport</a>&nbsp;在1978年提出逻辑时钟的概念，并描述了一种逻辑时钟的表示方法，这个方法被称为Lamport时间戳(Lamport timestamps)<sup>[3]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">分布式系统中按是否存在节点交互可分为三类事件，一类发生于节点内部，二是发送事件，三是接收事件。Lamport时间戳原理如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160501174922566-1686627384.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图1: Lamport timestamps space time (图片来源: wikipedia)</em></p><ol style="padding-left: 40px;"><li style="padding: 0px; list-style-type: decimal;">每个事件对应一个Lamport时间戳，初始值为0</li><li style="padding: 0px; list-style-type: decimal;">如果事件在节点内发生，时间戳加1</li><li style="padding: 0px; list-style-type: decimal;">如果事件属于发送事件，时间戳加1并在消息中带上该时间戳</li><li style="padding: 0px; list-style-type: decimal;">如果事件属于接收事件，时间戳 = Max(本地时间戳，消息中的时间戳) + 1</li></ol><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">假设有事件a、b，C(a)、C(b)分别表示事件a、b对应的Lamport时间戳，如果C(a) &lt; C(b)，则有a发生在b之前(happened before)，记作 a -&gt; b，例如图1中有 C1 -&gt; B1。通过该定义，事件集中Lamport时间戳不等的事件可进行比较，我们获得事件的<a href="https://en.wikipedia.org/wiki/Partially_ordered_set#Formal_definition" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">偏序关系</a>(partial order)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">如果C(a) = C(b)，那a、b事件的顺序又是怎样的？假设a、b分别在节点P、Q上发生，P<sub>i、</sub>Q<sub>j</sub>分别表示我们给P、Q的编号，如果&nbsp;C(a) = C(b) 并且&nbsp;P<sub>i</sub>&lt;<sub>&nbsp;</sub>Q<sub>j</sub>，同样定义为a发生在b之前，记作 a =&gt; b。假如我们对图1的A、B、C分别编号A<sub>i</sub>&nbsp;= 1、B<sub>j</sub>&nbsp;= 2、C<sub>k</sub>&nbsp;= 3，因 C(B4) = C(C3) 并且 B<sub>j</sub>&nbsp;&lt; C<sub>k</sub>，则 B4 =&gt; C3。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">通过以上定义，我们可以对所有事件排序、获得事件的<a href="https://en.wikipedia.org/wiki/Total_order" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">全序关系</a>(total order)。上图例子，我们可以从C1到A4进行排序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Vector clock</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Lamport时间戳帮助我们得到事件顺序关系，但还有一种顺序关系不能用Lamport时间戳很好地表示出来，那就是同时发生关系(concurrent)<sup>[4]</sup>。例如图1中事件B4和事件C3没有因果关系，属于同时发生事件，但Lamport时间戳定义两者有先后顺序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Vector clock是在Lamport时间戳基础上演进的另一种逻辑时钟方法，它通过vector结构不但记录本节点的Lamport时间戳，同时也记录了其他节点的Lamport时间戳<sup>[5][6]</sup>。Vector clock的原理与Lamport时间戳类似，使用图例如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160502134654404-1109556515.png" alt="" width="656" height="371" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图2: Vector clock space time (<em>图片来源: wikipedia)</em></em></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">假设有事件a、b分别在节点P、Q上发生，Vector clock分别为T<sub>a</sub>、T<sub>b</sub>，如果 T<sub>b</sub>[Q] &gt; T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] &gt;= T<sub>a</sub>[P]，则a发生于b之前，记作 a -&gt; b。到目前为止还和Lamport时间戳差别不大，那Vector clock怎么判别同时发生关系呢？</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">如果&nbsp;T<sub>b</sub>[Q] &gt; T<sub>a</sub>[Q] 并且 T<sub>b</sub>[P] &lt; T<sub>a</sub>[P]，则认为a、b同时发生，记作 a &lt;-&gt; b。例如图2中节点B上的第4个事件 (A:2，B:4，C:1) 与节点C上的第2个事件 (B:3，C:2) 没有因果关系、属于同时发生事件。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>Version vector</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">基于Vector clock我们可以获得任意两个事件的顺序关系，结果或为先后顺序或为同时发生，识别事件顺序在工程实践中有很重要的引申应用，最常见的应用是发现数据冲突(detect conflict)。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">分布式系统中数据一般存在多个副本(replication)，多个副本可能被同时更新，这会引起副本间数据不一致<sup>[7]</sup>，Version vector的实现与Vector clock非常类似<sup>[8]</sup>，目的用于发现数据冲突<sup>[9]</sup>。下面通过一个例子说明Version vector的用法<sup>[10]</sup>：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/116770/201605/116770-20160502183034013-800335383.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><em>图3: Version vector</em></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">client端写入数据，该请求被S<sub>x</sub>处理并创建相应的vector ([S<sub>x</sub>, 1])，记为数据D1</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第2次请求也被S<sub>x</sub>处理，数据修改为D2，vector修改为([S<sub>x</sub>, 2])</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第3、第4次请求分别被S<sub>y</sub>、S<sub>z</sub>处理，client端先读取到D2，然后D3、D4被写入S<sub>y</sub>、S<sub>z</sub></li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;">第5次更新时client端读取到D2、D3和D4 3个数据版本，通过类似Vector clock判断同时发生关系的方法可判断D3、D4存在数据冲突，最终通过一定方法解决数据冲突并写入D5</li></ul><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">Vector clock只用于发现数据冲突，不能解决数据冲突。如何解决数据冲突因场景而异，具体方法有以最后更新为准(last write win)，或将冲突的数据交给client由client端决定如何处理，或通过quorum决议事先避免数据冲突的情况发生<sup>[11]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">由于记录了所有数据在所有节点上的逻辑时钟信息，Vector clock和Version&nbsp;vector在实际应用中可能面临的一个问题是vector过大，用于数据管理的元数据(meta data)甚至大于数据本身<sup>[12]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">解决该问题的方法是使用server id取代client id创建vector (因为server的数量相对client稳定)，或设定最大的size、如果超过该size值则淘汰最旧的vector信息<sup>[10][13]</sup>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>小结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">以上介绍了分布式系统里逻辑时钟的表示方法，通过Lamport timestamps可以建立事件的全序关系，通过Vector clock可以比较任意两个事件的顺序关系并且能表示无因果关系的事件，将Vector clock的方法用于发现数据版本冲突，于是有了Version vector。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[1]&nbsp;<a href="https://queue.acm.org/detail.cfm?id=2878574" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Time is an illusion</a>, George Neville-Neil, 2016</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[2]&nbsp;<a href="https://queue.acm.org/detail.cfm?id=2745385&amp;__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&amp;__hssc=53389751.1.1461997241982&amp;__hsfp=1028666893" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">There is No Now</a>,&nbsp;Justin Sheehy, 2015</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[3]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Time, Clocks, and the Ordering of Events in a Distributed System</a>, Leslie Lamport, 1978</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[4]&nbsp;<a href="http://zoo.cs.yale.edu/classes/cs426/2012/lab/bib/fidge88timestamps.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Timestamps in Message-Passing Systems That Preserve the Partial Ordering</a>, Colin J. Fidge, 1988</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[5]&nbsp;<a href="http://www.vs.inf.ethz.ch/publ/papers/VirtTimeGlobStates.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Virtual Time and Global States of Distributed Systems</a>, Friedemann Mattern, 1988</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[6]&nbsp;<a href="http://basho.com/posts/technical/why-vector-clocks-are-easy/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why Vector Clocks are Easy</a>, Bryan Fink, 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[7]&nbsp;<a href="http://guide.couchdb.org/draft/conflicts.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Conflict Management</a>, CouchDB</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[8]&nbsp;<a href="https://haslab.wordpress.com/2011/07/08/version-vectors-are-not-vector-clocks/?__hstc=53389751.f1483a2189ec5c779270b00cdb849993.1461983406379.1461983406379.1461997241982.2&amp;__hssc=53389751.1.1461997241982&amp;__hsfp=1028666893" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Version Vectors are not Vector Clocks</a>, Carlos Baquero,&nbsp;2011</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[9]&nbsp;<a href="http://zoo.cs.yale.edu/classes/cs422/2013/bib/parker83detection.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Detection of Mutual Inconsistency in Distributed Systems</a>, IEEE Transactions on Software Engineering , 1983</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[10]&nbsp;<a href="http://s3.amazonaws.com/AllThingsDistributed/sosp/amazon-dynamo-sosp2007.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Dynamo: Amazon&#8217;s Highly Available Key-value Store</a>, Amazon, 2007</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[11]&nbsp;<a href="http://pl.atyp.us/wordpress/?p=2601" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Conflict Resolution</a>, Jeff Darcy , 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[12]&nbsp;<a href="http://basho.com/posts/technical/why-vector-clocks-are-hard/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why Vector Clocks Are Hard</a>, Justin Sheehy, 2010</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">[13]&nbsp;<a href="http://www.bailis.org/blog/causality-is-expensive-and-what-to-do-about-it/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Causality Is Expensive (and What To Do About It)</a>, Peter Bailis ,2014</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432285.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:44 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432285.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统理论基础 - 选举、多数派和租约</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:25:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432284.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432284.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432284.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5767845.html<br /><br /><br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">选举(election)是分布式系统实践中常见的问题，通过打破节点间的对等关系，选得的leader(或叫master、coordinator)有助于实现事务原子性、提升决议效率。 多数派(quorum)的思路帮助我们在网络分化的情况下达成决议一致性，在leader选举的场景下帮助我们选出唯一leader。租约(lease)在一定期限内给予节点特定权利，也可以用于实现leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">下面我们就来学习分布式系统理论中的选举、多数派和租约。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>选举(electioin)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">一致性问题(consistency)是独立的节点间如何达成决议的问题，选出大家都认可的leader本质上也是一致性问题，因而如何应对宕机恢复、网络分化等在leader选举中也需要考量。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">Bully算法<sup>[1]</sup>是最常见的选举算法，其要求每个节点对应一个序号，序号最高的节点为leader。leader宕机后次高序号的节点被重选为leader，过程如下：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160814110211906-1201598126.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(a). 节点4发现leader不可达，向序号比自己高的节点发起重新选举，重新选举消息中带上自己的序号</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(b)(c). 节点5、6接收到重选信息后进行序号比较，发现自身的序号更大，向节点4返回OK消息并各自向更高序号节点发起重新选举</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(d). 节点5收到节点6的OK消息，而节点6经过超时时间后收不到更高序号节点的OK消息，则认为自己是leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(e). 节点6把自己成为leader的信息广播到所有节点</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">回顾<a href="http://www.cnblogs.com/bangerlee/p/5268485.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">《分布式系统理论基础 - 一致性、2PC和3PC》</a>就可以看到，Bully算法中有2PC的身影，都具有提议(propose)和收集反馈(vote)的过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在一致性算法<a href="http://www.cnblogs.com/bangerlee/p/5655754.html" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Paxos</a>、ZAB<sup>[2]</sup>、Raft<sup>[3]</sup>中，为提升决议效率均有节点充当leader的角色。ZAB、Raft中描述了具体的leader选举实现，与Bully算法类似ZAB中使用zxid标识节点，具有最大zxid的节点表示其所具备的事务(transaction)最新、被选为leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>多数派(quorum)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在网络分化的场景下以上Bully算法会遇到一个问题，被分隔的节点都认为自己具有最大的序号、将产生多个leader，这时候就需要引入多数派(quorum)<sup>[4]</sup>。多数派的思路在分布式系统中很常见，其确保网络分化情况下决议唯一。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">多数派的原理说起来很简单，假如节点总数为2f+1，则一项决议得到多于 f 节点赞成则获得通过。leader选举中，网络分化场景下只有具备多数派节点的部分才可能选出leader，这避免了多leader的产生。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160814195846250-9979865.png" alt="" width="601" height="318" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">多数派的思路还被应用于副本(replica)管理，根据业务实际读写比例调整写副本数V<sub>w</sub>、读副本数V<sub>r</sub>，用以在可靠性和性能方面取得平衡<sup>[5]</sup>。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>租约(lease)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">选举中很重要的一个问题，以上尚未提到：怎么判断leader不可用、什么时候应该发起重新选举？最先可能想到会通过心跳(heart beat)判别leader状态是否正常，但在网络拥塞或瞬断的情况下，这容易导致出现双主。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约(lease)是解决该问题的常用方法，其最初提出时用于解决分布式缓存一致性问题<sup>[6]</sup>，后面在分布式锁<sup>[7]</sup>等很多方面都有应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201608/116770-20160821195833933-818514275.png" alt="" width="449" height="236" style="border: 0px; max-width: 900px;" />&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约的原理同样不复杂，中心思想是每次租约时长内只有一个节点获得租约、到期后必须重新颁发租约。假设我们有租约颁发节点Z，节点0、1和2竞选leader，租约过程如下：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(a). 节点0、1、2在Z上注册自己，Z根据一定的规则(例如先到先得)颁发租约给节点，该租约同时对应一个有效时长；这里假设节点0获得租约、成为leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">(b). leader宕机时，只有租约到期(timeout)后才重新发起选举，这里节点1获得租约、成为leader</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">租约机制确保了一个时刻最多只有一个leader，避免只使用心跳机制产生双主的问题。在实践应用中，zookeeper、ectd可用于租约颁发。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>小结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在分布式系统理论和实践中，常见leader、quorum和lease的身影。分布式系统内不一定事事协商、事事民主，leader的存在有助于提升决议效率。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">本文以leader选举作为例子引入和讲述quorum、lease，当然quorum和lease是两种思想，并不限于leader选举应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">最后提一个有趣的问题与大家思考，leader选举的本质是一致性问题，Paxos、Raft和ZAB等解决一致性问题的协议和算法本身又需要或依赖于leader，怎么理解这个看似&#8220;蛋生鸡、鸡生蛋&#8221;的问题？<sup>[8]</sup></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[1]&nbsp;<a href="http://homepage.divms.uiowa.edu/~ghosh/Bully.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Elections in a Distributed Computing System</a>, Hector Garcia-Molina, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[2]&nbsp;<a href="http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">ZooKeeper&#8217;s atomic broadcast protocol: Theory and practice</a>, Andre Medeiros, 2012</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[3]&nbsp;<a href="https://ramcloud.atlassian.net/wiki/download/attachments/6586375/raft.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">In Search of an Understandable Consensus Algorithm</a>, Diego Ongaro and John Ousterhout, 2013</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[4]&nbsp;<a href="https://ecommons.cornell.edu/bitstream/handle/1813/6323/82-483.pdf?sequence=1" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">A quorum-based commit protocol</a>, Dale Skeen, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[5]&nbsp;<a href="http://lass.cs.umass.edu/~shenoy/courses/spring04/677/readings/gifford.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Weighted Voting for Replicated Data</a>, David K. Gifford, 1979</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[6]&nbsp;<a href="http://web.stanford.edu/class/cs240/readings/89-leases.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency</a>, Cary G. Gray and David R. Cheriton, 1989</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[7]&nbsp;<a href="http://research.google.com/archive/chubby-osdi06.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">The Chubby lock service for loosely-coupled distributed systems</a>, Mike Burrows, 2006</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[8]&nbsp;<a href="http://stackoverflow.com/questions/23798724/why-is-paxos-leader-election-not-done-using-paxos" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Why is Paxos leader election not done using Paxos?</a></p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432284.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:25 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432284.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式系统理论基础 - 一致性、2PC和3PC</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 07:02:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432283.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432283.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432283.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/bangerlee/p/5268485.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>引言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">狭义的分布式系统指由网络连接的计算机系统，每个节点独立地承担计算或存储任务，节点间通过网络协同工作。广义的分布式系统是一个相对的概念，正如<a href="https://en.wikipedia.org/wiki/Leslie_Lamport" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Leslie Lamport</a>所说<sup>[1]</sup>：</p><blockquote style="padding: 0px 0px 0px 45px; margin: 0px auto; color: #666666; font-size: 13px; width: 827.094px; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-image: url(&quot;images/bq.gif&quot;); background-color: #ffffff; background-position: 0% 0%; background-repeat: no-repeat;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">What is a distributed systeme.&nbsp;<strong><span style="line-height: 1.8; color: #000000;">Distribution is in the eye of the beholder</span>.</strong><br />To the user sitting at the keyboard, his IBM personal computer is a nondistributed system.&nbsp;<br />To a flea crawling around on the circuit board, or to the engineer who designed it, it's very much a distributed system.</p></blockquote><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;一致性是分布式理论中的根本性问题，近半个世纪以来，科学家们围绕着一致性问题提出了很多理论模型，依据这些理论模型，业界也出现了很多工程实践投影。下面我们从一致性问题、特定条件下解决一致性问题的两种方法(2PC、3PC)入门，了解最基础的分布式系统理论。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一致性(consensus)</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">何为一致性问题？简单而言，一致性问题就是相互独立的节点之间如何达成一项决议的问题。分布式系统中，进行数据库事务提交(commit transaction)、Leader选举、序列号生成等都会遇到一致性问题。这个问题在我们的日常生活中也很常见，比如牌友怎么商定几点在哪打几圈麻将：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313132041413-375351900.jpg" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>《赌圣》，1990</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">假设一个具有N个节点的分布式系统，当其满足以下条件时，我们说这个系统满足一致性：</p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;"><strong>全认同(agreement)</strong>: 所有N个节点都认同一个结果</li><li style="padding: 0px; list-style-type: decimal;"><strong>值合法(validity)</strong>: 该结果必须由N个节点中的节点提出</li><li style="padding: 0px; list-style-type: decimal;"><strong>可结束(termination)</strong>: 决议过程在一定时间内结束，不会无休止地进行下去</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">有人可能会说，决定什么时候在哪搓搓麻将，4个人商量一下就ok，这不很简单吗？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">但就这样看似简单的事情，分布式系统实现起来并不轻松，因为它面临着这些问题：</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>消息传递异步无序(asynchronous)</strong>: 现实网络不是一个可靠的信道，存在消息延时、丢失，节点间消息传递做不到同步有序(synchronous)</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>节点宕机(fail-stop)</strong>: 节点持续宕机，不会恢复</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>节点宕机恢复(fail-recover)</strong>: 节点宕机一段时间后恢复，在分布式系统中最常见</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>网络分化(network partition)</strong>: 网络链路出现问题，将N个节点隔离成多个部分</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>拜占庭将军问题(byzantine failure)</strong><sup>[2]</sup>: 节点或宕机或逻辑失败，甚至不按套路出牌抛出干扰决议的信息</li></ul><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">假设现实场景中也存在这样的问题，我们看看结果会怎样：</p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; max-width: 600px; font-family: 'Courier New' !important;"><span style="line-height: 1.5 !important;">我: 老王，今晚7点老地方，搓够48圈不见不散！ &#8230;&#8230; （第二天凌晨3点） 隔壁老王: 没问题！       </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 消息延迟</span> <span style="line-height: 1.5 !important;">我: &#8230;&#8230; ---------------------------------------------- 我: 小张，今晚7点老地方，搓够48圈不见不散！ 小张: No &#8230;&#8230;                           </span> <span style="line-height: 1.5 !important;">（两小时后&#8230;&#8230;） 小张: No problem！                     </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 宕机节点恢复</span> <span style="line-height: 1.5 !important;">我: &#8230;&#8230; ----------------------------------------------- 我: 老李头，今晚7点老地方，搓够48圈不见不散！ 老李: 必须的，大保健走起！               </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 拜占庭将军<br /></span>（这是要打麻将呢？还是要大保健？还是一边打麻将一边大保健&#8230;&#8230;）</pre><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div></div><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">还能不能一起愉快地玩耍...<img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313010025194-2394933.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">我们把以上所列的问题称为系统模型(system model)，讨论分布式系统理论和工程实践的时候，必先划定模型。例如有以下两种模型：</p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;">异步环境(asynchronous)下，节点宕机(fail-stop)</li><li style="padding: 0px; list-style-type: decimal;">异步环境(asynchronous)下，节点宕机恢复(fail-recover)、网络分化(network partition)</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">2比1多了节点恢复、网络分化的考量，因而对这两种模型的理论研究和工程解决方案必定是不同的，在还没有明晰所要解决的问题前谈解决方案都是一本正经地耍流氓。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">一致性还具备两个属性，一个是强一致(safety)，它要求所有节点状态一致、共进退；一个是可用(liveness)，它要求分布式系统24*7无间断对外服务。FLP定理(FLP impossibility)<sup>[3][4]&nbsp;</sup>已经证明在一个收窄的模型中(异步环境并只存在节点宕机)，不能同时满足 safety 和 liveness。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">FLP定理是分布式系统理论中的基础理论，正如物理学中的能量守恒定律彻底否定了永动机的存在，FLP定理否定了同时满足safety 和 liveness 的一致性协议的存在。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160314181639599-564845788.jpg" alt="" width="599" height="337" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>《怦然心动 (Flipped)》，2010<br /></em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">工程实践上根据具体的业务场景，或保证强一致(safety)，或在节点宕机、网络分化的时候保证可用(liveness)。2PC、3PC是相对简单的解决一致性问题的协议，下面我们就来了解2PC和3PC。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>2PC</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">2PC(tow phase commit)两阶段提交<sup>[5]</sup>顾名思义它分成两个阶段，先由一方进行提议(propose)并收集其他节点的反馈(vote)，再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator)，其他参与决议节点称为参与者(participants, 或cohorts)：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313202532507-1396598167.png" alt="" width="646" height="310" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>2PC, phase one</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在阶段1中，coordinator发起一个提议，分别问询各participant是否接受。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160313203429600-179395429.png" alt="" width="306" height="308" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>2PC, phase two</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在阶段2中，coordinator根据participant的反馈，提交或中止事务，如果participant全部同意则提交，只要有一个participant不同意就中止。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在异步环境(asynchronous)并且没有节点宕机(fail-stop)的模型下，2PC可以满足全认同、值合法、可结束，是解决一致性问题的一种协议。但如果再加上节点宕机(fail-recover)的考虑，2PC是否还能解决一致性问题呢？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">coordinator如果在发起提议后宕机，那么participant将进入阻塞(block)状态、一直等待coordinator回应以完成该次决议。这时需要另一角色把系统从不可结束的状态中带出来，我们把新增的这一角色叫协调者备份(coordinator watchdog)。coordinator宕机一定时间后，watchdog接替原coordinator工作，通过问询(query) 各participant的状态，决定阶段2是提交还是中止。这也要求&nbsp;coordinator/participant 记录(logging)历史状态，以备coordinator宕机后watchdog对participant查询、coordinator宕机恢复后重新找回状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">从coordinator接收到一次事务请求、发起提议到事务完成，经过2PC协议后增加了2次RTT(propose+commit)，带来的时延(latency)增加相对较少。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>3PC</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">3PC(three phase commit)即三阶段提交<sup>[6][7]</sup>，既然2PC可以在异步网络+节点宕机恢复的模型下实现一致性，那还需要3PC做什么，3PC是什么鬼？</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">在2PC中一个participant的状态只有它自己和coordinator知晓，假如coordinator提议后自身宕机，在watchdog启用前一个participant又宕机，其他participant就会进入既不能回滚、又不能强制commit的阻塞状态，直到<span style="line-height: 1.5;">participant宕机恢复。这引出两个疑问：</span></p><ol style="padding-left: 40px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px; list-style-type: decimal;">能不能去掉阻塞，使系统可以在commit/abort前回滚(rollback)到决议发起前的初始状态</li><li style="padding: 0px; list-style-type: decimal;">当次决议中，participant间能不能相互知道对方的状态，又或者participant间根本不依赖对方的状态</li></ol><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">相比2PC，3PC增加了一个准备提交(prepare to commit)阶段来解决以上问题：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/116770/201603/116770-20160314002734304-489496391.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><em>图片截取自wikipedia</em></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">coordinator接收完participant的反馈(vote)之后，进入阶段2，给各个participant发送准备提交(prepare to commit)指令。participant接到准备提交指令后可以锁资源，但要求相关操作必须可回滚。coordinator接收完确认(ACK)后进入阶段3、进行commit/abort，3PC的阶段3与2PC的阶段2无异。协调者备份(coordinator watchdog)、状态记录(logging)同样应用在3PC。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">participant如果在不同阶段宕机，我们来看看3PC如何应对：</p><ul style="list-style: none; margin: 10px 10px 10px 30px; padding: 0px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段1</strong>:&nbsp;coordinator或watchdog未收到宕机participant的vote，直接中止事务；宕机的participant恢复后，读取logging发现未发出赞成vote，自行中止该次事务</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段2</strong>:&nbsp;coordinator未收到宕机participant的precommit ACK，但因为之前已经收到了宕机participant的赞成反馈(不然也不会进入到阶段2)，coordinator进行commit；watchdog可以通过问询其他participant获得这些信息，过程同理；宕机的participant恢复后发现收到precommit或已经发出赞成vote，则自行commit该次事务</li><li style="padding: 0px 0px 0px 15px; list-style-type: disc; background-image: url(&quot;images/icon_miniarrow.gif&quot;); background-position: 0px 9px; background-repeat: no-repeat;"><strong>阶段3</strong>: 即便coordinator或watchdog未收到宕机participant的commit ACK，也结束该次事务；宕机的participant恢复后发现收到commit或者precommit，也将自行commit该次事务</li></ul><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">因为有了准备提交(prepare to commit)阶段，3PC的事务处理延时也增加了1个RTT，变为3个RTT(propose+precommit+commit)，但是它防止participant宕机后整个系统进入阻塞态，增强了系统的可用性，对一些现实业务场景是非常值得的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>小结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">以上介绍了分布式系统理论中的部分基础知识，阐述了一致性(consensus)的定义和实现一致性所要面临的问题，最后讨论在异步网络(asynchronous)、节点宕机恢复(fail-recover)模型下2PC、3PC怎么解决一致性问题。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">阅读前人对分布式系统的各项理论研究，其中有严谨地推理、证明，有一种数学的美；观现实中的分布式系统实现，是综合各种因素下妥协的结果。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">&nbsp;</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[1]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/solved-and-unsolved.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Solved Problems, Unsolved Problems and Problems in Concurrency</a>,&nbsp;Leslie Lamport, 1983</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[2]&nbsp;<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/byz.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">The Byzantine Generals Problem</a>,&nbsp;Leslie Lamport,Robert Shostak and Marshall Pease, 1982</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[3]&nbsp;<a href="http://cs-www.cs.yale.edu/homes/arvind/cs425/doc/fischer.pdf" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Impossibility of Distributed Consensus with One Faulty Process</a>,&nbsp;Fischer, Lynch and Patterson, 1985</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[4]&nbsp;<a href="http://danielw.cn/FLP-proof/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">FLP Impossibility的证明</a>,&nbsp;Daniel Wu, 2015</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[5]&nbsp;<a href="http://the-paper-trail.org/blog/consensus-protocols-two-phase-commit/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Consensus Protocols: Two-Phase Commit</a>,&nbsp;Henry Robinson, 2008</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[6]&nbsp;<a href="http://the-paper-trail.org/blog/consensus-protocols-three-phase-commit/" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Consensus Protocols: Three-phase Commit</a>,&nbsp;Henry Robinson, 2008</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">[7]&nbsp;<a href="https://en.wikipedia.org/wiki/Three-phase_commit_protocol" target="_blank" style="outline: none; text-decoration: none; color: #3d81ee; border-bottom-width: 1px; border-bottom-style: dashed;">Three-phase commit protocol</a>,&nbsp;Wikipedia</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432283.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 15:02 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432283.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>CoreOS 实战：剖析 etcd</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 03 Feb 2017 06:12:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432282.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432282.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432282.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.infoq.com/cn/articles/coreos-analyse-etcd/【编者按】CoreOS是一个基于Docker的轻量级容器化Linux发行版，专为大型数据中心而设计，旨在通过轻量的系统架构和灵活的应用程序部署能力简化数据中心的维护成本和复杂度。CoreOS作为Docker生态圈中的重要一员，日益得到各大云服务商的重视，目前已经完成了A轮融资，发展风头正劲。I...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432282.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-02-03 14:12 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/02/03/432282.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>水平分库分表的关键步骤以及可能遇到的问题</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 06:29:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432269.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432269.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432269.html</trackback:ping><description><![CDATA[<div><div>http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-split-table</div><div>http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table</div><br /><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在谈论数据库架构和数据库优化的时候，我们经常会听到&#8220;分库分表&#8221;、&#8220;分片&#8221;、&#8220;Sharding&#8221;&#8230;这样的关键词。让人感到高兴的是，这些朋友所服务的公司业务量正在（或者即将面临）高速增长，技术方面也面临着一些挑战。让人感到担忧的是，他们系统真的就需要&#8220;分库分表&#8221;了吗？&#8220;分库分表&#8221;有那么容易实践吗？为此，笔者整理了分库分表中可能遇到的一些问题，并结合以往经验介绍了对应的解决思路和建议。</p><h2>垂直分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分表在日常开发和设计中比较常见，通俗的说法叫做&#8220;大表拆小表&#8221;，拆分是基于关系型数据库中的&#8220;列&#8221;（字段）进行的。通常情况，某个表中的字段比较多，可以新建立一张&#8220;扩展表&#8221;，将不经常使用或者长度较大的字段拆分出去放到&#8220;扩展表&#8221;中，如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/20.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在字段很多的情况下，拆分开确实更便于开发和维护（笔者曾见过某个遗留系统中，一个大表中包含100多列的）。某种意义上也能避免&#8220;跨页&#8221;的问题（MySQL、MSSQL底层都是通过&#8220;数据页&#8221;来存储的，&#8220;跨页&#8221;问题可能会造成额外的性能开销，这里不展开，感兴趣的朋友可以自行查阅相关资料进行研究）。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分，则需要改写以前的查询语句，会额外带来一定的成本和风险，建议谨慎。</p><h2>垂直分库</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分库在&#8220;微服务&#8221;盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库，而不是像早期一样将所有的数据表都放到同一个数据库中。如下图：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/21.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">系统层面的&#8220;服务化&#8221;拆分操作，能够解决业务系统层面的耦合和性能瓶颈，有利于系统的扩展维护。而数据库层面的拆分，道理也是相通的。与服务的&#8220;治理&#8221;和&#8220;降级&#8221;机制类似，我们也能对不同业务类型的数据进行&#8220;分级&#8221;管理、维护、监控、扩展等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">众所周知，数据库往往最容易成为应用系统的瓶颈，而数据库本身属于&#8220;有状态&#8221;的，相对于Web和应用服务器来讲，是比较难实现&#8220;横向扩展&#8221;的。数据库的连接资源比较宝贵且单机处理能力也有限，在高并发场景下，垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈，是大型分布式系统中优化数据库架构的重要手段。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">然后，很多人并没有从根本上搞清楚为什么要拆分，也没有掌握拆分的原则和技巧，只是一味的模仿大厂的做法。导致拆分后遇到很多问题（例如：跨库join，分布式事务等）。</p><h2>水平分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分表也称为横向分表，比较容易理解，就是将表中不同的数据行按照一定规律分布到不同的数据库表中（这些表保存在同一个数据库中），这样来降低单表数据量，优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/22.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分表，能够降低单表的数据量，一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中，所以库级别还是会有IO瓶颈。所以，一般不建议采用这种做法。</p><h2>水平分库分表</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">水平分库分表与上面讲到的水平分表的思想相同，唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法。如下图：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/23.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">某种意义上来讲，有些系统中使用的&#8220;冷热数据分离&#8221;（将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上，通常默认只提供热点数据的查询），也是类似的实践。在高并发和海量数据的场景下，分库分表能够有效缓解单机和单库的性能瓶颈和压力，突破IO、连接数、硬件资源的瓶颈。当然，投入的硬件成本也会更高。同时，这也会带来一些复杂的技术问题和挑战（例如：跨分片的复杂查询，跨分片事务等）</p><h3>分库分表的难点</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">垂直分库带来的问题和解决思路：</p><h3>跨库join的问题</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在拆分之前，系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后，数据库可能是分布式在不同实例和不同的主机上，join将变得非常麻烦。而且基于架构规范，性能，安全性等方面考虑，一般是禁止跨库join的。那该怎么办呢？首先要考虑下垂直分库的设计问题，如果可以调整，那就优先调整。如果无法调整的情况，下面笔者将结合以往的实际经验，总结几种常见的解决思路，并分析其适用场景。</p><h3>跨库Join的几种解决思路</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">全局表</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">所谓全局表，就是有可能系统中所有模块都可能会依赖到的一些表。比较类似我们理解的&#8220;数据字典&#8221;。为了避免跨库join查询，我们可以将这类表在其他每个数据库中均保存一份。同时，这类数据通常也很少发生修改（甚至几乎不会），所以也不用太担心&#8220;一致性&#8221;问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">字段冗余</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这是一种典型的反范式设计，在互联网行业中比较常见，通常是为了性能来避免join查询。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">举个电商业务中很简单的场景：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">&#8220;订单表&#8221;中保存&#8220;卖家Id&#8221;的同时，将卖家的&#8220;Name&#8221;字段也冗余，这样查询订单详情的时候就不需要再去查询&#8220;卖家用户表&#8221;。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">字段冗余能带来便利，是一种&#8220;空间换时间&#8221;的体现。但其适用场景也比较有限，比较适合依赖字段较少的情况。最复杂的还是数据一致性问题，这点很难保证，可以借助数据库中的触发器或者在业务代码层面去保证。当然，也需要结合实际业务场景来看一致性的要求。就像上面例子，如果卖家修改了Name之后，是否需要在订单信息中同步更新呢？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">数据同步</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">定时A库中的tab_a表和B库中tbl_b有关联，可以定时将指定的表做同步。当然，同步本来会对数据库带来一定的影响，需要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。笔者曾经在项目中是通过ETL工具来实施的。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">系统层组装</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在系统层面，通过调用不同模块的组件或者服务，获取到数据并进行字段拼装。说起来很容易，但实践起来可真没有这么简单，尤其是数据库设计上存在问题但又无法轻易调整的时候。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">具体情况通常会比较复杂。下面笔者结合以往实际经验，并通过伪代码方式来描述。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">简单的列表查询的情况</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/24.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">伪代码很容易理解，先获取&#8220;我的提问列表&#8221;数据，然后再根据列表中的UserId去循环调用依赖的用户服务获取到用户的RealName，拼装结果并返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">有经验的读者一眼就能看出上诉伪代码存在效率问题。循环调用服务，可能会有循环RPC，循环查询数据库&#8230;不推荐使用。再看看改进后的：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/25.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这种实现方式，看起来要优雅一点，其实就是把循环调用改成一次调用。当然，用户服务的数据库查询中很可能是In查询，效率方面比上一种方式更高。（坊间流传In查询会全表扫描，存在性能问题，传闻不可全信。其实查询优化器都是基本成本估算的，经过测试，在In语句中条件字段有索引的时候，条件较少的情况是会走索引的。这里不细展开说明，感兴趣的朋友请自行测试）。</p><h3>小结</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">简单字段组装的情况下，我们只需要先获取&#8220;主表&#8221;数据，然后再根据关联关系，调用其他模块的组件或服务来获取依赖的其他字段（如例中依赖的用户信息），最后将数据进行组装。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">通常，我们都会通过缓存来避免频繁RPC通信和数据库查询的开销。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">列表查询带条件过滤的情况</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">在上述例子中，都是简单的字段组装，而不存在条件过滤。看拆分前的SQL：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-split-table/zh/resources/26.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">这种连接查询并且还带条件过滤的情况，想在代码层面组装数据其实是非常复杂的（尤其是左表和右表都带条件过滤的情况会更复杂），不能像之前例子中那样简单的进行组装了。试想一下，如果像上面那样简单的进行组装，造成的结果就是返回的数据不完整，不准确。&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">有如下几种解决思路：</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; background-color: #ffffff;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">查出所有的问答数据，然后调用用户服务进行拼装数据，再根据过滤字段state字段进行过滤，最后进行排序和分页并返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">这种方式能够保证数据的准确性和完整性，但是性能影响非常大，不建议使用。</p></li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">查询出state字段符合/不符合的UserId，在查询问答数据的时候使用in/not in进行过滤，排序，分页等。过滤出有效的问答数据后，再调用用户服务获取数据进行组装。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">这种方式明显更优雅点。笔者之前在某个项目的特殊场景中就是采用过这种方式实现。</p></li></ol><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">跨库事务（分布式事务）的问题</span></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">按业务拆分数据库之后，不可避免的就是&#8220;分布式事务&#8221;的问题。以往在代码中通过spring注解简单配置就能实现事务的，现在则需要花很大的成本去保证一致性。这里不展开介绍，&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" />感兴趣的读者可以自行参考《分布式事务一致性解决方案》，链接地址：&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" />http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency</p><h2>垂直分库总结和实践建议</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">本篇中主要描述了几种常见的拆分方式，并着重介绍了垂直分库带来的一些问题和解决思路。读者朋友可能还有些问题和疑惑。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">1. 我们目前的数据库是否需要进行垂直分库？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">根据系统架构和公司实际情况来，如果你们的系统还是个简单的单体应用，并且没有什么访问量和数据量，那就别着急折腾&#8220;垂直分库&#8221;了，否则没有任何收益，也很难有好结果。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">切记，&#8220;过度设计&#8221;和&#8220;过早优化&#8221;是很多架构师和技术人员常犯的毛病。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">2. 垂直拆分有没有原则或者技巧？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">没有什么黄金法则和标准答案。一般是参考系统的业务模块拆分来进行数据库的拆分。比如&#8220;用户服务&#8221;，对应的可能就是&#8220;用户数据库&#8221;。但是也不一定严格一一对应。有些情况下，数据库拆分的粒度可能会比系统拆分的粒度更粗。笔者也确实见过有些系统中的某些表原本应该放A库中的，却放在了B库中。有些库和表原本是可以合并的，却单独保存着。还有些表，看起来放在A库中也OK，放在B库中也合理。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">如何设计和权衡，这个就看实际情况和架构师/开发人员的水平了。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;"><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">3. 上面举例的都太简单了，我们的后台报表系统中join的表都有n个了，&nbsp;<br style="margin: 0px; border: 0px; padding: 0px;" /></span><span style="font-weight: 600; margin: 0px; border: 0px; padding: 0px;">分库后该怎么查？</span></p><blockquote style="margin-top: 0px; margin-right: 0px; margin-left: 0px; padding-top: 10px; padding-bottom: 10px; padding-left: 45px; border-width: 1px; border-color: #e8e8e8; clear: both; width: 553px; color: #000000; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: 25.2px; float: none !important; background-image: url(&quot;i/gg.jpg&quot;); background-color: #f4f4f4; background-position: 0% 0%; background-repeat: no-repeat;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 553px; word-wrap: break-word !important;">有很多朋友跟我提过类似的问题。其实互联网的业务系统中，本来就应该尽量避免join的，如果有多个join的，要么是设计不合理，要么是技术选型有误。请自行科普下OLAP和OLTP，报表类的系统在传统BI时代都是通过OLAP数据仓库去实现的（现在则更多是借助离线分析、流式计算等手段实现），而不该向上面描述的那样直接在业务库中执行大量join和统计。</p></blockquote><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 25.2px; clear: none; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; background-color: #ffffff;">由于篇幅关系，下篇中我们再继续细聊&#8220;水平分库分表&#8221;相关的话题。&nbsp;</p><div><ul style="margin: 0px 0px 20px; padding: 5px 0px; list-style: none; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ebf0f3; border-bottom-color: #ebf0f3; float: left; clear: both; width: 969px; position: relative; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><div bdshare-button-style0-16"="" data-bd-bind="1484634439652" style="margin: 0px; border: 0px; zoom: 1;"><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="more" style="text-decoration: none; color: #333333; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px 0px; background-repeat: no-repeat;">分享到：</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="tsina" title="分享到微博" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -104px; background-repeat: no-repeat;">微博</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="weixin" title="分享到微信" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1612px; background-repeat: no-repeat;">微信</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="fbook" title="分享到Facebook" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1352px; background-repeat: no-repeat;">Facebook</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="twi" title="分享到Twitter" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1404px; background-repeat: no-repeat;">Twitter</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="youdao" title="分享到有道云笔记" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2080px; background-repeat: no-repeat;">有道云笔记</a><a href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" data-cmd="mail" title="分享到邮件分享" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2340px; background-repeat: no-repeat;">邮件分享</a></div></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a id="bookmarkBtn" href="http://www.infoq.com/cn/articles/key-steps-and-likely-problems-of-horizontal-split-table#" title="" style="text-decoration: none; color: #ffffff; margin: -2px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/read_later_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-right-color: #0c3b6f; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/read_later_right.jpg&quot;) 0% 50% repeat-x;">稍后阅读</q></a></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a href="http://www.infoq.com/cn/showbookmarks.action" style="text-decoration: none; color: #133660; margin: -2px 104px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/reading_list_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-width: 0px 1px 0px 0px; border-right-style: solid; border-right-color: #afc0c7; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/reading_list_right.jpg&quot;) 0% 50% repeat-x;">我的阅读清单</q></a></li></ul><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><div text_content_container"="" style="margin: 0px; border: 0px; float: left; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><div text_info_article"="" style="margin: 10px 0px; border: 0px; float: left; clear: both; width: 610px; line-height: 1.8;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><a href="http://mp.weixin.qq.com/s?__biz=MzA5Nzc4OTA1Mw==&amp;mid=2659598135&amp;idx=1&amp;sn=2f1daf51d92b9c5ed06d9422fdd19d49&amp;scene=21#wechat_redirect" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">在之前的文章中</a>，我介绍了分库分表的几种表现形式和玩法，也重点介绍了垂直分库所带来的问题和解决方法。本篇中，我们将继续聊聊水平分库分表的一些技巧。</p><h2>分片技术的由来</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">关系型数据库本身比较容易成为系统性能瓶颈，单机存储容量、连接数、处理能力等都很有限，数据库本身的&#8220;有状态性&#8221;导致了它并不像Web和应用服务器那么容易扩展。在互联网行业海量数据和高并发访问的考验下，聪明的技术人员提出了分库分表技术（有些地方也称为Sharding、分片）。同时，流行的分布式系统中间件（例如MongoDB、ElasticSearch等）均自身友好支持Sharding，其原理和思想都是大同小异的。</p><h2>分布式全局唯一ID</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在很多中小项目中，我们往往直接使用数据库自增特性来生成主键ID，这样确实比较简单。而在分库分表的环境中，数据分布在不同的分片上，不能再借助数据库自增长特性直接生成，否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">&nbsp;</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">Twitter的Snowflake（又名&#8220;雪花算法&#8221;）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">UUID/GUID（一般应用程序和数据库均支持）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">MongoDB ObjectID（类似UUID的方式）</li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;">Ticket Server（数据库生存方式，Flickr采用的就是这种方式）</li></ol><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其中，Twitter 的Snowflake算法是笔者近几年在分布式系统项目中使用最多的，未发现重复或并发的问题。该算法生成的是64位唯一Id（由41位的timestamp+ 10位自定义的机器码+ 13位累加计数器组成）。这里不做过多介绍，感兴趣的读者可自行查阅相关资料。</p><h2>常见分片规则和策略</h2><h3>分片字段该如何选择</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在开始分片之前，我们首先要确定分片字段（也可称为&#8220;片键&#8221;）。很多常见的例子和场景中是采用ID或者时间字段进行拆分。这也并不绝对的，我的建议是结合实际业务，通过对系统中执行的sql语句进行统计分析，选择出需要分片的那个表中最频繁被使用，或者最重要的字段来作为分片字段。</p><h3>常见分片规则</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">常见的分片策略有随机分片和连续分片这两种，如下图所示：</p><div id="lowerFullwidthVCR" style="margin: 0px; border: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/10.png" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">当需要使用分片字段进行范围查找时，连续分片可以快速定位分片进行高效查询，大多数情况下可以有效避免跨分片查询的问题。后期如果想对整个分片集群扩容时，只需要添加节点即可，无需对其他分片的数据进行迁移。但是，连续分片也有可能存在数据热点的问题，就像图中按时间字段分片的例子，有些节点可能会被频繁查询压力较大，热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据，很少需要被查询到。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">随机分片其实并不是随机的，也遵循一定规则。通常，我们会采用Hash取模的方式进行分片拆分，所以有些时候也被称为离散分片。随机分片的数据相对比较均匀，不容易出现热点和并发访问的瓶颈。但是，后期分片集群扩容起来需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题，所以很多中间件的分片集群都会采用一致性Hash算法。离散分片也很容易面临跨分片查询的复杂问题。</p><h3>数据迁移，容量规划，扩容等问题</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">很少有项目会在初期就开始考虑分片设计的，一般都是在业务高速发展面临性能和存储的瓶颈时才会提前准备。因此，不可避免的就需要考虑历史数据迁移的问题。一般做法就是通过程序先读出历史数据，然后按照指定的分片规则再将数据写入到各个分片节点中。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">此外，我们需要根据当前的数据量和QPS等进行容量规划，综合成本因素，推算出大概需要多少分片（一般建议单个分片上的单表数据量不要超过1000W）。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">如果是采用随机分片，则需要考虑后期的扩容问题，相对会比较麻烦。如果是采用的范围分片，只需要添加节点就可以自动扩容。</p><h2>跨分片技术问题</h2><h3>跨分片的排序分页</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">一般来讲，分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候，我们通过分片规则可以比较容易定位到指定的分片，而当排序字段非分片字段的时候，情况就会变得比较复杂了。为了最终结果的准确性，我们需要在不同的分片节点中将数据进行排序并返回，并将不同分片返回的结果集进行汇总和再次排序，最后再返回给用户。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/11.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">上面图中所描述的只是最简单的一种情况（取第一页数据），看起来对性能的影响并不大。但是，如果想取出第10页数据，情况又将变得复杂很多，如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/12.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有些读者可能并不太理解，为什么不能像获取第一页数据那样简单处理（排序取出前10条再合并、排序）。其实并不难理解，因为各分片节点中的数据可能是随机的，为了排序的准确性，必须把所有分片节点的前N页数据都排序好后做合并，最后再进行整体的排序。很显然，这样的操作是比较消耗资源的，用户越往后翻页，系统性能将会越差。</p><h3>跨分片的函数处理</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候，需要先在每个分片数据源上执行相应的函数处理，然后再将各个结果集进行二次处理，最终再将处理结果返回。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/13.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h3>跨分片join</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">Join是关系型数据库中最常用的特性，但是在分片集群中，join也变得非常复杂。应该尽量避免跨分片的join查询（这种场景，比上面的跨分片分页更加复杂，而且对性能的影响很大）。通常有以下几种方式来避免：</p><h3>全局表</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">全局表的概念之前在&#8220;垂直分库&#8221;时提过。基本思想一致，就是把一些类似数据字典又可能会产生join查询的表信息放到各分片中，从而避免跨分片的join。</p><h3>ER分片</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在关系型数据库中，表之间往往存在一些关联的关系。如果我们可以先确定好关联关系，并将那些存在关联关系的表记录存放在同一个分片上，那么就能很好的避免跨分片join问题。在一对多关系的情况下，我们通常会选择按照数据较多的那一方进行拆分。如下图所示：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn4.infoqstatic.com/statics_s2_20170116-0538/resource/articles/key-steps-and-likely-problems-of-horizontal-split-table/zh/resources/14.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这样一来，Data Node1上面的订单表与订单详细表就可以直接关联，进行局部的join查询了，Data Node2上也一样。基于ER分片的这种方式，能够有效避免大多数业务场景中的跨分片join问题。</p><h3>内存计算</h3><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">随着spark内存计算的兴起，理论上来讲，很多跨数据源的操作问题看起来似乎都能够得到解决。可以将数据丢给spark集群进行内存计算，最后将计算结果返回。</p><h2>跨分片事务问题</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">跨分片事务也分布式事务，想要了解分布式事务，就需要了解&#8220;XA接口&#8221;和&#8220;两阶段提交&#8221;。值得提到的是，MySQL5.5x和5.6x中的xa支持是存在问题的，会导致主从数据不一致。直到5.7x版本中才得到修复。Java应用程序可以采用Atomikos框架来实现XA事务（J2EE中JTA）。感兴趣的读者可以自行参考《分布式事务一致性解决方案》，链接地址：</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency</p><h2>我们的系统真的需要分库分表吗</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">读完上面内容，不禁引起有些读者的思考，我们的系统是否需要分库分表吗？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其实这点没有明确的判断标准，比较依赖实际业务情况和经验判断。依照笔者个人的经验，一般MySQL单表1000W左右的数据是没有问题的（前提是应用系统和数据库等层面设计和优化的比较好）。当然，除了考虑当前的数据量和性能情况时，作为架构师，我们需要提前考虑系统半年到一年左右的业务增长情况，对数据库服务器的QPS、连接数、容量等做合理评估和规划，并提前做好相应的准备工作。如果单机无法满足，且很难再从其他方面优化，那么说明是需要考虑分片的。这种情况可以先去掉数据库中自增ID，为分片和后面的数据迁移工作提前做准备。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">很多人觉得&#8220;分库分表&#8221;是宜早不宜迟，应该尽早进行，因为担心越往后公司业务发展越快、系统越来越复杂、系统重构和扩展越困难&#8230;这种话听起来是有那么一点道理，但我的观点恰好相反，对于关系型数据库来讲，我认为&#8220;能不分片就别分片&#8221;，除非是系统真正需要，因为数据库分片并非低成本或者免费的。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这里笔者推荐一个比较靠谱的过渡技术&#8211;&#8220;表分区&#8221;。主流的关系型数据库中基本都支持。不同的分区在逻辑上仍是一张表，但是物理上却是分开的，能在一定程度上提高查询性能，而且对应用程序透明，无需修改任何代码。笔者曾经负责优化过一个系统，主业务表有大约8000W左右的数据，考虑到成本问题，当时就是采用&#8220;表分区&#8221;来做的，效果比较明显，且系统运行的很稳定。</p><h2>小结</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">最后，有很多读者都想了解当前社区中有没有开源免费的分库分表解决方案，毕竟站在巨人的肩膀上能省力很多。当前主要有两类解决方案：</p><ol style="margin: 10px 0px 10px 10px; padding: 0px 0px 0px 20px; border: 0px; width: 549px; clear: left;"><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">基于应用程序层面的DDAL（分布式数据库访问层）&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">比较典型的就是淘宝半开源的TDDL，当当网开源的Sharding-JDBC等。分布式数据访问层无需硬件投入，技术能力较强的大公司通常会选择自研或参照开源框架进行二次开发和定制。对应用程序的侵入性一般较大，会增加技术成本和复杂度。通常仅支持特定编程语言平台（Java平台的居多），或者仅支持特定的数据库和特定数据访问框架技术（一般支持MySQL数据库，JDBC、MyBatis、Hibernate等框架技术）。</p></li><li style="margin: 4px 0px; padding: 0px 0px 0px 10px; border: none; float: none; clear: none;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">数据库中间件，比较典型的像mycat（在阿里开源的cobar基础上做了很多优化和改进，属于后起之秀，也支持很多新特性），基于Go语言实现kingSharding，比较老牌的Atlas（由360开源）等。这些中间件在互联网企业中大量被使用。另外，MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术，不过国内使用的企业较少。&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; line-height: 1.8; clear: none; width: 539px; float: none !important;">中间件也可以称为&#8220;透明网关&#8221;，大名鼎鼎的mysql_proxy大概是该领域的鼻祖（由MySQL官方提供，仅限于实现&#8220;读写分离&#8221;）。中间件一般实现了特定数据库的网络通信协议，模拟一个真实的数据库服务，屏蔽了后端真实的Server，应用程序通常直接连接中间件即可。而在执行SQL操作时，中间件会按照预先定义分片规则，对SQL语句进行解析、路由，并对结果集做二次计算再最终返回。引入数据库中间件的技术成本更低，对应用程序来讲侵入性几乎没有，可以满足大部分的业务。增加了额外的硬件投入和运维成本，同时，中间件自身也存在性能瓶颈和单点故障问题，需要能够保证中间件自身的高可用、可扩展。</p></li></ol><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">总之，不管是使用分布式数据访问层还是数据库中间件，都会带来一定的成本和复杂度，也会有一定的性能影响。所以，还需读者根据实际情况和业务发展需要慎重考虑和选择。</p></div></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432269.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 14:29 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432269.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>微信开源：生产级paxos类库PhxPaxos实现原理介绍</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 03:35:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432267.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432267.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432267.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: http://www.infoq.com/cn/articles/weinxin-open-source-paxos-phxpaxos微信重磅开源生产级Paxos类库PhxPaxos！本文将用科普的口吻向大家介绍PhxPaxos背后的实现原理以及一些有趣的细节。本文由微信后台团队授权转载，ID：gh_93b1115dc96f开源地址：https://github.com/tencent-wecha...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432267.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 11:35 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432267.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>蘑菇街每秒订单数25倍提升历程</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 17 Jan 2017 03:34:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432266.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432266.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432266.html</trackback:ping><description><![CDATA[<div>http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance<br /><br /><div style="margin: 0px; border: 0px; display: flex; flex-flow: row nowrap; justify-content: space-between; align-items: center; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><span style="margin: 0px 0px 10px; border: 0px; padding: 0px; color: #666666; font-size: 12px; float: left; display: block; clear: both; width: 969px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;">作者&nbsp;<ul style="margin: 0px; padding: 0px; list-style: none; border: 0px; display: inline;"><li style="margin: 0px; padding: 0px; border: 0px; float: none; clear: none; display: inline;"><a f_taxonomyeditor"="" href="http://www.infoq.com/cn/author/%E4%B8%83%E5%85%AC" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;">七公</a></li>&nbsp;</ul>发布于 2016年12月27日&nbsp;<span style="margin: 0px; border: 0px; padding: 0px;">|</span>&nbsp;<span style="margin: 1px 0px 0px; border: 0px; padding: 5px; float: right; background-color: #fff8cc;">欲知区块链、VR、TensorFlow等潮流技术和框架，请锁定<a href="http://2017.qconbeijing.com/?utm_source=infoq&amp;utm_medium=notices&amp;utm_campaign=seven" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;">QCon北京站！</a></span><a id="noOfComments" title="" href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#theCommentsSection" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px; font-weight: 600;"><span style="margin: -3px 0px 0px 7px; border: 0px; padding: 0px 1px 0px 5px; display: inline-block; font-family: Arial, Helvetica, sans-serif; font-weight: normal; position: relative; bottom: -5px; background: url(&quot;i/bg_comment_left.png&quot;) 0% 0% no-repeat, url(&quot;i/bg_comment_right.png&quot;) 100% 0% no-repeat;"><span style="margin: 0px; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ccdceb; border-bottom-color: #ccdceb; padding: 1px 5px 0px; float: left; color: #000000; text-align: center; line-height: 15px; display: block; position: relative;">1</span></span>&nbsp;讨论</a></span><ul style="margin: 0px 0px 20px; padding: 5px 0px; list-style: none; border-width: 1px 0px; border-top-style: solid; border-bottom-style: solid; border-top-color: #ebf0f3; border-bottom-color: #ebf0f3; float: left; clear: both; width: 969px; position: relative; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><div bdshare-button-style0-16"="" data-bd-bind="1484557566087" style="margin: 0px; border: 0px; zoom: 1;"><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="more" style="text-decoration: none; color: #333333; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px 0px; background-repeat: no-repeat;">分享到：</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="tsina" title="分享到微博" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -104px; background-repeat: no-repeat;">微博</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="weixin" title="分享到微信" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1612px; background-repeat: no-repeat;">微信</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="fbook" title="分享到Facebook" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1352px; background-repeat: no-repeat;">Facebook</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="twi" title="分享到Twitter" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -1404px; background-repeat: no-repeat;">Twitter</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="youdao" title="分享到有道云笔记" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2080px; background-repeat: no-repeat;">有道云笔记</a><a href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" data-cmd="mail" title="分享到邮件分享" style="text-decoration: none; color: #000000; margin: 6px 6px 6px 0px; border: 0px; padding: 0px 0px 0px 17px; float: left; line-height: 16px; height: 16px; cursor: pointer; outline: none !important; background-image: url(&quot;../img/share/icons_0_16.png?v=91362611.png&quot;); background-position: 0px -2340px; background-repeat: no-repeat;">邮件分享</a></div></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a id="bookmarkBtn" href="http://www.infoq.com/cn/articles/mogujie-orders-per-second-25-times-enhance#" title="" style="text-decoration: none; color: #ffffff; margin: -2px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/read_later_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; border-style: initial; border-right-color: #0c3b6f; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/read_later_right.jpg&quot;) 0% 50% repeat-x;">稍后阅读</q></a></li><li style="margin: 0px; padding: 0px; border: 0px; float: left; clear: none; text-align: left; display: inline; font-size: 12px; color: #a1a1a1;"><a href="http://www.infoq.com/cn/showbookmarks.action" style="text-decoration: none; color: #133660; margin: -2px 104px 0px 0px; border: 0px; padding: 0px 0px 0px 18px; height: 22px; line-height: 22px; display: block; font-weight: 600; border-radius: 3px; position: absolute; right: 0px; top: 5px; outline: none !important; background: url(&quot;i/reading_list_left.jpg&quot;) 0% 50% no-repeat;"><q style="margin: 0px; border-width: 0px 1px 0px 0px; border-right-style: solid; border-right-color: #afc0c7; padding: 0px 10px 0px 5px; border-radius: 3px; display: inline-block; cursor: pointer; background: url(&quot;i/reading_list_right.jpg&quot;) 0% 50% repeat-x;">我的阅读清单</q></a></li></ul><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"></div><div text_content_container"="" style="margin: 0px; border: 0px; float: left; width: 610px; font-family: Helvetica, 'Open Sans', Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 微软雅黑, STHeiti, 'WenQuanYi Micro Hei', SimSun, sans-serif; line-height: normal; background-color: #ffffff;"><div text_info_article"="" style="margin: 10px 0px; border: 0px; float: left; clear: both; width: 610px; line-height: 1.8;"><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">本文根据白辉在2016ArchSummit全球架构师（深圳）峰会上的演讲整理而成。ArchSummit北京站即将在12月2日开幕，更多专题讲师信息请到<a href="http://bj2016.archsummit.com/" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">北京站官网</a>查询。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">非常荣幸在这里跟大家一起来探讨&#8220;海量服务架构探索&#8221;相关专题的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我叫白辉，花名是七公。2014年之前主要在阿里B2B负责资金中心、评价、任务中心等系统。2015年加入蘑菇街，随着蘑菇街的飞速成长，经历了网站技术架构的大</p><div style="margin: 0px; border: 0px; height: 0px; clear: both; font-size: 0px;"></div><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">变革。今天分享的内容来自于去年我们做的事情，题目用了一个关键词是&#8220;篱笆&#8221;，篱笆的英文是Barrier，是指2015年蘑菇街面临的问题和艰巨的困难。我们越过了这些篱笆，取得了很好的成果。</p><h2>引言</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/40.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">今天分享的内容主要分为五部分。第一部分，概述电商系统发展中期面临的一般性问题。第二部分，如何解决面临的问题，主要的策略是做拆分、做服务化。第三、四部分，服务化之后业务的大增长、网站流量飞速的增加、&#8220;双11&#8221;大促等的挑战很大，我们做了服务的专项系统优化以及稳定性治理。第五部分，进行了总结和展望。</p><h2>电商系统发展中期面临的一般性问题</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们先看第一部分的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/41.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我总结了一下，一般电商系统发展到中期都会面临三个方面的问题（如图）。第一方面是业务问题。比如，一开始做业务的时候可能很随意，一是并不考虑业务模型、系统架构，二是业务之间的耦合比较严重，比如交易和资金业务，有可能资金和外部第三方支付公司的交互状态耦合在交易系统里，这些非常不利于业务发展。第二方面是系统问题。2014年我们面临单体应用，400人开发一个大应用，扩展性很差，业务比较难做。第三方面是支撑问题，比如关于环境、开发框架和质量工具等。这些是电商系统发展到中期都会面临的问题，中期的概念是用户过了千万，PV过了1亿。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/42.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们来看一下蘑菇街2015年初面临的问题。蘑菇街2015年用户过亿，PV过10亿，业务在超高速发展，每年保持3倍以上的增长。电商促销、交易、支付等业务形态都在快速膨胀，我们需要快速支持业务发展，而不是成为业务的瓶颈。那么就是要去做系统的拆分和服务化。</p><h2>系统拆分与服务化过程</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">第二部分的内容，是关于蘑菇街系统拆分与服务化的历程。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">按照如下几条思路（见图），我们进行系统拆分以及服务化。最开始，大家在同一个应用里开发一些业务功能，都是选择速度最快的方式，所有的DB和业务代码都是在一起的。首先我们将DB做垂直拆分。第二步是做业务系统垂直拆分，包括交易、资金等。第三步是在系统拆完了之后要考虑提供什么样的API来满足业务的需求？这里我们要做数据建模+业务建模，数据建模方面包括数据表的设计和扩展支持，数据模型应该非常稳定；业务建模方面，使用标准和灵活的API，而且尽量不用修改代码或者改少量代码就能支持业务需求。第四步是需要将业务逻辑下沉到服务，Web层专注于展示逻辑和编排，不要涉及过多业务的事情。然后用SOA中间件建设服务化系统。最后会做一些服务的治理。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/43.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">来看一个API服务化的例子，在做服务化之前和做服务化之后，交易创建下单业务有什么不一样。服务化之前我们面临的问题有：入口分散，如果要在底层做任何一个微小的改动，十几个入口需要几十个人配合修改，这是非常不合理的一种方式；多端维护多套接口，成本非常高；还有稳定性的问题，依赖非常复杂，维护很难。我刚到蘑菇街的时候，一次大促活动就导致数据库崩溃，暴露了系统架构很大的问题和总量上的瓶颈。按照上面提到几条思路去做服务化，看看有了哪些改善？首先是API统一，多个端、多个业务都用统一的API提供；其次是依赖有效管理起来，大事务拆分成多个本地小事务；最后降低了链路风险，逻辑更加清晰，稳定性更好。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/44.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">2015年3月我来到蘑菇街之后，先制订了服务化的规范，探讨了到底什么是标准的服务化。在做服务化的过程中，发现大家代码风格完全不一样，所以制定编码规范非常重要。2015年8月，我们完成了各个模块的改造，包括用户、商品、交易、订单、促销、退款等，然后有了服务化架构1.0的体系。在此基础之上，我们进一步做了提升流量和稳定性等更深度的建设。2015年9月，我们实施了分库分表和链路性能提升优化，2015年10月做了服务治理和服务保障。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/45.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来，以服务架构和服务体系建设为主线，讲一下去年整个网站架构升级的过程。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/46.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">在服务化1.0体系完成之后，我们得到了一个简单的体系，包含下单服务、营销服务、店铺服务、商品服务和用户服务，还有简单的RPC框架Tesla。当时，我们并没有做很多性能优化的事情，但是通过业务流程化简和逻辑优化，每秒最大订单数从400提升到1K，基础服务也都搭建了起来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/47.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有了1.0初步的服务化体系之后，更进一步，我们一是要继续深入网站如资金等的服务化，二是要做服务内部的建设，比如容量、性能，这也是接下来要讲的内容。</p><h2>购买链路的性能提升</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/48.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这个链路（见图）是比较典型的电商链路，有商品页、下单、支付、营销和库存等内容。一开始每个点都有瓶颈，每个瓶颈都是一个篱笆，我们要正视它，然后翻越它。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/49.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们先来看第一个篱笆墙：下单的瓶颈。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">2015年&#8220;3.21&#8221;大促的时候，DB崩溃了，这个瓶颈很难突破。下一个订单要插入很多条数据记录到单DB的DB表。我们已经用了最好的硬件，但是瓶颈依然存在，最主要的问题就是DB单点，需要去掉单点，做成可水平扩展的。流量上来了，到DB的行写入数是2万/秒，对DB的压力很大。写应该控制在一个合理的量，DB负载维持在较低水平，主从延时也才会在可控范围内。所以DB单点的问题非常凸显，这座大山必须迈过去，我们做了一个分库分表组件TSharding来实施分库分表。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/50.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">将我们写的分库分表工具与业界方案对比，业界有淘宝TDDL Smart Client的方式，还有Google的Vitess等的Proxy方式，这两种成熟方案研发和运维的成本都太高，短期内我们接受不了，所以借鉴了Mybatis Plugin的方式，但Mybatis Plugin不支持数据源管理，也不支持事务。我大概花了一周时间写了一个组件&#8212;&#8212;自研分库分表组件TSharding（<a href="https://github.com/baihui212/tsharding" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">https://github.com/baihui212/tsharding</a>），然后快速做出方案，把这个组件应用到交易的数据库，在服务层和DAO层，订单容量扩展到千亿量级，并且可以继续水平扩展。TSharding上线一年之后，我们将其开放出来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/51.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">第二个篱笆墙就是营销服务RT的问题。促销方式非常多，包括各种红包、满减、打折、优惠券等。实际上促销的接口逻辑非常复杂，在&#8220;双11&#8221;备战的时候，面对这个复杂的接口，每轮链路压测促销服务都会发现问题，之后优化再压测，又发现新的问题。我们来一起看看遇到的各种问题以及是如何解决的。首先是压测出现接口严重不可用，这里可以看到DB查询频次高，响应很慢，流量一上来，这个接口就崩溃了。那怎么去排查原因和解决呢？</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">首先是SQL优化，用工具识别慢SQL，即全链路跟踪系统Lurker。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/52.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这张图我简单介绍一下。遇到SQL执行效率问题的时候，就看是不是执行到最高效的索引，扫表行数是不是很大，是不是有filesort。有ORDER BY的时候，如果要排序的数据量不大或者已经有索引可以走到，在数据库的内存排序缓存区一次就可以排序完。如果一次不能排序完，那就先拿到1000个做排序，然后输出到文件，然后再对下1000个做排序，最后再归并起来，这就是filesort的大致过程，效率比较低。所以尽量要走上索引，一般类的查询降低到2毫秒左右可以返回。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">其次是要读取很多优惠规则和很多优惠券，数据量大的时候DB是很难扛的，这时候我们要做缓存和一些预处理。特别是查询DB的效率不是很高的时候，尽量缓存可以缓存的数据、尽量缓存多一些数据。但如果做缓存，DB和缓存数据的一致性是一个问题。在做数据查询时，首先要看本地缓存有没有开启，如果本地缓存没有打开，就去查分布式缓存，如果分布式缓存中没有就去查DB，然后从DB获取数据过来。需要尽量保持DB、缓存数据的一致性，如果DB有变化，可以异步地做缓存数据失效处理，数据百毫秒内就失效掉，减少不一致的问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">另外，如果读到本地缓存，这个内存访问比走网络请求性能直接提升了一个量级，但是带来的弊端也很大，因为本地缓存没有办法及时更新，平时也不能打开，因为会带来不一致问题。但大促高峰期间我们会关闭关键业务数据变更入口，开启本地缓存，把本地缓存设置成一分钟失效，一分钟之内是可以缓存的，也能容忍短暂的数据不一致，所以这也是一个很好的做法。同样的思路，我们也会把可能会用到的数据提前放到缓存里面，做预处理。在客户端进行数据预处理，要么直接取本地数据，或者在本地直接做计算，这样更高效，避免了远程的RPC。大促期间我们就把活动价格信息预先放到商品表中，这样部分场景可以做本地计价，有效解决了计价接口性能的问题。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">再就是读容量问题，虽然缓存可以缓解压力，但是DB还是会有几十K的读压力，单点去扛也是不现实的，所以要把读写分离，如果从库过多也有延时的风险，我们会把数据库的并行复制打开。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/53.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们来看一下数据。这是去年&#8220;双11&#8221;的情况（如图）。促销服务的RT得到了有效控制，所以去年&#8220;双11&#8221;平稳度过。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/54.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来讲一个更基础、更全局的优化，就是异步化。比如说下单的流程，有很多业务是非实时性要求的，比如下单送优惠券，如果在下单的时候同步做，时间非常长，风险也更大，其实业务上是非实时性或者准实时性的要求，可以做异步化处理，这样可以减少下单对机器数量的要求。另外是流量高峰期的一些热点数据。大家可以想象一下，下单的时候，一万个人竞争同一条库存数据，一万个节点锁在这个请求上，这是多么恐怖的事情。所以我们会有异步队列去削峰，先直接修改缓存中的库存数目，改完之后能读到最新的结果，但是不会直接竞争DB，这是异步队列削峰很重要的作用。还有，数据库的竞争非常厉害，我们需要把大事务做拆分，尽量让本地事务足够小，同时也要让多个本地事务之间达到一致。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">异步是最终达到一致的关键，异步的处理是非常复杂的。可以看一下这个场景（见图），这是一个1-6步的处理过程，如果拆分成步骤1、2、3、4、end，然后到5，可以异步地做；6也一样，并且5和6可以并行执行。同时，这个步骤走下来链路更短，保障也更容易；步骤5和6也可以单独保障。所以异步化在蘑菇街被广泛使用。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/55.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">异步化之后面临的困难也是很大的，会有分布式和一致性的问题。交易创建过程中，订单、券和库存要把状态做到绝对一致。但下单的时候如果先锁券，锁券成功了再去减库存，如果减库存失败了就是很麻烦的事情，因为优化券服务在另外一个系统里，如果要同步调用做券的回滚，有可能这个回滚也会失败，这个时候处理就会非常复杂。我们的做法是，调用服务超时或者失败的时候，我们就认为失败了，就会异步发消息通知回滚。优惠券服务和库存服务被通知要做回滚时，会根据自身的状态来判断是否要回滚，如果锁券成功了券就回滚，减库存也成功了库存做回滚；如果库存没有减就不用回滚。所以我们是通过异步发消息的方式保持多个系统之间的一致性；如果不做异步就非常复杂，有的场景是前面所有的服务都调用成功，第N个服务调用失败。另外的一致性保障策略包括Corgi MQ生产端发送失败会自动重试保证发成功，消费端接收ACK机制保证最终的一致。另外，与分布式事务框架比起来，异步化方案消除了二阶段提交等分布式事务框架的侵入性影响，降低了开发的成本和门槛。&nbsp;&nbsp;</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/56.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">另一个场景是，服务调用上会有一些异步的处理。以购物车业务为例，购物车列表要调用10个Web服务，每一个服务返回的时间都不一样，比如第1个服务20毫秒返回，第10个服务40毫秒返回，串行执行的效率很低。而电商类的大多数业务都是IO密集型的，而且数据量大时还要分批查询。所以我们要做服务的异步调用。比如下图中这个场景，步骤3处理完了之后callback马上会处理，步骤4处理完了callback也会马上处理，步骤3和4并不相互依赖，且处理可以同时进行了，提高了业务逻辑执行的并行度。目前我们是通过JDK7的Future和Callback实现的，在逐步往JDK8的Completable Future迁移。这是异步化在网站整体的应用场景，异步化已经深入到我们网站的各个环节。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/57.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">刚才我们讲了链路容量的提升、促销RT的优化，又做了异步化的一些处理。那么优化之后怎么验证来优化的效果呢？到底有没有达到预期？我们有几个压测手段，如线下单机压测识别应用单机性能瓶颈，单链路压测验证集群水位及各层核?系统容量配比，还有全链路压测等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">这是去年&#8220;双11&#8221;之前做的压测（见图），达到了5K容量的要求。今年对每个点进一步深入优化，2016年最大订单提升到了10K，比之前提升了25倍。实际上这些优化可以不断深入，不仅可以不断提高单机的性能和单机的QPS，还可以通过对服务整体上的优化达到性能的极致，并且可以引入一些廉价的机器（如云主机）来支撑更大的量。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/58.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们为什么要做这些优化？业务的发展会对业务系统、服务框架提出很多很高的要求。因此，我们对Tesla做了这些改善（见图），服务的配置推送要更快、更可靠地到达客户端，所以有了新的配置中心Metabase，也有了Lurker全链路监控，服务和服务框架的不断发展推动了网站其他基础中间件产品的诞生和发展。2015年的下半年我们进行了一系列中间件的自研和全站落地。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/59.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们得到了服务架构1.5的体系（见图），首先是用户服务在最底层，用户服务1200K的QPS，库存250K，商品服务400K，营销200K，等等。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">接下来我们看一下这一阶段，Tesla开始做服务管控，真正成为了一个服务框架。我们最开始做发布的时候，客户端、服务端由于做的只是初级的RPC调用，如果服务端有变更，客户端可能是几秒甚至数十秒才能拉到新配置，导致经常有客户投诉。有了对服务变更推送更高的要求后，我们就有了Matabase配置中心，服务端如果有发布或者某一刻崩溃了，客户端马上可以感知到，这样就完成了整个服务框架连接优化的改进，真正变成服务管控、服务治理框架的开端。</p><h2>购买链路的稳定性提升&nbsp;</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/60.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">有了上面讲到的服务化改进和性能提升之后，是不是大促的时候看一看监控就行了？其实不是。大流量来的时候，万一导致整个网站崩溃了，一分钟、两分钟的损失是非常大的，所以还要保证服务是稳的和高可用的。只有系统和服务是稳定的，才能更好地完成业务指标和整体的经营目标。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">下面会讲一下服务SLA保证的内容。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/61.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">首先SLA体现在对容量、性能、程度的约束，包括程度是多少的比例。那么要保证这个SLA约束和目标达成，首先要把关键指标监控起来；第二是依赖治理、逻辑优化；第三是负载均衡、服务分组和限流；第四是降级预案、容灾、压测、在线演练等。这是我们服务的关键指标的监控图（见上图）。支付回调服务要满足8K QPS，99%的RT在30ms内，但是图中监控说明SLA未达到，RT程度指标方面要优化。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">服务的SLA保证上，服务端超时和限流非常重要。如果没有超时，很容易引起雪崩。我们来讲一个案例，有次商品服务响应变慢，就导致上层的其他服务都慢，而且商品服务积压了很多请求在线程池中，很多请求响应过慢导致客户端等待超时，客户端早就放弃调用结果结束掉了，但是在商品服务线程池线程做处理时拿到这个请求还会处理，客户都跑了，再去处理，客户也拿不到这个结果，最后还会造成上层服务请求的堵塞，堵塞原因缓解时产生洪流。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/62.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">限流是服务稳定的最后一道保障。一个是HTTP服务的限流，一个是RPC服务的限流。我们服务的处理线程是Tesla框架分配的，所以服务限流可以做到非常精确，可以控制在服务级别和服务方法级别，也可以针对来源做限流。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">我们做了这样一系列改造之后，服务框架变成了有完善的监控、有负载均衡、有服务分组和限流等完整管控能力的服务治理框架。服务分组之后，如果通用的服务崩溃了，购买链路的服务可以不受影响，这就做到了隔离。这样的一整套服务体系（如图）就构成了我们的服务架构2.0，最终网站的可用性做到了99.979%，这是今年6月份的统计数据。我们还会逐步把服务的稳定性和服务质量做到更好。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/63.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><h2>总结及下一步展望</h2><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/64.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">最后总结一下，服务框架的体系完善是一个漫长的发展过程，不需要一开始就很强、什么都有的服务框架，最早可能就是一个RPC框架。服务治理慢慢随着业务量增长也会发展起来，服务治理是服务框架的重要组成部分。另外，Tesla是为蘑菇街业务体系量身打造的服务框架。可以说服务框架是互联网网站架构的核心和持续发展的动力。选择开源还是自建，要看团队能力、看时机。我们要深度定制服务框架，所以选择了自研，以后可能会开源出来。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;"><img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20170111-0710/resource/articles/mogujie-orders-per-second-25-times-enhance/zh/resources/65.jpg" width="550" style="border: 0px; margin: 0px 10px 10px 0px; padding: 0px;"  alt="" /></p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">服务框架是随着业务发展不断演变的，我们有1.0、1.5和2.0架构的迭代。要前瞻性地谋划和实施，要考虑未来三年、五年的容量。有一些系统瓶颈可能是要提前解决的，每一个场景不一样，根据特定的场景选择最合适的方案。容量和性能关键字是一切可扩展、Cache、IO、异步化。目前我们正在做的是服务治理和SLA保障系统化，未来会做同城异地的双活。</p><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">谢谢大家！</p><hr style="margin: 0px; border: 0px; padding: 0px;" /><p style="margin: 0px 0px 15px; padding: 0px; border: 0px; float: none; line-height: 1.8; clear: none; width: 610px;">感谢<a href="http://www.infoq.com/cn/author/%E9%99%88%E5%85%B4%E7%92%90" style="text-decoration: none; color: #286ab2; outline: none !important; margin: 0px; border: 0px; padding: 0px;">陈兴璐</a>对本文的审校。</p></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432266.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-17 11:34 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/17/432266.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Elastic-Job - 分布式定时任务框架</title><link>http://www.blogjava.net/jinfeng_wang/archive/2017/01/16/432263.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 16 Jan 2017 07:54:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2017/01/16/432263.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432263.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2017/01/16/432263.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432263.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432263.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: https://my.oschina.net/u/719192/blog/506062?p={{page}}摘要: Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架。去掉了和dd-job中的监控和ddframe接入规范部分。该项目基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发。 ddframe其他模块也有可独立开源...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2017/01/16/432263.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432263.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2017-01-16 15:54 <a href="http://www.blogjava.net/jinfeng_wang/archive/2017/01/16/432263.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Sharding-JDBC 柔性事务</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/29/432214.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Thu, 29 Dec 2016 06:49:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/29/432214.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432214.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/29/432214.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432214.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432214.html</trackback:ping><description><![CDATA[<div>跨数据库的事务，只能在业务上保证。<br />不能保证的是，2个SQL能够在同一时间成功。<br />业务上的先期检查、校验、分布式锁的设计，再加上一部分的措施（不停重试、先划一部分蛋糕再恢复）是必须要考虑的问题和方案。<br /><br /><br /><br />http://dangdangdotcom.github.io/sharding-jdbc/post/soft_transaction/<br /><div>http://dangdangdotcom.github.io/sharding-jdbc/post/transaction/</div><br /><h1>最大努力送达型</h1><h2>概念</h2><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">在分布式数据库的场景下，相信对于该数据库的操作最终一定可以成功，所以通过最大努力反复尝试送达操作。</p><h2>架构图</h2><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;"><img src="http://dangdangdotcom.github.io/sharding-jdbc/img/architecture-soft-transaction-bed.png" alt="最大努力送达型事务" style="box-sizing: border-box; border: 0px; vertical-align: middle;" /></p><h2>适用场景</h2><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; line-height: 1.6em; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; background-color: #f8f8f8;"><li style="box-sizing: border-box;">根据主键删除数据。</li><li style="box-sizing: border-box;">更新记录永久状态，如更新通知送达状态。</li></ul><h2>使用限制</h2><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">使用最大努力送达型柔性事务的<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">SQL</code>需要满足幂等性。</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; line-height: 1.6em; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; background-color: #f8f8f8;"><li style="box-sizing: border-box;">INSERT语句要求必须包含主键，且不能是自增主键。</li><li style="box-sizing: border-box;">UPDATE语句要求幂等，不能是<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">UPDATE xxx SET x=x+1</code></li><li style="box-sizing: border-box;">DELETE语句无要求。</li></ul><h2>开发指南</h2><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; line-height: 1.6em; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; background-color: #f8f8f8;"><li style="box-sizing: border-box;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC-transaction</code>完全基于<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">java</code>开发，直接提供<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">jar</code>包，可直接使用maven导入坐标即可使用。</li><li style="box-sizing: border-box;">为了保证事务不丢失，<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC-transaction</code>需要提供数据库存储事务日志，配置方法可参见事务管理器配置项。</li><li style="box-sizing: border-box;">由于柔性事务采用异步尝试，需要部署独立的作业和<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Zookeeper</code>。<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC-transaction</code>采用<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">elastic-job</code>实现的<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC-transaction-async-job</code>，通过简单配置即可启动高可用作业异步送达柔性事务，启动脚本为<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">start.sh</code>。</li><li style="box-sizing: border-box;">为了便于开发，<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC-transaction</code>提供了基于内存的事务日志存储器和内嵌异步作业。</li></ul><h2>开发示例</h2><pre language-java"="" style="box-sizing: border-box; overflow: auto; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: 13px; padding: 1em; margin-top: 0.5em; margin-bottom: 0.5em; line-height: 1.5; word-break: normal; word-wrap: normal; border: 1px solid #cccccc; border-radius: 4px; text-shadow: #ffffff 0px 1px; direction: ltr; tab-size: 4; background: #f5f2f0;"><code language-java"="" style="box-sizing: border-box; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: inherit; padding: 0px; border-radius: 0px; text-shadow: #ffffff 0px 1px; direction: ltr; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; tab-size: 4; background: 0px 0px;">    <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 1. 配置SoftTransactionConfiguration</span>     SoftTransactionConfiguration transactionConfig <span operator"="" style="box-sizing: border-box; color: #a67f59; background: rgba(255, 255, 255, 0.498039);">=</span> <span keyword"="" style="box-sizing: border-box; color: #0077aa;">new</span> <span class-name"="" style="box-sizing: border-box;">SoftTransactionConfiguration</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span>dataSource<span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>     transactionConfig<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span><span function"="" style="box-sizing: border-box; color: #dd4a68;">setXXX</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>          <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 2. 初始化SoftTransactionManager</span>     SoftTransactionManager transactionManager <span operator"="" style="box-sizing: border-box; color: #a67f59; background: rgba(255, 255, 255, 0.498039);">=</span> <span keyword"="" style="box-sizing: border-box; color: #0077aa;">new</span> <span class-name"="" style="box-sizing: border-box;">SoftTransactionManager</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span>transactionConfig<span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>     transactionManager<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span><span function"="" style="box-sizing: border-box; color: #dd4a68;">init</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>          <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 3. 获取BEDSoftTransaction</span>     BEDSoftTransaction transaction <span operator"="" style="box-sizing: border-box; color: #a67f59; background: rgba(255, 255, 255, 0.498039);">=</span> <span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span>BEDSoftTransaction<span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span> transactionManager<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span><span function"="" style="box-sizing: border-box; color: #dd4a68;">getTransaction</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span>SoftTransactionType<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span>BestEffortsDelivery<span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>          <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 4. 开启事务</span>     transaction<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span><span function"="" style="box-sizing: border-box; color: #dd4a68;">begin</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span>connection<span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span>          <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 5. 执行JDBC</span>     <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">/*          codes here     */</span>     <span operator"="" style="box-sizing: border-box; color: #a67f59; background: rgba(255, 255, 255, 0.498039);">*</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">// 6.关闭事务</span>     transaction<span punctuation"="" style="box-sizing: border-box; color: #999999;">.</span><span function"="" style="box-sizing: border-box; color: #dd4a68;">end</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">(</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">)</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">;</span> </code></pre><h2>事务管理器配置项</h2><h3><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 21.6px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">SoftTransactionConfiguration</code>配置</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">用于配置事务管理器。</p><table style="box-sizing: border-box; border-spacing: 0px; border-collapse: collapse; display: block; width: 872.5px; overflow: auto; word-break: keep-all; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;"><thead style="box-sizing: border-box;"><tr style="box-sizing: border-box;"><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">名称</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">类型</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">必填</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">默认值</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">说明</em></th></tr></thead><tbody style="box-sizing: border-box;"><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">shardingDataSource</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">ShardingDataSource</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">是</code></td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;"></td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">事务管理器管理的数据源</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">syncMaxDeliveryTryTimes</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">int</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">3</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">同步的事务送达的最大尝试次数</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">storageType</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">enum</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">RDB</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">事务日志存储类型。可选值: RDB,MEMORY。使用RDB类型将自动建表</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">transactionLogDataSource</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">DataSource</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">null</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">存储事务日志的数据源，如果storageType为RDB则必填</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">bestEffortsDeliveryJobConfiguration</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">NestedBestEffortsDeliveryJobConfiguration</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">null</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">最大努力送达型内嵌异步作业配置对象。如需使用，请参考<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">NestedBestEffortsDeliveryJobConfiguration</code>配置</td></tr></tbody></table><h3><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 21.6px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">NestedBestEffortsDeliveryJobConfiguration</code>配置 (仅开发环境)</h3><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">用于配置内嵌的异步作业，仅用于开发环境。生产环境应使用独立部署的作业版本。</p><table style="box-sizing: border-box; border-spacing: 0px; border-collapse: collapse; display: block; width: 872.5px; overflow: auto; word-break: keep-all; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;"><thead style="box-sizing: border-box;"><tr style="box-sizing: border-box;"><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">名称</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">类型</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">必填</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">默认值</em></th><th style="box-sizing: border-box; padding: 0.5rem 1rem; border: 1px solid #e9ebec;"><em style="box-sizing: border-box;">说明</em></th></tr></thead><tbody style="box-sizing: border-box;"><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">zookeeperPort</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">int</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">4181</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">内嵌的注册中心端口号</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">zookeeperDataDir</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">String</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">target/test_zk_data/nano/</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">内嵌的注册中心的数据存放目录</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">asyncMaxDeliveryTryTimes</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">int</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">3</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">异步的事务送达的最大尝试次数</td></tr><tr style="box-sizing: border-box;"><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">asyncMaxDeliveryTryDelayMillis</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">long</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">否</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">60000</td><td style="box-sizing: border-box; padding: 0.5rem 1rem; border-style: solid; border-color: #e9ebec;">执行异步送达事务的延迟毫秒数，早于此间隔时间的入库事务才会被异步作业执行</td></tr></tbody></table><h2>独立部署作业指南</h2><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; line-height: 1.6em; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; background-color: #f8f8f8;"><li style="box-sizing: border-box;">部署用于存储事务日志的数据库。</li><li style="box-sizing: border-box;">部署用于异步作业使用的<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Zookeeper</code>。</li><li style="box-sizing: border-box;">配置<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">yaml</code>文件,参照示例。</li><li style="box-sizing: border-box;">下载并解压文件<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">sharding-jdbc-transaction-async-job-$VERSION.tar</code>，通过<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">start.sh</code>脚本启动异步作业。</li></ul><h2>异步作业yaml文件配置</h2><pre language-yaml"="" style="box-sizing: border-box; overflow: auto; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: 13px; padding: 1em; margin-top: 0.5em; margin-bottom: 0.5em; line-height: 1.5; word-break: normal; word-wrap: normal; border: 1px solid #cccccc; border-radius: 4px; text-shadow: #ffffff 0px 1px; direction: ltr; tab-size: 4; background: #f5f2f0;"><code language-yaml"="" style="box-sizing: border-box; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: inherit; padding: 0px; border-radius: 0px; text-shadow: #ffffff 0px 1px; direction: ltr; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; tab-size: 4; background: 0px 0px;"><span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#目标数据库的数据源.</span> <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">targetDataSource</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">ds_0</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span tag"="" style="box-sizing: border-box; color: #990055;">!!org.apache.commons.dbcp.BasicDataSource</span>     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">driverClassName</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> com.mysql.jdbc.Driver     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">url</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> jdbc<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>mysql<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>//localhost<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>3306/ds_0     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">username</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> root     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">password</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">ds_1</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span tag"="" style="box-sizing: border-box; color: #990055;">!!org.apache.commons.dbcp.BasicDataSource</span>     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">driverClassName</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> com.mysql.jdbc.Driver     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">url</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> jdbc<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>mysql<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>//localhost<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>3306/ds_1     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">username</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> root     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">password</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>  <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#事务日志的数据源.</span> <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">transactionLogDataSource</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">ds_trans</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span tag"="" style="box-sizing: border-box; color: #990055;">!!org.apache.commons.dbcp.BasicDataSource</span>     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">driverClassName</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> com.mysql.jdbc.Driver     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">url</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> jdbc<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>mysql<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>//localhost<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>3306/trans_log     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">username</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> root     <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">password</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>  <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#注册中心配置</span> <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">zkConfig</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>   <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#注册中心的连接地址</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">connectionString</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> localhost<span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span><span number"="" style="box-sizing: border-box; color: #990055;">2181</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#作业的命名空间</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">namespace</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> Best<span punctuation"="" style="box-sizing: border-box; color: #999999;">-</span>Efforts<span punctuation"="" style="box-sizing: border-box; color: #999999;">-</span>Delivery<span punctuation"="" style="box-sizing: border-box; color: #999999;">-</span>Job      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#注册中心的等待重试的间隔时间的初始值</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">baseSleepTimeMilliseconds</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">1000</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#注册中心的等待重试的间隔时间的最大值</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">maxSleepTimeMilliseconds</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">3000</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#注册中心的最大重试次数</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">maxRetries</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">3</span>  <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#作业配置</span> <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">jobConfig</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span>   <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#作业名称</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">name</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> bestEffortsDeliveryJob      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#触发作业的cron表达式</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">cron</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> 0/5 * * * * <span punctuation"="" style="box-sizing: border-box; color: #999999;">?</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#每次作业获取的事务日志最大数量</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">transactionLogFetchDataCount</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">100</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#事务送达的最大尝试次数.</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">maxDeliveryTryTimes</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">3</span>      <span comment"="" spellcheck="true" style="box-sizing: border-box; color: #708090;">#执行送达事务的延迟毫秒数,早于此间隔时间的入库事务才会被作业执行</span>   <span key=""  atrule"="" style="box-sizing: border-box; color: #0077aa;">maxDeliveryTryDelayMillis</span><span punctuation"="" style="box-sizing: border-box; color: #999999;">:</span> <span number"="" style="box-sizing: border-box; color: #990055;">60000</span> </code></pre><div><code language-yaml"="" style="box-sizing: border-box; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: inherit; padding: 0px; border-radius: 0px; text-shadow: #ffffff 0px 1px; direction: ltr; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; tab-size: 4; background-image: initial; background-attachment: initial; background-size: initial; background-origin: initial; background-clip: initial; background-position: 0px 0px; background-repeat: initial;"><span number"="" style="box-sizing: border-box; color: #990055;"><br /><br /><h1>事务支持说明</h1><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;"><code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">Sharding-JDBC</code>由于性能方面的考量，决定不支持<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">强一致性</code>分布式事务。我们已明确规划线路图，未来会支持最终一致性的柔性事务。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">目前<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">最大努力送达型</code>柔性事务已开发完成。</p><p style="box-sizing: border-box; margin: 0px 0px 10px; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; line-height: 19.2px; background-color: #f8f8f8;">如果不使用柔性事务，也会自动包含<code style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 14.4px; padding: 2px 4px; color: #c7254e; border-radius: 4px; background-color: #f9f2f4;">弱XA</code>事务支持，有以下几点说明：</p><ul style="box-sizing: border-box; margin-top: 0px; margin-bottom: 10px; line-height: 1.6em; color: #333333; font-family: -apple-system-headline, 'Helvetica Neue', Helvetica, sans-serif; font-size: 16px; background-color: #f8f8f8;"><li style="box-sizing: border-box;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">完全支持非跨库事务，例如：仅分表，或分库但是路由的结果在单库中。</p></li><li style="box-sizing: border-box;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">完全支持因逻辑异常导致的跨库事务。例如：同一事务中，跨两个库更新。更新完毕后，抛出空指针，则两个库的内容都能回滚。</p></li><li style="box-sizing: border-box;"><p style="box-sizing: border-box; margin: 0px 0px 10px;">不支持因网络、硬件异常导致的跨库事务。例如：同一事务中，跨两个库更新，更新完毕后、未提交之前，第一个库死机，则只有第二个库数据提交。</p></li></ul></span></code></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432214.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-29 14:49 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/29/432214.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Spring XML 文件中自定义标签</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432208.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 11:01:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432208.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432208.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432208.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432208.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432208.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 代码：sharding-jdbc-config-spring 目录https://my.oschina.net/nalenwind/blog/599044spring扩展之自定义标签&nbsp;&nbsp; &nbsp; 不知大家在看到那些大牛们在spring里写各种扩展工具，各种方便有没有很羡慕呢？接下来我给大家介绍一下如何通过自定义标签的形式来扩展spring.&nbsp;&nbsp; &nb...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432208.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432208.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 19:01 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432208.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Sharding-JDBC的分库分表能力</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432207.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 10:57:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432207.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432207.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432207.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432207.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432207.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 代码：sharding-jdbc-config-common 目录面临的问题： select * from a, b where a.user_id =b.user_id &nbsp;在分库分表的情况下，如何决定一个正确的JDBC DataSource，一个正确的Table Namehttp://dangdangdotcom.github.io/sharding-jdbc/post/user_gu...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432207.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 18:57 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper数据与存储</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432206.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 07:14:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432206.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432206.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432206.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432206.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432206.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6179118.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　前面分析了Zookeeper对请求的处理，本篇博文接着分析Zookeeper中如何对底层数据进行存储，数据存储被分为内存数据存储于磁盘数据存储。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、数据与存储</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 内存数据</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper的数据模型是树结构，在内存数据库中，存储了整棵树的内容，包括所有的节点路径、节点数据、ACL信息，Zookeeper会定时将这个数据存储到磁盘上。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. DataTree</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　DataTree是内存数据存储的核心，是一个树结构，<strong>代表了内存中一份完整的数据</strong>。DataTree不包含任何与网络、客户端连接及请求处理相关的业务逻辑，是一个独立的组件。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">2. DataNode</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>DataNode是数据存储的最小单元</strong>，其内部除了保存了结点的数据内容、ACL列表、节点状态之外，还记录了父节点的引用和子节点列表两个属性，其也提供了对子节点列表进行操作的接口。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">3. ZKDatabase</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper的内存数据库，管理Zookeeper的所有会话、DataTree存储和事务日志。ZKDatabase会定时向磁盘dump快照数据，同时在Zookeeper启动时，会通过磁盘的事务日志和快照文件恢复成一个完整的内存数据库。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 事务日志</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. 文件存储</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在配置Zookeeper集群时需要配置dataDir目录，其用来存储事务日志文件。也可以为事务日志单独分配一个文件存储目录:dataLogDir。若配置dataLogDir为/home/admin/zkData/zk_log，那么Zookeeper在运行过程中会在该目录下建立一个名字为version-2的子目录，该目录确定了当前Zookeeper使用的事务日志格式版本号，当下次某个Zookeeper版本对事务日志格式进行变更时，此目录也会变更，即在version-2子目录下会生成一系列文件大小一致(64MB)的文件。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">2. 日志格式</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在配置好日志文件目录，启动Zookeeper后，完成如下操作</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 创建/test_log节点，初始值为v1。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 更新/test_log节点的数据为v2。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 创建/test_log/c节点，初始值为v1。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 删除/test_log/c节点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　经过四步操作后，会在/log/version-2/目录下生成一个日志文件，笔者下是log.cec。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　将Zookeeper下的zookeeper-3.4.6.jar和slf4j-api-1.6.1.jar复制到/log/version-2目录下，使用如下命令打开log.cec文件。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　java -classpath ./zookeeper-3.4.6.jar:./slf4j-api-1.6.1.jar org.apache.zookeeper.server.LogFormatter log.cec</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161215144917026-604508704.png" alt="" style="border: 0px; max-width: 900px;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ZooKeeper Transactional Log File with dbid 0 txnlog format version 2 。是文件头信息，主要是事务日志的DBID和日志格式版本号。　　</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　...session 0x159...0xcec createSession 30000。表示客户端会话创建操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　...session 0x159...0xced create '/test_log,... 。表示创建/test_log节点，数据内容为#7631(v1)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　...session 0x159...0xcee setData &#8216;/test_log,...。表示设置了/test_log节点数据，内容为#7632(v2)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　...session 0x159...0xcef create &#8217;/test_log/c,...。表示创建节点/test_log/c。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　...session 0x159...0xcf0 delete '/test_log/c。表示删除节点/test_log/c。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">3. 日志写入</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　FileTxnLog负责维护事务日志对外的接口，包括事务日志的写入和读取等。Zookeeper的事务日志写入过程大体可以分为如下6个步骤。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>确定是否有事务日志可写</strong>。当Zookeeper服务器启动完成需要进行第一次事务日志的写入，或是上一次事务日志写满时，都会处于与事务日志文件断开的状态，即Zookeeper服务器没有和任意一个日志文件相关联。因此在进行事务日志写入前，Zookeeper首先会判断FileTxnLog组件是否已经关联上一个可写的事务日志文件。若没有，则会使用该事务操作关联的ZXID作为后缀创建一个事务日志文件，同时构建事务日志的文件头信息，并立即写入这个事务日志文件中去，同时将该文件的文件流放入streamToFlush集合，该集合用来记录当前需要强制进行数据落盘的文件流。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)&nbsp;<strong>确定事务日志文件是否需要扩容(预分配)</strong>。Zookeeper会采用磁盘空间预分配策略。当检测到当前事务日志文件剩余空间不足4096字节时，就会开始进行文件空间扩容，即在现有文件大小上，将文件增加65536KB(64MB)，然后使用"0"填充被扩容的文件空间。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)&nbsp;<strong>事务序列化</strong>。对事务头和事务体的序列化，其中事务体又可分为会话创建事务、节点创建事务、节点删除事务、节点数据更新事务等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4)&nbsp;<strong>生成Checksum</strong>。为保证日志文件的完整性和数据的准确性，Zookeeper在将事务日志写入文件前，会计算生成Checksum。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5)&nbsp;<strong>写入事务日志文件流</strong>。将序列化后的事务头、事务体和Checksum写入文件流中，此时并为写入到磁盘上。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6)&nbsp;<strong>事务日志刷入磁盘</strong>。由于步骤5中的缓存原因，无法实时地写入磁盘文件中，因此需要将缓存数据强制刷入磁盘。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">4. 日志截断</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在Zookeeper运行过程中，可能出现非Leader记录的事务ID比Leader上大，这是非法运行状态。此时，需要保证所有机器必须与该Leader的数据保持同步，即Leader会发送TRUNC命令给该机器，要求进行日志截断，Learner收到该命令后，就会删除所有包含或大于该事务ID的事务日志文件。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 snapshot-数据快照</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　数据快照是Zookeeper数据存储中非常核心的运行机制，数据快照用来记录Zookeeper服务器上某一时刻的全量内存数据内容，并将其写入指定的磁盘文件中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. 文件存储</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　与事务文件类似，Zookeeper快照文件也可以指定特定磁盘目录，通过dataDir属性来配置。若指定dataDir为/home/admin/zkData/zk_data，则在运行过程中会在该目录下创建version-2的目录，该目录确定了当前Zookeeper使用的快照数据格式版本号。在Zookeeper运行时，会生成一系列文件。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">2. 数据快照</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　FileSnap负责维护快照数据对外的接口，包括快照数据的写入和读取等，将内存数据库写入快照数据文件其实是一个序列化过程。针对客户端的每一次事务操作，Zookeeper都会将他们记录到事务日志中，同时也会将数据变更应用到内存数据库中，Zookeeper在进行若干次事务日志记录后，将内存数据库的全量数据Dump到本地文件中，这就是数据快照。其步骤如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>确定是否需要进行数据快照</strong>。每进行一次事务日志记录之后，Zookeeper都会检测当前是否需要进行数据快照，考虑到数据快照对于Zookeeper机器的影响，需要尽量避免Zookeeper集群中的所有机器在同一时刻进行数据快照。采用过半随机策略进行数据快照操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)&nbsp;<strong>切换事务日志文件</strong>。表示当前的事务日志已经写满，需要重新创建一个新的事务日志。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)&nbsp;<strong>创建数据快照异步线程</strong>。创建单独的异步线程来进行数据快照以避免影响Zookeeper主流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4)&nbsp;<strong>获取全量数据和会话信息</strong>。从ZKDatabase中获取到DataTree和会话信息。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5)&nbsp;<strong>生成快照数据文件名</strong>。Zookeeper根据当前已经提交的最大ZXID来生成数据快照文件名。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6)&nbsp;<strong>数据序列化</strong>。首先序列化文件头信息，然后再对会话信息和DataTree分别进行序列化，同时生成一个Checksum，一并写入快照数据文件中去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　2.4 初始化</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在Zookeeper服务器启动期间，首先会进行数据初始化工作，用于将存储在磁盘上的数据文件加载到Zookeeper服务器内存中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. 初始化流程</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper的书初始化过程如下图所示</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161216214526120-1735909566.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　数据的初始化工作是从磁盘上加载数据的过程，主要包括了从快照文件中加载快照数据和根据实物日志进行数据修正两个过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>初始化FileTxnSnapLog</strong>。FileTxnSnapLog是Zookeeper事务日志和快照数据访问层，用于衔接上层业务和底层数据存储，底层数据包含了事务日志和快照数据两部分。FileTxnSnapLog中对应FileTxnLog和FileSnap。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)&nbsp;<strong>初始化ZKDatabase</strong>。首先构建DataTree，同时将FileTxnSnapLog交付ZKDatabase，以便内存数据库能够对事务日志和快照数据进行访问。在ZKDatabase初始化时，DataTree也会进行相应的初始化工作，如创建一些默认结点，如/、/zookeeper、/zookeeper/quota三个节点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)&nbsp;<strong>创建PlayBackListener</strong>。其主要用来接收事务应用过程中的回调，在Zookeeper数据恢复后期，会有事务修正过程，此过程会回调PlayBackListener来进行对应的数据修正。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4)&nbsp;<strong>处理快照文件</strong>。此时可以从磁盘中恢复数据了，首先从快照文件开始加载。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5)&nbsp;<strong>获取最新的100个快照文件</strong>。更新时间最晚的快照文件包含了最新的全量数据。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6)&nbsp;<strong>解析快照文件</strong>。逐个解析快照文件，此时需要进行反序列化，生成DataTree和sessionsWithTimeouts，同时还会校验Checksum及快照文件的正确性。对于100个快找文件，如果正确性校验通过时，通常只会解析最新的那个快照文件。只有最新快照文件不可用时，才会逐个进行解析，直至100个快照文件全部解析完。若将100个快照文件解析完后还是无法成功恢复一个完整的DataTree和sessionWithTimeouts，此时服务器启动失败。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(7)&nbsp;<strong>获取最新的ZXID</strong>。此时根据快照文件的文件名即可解析出最新的ZXID：zxid_for_snap。该ZXID代表了Zookeeper开始进行数据快照的时刻。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(8)<strong>&nbsp;处理事务日志</strong>。此时服务器内存中已经有了一份近似全量的数据，现在开始通过事务日志来更新增量数据。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(9)&nbsp;<strong>获取所有zxid_for_snap之后提交的事务</strong>。此时，已经可以获取快照数据的最新ZXID。只需要从事务日志中获取所有ZXID比步骤7得到的ZXID大的事务操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(10)&nbsp;<strong>事务应用</strong>。获取大于zxid_for_snap的事务后，将其逐个应用到之前基于快照数据文件恢复出来的DataTree和sessionsWithTimeouts。每当有一个事务被应用到内存数据库中后，Zookeeper同时会回调PlayBackListener，将这事务操作记录转换成Proposal，并保存到ZKDatabase的committedLog中，以便Follower进行快速同步。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(11)&nbsp;<strong>获取最新的ZXID</strong>。待所有的事务都被完整地应用到内存数据库中后，也就基本上完成了数据的初始化过程，此时再次获取ZXID，用来标识上次服务器正常运行时提交的最大事务ID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(12)&nbsp;<strong>校验epoch</strong>。epoch标识了当前Leader周期，集群机器相互通信时，会带上这个epoch以确保彼此在同一个Leader周期中。完成数据加载后，Zookeeper会从步骤11中确定ZXID中解析出事务处理的Leader周期：epochOfZxid。同时也会从磁盘的currentEpoch和acceptedEpoch文件中读取上次记录的最新的epoch值，进行校验。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 数据同步</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　整个集群完成Leader选举后，Learner会向Leader进行注册，当Learner向Leader完成注册后，就进入数据同步环节，同步过程就是Leader将那些没有在Learner服务器上提交过的事务请求同步给Learner服务器，大体过程如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161217172721136-1675752544.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>获取Learner状态</strong>。在注册Learner的最后阶段，Learner服务器会发送给Leader服务器一个ACKEPOCH数据包，Leader会从这个数据包中解析出该Learner的currentEpoch和lastZxid。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)<strong>&nbsp;数据同步初始化</strong>。首先从Zookeeper内存数据库中提取出事务请求对应的提议缓存队列proposals，同时完成peerLastZxid(该Learner最后处理的ZXID)、minCommittedLog(Leader提议缓存队列commitedLog中最小的ZXID)、maxCommittedLog(Leader提议缓存队列commitedLog中的最大ZXID)三个ZXID值的初始化。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于集群数据同步而言，通常分为四类，<strong>直接差异化同步(DIFF同步)、先回滚再差异化同步(TRUNC+DIFF同步)、仅回滚同步(TRUNC同步)、全量同步(SNAP同步)</strong>，在初始化阶段，Leader会优先以全量同步方式来同步数据。同时，会根据Leader和Learner之间的数据差异情况来决定最终的数据同步方式。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183;&nbsp;直接差异化同步</strong>(DIFF同步，peerLastZxid介于minCommittedLog和maxCommittedLog之间)。Leader首先向这个Learner发送一个DIFF指令，用于通知Learner进入差异化数据同步阶段，Leader即将把一些Proposal同步给自己，针对每个Proposal，Leader都会通过发送PROPOSAL内容数据包和COMMIT指令数据包来完成，</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183;&nbsp;先回滚再差异化同步</strong>(TRUNC+DIFF同步，Leader已经将事务记录到本地事务日志中，但是没有成功发起Proposal流程)。当Leader发现某个Learner包含了一条自己没有的事务记录，那么就需要该Learner进行事务回滚，回滚到Leader服务器上存在的，同时也是最接近于peerLastZxid的ZXID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183;&nbsp;仅回滚同步</strong>(TRUNC同步，peerLastZxid大于maxCommittedLog)。Leader要求Learner回滚到ZXID值为maxCommittedLog对应的事务操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183;&nbsp;全量同步</strong>(SNAP同步，peerLastZxid小于minCommittedLog或peerLastZxid不等于lastProcessedZxid)。Leader无法直接使用提议缓存队列和Learner进行同步，因此只能进行全量同步。Leader将本机的全量内存数据同步给Learner。Leader首先向Learner发送一个SNAP指令，通知Learner即将进行全量同步，随后，Leader会从内存数据库中获取到全量的数据节点和会话超时时间记录器，将他们序列化后传输给Learner。Learner接收到该全量数据后，会对其反序列化后载入到内存数据库中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　本篇博文主要讲解了Zookeeper的数据与存储，包括内存数据，快照数据，以及如何进行数据的同步等细节，至此，Zookeeper的理论学习部分已经全部完成，之后会进行源码分析，也谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432206.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 15:14 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432206.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper请求处理</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432205.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 07:06:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432205.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432205.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432205.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432205.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432205.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6140503.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在前面学习了Zookeeper中服务器的三种角色及其之间的通信，接着学习对于客户端的一次请求，Zookeeper是如何进行处理的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、请求处理</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 会话创建请求</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper服务端对于会话创建的处理，大体可以分为请求接收、会话创建、预处理、事务处理、事务应用和会话响应六大环节，其大体流程如</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161207095042507-929689583.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /><br />　　<strong><span style="line-height: 1.8; color: #000000;">1. 请求接收</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) I/O层接收来自客户端的请求。NIOServerCnxn维护每一个客户端连接，客户端与服务器端的所有通信都是由NIOServerCnxn负责，其负责统一接收来自客户端的所有请求，并将请求内容从底层网络I/O中完整地读取出来。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 判断是否是客户端会话创建请求。每个会话对应一个NIOServerCnxn实体，对于每个请求，Zookeeper都会检查当前NIOServerCnxn实体是否已经被初始化，如果尚未被初始化，那么就可以确定该客户端一定是会话创建请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 反序列化ConnectRequest请求。一旦确定客户端请求是否是会话创建请求，那么服务端就可以对其进行反序列化，并生成一个ConnectRequest载体。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 判断是否是ReadOnly客户端。如果当前Zookeeper服务器是以ReadOnly模式启动，那么所有来自非ReadOnly型客户端的请求将无法被处理。因此，服务端需要先检查是否是ReadOnly客户端，并以此来决定是否接受该会话创建请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5) 检查客户端ZXID。正常情况下，在一个Zookeeper集群中，服务端的ZXID必定大于客户端的ZXID，因此若发现客户端的ZXID大于服务端ZXID，那么服务端不接受该客户端的会话创建请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6) 协商sessionTimeout。在客户端向服务器发送超时时间后，服务器会根据自己的超时时间限制最终确定该会话超时时间，这个过程就是sessionTimeout协商过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(7) 判断是否需要重新激活创建会话。服务端根据客户端请求中是否包含sessionID来判断该客户端是否需要重新创建会话，若客户单请求中包含sessionID，那么就认为该客户端正在进行会话重连，这种情况下，服务端只需要重新打开这个会话，否则需要重新创建。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong><span style="line-height: 1.8; color: #000000;">　2. 会话创建</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 为客户端生成sessionID。在为客户端创建会话之前，服务端首先会为每个客户端分配一个sessionID，服务端为客户端分配的sessionID是全局唯一的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 注册会话。向SessionTracker中注册会话，SessionTracker中维护了sessionsWithTimeout和sessionsById，在会话创建初期，会将客户端会话的相关信息保存到这两个数据结构中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 激活会话。激活会话涉及Zookeeper会话管理的分桶策略，其核心是为会话安排一个区块，以便会话清理程序能够快速高效地进行会话清理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 生成会话密码。服务端在创建一个客户端会话时，会同时为客户端生成一个会话密码，连同sessionID一同发给客户端，作为会话在集群中不同机器间转移的凭证。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong><span style="line-height: 1.8; color: #000000;">3. 预处理</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 将请求交给PrepRequestProcessor处理器处理。在提交给第一个请求处理器之前，Zookeeper会根据该请求所属的会话，进行一次激活会话操作，以确保当前会话处于激活状态，完成会话激活后，则提交请求至处理器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 创建请求事务头。对于事务请求，Zookeeper会为其创建请求事务头，服务端后续的请求处理器都是基于该请求头来识别当前请求是否是事务请求，请求事务头包含了一个事务请求最基本的一些信息，包括sessionID、ZXID（事务请求对应的事务ZXID）、CXID（客户端的操作序列）和请求类型（如create、delete、setData、createSession等）等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 创建请求事务体。由于此时是会话创建请求，其事务体是CreateSessionTxn。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 注册于激活会话。处理由非Leader服务器转发过来的会话创建请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #0000ff;">　<strong><span style="line-height: 1.8; color: #000000;">4. 事务处理</span></strong></span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 将请求交给ProposalRequestProcessor处理器。与提议相关的处理器，从ProposalRequestProcessor开始，请求的处理将会进入三个子处理流程，分别是Sync流程、Proposal流程、Commit流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161207103040257-77514359.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>Sync流程</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　使用SyncRequestProcessor处理器记录事务日志，针对每个事务请求，都会通过事务日志的形式将其记录，完成日志记录后，每个Follower都会向Leader发送ACK消息，表明自身完成了事务日志的记录，以便Leader统计每个事务请求的投票情况。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>Proposal流程</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　每个事务请求都需要集群中过半机器投票认可才能被真正应用到内存数据库中，这个投票与统计过程就是Proposal流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 发起投票。若当前请求是事务请求，Leader会发起一轮事务投票，在发起事务投票之前，会检查当前服务端的ZXID是否可用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 生成提议Proposal。若ZXID可用，Zookeeper会将已创建的请求头和事务体以及ZXID和请求本身序列化到Proposal对象中，此Proposal对象就是一个提议。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 广播提议。Leader以ZXID作为标识，将该提议放入投票箱outstandingProposals中，同时将该提议广播给所有Follower。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 收集投票。Follower接收到Leader提议后，进入Sync流程进行日志记录，记录完成后，发送ACK消息至Leader服务器，Leader根据这些ACK消息来统计每个提议的投票情况，当一个提议获得半数以上投票时，就认为该提议通过，进入Commit阶段。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 将请求放入toBeApplied队列中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 广播Commit消息。Leader向Follower和Observer发送COMMIT消息。向Observer发送INFORM消息，向Leader发送ZXID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>Commit流程</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 将请求交付CommitProcessor。CommitProcessor收到请求后，将其放入queuedRequests队列中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 处理queuedRequest队列请求。CommitProcessor中单独的线程处理queuedRequests队列中的请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 标记nextPending。若从queuedRequests中取出的是事务请求，则需要在集群中进行投票处理，同时将nextPending标记位当前请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 等待Proposal投票。在进行Commit流程的同时，Leader会生成Proposal并广播给所有Follower服务器，此时，Commit流程等待，直到投票结束。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 投票通过。若提议获得过半机器认可，则进入请求提交阶段，该请求会被放入commitedRequests队列中，同时唤醒Commit流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 提交请求。若commitedRequests队列中存在可以提交的请求，那么Commit流程则开始提交请求，将请求放入toProcess队列中，然后交付下一个请求处理器：FinalRequestProcessor。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong><span style="line-height: 1.8; color: #000000;">5. 事务应用</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 交付给FinalRequestProcessor处理器。FinalRequestProcessor处理器检查outstandingChanges队列中请求的有效性，若发现这些请求已经落后于当前正在处理的请求，那么直接从outstandingChanges队列中移除。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 事务应用。之前的请求处理仅仅将事务请求记录到了事务日志中，而内存数据库中的状态尚未改变，因此，需要将事务变更应用到内存数据库。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 将事务请求放入队列commitProposal。完成事务应用后，则将该请求放入commitProposal队列中，commitProposal用来保存最近被提交的事务请求，以便集群间机器进行数据的快速同步。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong><span style="line-height: 1.8; color: #000000;">6. 会话响应</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 统计处理。Zookeeper计算请求在服务端处理所花费的时间，统计客户端连接的基本信息，如lastZxid(最新的ZXID)、lastOp(最后一次和服务端的操作)、lastLatency(最后一次请求处理所花费的时间)等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 创建响应ConnectResponse。会话创建成功后的响应，包含了当前客户端和服务端之间的通信协议版本号、会话超时时间、sessionID和会话密码。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 序列化ConnectResponse。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) I/O层发送响应给客户端。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 SetData请求</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　服务端对于SetData请求大致可以分为四步，预处理、事务处理、事务应用、请求响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　1. 预处理</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) I/O层接收来自客户端的请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 判断是否是客户端"会话创建"请求。对于SetData请求，按照正常事务请求进行处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 将请求交给PrepRequestProcessor处理器进行处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 创建请求事务头。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5) 会话检查。检查该会话是否有效。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6) 反序列化请求，并创建ChangeRecord记录。反序列化并生成特定的SetDataRequest请求，请求中包含了数据节点路径path、更新的内容data和期望的数据节点版本version。同时根据请求对应的path，Zookeeper生成一个ChangeRecord记录，并放入outstandingChanges队列中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(7) ACL检查。检查客户端是否具有数据更新的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(8) 数据版本检查。通过version属性来实现乐观锁机制的写入校验。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(9) 创建请求事务体SetDataTxn。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(10) 保存事务操作到outstandingChanges队列中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2. 事务处理</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于事务请求，服务端都会发起事务处理流程。所有事务请求都是由ProposalRequestProcessor处理器处理，通过Sync、Proposal、Commit三个子流程相互协作完成。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>3. 事务应用</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 交付给FinalRequestProcessor处理器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 事务应用。将请求事务头和事务体直接交给内存数据库ZKDatabase进行事务应用，同时返回ProcessTxnResult对象，包含了数据节点内容更新后的stat。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 将事务请求放入commitProposal队列。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　4. 请求响应</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 创建响应体SetDataResponse。其包含了当前数据节点的最新状态stat。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 创建响应头。包含当前响应对应的事务ZXID和请求处理是否成功的标识。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 序列化响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) I/O层发送响应给客户端。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 GetData请求</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　服务端对于GetData请求的处理，大致分为三步，预处理、非事务处理、请求响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>1. 预处理</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) I/O层接收来自客户端的请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 判断是否是客户端"会话创建"请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 将请求交给PrepRequestProcessor处理器进行处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 会话检查。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2. 非事务处理</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 反序列化GetDataRequest请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 获取数据节点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) ACL检查。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 获取数据内容和stat，注册Watcher。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>3. 请求响应</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 创建响应体GetDataResponse。响应体包含当前数据节点的内容和状态stat。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 创建响应头。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 统计处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) 序列化响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5) I/O层发送响应给客户端。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　本篇博文讲解了Zookeeper服务端对于客户端不同请求的处理的具体流程，可能从文字上看步骤会显得相对枯燥，但是会为之后的源码分析打下很好的基础，谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432205.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 15:06 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432205.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper的Leader选举</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432203.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 07:05:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432203.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432203.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432203.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432203.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432203.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6107600.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　前面学习了Zookeeper服务端的相关细节，其中对于集群启动而言，很重要的一部分就是Leader选举，接着就开始深入学习Leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、Leader选举</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 Leader选举概述</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时，需要进入Leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 服务器初始化启动。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 服务器运行期间无法和Leader保持连接。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　下面就两种情况进行分析讲解。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. 服务器启动时期的Leader选举</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　若进行Leader选举，则至少需要两台机器，这里选取3台机器组成的服务器集群为例。在集群初始化阶段，当有一台服务器Server1启动时，其单独无法进行和完成Leader选举，当第二胎服务器Server2启动时，此时两台机器可以相互通信，每台机器都试图找到Leader，于是进入Leader选举过程。选举过程如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>(1) 每个Server发出一个投票</strong>。由于是初始情况，Server1和Server2都会将自己作为Leader服务器来进行投票，每次投票会包含所推举的服务器的myid和ZXID，使用(myid, ZXID)来表示，此时Server1的投票为(1, 0)，Server2的投票为(2, 0)，然后各自将这个投票发给集群中其他机器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>(2)&nbsp;接受来自各个服务的投票</strong>。集群的每个服务器收到投票后，首先判断该投票的有效性，如检查是否是本轮投票、是否来自LOOKING状态的服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>(3) 处理投票</strong>。针对每一个投票，服务器都需要将别人的投票和自己的投票进行PK，PK规则如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; 优先检查ZXID</strong>。ZXID比较大的服务器优先作为Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; 如果ZXID相同，那么就比较myid</strong>。myid较大的服务器作为Leader服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于Server1而言，它的投票是(1, 0)，接收Server2的投票为(2, 0)，首先会比较两者的ZXID，均为0，再比较myid，此时Server2的myid最大，于是更新自己的投票为(2, 0)，然后重新投票，对于Server2而言，其无须更新自己的投票，只是再次向集群中所有机器发出上一次投票信息即可。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>(4) 统计投票</strong>。每次投票后，服务器都会统计投票信息，判断是否已经有过半机器接受到相同的投票信息，对于Server1、Server2而言，都统计出集群中已经有两台机器接受了(2, 0)的投票信息，此时便认为已经选出了Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>(5) 改变服务器状态</strong>。一旦确定了Leader，每个服务器就会更新自己的状态，如果是Follower，那么就变更为FOLLOWING，如果是Leader，就变更为LEADING。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">2. 服务器运行时期的Leader选举</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在Zookeeper运行期间，Leader与非Leader服务器各司其职，即便当有非Leader服务器宕机或新加入，此时也不会影响Leader，但是一旦Leader服务器挂了，那么整个集群将暂停对外服务，进入新一轮Leader选举，其过程和启动时期的Leader选举过程基本一致。假设正在运行的有Server1、Server2、Server3三台服务器，当前Leader是Server2，若某一时刻Leader挂了，此时便开始Leader选举。选举过程如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)<strong>&nbsp;变更状态</strong>。Leader挂后，余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING，然后开始进入Leader选举过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)<strong>&nbsp;每个Server会发出一个投票</strong>。在运行期间，每个服务器上的ZXID可能不同，此时假定Server1的ZXID为123，Server3的ZXID为122；在第一轮投票中，Server1和Server3都会投自己，产生投票(1, 123)，(3, 122)，然后各自将投票发送给集群中所有机器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)<strong>&nbsp;接收来自各个服务器的投票</strong>。与启动时过程相同。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4)<strong>&nbsp;处理投票</strong>。与启动时过程相同，此时，Server1将会成为Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5)<strong>&nbsp;统计投票</strong>。与启动时过程相同。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6)<strong>&nbsp;改变服务器的状态</strong>。与启动时过程相同。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 Leader选举算法分析</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在3.4.0后的Zookeeper的版本只保留了TCP版本的FastLeaderElection选举算法。当一台机器进入Leader选举时，当前集群可能会处于以下两种状态</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 集群中已经存在Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 集群中不存在Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于集群中已经存在Leader而言，此种情况一般都是某台机器启动得较晚，在其启动之前，集群已经在正常工作，对这种情况，该机器试图去选举Leader时，会被告知当前服务器的Leader信息，对于该机器而言，仅仅需要和Leader机器建立起连接，并进行状态同步即可。而在集群中不存在Leader情况下则会相对复杂，其步骤如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>第一次投票</strong>。无论哪种导致进行Leader选举，集群的所有机器都处于试图选举出一个Leader的状态，即LOOKING状态，LOOKING机器会向所有其他机器发送消息，该消息称为投票。投票中包含了SID（服务器的唯一标识）和ZXID（事务ID），(SID, ZXID)形式来标识一次投票信息。假定Zookeeper由5台机器组成，SID分别为1、2、3、4、5，ZXID分别为9、9、9、8、8，并且此时SID为2的机器是Leader机器，某一时刻，1、2所在机器出现故障，因此集群开始进行Leader选举。在第一次投票时，每台机器都会将自己作为投票对象，于是SID为3、4、5的机器投票情况分别为(3, 9)，(4, 8)， (5, 8)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)&nbsp;<strong>变更投票</strong>。每台机器发出投票后，也会收到其他机器的投票，每台机器会根据一定规则来处理收到的其他机器的投票，并以此来决定是否需要变更自己的投票，这个规则也是整个Leader选举算法的核心所在，其中术语描述如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; vote_sid</strong>：接收到的投票中所推举Leader服务器的SID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; vote_zxid</strong>：接收到的投票中所推举Leader服务器的ZXID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; self_sid</strong>：当前服务器自己的SID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; self_zxid</strong>：当前服务器自己的ZXID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　每次对收到的投票的处理，都是对(vote_sid, vote_zxid)和(self_sid, self_zxid)对比的过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　规则一：如果vote_zxid大于self_zxid，就认可当前收到的投票，并再次将该投票发送出去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　规则二：如果vote_zxid小于self_zxid，那么坚持自己的投票，不做任何变更。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　规则三：如果vote_zxid等于self_zxid，那么就对比两者的SID，如果vote_sid大于self_sid，那么就认可当前收到的投票，并再次将该投票发送出去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　规则四：如果vote_zxid等于self_zxid，并且vote_sid小于self_sid，那么坚持自己的投票，不做任何变更。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　结合上面规则，给出下面的集群变更过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161202213100568-693960760.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)<strong>&nbsp;确定Leader</strong>。经过第二轮投票后，集群中的每台机器都会再次接收到其他机器的投票，然后开始统计投票，如果一台机器收到了超过半数的相同投票，那么这个投票对应的SID机器即为Leader。此时Server3将成为Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　由上面规则可知，通常那台服务器上的数据越新（ZXID会越大），其成为Leader的可能性越大，也就越能够保证数据的恢复。如果ZXID相同，则SID越大机会越大。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 Leader选举实现细节</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">1. 服务器状态</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　服务器具有四种状态，分别是LOOKING、FOLLOWING、LEADING、OBSERVING。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>LOOKING</strong>：寻找Leader状态。当服务器处于该状态时，它会认为当前集群中没有Leader，因此需要进入Leader选举状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>FOLLOWING</strong>：跟随者状态。表明当前服务器角色是Follower。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>LEADING</strong>：领导者状态。表明当前服务器角色是Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>OBSERVING</strong>：观察者状态。表明当前服务器角色是Observer。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #0000ff;">　2. 投票数据结构</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　每个投票中包含了两个最基本的信息，所推举服务器的SID和ZXID，投票（Vote）在Zookeeper中包含字段如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>id</strong>：被推举的Leader的SID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>zxid</strong>：被推举的Leader事务ID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　electionEpoch</strong>：逻辑时钟，用来判断多个投票是否在同一轮选举周期中，该值在服务端是一个自增序列，每次进入新一轮的投票后，都会对该值进行加1操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>peerEpoch</strong>：被推举的Leader的epoch。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>state</strong>：当前服务器的状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">3. QuorumCnxManager：网络I/O</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　每台服务器在启动的过程中，会启动一个QuorumPeerManager，负责各台服务器之间的底层Leader选举过程中的网络通信。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>消息队列</strong>。QuorumCnxManager内部维护了一系列的队列，用来保存接收到的、待发送的消息以及消息的发送器，除接收队列以外，其他队列都按照SID分组形成队列集合，如一个集群中除了自身还有3台机器，那么就会为这3台机器分别创建一个发送队列，互不干扰。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; recvQueue</strong>：消息接收队列，用于存放那些从其他服务器接收到的消息。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; queueSendMap</strong>：消息发送队列，用于保存那些待发送的消息，按照SID进行分组。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; senderWorkerMap</strong>：发送器集合，每个SenderWorker消息发送器，都对应一台远程Zookeeper服务器，负责消息的发送，也按照SID进行分组。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<strong>&#183; lastMessageSent</strong>：最近发送过的消息，为每个SID保留最近发送过的一个消息。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)&nbsp;<strong>建立连接</strong>。为了能够相互投票，Zookeeper集群中的所有机器都需要两两建立起网络连接。QuorumCnxManager在启动时会创建一个ServerSocket来监听Leader选举的通信端口(默认为3888)。开启监听后，Zookeeper能够不断地接收到来自其他服务器的创建连接请求，在接收到其他服务器的TCP连接请求时，会进行处理。为了避免两台机器之间重复地创建TCP连接，Zookeeper只允许SID大的服务器主动和其他机器建立连接，否则断开连接。在接收到创建连接请求后，服务器通过对比自己和远程服务器的SID值来判断是否接收连接请求，如果当前服务器发现自己的SID更大，那么会断开当前连接，然后自己主动和远程服务器建立连接。一旦连接建立，就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker，并启动。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)&nbsp;<strong>消息接收与发送</strong>。<strong>消息接收</strong>：由消息接收器RecvWorker负责，由于Zookeeper为每个远程服务器都分配一个单独的RecvWorker，因此，每个RecvWorker只需要不断地从这个TCP连接中读取消息，并将其保存到recvQueue队列中。<strong>消息发送</strong>：由于Zookeeper为每个远程服务器都分配一个单独的SendWorker，因此，每个SendWorker只需要不断地从对应的消息发送队列中获取出一个消息发送即可，同时将这个消息放入lastMessageSent中。在SendWorker中，一旦Zookeeper发现针对当前服务器的消息发送队列为空，那么此时需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送，这是为了解决接收方在消息接收前或者接收到消息后服务器挂了，导致消息尚未被正确处理。同时，Zookeeper能够保证接收方在处理消息时，会对重复消息进行正确的处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">4. FastLeaderElection：选举算法核心</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 外部投票</strong>：特指其他服务器发来的投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 内部投票</strong>：服务器自身当前的投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 选举轮次</strong>：Zookeeper服务器Leader选举的轮次，即logicalclock。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; PK</strong>：对内部投票和外部投票进行对比来确定是否需要变更内部投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">(1) 选票管理</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; sendqueue</strong>：选票发送队列，用于保存待发送的选票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; recvqueue</strong>：选票接收队列，用于保存接收到的外部投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; WorkerReceiver</strong>：选票接收器。其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息，并将其转换成一个选票，然后保存到recvqueue中，在选票接收过程中，如果发现该外部选票的选举轮次小于当前服务器的，那么忽略该外部投票，同时立即发送自己的内部投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　&#183; WokerSender</strong>：选票发送器，不断地从sendqueue中获取待发送的选票，并将其传递到底层QuorumCnxManager中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">(2) 算法核心</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206114702772-1120304539.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　上图展示了FastLeaderElection模块是如何与底层网络I/O进行交互的。Leader选举的基本流程如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.<strong>&nbsp;自增选举轮次</strong>。Zookeeper规定所有有效的投票都必须在同一轮次中，在开始新一轮投票时，会首先对logicalclock进行自增操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.<strong>&nbsp;初始化选票</strong>。在开始进行新一轮投票之前，每个服务器都会初始化自身的选票，并且在初始化阶段，每台服务器都会将自己推举为Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.<strong>&nbsp;发送初始化选票</strong>。完成选票的初始化后，服务器就会发起第一次投票。Zookeeper会将刚刚初始化好的选票放入sendqueue中，由发送器WorkerSender负责发送出去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　4.<strong>&nbsp;接收外部投票</strong>。每台服务器会不断地从recvqueue队列中获取外部选票。如果服务器发现无法获取到任何外部投票，那么就会立即确认自己是否和集群中其他服务器保持着有效的连接，如果没有连接，则马上建立连接，如果已经建立了连接，则再次发送自己当前的内部投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　</strong>5.<strong>&nbsp;判断选举轮次</strong>。在发送完初始化选票之后，接着开始处理外部投票。在处理外部投票时，会根据选举轮次来进行不同的处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<span style="line-height: 1.8; color: #ff0000;"><strong>&#183; 外部投票的选举轮次大于内部投票</strong></span>。若服务器自身的选举轮次落后于该外部投票对应服务器的选举轮次，那么就会立即更新自己的选举轮次(logicalclock)，并且清空所有已经收到的投票，然后使用初始化的投票来进行PK以确定是否变更内部投票。最终再将内部投票发送出去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<span style="line-height: 1.8; color: #ff0000;"><strong>&#183; 外部投票的选举轮次小于内部投</strong></span>票。若服务器接收的外选票的选举轮次落后于自身的选举轮次，那么Zookeeper就会直接忽略该外部投票，不做任何处理，并返回步骤4。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　<span style="line-height: 1.8; color: #ff0000;"><strong>&#183; 外部投票的选举轮次等于内部投票</strong></span>。此时可以开始进行选票PK。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　6.<strong>&nbsp;选票PK</strong>。在进行选票PK时，符合任意一个条件就需要变更投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 若外部投票中推举的Leader服务器的选举轮次大于内部投票，那么需要变更投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 若选举轮次一致，那么就对比两者的ZXID，若外部投票的ZXID大，那么需要变更投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 若两者的ZXID一致，那么就对比两者的SID，若外部投票的SID大，那么就需要变更投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　7.<strong>&nbsp;变更投票</strong>。经过PK后，若确定了外部投票优于内部投票，那么就变更投票，即使用外部投票的选票信息来覆盖内部投票，变更完成后，再次将这个变更后的内部投票发送出去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　8.&nbsp;<strong>选票归档</strong>。无论是否变更了投票，都会将刚刚收到的那份外部投票放入选票集合recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票（按照服务队的SID区别，如{(1, vote1), (2, vote2)...}）。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　9.&nbsp;<strong>统计投票</strong>。完成选票归档后，就可以开始统计投票，统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票，如果确定已经有过半服务器认可了该投票，则终止投票。否则返回步骤4。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　10.<strong>&nbsp;更新服务器状态</strong>。若已经确定可以终止投票，那么就开始更新服务器状态，服务器首选判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己，若是自己，则将自己的服务器状态更新为LEADING，若不是，则根据具体情况来确定自己是FOLLOWING或是OBSERVING。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　以上10个步骤就是FastLeaderElection的核心，其中步骤4-9会经过几轮循环，直到有Leader选举产生。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　经过本篇博文的学习，了解了Leader选举的具体细节，这对于之后的代码分析会打下很好的基础。也谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432203.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 15:05 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432203.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper的服务器角色</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432204.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 07:05:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432204.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432204.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432204.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432204.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432204.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6139266.html<br /><br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　前一篇已经详细的讲解了Zookeeper的Leader选举过程，下面接着学习Zookeeper中服务器的各个角色及其细节。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、服务器角色</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 Leader</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Leader服务器是Zookeeper集群工作的核心，其主要工作如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 事务请求的唯一调度和处理者，保证集群事务处理的顺序性。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 集群内部各服务器的调度者。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #0000ff;">　1. 请求处理链</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　使用责任链来处理每个客户端的请求时Zookeeper的特色，Leader服务器的请求处理链如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206203032319-1806823400.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) PrepRequestProcessor。请求预处理器。在Zookeeper中，那些会改变服务器状态的请求称为事务请求（创建节点、更新数据、删除节点、创建会话等），PrepRequestProcessor能够识别出当前客户端请求是否是事务请求。对于事务请求，PrepRequestProcessor处理器会对其进行一系列预处理，如创建请求事务头、事务体、会话检查、ACL检查和版本检查等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) ProposalRequestProcessor。事务投票处理器。Leader服务器事务处理流程的发起者，对于非事务性请求，ProposalRequestProcessor会直接将请求转发到CommitProcessor处理器，不再做任何处理，而对于事务性请求，处理将请求转发到CommitProcessor外，还会根据请求类型创建对应的Proposal提议，并发送给所有的Follower服务器来发起一次集群内的事务投票。同时，ProposalRequestProcessor还会将事务请求交付给SyncRequestProcessor进行事务日志的记录。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) SyncRequestProcessor。事务日志记录处理器。用来将事务请求记录到事务日志文件中，同时会触发Zookeeper进行数据快照。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) AckRequestProcessor。负责在SyncRequestProcessor完成事务日志记录后，向Proposal的投票收集器发送ACK反馈，以通知投票收集器当前服务器已经完成了对该Proposal的事务日志记录。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4) CommitProcessor。事务提交处理器。对于非事务请求，该处理器会直接将其交付给下一级处理器处理；对于事务请求，其会等待集群内针对Proposal的投票直到该Proposal可被提交，利用CommitProcessor，每个服务器都可以很好地控制对事务请求的顺序处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(5) ToBeCommitProcessor。该处理器有一个toBeApplied队列，用来存储那些已经被CommitProcessor处理过的可被提交的Proposal。其会将这些请求交付给FinalRequestProcessor处理器处理，待其处理完后，再将其从toBeApplied队列中移除。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(6) FinalRequestProcessor。用来进行客户端请求返回之前的操作，包括创建客户端请求的响应，针对事务请求，该处理还会负责将事务应用到内存数据库中去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #0000ff;">2. LearnerHandler</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　为了保证整个集群内部的实时通信，同时为了确保可以控制所有的Follower/Observer服务器，Leader服务器会与每个Follower/Observer服务器建立一个TCP长连接。同时也会为每个Follower/Observer服务器创建一个名为LearnerHandler的实体。LearnerHandler是Learner服务器的管理者，主要负责Follower/Observer服务器和Leader服务器之间的一系列网络通信，包括数据同步、请求转发和Proposal提议的投票等。Leader服务器中保存了所有Follower/Observer对应的LearnerHandler。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　2.2 Follower</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Follower是Zookeeper集群的跟随者，其主要工作如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) 处理客户端非事务性请求（读取数据），转发事务请求给Leader服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) 参与事务请求Proposal的投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3) 参与Leader选举投票。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Follower也采用了责任链模式组装的请求处理链来处理每一个客户端请求，由于不需要对事务请求的投票处理，因此Follower的请求处理链会相对简单，其处理链如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206205916319-94850171.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1) FollowerRequestProcessor。其用作识别当前请求是否是事务请求，若是，那么Follower就会将该请求转发给Leader服务器，Leader服务器是在接收到这个事务请求后，就会将其提交到请求处理链，按照正常事务请求进行处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2) SendAckRequestProcessor。其承担了事务日志记录反馈的角色，在完成事务日志记录后，会向Leader服务器发送ACK消息以表明自身完成了事务日志的记录工作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　2.3 Observer</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Observer充当观察者角色，观察Zookeeper集群的最新状态变化并将这些状态同步过来，其对于非事务请求可以进行独立处理，对于事务请求，则会转发给Leader服务器进行处理。Observer不会参与任何形式的投票，包括事务请求Proposal的投票和Leader选举投票。其处理链如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206210521179-2100336705.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><span style="line-height: 1.8; color: #ff0000;">　　2.4 集群间消息通信</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper的消息类型大体分为数据同步型、服务器初始化型、请求处理型和会话管理型。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(1)&nbsp;<strong>数据同步型</strong>。指在Learner和Leader服务器进行数据同步时，网络通信所用到的消息，通常有DIFF、TRUNC、SNAP、UPTODATE。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206211659288-380771789.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(2)<strong>&nbsp;服务器初始化型</strong>。指在整个集群或是某些新机器初始化时，Leader和Learner之间相互通信所使用的消息类型，常见的有OBSERVERINFO、FOLLOWERINFO、LEADERINFO、ACKEPOCH和NEWLEADER五种。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206213138194-152453727.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(3)<strong>&nbsp;请求处理型</strong>。指在进行清理时，Leader和Learner服务器之间互相通信所使用的消息，常见的有REQUEST、PROPOSAL、ACK、COMMIT、INFORM和SYNC六种。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206214318897-1132940651.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　(4)&nbsp;<strong>会话管理型</strong>。指Zookeeper在进行会话管理时和Learner服务器之间互相通信所使用的消息，常见的有PING和REVALIDATE两种。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201612/616953-20161206215016601-1432875769.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　经过本篇博文的讲解，明白了服务器的各种角色及其作用，以及集群间如何进行通信。谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432204.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 15:05 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432204.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper服务端启动</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432202.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Wed, 28 Dec 2016 07:04:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432202.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432202.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432202.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432202.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432202.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6105276.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　前面已经了解了Zookeeper会话相关知识点，接着来学习Zookeeper服务端相关细节。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、服务端</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　服务端整体架构如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161126210233253-1268373589.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper服务器的启动，大致可以分为以下五个步骤</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1. 配置文件解析。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2. 初始化数据管理器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3. 初始化网络I/O管理器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　4. 数据恢复。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　5. 对外服务。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 单机版服务器启动</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　单机版服务器的启动其流程图如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161127105308893-475081399.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　上图的过程可以分为<strong>预启动</strong>和<strong>初始化</strong>过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>1. 预启动</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 统一由QuorumPeerMain作为启动类。无论单机或集群，在zkServer.cmd和zkServer.sh中都配置了QuorumPeerMain作为启动入口类。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 解析配置文件zoo.cfg。zoo.cfg配置运行时的基本参数，如tickTime、dataDir、clientPort等参数。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 创建并启动历史文件清理器DatadirCleanupManager。对事务日志和快照数据文件进行定时清理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. 判断当前是集群模式还是单机模式启动。若是单机模式，则委托给ZooKeeperServerMain进行启动。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　5. 再次进行配置文件zoo.cfg的解析。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　6. 创建服务器实例ZooKeeperServer。Zookeeper服务器首先会进行服务器实例的创建，然后对该服务器实例进行初始化，包括连接器、内存数据库、请求处理器等组件的初始化。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2. 初始化</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 创建服务器统计器ServerStats。ServerStats是Zookeeper服务器运行时的统计器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 创建Zookeeper数据管理器FileTxnSnapLog。FileTxnSnapLog是Zookeeper上层服务器和底层数据存储之间的对接层，提供了一系列操作数据文件的接口，如事务日志文件和快照数据文件。Zookeeper根据zoo.cfg文件中解析出的快照数据目录dataDir和事务日志目录dataLogDir来创建FileTxnSnapLog。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 设置服务器tickTime和会话超时时间限制。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. 创建ServerCnxnFactory。通过配置系统属性zookeper.serverCnxnFactory来指定使用Zookeeper自己实现的NIO还是使用Netty框架作为Zookeeper服务端网络连接工厂。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　5. 初始化ServerCnxnFactory。Zookeeper会初始化Thread作为ServerCnxnFactory的主线程，然后再初始化NIO服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　6. 启动ServerCnxnFactory主线程。进入Thread的run方法，此时服务端还不能处理客户端请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　7. 恢复本地数据。启动时，需要从本地快照数据文件和事务日志文件进行数据恢复。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　8. 创建并启动会话管理器。Zookeeper会创建会话管理器SessionTracker进行会话管理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　9. 初始化Zookeeper的请求处理链。Zookeeper请求处理方式为责任链模式的实现。会有多个请求处理器依次处理一个客户端请求，在服务器启动时，会将这些请求处理器串联成一个请求处理链。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　10. 注册JMX服务。Zookeeper会将服务器运行时的一些信息以JMX的方式暴露给外部。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　11.&nbsp;注册Zookeeper服务器实例。将Zookeeper服务器实例注册给ServerCnxnFactory，之后Zookeeper就可以对外提供服务。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　至此，单机版的Zookeeper服务器启动完毕。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　2.2 集群服务器启动</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　单机和集群服务器的启动在很多地方是一致的，其流程图如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161127110817253-53261436.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　上图的过程可以分为<strong>预启动、初始化、Leader选举、Leader与Follower启动期交互过程、Leader与Follower启动</strong>等过程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>1. 预启动</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 统一由QuorumPeerMain作为启动类。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 解析配置文件zoo.cfg。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 创建并启动历史文件清理器DatadirCleanupFactory。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. 判断当前是集群模式还是单机模式的启动。在集群模式中，在zoo.cfg文件中配置了多个服务器地址，可以选择集群启动。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2. 初始化</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 创建ServerCnxnFactory。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 初始化ServerCnxnFactory。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 创建Zookeeper数据管理器FileTxnSnapLog。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. 创建QuorumPeer实例。Quorum是集群模式下特有的对象，是Zookeeper服务器实例（ZooKeeperServer）的托管者，QuorumPeer代表了集群中的一台机器，在运行期间，QuorumPeer会不断检测当前服务器实例的运行状态，同时根据情况发起Leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　5. 创建内存数据库ZKDatabase。ZKDatabase负责管理ZooKeeper的所有会话记录以及DataTree和事务日志的存储。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　6. 初始化QuorumPeer。将核心组件如FileTxnSnapLog、ServerCnxnFactory、ZKDatabase注册到QuorumPeer中，同时配置QuorumPeer的参数，如服务器列表地址、Leader选举算法和会话超时时间限制等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　7. 恢复本地数据。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　8. 启动ServerCnxnFactory主线程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　3. Leader选举</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 初始化Leader选举。集群模式特有，Zookeeper首先会根据自身的服务器ID（SID）、最新的ZXID（lastLoggedZxid）和当前的服务器epoch（currentEpoch）来生成一个初始化投票，在初始化过程中，每个服务器都会给自己投票。然后，根据zoo.cfg的配置，创建相应Leader选举算法实现，Zookeeper提供了三种默认算法（LeaderElection、AuthFastLeaderElection、FastLeaderElection），可通过zoo.cfg中的electionAlg属性来指定，但现只支持FastLeaderElection选举算法。在初始化阶段，Zookeeper会创建Leader选举所需的网络I/O层QuorumCnxManager，同时启动对Leader选举端口的监听，等待集群中其他服务器创建连接。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 注册JMX服务。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 检测当前服务器状态。运行期间，QuorumPeer会不断检测当前服务器状态。在正常情况下，Zookeeper服务器的状态在LOOKING、LEADING、FOLLOWING/OBSERVING之间进行切换。在启动阶段，QuorumPeer的初始状态是LOOKING，因此开始进行Leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. Leader选举。通过投票确定Leader，其余机器称为Follower和Observer。具体算法在后面会给出。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>4. Leader和Follower启动期交互过程</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 创建Leader服务器和Follower服务器。完成Leader选举后，每个服务器会根据自己服务器的角色创建相应的服务器实例，并进入各自角色的主流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. Leader服务器启动Follower接收器LearnerCnxAcceptor。运行期间，Leader服务器需要和所有其余的服务器（统称为Learner）保持连接以确集群的机器存活情况，LearnerCnxAcceptor负责接收所有非Leader服务器的连接请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. Leader服务器开始和Leader建立连接。所有Learner会找到Leader服务器，并与其建立连接。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　4. Leader服务器创建LearnerHandler。Leader接收到来自其他机器连接创建请求后，会创建一个LearnerHandler实例，每个LearnerHandler实例都对应一个Leader与Learner服务器之间的连接，其负责Leader和Learner服务器之间几乎所有的消息通信和数据同步。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　5. 向Leader注册。Learner完成和Leader的连接后，会向Leader进行注册，即将Learner服务器的基本信息（LearnerInfo），包括SID和ZXID，发送给Leader服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　6. Leader解析Learner信息，计算新的epoch。Leader接收到Learner服务器基本信息后，会解析出该Learner的SID和ZXID，然后根据ZXID解析出对应的epoch_of_learner，并和当前Leader服务器的epoch_of_leader进行比较，如果该Learner的epoch_of_learner更大，则更新Leader的epoch_of_leader = epoch_of_learner + 1。然后LearnHandler进行等待，直到过半Learner已经向Leader进行了注册，同时更新了epoch_of_leader后，Leader就可以确定当前集群的epoch了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　7. 发送Leader状态。计算出新的epoch后，Leader会将该信息以一个LEADERINFO消息的形式发送给Learner，并等待Learner的响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　8. Learner发送ACK消息。Learner接收到LEADERINFO后，会解析出epoch和ZXID，然后向Leader反馈一个ACKEPOCH响应。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　9. 数据同步。Leader收到Learner的ACKEPOCH后，即可进行数据同步。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　10. 启动Leader和Learner服务器。当有过半Learner已经完成了数据同步，那么Leader和Learner服务器实例就可以启动了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>5. Leader和Follower启动</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　1. 创建启动会话管理器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　2. 初始化Zookeeper请求处理链，集群模式的每个处理器也会在启动阶段串联请求处理链。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　3. 注册JMX服务。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　至此，集群版的Zookeeper服务器启动完毕。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　本篇博文分析了Zookeeper服务端的启动的详细细节，之后会给出具体的代码分析。也谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432202.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-28 15:04 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/28/432202.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>zookeeper有哪些坑？</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432190.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 27 Dec 2016 07:49:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432190.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432190.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432190.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432190.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432190.html</trackback:ping><description><![CDATA[<div>http://www.tuicool.com/articles/Z3MjuuE<br /><br /><h2>1. 介绍</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">不得不说ZK的出现是解决分布式一致性问题的一道曙光。但是事务都是发展的，即使是ZK也不是十全十美的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">今天和小伙伴聊了点ZK的问题。一些ZK使用攻略也希望在此跟大家分享下。</p><h2>2. ZK的缺点</h2><ol style="padding: 0px; margin: 0px 0px 0.75em 25px; font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><li style="line-height: 1.7em;">读写性能不佳：ZK的读写性能测试可以参考&nbsp;<a href="http://wiki.apache.org/hadoop/ZooKeeper/ServiceLatencyOverview" rel="nofollow,noindex" target="_blank" style="color: #949494; text-decoration: none; transition: 0.25s; outline: none 0px; border-bottom-width: 1px; border-bottom-style: dashed; border-bottom-color: #949494; font-style: italic; font-weight: bold;">ZooKeeper service latencies under various loads &amp; configurations</a></li></ol><div style="font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em; text-indent: 1em;">下图可以看到的是220万操作，在4核20client上的效果。简单总结是相同core，增加client整体性能会下降。</p><img src="http://img2.tuicool.com/r2EvMbe.jpg!web" style="max-width: 96%; height: auto; vertical-align: middle; border: 0px none; margin: 0px auto 10px; text-align: center; display: block;"  alt="" /></div><ol style="padding: 0px; margin: 0px 0px 0.75em 25px; font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><li style="line-height: 1.7em;">不适合主数据存储：zk的quorum选举适用在共享集群配置而不是主数据存储。因为其吞吐量低，容忍故障所需要的冗余副本比较多</li><li style="line-height: 1.7em;">只容忍（N-1）/2的故障</li><li style="line-height: 1.7em;">ZK设计的时候是基于session的，也就是基于TTL机制。保持会话需要不断续期TTL。后起之秀如etcd等都已通过grpc改进了TTL。后续我会专门聊聊etcd</li></ol><h2>3. ZK在实际应用中的问题</h2><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">ZK在实际使用中肯能会受到网络抖动的影响，有时候这些影响对应用会造成&#8220;灾难&#8221;级的伤害。例如发生网络问题时，ZK集群需要开始选主，选主过程如果持续较长，应用都会抛异常。而且后续可能会出现follower不能及时跟上leader的情况。如果这个过程持续数十分钟，那么将会导致应用在这个期间内无法提供服务。影响是非常大的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">以上故事来自小伙伴的真实经历。但是到底哪些行为会造成ZK异常的选主行为尚没搞清楚。有谁知道也可以教下我。</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432190.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-27 15:49 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432190.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Zookeeper 运维的一些经验</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432189.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 27 Dec 2016 07:30:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432189.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432189.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432189.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432189.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432189.html</trackback:ping><description><![CDATA[<div>http://www.tuicool.com/articles/6ZvaQzQ<br /><br /><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">Zookeeper是一个分布式协调框架，有不错的性能，也经过许多公司的验证，所以在很多场景都有使用。大家一般用Zookeeper来实现服务发现(类似DNS)，配置管理，分布式锁，leader选举等。在这些场景中，Zookeeper成为了一个被依赖的核心组件，Zookeeper的稳定性是需要特别关注的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">去哪儿网也在很多场景依赖Zookeeper，所以我们也一直在摸索怎么更好的运维稳定的Zookeeper集群。在过去的几年我们也踩过一些坑，也因为Zookeeper导致了故障。现在将我们运维Zookeeper集群的一些经验分享，也欢迎大家提供更好的建议。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">那么在打算运维一套Zookeeper集群之前，我们先了解一些Zookeeper的基本原理。</p><ol style="padding: 0px; margin: 0px 0px 0.75em 25px; font-size: 16px; line-height: 27.2px; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;"><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">集群里分三种角色: Leader, Follower和Observer。Leader和Follower参与投票，Observer只会『听』投票的结果，不参与投票。</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">投票集群里的节点数要求是奇数</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">一个集群容忍挂掉的节点数的等式为 N = 2F + 1，N为投票集群节点数，F为能同时容忍失败节点数。比如一个三节点集群，可以挂掉一个节点，5节点集群可以挂掉两个...</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">一个写操作需要半数以上的节点ack，所以集群节点数越多，整个集群可以抗挂点的节点数越多(越可靠)，但是吞吐量越差。</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">Zookeeper里所有节点以及节点的数据都会放在内存里，形成一棵树的数据结构。并且定时的dump snapshot到磁盘。</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">Zookeeper的Client与Zookeeper之间维持的是长连接，并且保持心跳，Client会与Zookeeper之间协商出一个Session超时时间出来(其实就是Zookeeper Server里配置了最小值，最大值，如果client的值在这两个值之间则采用client的，小于最小值就是最小值，大于最大值就用最大值)，如果在Session超时时间内没有收到心跳，则该Session过期。</p></li><li style="line-height: 1.7em;"><p style="margin: 0px 0px 0.75em; line-height: 1.7em;">Client可以watch Zookeeper那个树形数据结构里的某个节点或数据，当有变化的时候会得到通知。</p></li></ol><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">有了这些了解后，那么我们心里其实有底了。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">1. 最小生产集群</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">要确保Zookeeper能够稳定运行，那么就需要确保投票能够正常进行，最好不要挂一个节点整个就不work了，所以我们一般要求&nbsp;<strong style="text-indent: 0px;">最少5个节点部署</strong>&nbsp;。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">2. 网络</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">除了节点外，我们还要看不能一台物理机器，一个机柜或一个交换机挂掉然后影响了整个集群，所以节点的&nbsp;<strong style="text-indent: 0px;">网络结构也要考虑</strong>&nbsp;。这个可能就比很多应用服务器的要求更加严格。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">3. 分Group，保护核心Group</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">要确保Zookeeper整个集群可靠运行，就是要确保投票集群可靠。那在我们这里，<strong style="text-indent: 0px;">将一个Zookeeper集群划分为多个小的Group</strong>&nbsp;，我们称Leader+Follower为核心Group，核心Group我们一般是不向外提供服务的，然后我们会根据不同的业务再加一些Observer，比如一个Zookeeper集群为服务发现，消息，定时任务三个不同的组件提供服务，那么我们为建立三个Observer Group，分别给这三个组件使用，而Client只会连接分配给它的Observer Group，不去连接核心Group。这样核心Group就不会给Client提供长连接服务，也不负责长连接的心跳，这大大的减轻了核心Group的压力，因为在实际环境中，一个Zookeeper集群要为上万台机器提供服务，维持长连接和心跳还是要消耗一定的资源的。因为Observer是不参与投票的所以加Observer并不会降低整体的吞吐量，而且Observer挂掉不会影响整个集群的健康。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">但是这里要注意的是，分Observer Group只能解决部分问题，因为毕竟所有的写入还是要交给核心Group来处理的，所以对于写入量特别大的应用来说，还是需要进行集群上的隔离，比如Storm和Kafka就对Zookeeper压力比较大，你就不能将其与服务发现的集群放在一起。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">4. 内存</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">因为Zookeeper将所有数据都放在内存里，所以对JVM以及机器的内存也要预先计划，如果出现Swap那将严重的影响Zookeeper集群的性能，所以我一般不怎么推荐将Zookeeper用作通用的配置管理服务。因为一般配置数据还是挺大的，这些全部放在内存里不太可控。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">5. 日志清理</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">因为Zookeeper要频繁的写txlog(Zookeeper写的一种顺序日志)以及定期dump内存snapshot到磁盘，这样磁盘占用就越来越大，所以Zookeeper提供了清理这些文件的机制，但是这种机制并不太合理，它只能设置间隔多久清理，而不能设置具体的时间段。那么就有可能碰到高峰期间清理，所以建议将其关闭:autopurge.purgeInterval=0。然后使用crontab等机制，在业务低谷的时候清理。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">6. 日志，jvm配置</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">从官网直接下载的包如果直接启动运行是很糟糕的，这个包默认的配置日志是不会轮转的，而且是直接输出到终端。我们最开始并不了解这点，然后运行一段时间后发现生成一个庞大的zookeeper.out的日志文件。除此之外，这个默认配置还没有设置任何jvm相关的参数(所以堆大小是个默认值)，这也是不可取的。那么有的同学说那我去修改Zookeeper的启动脚本吧。最好不要这样做，Zookeeper会加载conf文件夹下一个名为zookeeper-env.sh的脚本，所以你可以将一些定制化的配置写在这里，而不是直接去修改Zookeeper自带的脚本。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">#!/usr/bin/env bash</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">JAVA_HOME= #java home</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">ZOO_LOG_DIR= #日志文件放置的路径</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">ZOO_LOG4J_PROP="INFO,ROLLINGFILE" #设置日志轮转</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">JVMFLAGS="jvm的一些设置，比如堆大小，开gc log等"</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">7. 地址</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">在实际环境中，我们可能因为各种原因比如机器过保，硬件故障等需要迁移Zookeeper集群，所以Zookeeper的地址是一个很头痛的事情。这个地址有两方面，第一个是提供给Client的地址，建议这个地址通过配置的方式下发，不要让使用方直接使用，这一点我们前期做的不好。另外一个是集群配置里，集群之间需要通讯，也需要地址。我们的处理方式是设置hosts:</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">192.168.1.20 zk1</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">192.168.1.21 zk2</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">192.168.1.22 zk3</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">在配置里:</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">server.1=zk1:2081:3801</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">server.2=zk2:2801:3801</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">server.3=zk3:2801:3801</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">这样在需要迁移的时候，我们停老的节点，起新的节点只需要修改hosts映射就可以了。比如现在server.3需要迁移，那我们在hosts里将zk3映射到新的ip地址。但是对于java有一个问题是，java默认会永久缓存DNS cache，即使你将zk3映射到别的ip，如果并不重启server.1, server.2，它是不会解析到新的ip的，这个需要修改$JAVA_HOME/jre/lib/security/java.security文件里的networkaddress.cache.ttl=60，将其修改为一个比较小的数。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">对于这个迁移的问题，我们还遇到一个比较尴尬的情况，在最后的坑里会有提及。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">8. 日志位置</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">Zookeeper主要产生三种IO: txlog(每个写操作，包括新Session都会记录一条log)，Snapshot以及运行的应用日志。一般建议将这三个IO分散到三个不同的盘上。不过我们倒是一直没有这么实验过，我们的Zookeeper也是运行在虚拟机(一般认为虚拟机IO较差)上。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">9. 监控</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">我们对Zookeeper做了这样一些监控:</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">a. 是否可写。 就是一个定时任务定时的去创建节点，删节点等操作。这里要注意的是Zookeeper是一个集群，我们监控的时候我还是希望对单个节点做监控，所以这些操作的时候不要连接整个集群，而是直接去连接单个节点。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">b. 监控watcher数和连接数 特别是这两个数据有较大波动的时候，可以发现使用方是否有误用的情况</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">c. 网络流量以及client ip 这个会记录到监控系统里，这样很快能发现『害群之马』</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">10. 一些使用建议</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">a. 不要强依赖Zookeeper，也就是Zookeeper出现问题业务已然可以正常运行。Zookeeper是一个分布式的协调框架，主要做的事情就是分布式环境的一致性。这是一个非常苛刻的事情，所以它的稳定性受很多方面的影响。比如我们常常使用Zookeeper做服务发现，那么服务发现其实是不需要严格的一致性的，我们可以缓存server list，当Zookeeper出现问题的时候已然可以正常工作，在这方面etcd要做的更好一些，Zookeeper如果出现分区，少数派是不能提供任何服务的，读都不可以，而etcd的少数派仍然可以提供读服务，这在服务发现的时候还是不错的。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">b. 不要将很多东西塞到Zookeeper里，这个上面已经提到过。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">c. 不要使用Zookeeper做细粒度锁，比如很多业务在订单这个粒度上使用Zookeeper做分布式锁，这会频繁的和Zookeeper交互，对Zookeeper压力较大，而且一旦出现问题影响面广。但是可以使用粗粒度的锁(其实leader选举也是一种锁)。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">d. 不建议做通用配置的第二个理由是，通用配置要提供给特别多特别多系统使用，而且一些公共配置甚至所有系统都会使用，一旦这样的配置发生变更，Zookeeper会广播给所有的watcher，然后所有Client都来拉取，瞬间造成非常大的网络流量，引起所谓的『惊群』。而自己实现通用配置系统的时候，一般会对这种配置采取排队或分批通知的方式。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">11. 一些坑</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">a. zookeeper client 3.4.5 ping时间间隔算法有问题，在遇到网络抖动等原因导致一次ping失败后会断开连接。3.4.6解决了这个问题 Bug1751。</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">b. zookeeper client如果因为网络抖动断开了连接，如果后来又重连上了，zookeeper client会自动的将之前订阅的watcher等又全部订阅一遍，而Zookeeper默认对单个数据包的大小有个1M的限制，这往往就会超限，最后导致一直不断地的重试。这个问题在较新的版本得到了修复。Bug706</p><p style="margin: 0px 0px 0.75em; font-size: 16px; line-height: 27.2px; text-indent: 1em; color: #333333; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: #fefefe;">c. 抛出UnresolvedAddressException异常导致Zookeeper选举线程退出，整个集群无法再选举，处于崩溃的边缘。这个问题是，某次OPS迁移机器，将老的机器回收了，所以老的机器的IP和机器名不复存在，最后抛出UnresolvedAddressException这个异常，而Zookeeper的选举线程(QuorumCnxManager类里的Listener)只捕获了IOException，导致该线程退出，该线程一旦退出只要现在的leader出现问题，需要重新选举，则不会选出新的leader来，整个集群就会崩溃。Bug2319(PS，这个bug是我report的)</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432189.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-27 15:30 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432189.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper会话</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432187.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 27 Dec 2016 06:05:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432187.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432187.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432187.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432187.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432187.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6103870.html<br /><br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　前面分析了Zookeeper客户端的细节，接着继续学习Zookeeper中的一个非常重要的概念：会话。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、会话</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　客户端与服务端之间任何交互操作都与会话息息相关，如临时节点的生命周期、客户端请求的顺序执行、Watcher通知机制等。Zookeeper的连接与会话就是客户端通过实例化Zookeeper对象来实现客户端与服务端创建并保持TCP连接的过程.</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 会话状态</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在Zookeeper客户端与服务端成功完成连接创建后，就创建了一个会话，Zookeeper会话在整个运行期间的生命周期中，会在不同的会话状态中之间进行切换，这些状态可以分为<strong>CONNECTING、CONNECTED、RECONNECTING、RECONNECTED、CLOSE</strong>等。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　一旦客户端开始创建Zookeeper对象，那么客户端状态就会变成CONNECTING状态，同时客户端开始尝试连接服务端，连接成功后，客户端状态变为CONNECTED，通常情况下，由于断网或其他原因，客户端与服务端之间会出现断开情况，一旦碰到这种情况，<strong>Zookeeper客户端会自动进行重连服务，同时客户端状态再次变成CONNCTING，直到重新连上服务端后，状态又变为CONNECTED</strong>，在通常情况下，客户端的状态总是介于CONNECTING和CONNECTED之间。但是，如果出现诸如会话超时、权限检查或是客户端主动退出程序等情况，客户端的状态就会直接变更为CLOSE状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161126160605690-2108890253.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 会话创建</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Session是Zookeeper中的会话实体，代表了一个客户端会话，其包含了如下四个属性</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.<strong>&nbsp;sessionID</strong>。会话ID，唯一标识一个会话，每次客户端创建新的会话时，Zookeeper都会为其分配一个全局唯一的sessionID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.&nbsp;<strong>TimeOut</strong>。会话超时时间，客户端在构造Zookeeper实例时，会配置sessionTimeout参数用于指定会话的超时时间，Zookeeper客户端向服务端发送这个超时时间后，服务端会根据自己的超时时间限制最终确定会话的超时时间。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.&nbsp;<strong>TickTime</strong>。下次会话超时时间点，为了便于Zookeeper对会话实行"分桶策略"管理，同时为了高效低耗地实现会话的超时检查与清理，Zookeeper会为每个会话标记一个下次会话超时时间点，其值大致等于当前时间加上TimeOut。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　4.&nbsp;<strong>isClosing</strong>。标记一个会话是否已经被关闭，当服务端检测到会话已经超时失效时，会将该会话的isClosing标记为"已关闭"，这样就能确保不再处理来自该会话的心情求了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper为了保证请求会话的全局唯一性，在SessionTracker初始化时，调用initializeNextSession方法生成一个sessionID，之后在Zookeeper运行过程中，会在该sessionID的基础上为每个会话进行分配，初始化算法如下</p><div style="margin: 5px 0px; font-size: 12px !important;"><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div><pre style="margin-top: 0px; margin-bottom: 0px; white-space: pre-wrap; word-wrap: break-word; max-width: 600px; font-family: 'Courier New' !important;"><span style="color: #0000ff; line-height: 1.5 !important;">public</span> <span style="color: #0000ff; line-height: 1.5 !important;">static</span> <span style="color: #0000ff; line-height: 1.5 !important;">long</span> initializeNextSession(<span style="color: #0000ff; line-height: 1.5 !important;">long</span><span style="line-height: 1.5 !important;"> id) {     </span><span style="color: #0000ff; line-height: 1.5 !important;">long</span> nextSid = 0<span style="line-height: 1.5 !important;">;     </span><span style="color: #008000; line-height: 1.5 !important;">//</span><span style="color: #008000; line-height: 1.5 !important;"> 无符号右移8位使为了避免左移24后，再右移8位出现负数而无法通过高8位确定sid值</span>     nextSid = (System.currentTimeMillis() &lt;&lt; 24) &gt;&gt;&gt; 8<span style="line-height: 1.5 !important;">;     nextSid </span>= nextSid | (id &lt;&lt; 56<span style="line-height: 1.5 !important;">);     </span><span style="color: #0000ff; line-height: 1.5 !important;">return</span><span style="line-height: 1.5 !important;"> nextSid; }</span></pre><div style="margin-top: 5px;"><span style="padding-right: 5px; line-height: 1.5 !important;"><a title="复制代码" style="outline: none; color: #3d81ee; border: none !important;"><img src="http://common.cnblogs.com/images/copycode.gif" alt="复制代码" style="border: none #dddddd !important; max-width: 900px; background-color: #ffffff;" /></a></span></div></div><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　其中的id表示配置在myid文件中的值，通常是一个整数，如1、2、3。该算法的高8位确定了所在机器，后56位使用当前时间的毫秒表示进行随机。SessionTracker是Zookeeper服务端的会话管理器，负责会话的创建、管理和清理等工作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 会话管理</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper的会话管理主要是通过SessionTracker来负责，其采用了<strong>分桶策略</strong>（将类似的会话放在同一区块中进行管理）进行管理，以便Zookeeper对会话进行不同区块的隔离处理以及同一区块的统一处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161126165451643-351272465.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper将所有的会话都分配在不同的区块一种，分配的原则是每个会话的下次超时时间点（ExpirationTime）。ExpirationTime指该会话最近一次可能超时的时间点。同时，Zookeeper Leader服务器在运行过程中会定时地进行会话超时检查，时间间隔是ExpirationInterval，默认为tickTime的值，ExpirationTime的计算时间如下</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ExpirationTime = ((CurrentTime + SessionTimeOut) / ExpirationInterval + 1) * ExpirationInterval</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　会了保持客户端会话的有效性，<strong>客户端会在会话超时时间过期范围内向服务端发送PING请求来保持会话的有效性（心跳检测）</strong>。同时，服务端需要不断地接收来自客户端的心跳检测，并且需要重新激活对应的客户端会话，这个重新激活过程称为TouchSession。会话激活不仅能够使服务端检测到对应客户端的存货性，同时也能让客户端自己保持连接状态，其流程如下　　</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161126171119628-1511238863.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /><br />　　如上图所示，整个流程分为四步</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.&nbsp;<strong>检查该会话是否已经被关闭</strong>。若已经被关闭，则直接返回即可。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.&nbsp;<strong>计算该会话新的超时时间ExpirationTime_New</strong>。使用上面提到的公式计算下一次超时时间点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.&nbsp;<strong>获取该会话上次超时时间ExpirationTime_Old</strong>。计算该值是为了定位其所在的区块。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.&nbsp;<strong>迁移会话</strong>。将该会话从老的区块中取出，放入ExpirationTime_New对应的新区块中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161126171348878-1972526661.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在上面会话激活过程中，只要客户端发送心跳检测，服务端就会进行一次会话激活，心跳检测由客户端主动发起，以PING请求形式向服务端发送，在Zookeeper的实际设计中，<strong>只要客户端有请求发送到服务端，那么就会触发一次会话激活</strong>，以下两种情况都会触发会话激活。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1. 客户端向服务端发送请求，包括读写请求，就会触发会话激活。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2. 客户端发现在sessionTimeout/3时间内尚未和服务端进行任何通信，那么就会主动发起PING请求，服务端收到该请求后，就会触发会话激活。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于会话的超时检查而言，Zookeeper使用SessionTracker来负责，SessionTracker使用单独的线程（超时检查线程）专门进行会话超时检查，即逐个一次地对会话桶中剩下的会话进行清理。如果一个会话被激活，那么Zookeeper就会将其从上一个会话桶迁移到下一个会话桶中，如ExpirationTime 1 的session n 迁移到ExpirationTime n 中，此时ExpirationTime 1中留下的所有会话都是尚未被激活的，超时检查线程就定时检查这个会话桶中所有剩下的未被迁移的会话，超时检查线程只需要在这些指定时间点（ExpirationTime 1、ExpirationTime 2...）上进行检查即可，这样提高了检查的效率，性能也非常好。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.4 会话清理</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当SessionTracker的会话超时线程检查出已经过期的会话后，就开始进行会话清理工作，大致可以分为如下七步。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.&nbsp;<strong>标记会话状态为已关闭</strong>。由于会话清理过程需要一段时间，为了保证在此期间不再处理来自该客户端的请求，SessionTracker会首先将该会话的isClosing标记为true，这样在会话清理期间接收到该客户端的心情求也无法继续处理了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.&nbsp;<strong>发起会话关闭请求</strong>。为了使对该会话的关闭操作在整个服务端集群都生效，Zookeeper使用了提交会话关闭请求的方式，并立即交付给PreRequestProcessor进行处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.&nbsp;<strong>收集需要清理的临时节点</strong>。一旦某个会话失效后，那么和该会话相关的临时节点都需要被清理，因此，在清理之前，首先需要将服务器上所有和该会话相关的临时节点都整理出来。Zookeeper在内存数据库中会为每个会话都单独保存了一份由该会话维护的所有临时节点集合，在Zookeeper处理会话关闭请求之前，若正好有以下两类请求到达了服务端并正在处理中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 节点删除请求，删除的目标节点正好是上述临时节点中的一个。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; 临时节点创建请求，创建的目标节点正好是上述临时节点中的一个。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　对于第一类请求，需要将所有请求对应的数据节点路径从当前临时节点列表中移出，以避免重复删除，对于第二类请求，需要将所有这些请求对应的数据节点路径添加到当前临时节点列表中，以删除这些即将被创建但是尚未保存到内存数据库中的临时节点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　4.&nbsp;<strong>添加节点删除事务变更</strong>。完成该会话相关的临时节点收集后，Zookeeper会逐个将这些临时节点转换成"节点删除"请求，并放入事务变更队列outstandingChanges中。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　5.&nbsp;<strong>删除临时节点</strong>。FinalRequestProcessor会触发内存数据库，删除该会话对应的所有临时节点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　6.&nbsp;<strong>移除会话</strong>。完成节点删除后，需要将会话从SessionTracker中删除。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　7.&nbsp;<strong>关闭NIOServerCnxn</strong>。最后，从NIOServerCnxnFactory找到该会话对应的NIOServerCnxn，将其关闭。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 重连</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当客户端与服务端之间的网络连接断开时，Zookeeper客户端会自动进行反复的重连，直到最终成功连接上Zookeeper集群中的一台机器。此时，再次连接上服务端的客户端有可能处于以下两种状态之一</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.&nbsp;<strong>CONNECTED</strong>。如果在会话超时时间内重新连接上集群中一台服务器&nbsp;。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.&nbsp;<strong>EXPIRED</strong>。如果在会话超时时间以外重新连接上，那么服务端其实已经对该会话进行了会话清理操作，此时会话被视为非法会话。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在客户端与服务端之间维持的是一个长连接，在sessionTimeout时间内，服务端会不断地检测该客户端是否还处于正常连接，服务端会将客户端的每次操作视为一次有效的心跳检测来反复地进行会话激活。因此，在正常情况下，客户端会话时一直有效的。然而，当客户端与服务端之间的连接断开后，用户在客户端可能主要看到两类异常：<strong>CONNECTION_LOSS（连接断开）和SESSION_EXPIRED（会话过期）</strong>。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　1.&nbsp;<strong>CONNECTION_LOSS</strong>。此时，客户端会自动从地址列表中重新逐个选取新的地址并尝试进行重新连接，直到最终成功连接上服务器。若客户端在setData时出现了CONNECTION_LOSS现象，此时客户端会收到None-Disconnected通知，同时会抛出异常。应用程序需要捕捉异常并且等待Zookeeper客户端自动完成重连，一旦重连成功，那么客户端会收到None-SyncConnected通知，之后就可以重试setData操作。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　2.&nbsp;<strong>SESSION_EXPIRED</strong>。客户端与服务端断开连接后，重连时间耗时太长，超过了会话超时时间限制后没有成功连上服务器，服务器会进行会话清理，此时，客户端不知道会话已经失效，状态还是DISCONNECTED，如果客户端重新连上了服务器，此时状态为SESSION_EXPIRED，用于需要重新实例化Zookeeper对象，并且看应用的复杂情况，重新恢复临时数据。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　3.<strong>&nbsp;SESSION_MOVED</strong>。客户端会话从一台服务器转移到另一台服务器，即客户端与服务端S1断开连接后，重连上了服务端S2，此时会话就从S1转移到了S2。当多个客户端使用相同的sessionId/sessionPasswd创建会话时，会收到SessionMovedException异常。因为一旦有第二个客户端连接上了服务端，就被认为是会话转移了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　本篇博文介绍了Zookeeper会话的相关细节，通过本篇的学习理解了会话的细节，也谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432187.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-27 14:05 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432187.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>zookeeper client 代码解析</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432186.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 27 Dec 2016 05:51:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432186.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432186.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432186.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432186.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432186.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: ZooKeeper.classCode highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->&nbsp;1&nbsp;public&nbsp;String&nbsp;create(final&nbsp;String&nbsp;path,&nbsp;byte&nbsp...&nbsp;&nbsp;<a href='http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432186.html'>阅读全文</a><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432186.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-27 13:51 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432186.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper客户端</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432185.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 27 Dec 2016 05:40:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432185.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432185.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432185.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432185.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432185.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6098255.html<br /><br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>一、前言</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　前篇博客分析了Zookeeper的序列化和通信协议，接着继续学习客户端，客户端是开发人员使用Zookeeper最主要的途径，很有必要弄懂客户端是如何与服务端通信的。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>二、客户端</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 客户端组成</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper客户端主要由如下核心部件构成。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1.&nbsp;<strong>Zookeeper实例</strong>，客户端入口。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2.&nbsp;<strong>ClientWatchManager</strong>， 客户端Watcher管理器。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3.&nbsp;<strong>HostProvider</strong>，客户端地址列表管理器。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4.&nbsp;<strong>ClientCnxn</strong>，客户端核心线程，内部<strong>包含了SendThread和EventThread两个线程</strong>，SendThread为I/O线程，主要负责Zookeeper客户端和服务器之间的网络I/O通信；EventThread为事件线程，主要负责对服务端事件进行处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161124170014471-1163146559.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　Zookeeper客户端初始化与启动环节，就是Zookeeper对象的实例化过程。客户端在初始化和启动过程中大体可以分为如下3个步骤</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 设置默认Watcher</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 设置Zookeeper服务器地址列表</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 创建ClientCnxn。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　若在Zookeeper构造方法中传入Watcher对象时，那么Zookeeper就会将该Watcher对象保存在ZKWatcherManager的defaultWatcher中，并作为整个客户端会话期间的默认Watcher。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 会话的创建</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　下图表示了客户端与服务端会话建立的整个过程，包括初始化阶段（第一阶段）、会话创建阶段（第二阶段）、响应处理阶段（第三阶段）三个阶段。<img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161124205059237-613598814.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 服务器地址列表</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在实例化Zookeeper时，用户传入Zookeeper服务器地址列表，如192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181，此时，Zookeeper客户端在连接服务器的过程中，是如何从这个服务器列表中选择服务器的呢？Zookeeper收到服务器地址列表后，会解析出chrootPath和保存服务器地址列表。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1.&nbsp;<strong>Chroot</strong>，每个客户端可以设置自己的命名空间，若客户端设置了Chroot，此时，该客户端对服务器的任何操作都将被限制在自己的命名空间下，如设置Choot为/app/X，那么该客户端的所有节点路径都是以/app/X为根节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2.&nbsp;<strong>地址列表管理</strong>，Zookeeper使用StaticHostProvider打散服务器地址（shuffle），并将服务器地址形成一个环形循环队列，然后再依次取出服务器地址。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.4 网络I/O</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　ClientCnxn是Zookeeper客户端中负责维护客户端与服务端之间的网络连接并进行一系列网络通信的核心工作类，Packet是ClientCnxn内部定义的一个堆协议层的封装，用作Zookeeper中请求和响应的载体。Packet包含了请求头（requestHeader）、响应头（replyHeader）、请求体（request）、响应体（response）、节点路径（clientPath/serverPath）、注册的Watcher（watchRegistration）等信息，然而，<strong>并非Packet中所有的属性都在客户端与服务端之间进行网络传输，只会将requestHeader、request、readOnly三个属性序列化，并生成可用于底层网络传输的ByteBuffer，其他属性都保存在客户端的上下文中，不会进行与服务端之间的网络传输</strong>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　ClientCnxn维护着<strong>outgoingQueue</strong>（客户端的请求发送队列）和<strong>pendingQueue</strong>（服务端响应的等待队列），outgoingQueue专门用于存储那些需要发送到服务端的Packet集合，pendingQueue用于存储那些已经从客户端发送到服务端的，但是需要等待服务端响应的Packet集合。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在正常情况下，会从outgoingQueue中取出一个可发送的Packet对象，同时生成一个客户端请求序号XID并将其设置到Packet请求头中去，然后序列化后再发送，请求发送完毕后，会立即将该Packet保存到pendingQueue中，以便等待服务端响应返回后进行相应的处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161124211747487-571974849.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　客户端获取到来自服务端的完整响应数据后，根据不同的客户端请求类型，会进行不同的处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 若检测到此时客户端尚未进行初始化，那么说明当前客户端与服务端之间正在进行会话创建，直接将接收的ByteBuffer序列化成ConnectResponse对象。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 若当前客户端已经处于正常会话周期，并且接收到服务端响应是一个事件，那么将接收的ByteBuffer序列化成WatcherEvent对象，并将该事件放入待处理队列中。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 若是一个常规请求（Create、GetData、Exist等），那么从pendingQueue队列中取出一个Packet来进行相应处理。首先会检验响应中的XID来确保请求处理的顺序性，然后再将接收到的ByteBuffer序列化成Response对象。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>SendThread是客户端ClientCnxn内部的一个核心I/O调度线程</strong>，用于管理客户端与服务端之间的所有网络I/O操作，在Zookeeper客户端实际运行中，SendThread的作用如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 维护了客户端与服务端之间的会话生命周期（通过一定周期频率内向服务端发送PING包检测心跳），如果会话周期内客户端与服务端出现TCP连接断开，那么就会自动且透明地完成重连操作。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 管理了客户端所有的请求发送和响应接收操作，其将上层客户端API操作转换成相应的请求协议并发送到服务端，并完成对同步调用的返回和异步调用的回调。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 将来自服务端的事件传递给EventThread去处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>EventThread是客户端ClientCnxn内部的一个事件处理线程</strong>，负责客户端的事件处理，并触发客户端注册的Watcher监听。EventThread中的watingEvents队列用于临时存放那些需要被触发的Object，包括客户端注册的Watcher和异步接口中注册的回调器AsyncCallback。同时，EventThread会不断地从watingEvents中取出Object，识别具体类型（Watcher或AsyncCallback），并分别调用process和processResult接口方法来实现对事件的触发和回调。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>三、总结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　本篇博文讲解了客户端的相关细节，内容较为简单易懂，该模块知识会为之后分析源码打下好的基础，谢谢各位园友观看~　　</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><div style="margin-top: 100px;"></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432185.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-27 13:40 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/27/432185.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper系统模型</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432182.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 13:14:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432182.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432182.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432182.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432182.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432182.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6072597.html<br /><br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>一、前言</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　前面已经讲解了Zookeeper的一些应用场景，但是并没有深入到Zookeeper内部进行分析，本篇将讲解其系统模型。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>二、系统模型</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 数据模型</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper的数据节点称为ZNode，ZNode是Zookeeper中数据的最小单元，每个ZNode都可以保存数据，同时还可以挂载子节点，因此构成了一个层次化的命名空间，称为树。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161117093506826-316952237.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在Zookeeper中，事务是指能够改变Zookeeper服务器状态的操作，一般包括节点创建与删除，数据节点内容更新和客户端会话创建与失效，对于每个事务请求，Zookeeper都会为其分配一个全局唯一的事务ID，用ZXID表示，通常是64位的数字，每个ZXID对应一次更新操作，从这些ZXID中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 节点特性</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在Zookeeper中，每个数据节点都是由生命周期的，类型不同则会不同的生命周期，节点类型可以分为<strong>持久节点（PERSISTENT）、临时节点（EPHEMERAL）、顺序节点（SEQUENTIAL）</strong>三大类，可以通过组合生成如下四种类型节点</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1.&nbsp;<strong>持久节点</strong>（PERSISTENT）。节点创建后便一直存在于Zookeeper服务器上，直到有删除操作来主动清楚该节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2.&nbsp;<strong>持久顺序节点</strong>（PERSISTENT_SEQUENTIAL）。相比持久节点，其新增了顺序特性，每个父节点都会为它的第一级子节点维护一份顺序，用于记录每个子节点创建的先后顺序。在创建节点时，会自动添加一个数字后缀，作为新的节点名，该数字后缀的上限是整形的最大值。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3.&nbsp;<strong>临时节点</strong>（EPEMERAL）。临时节点的生命周期与客户端会话绑定，客户端失效，节点会被自动清理。同时，Zookeeper规定不能基于临时节点来创建子节点，即临时节点只能作为叶子节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4.&nbsp;<strong>临时顺序节点</strong>（EPEMERAL_SEQUENTIAL）。在临时节点的基础添加了顺序特性。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　每个节点除了存储数据外，还存储了节点本身的一些状态信息，可通过get命令获取。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 版本--保证分布式数据原子性操作</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　每个数据节点都具有三种类型的版本信息，对数据节点的任何更新操作都会引起版本号的变化。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　version-- 当前数据节点数据内容的版本号</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　cversion-- 当前数据子节点的版本号</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　aversion-- 当前数据节点ACL变更版本号</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　上述各版本号都是表示修改次数，如version为1表示对数据节点的内容变更了一次。即使前后两次变更并没有改变数据内容，version的值仍然会改变。version可以用于写入验证，类似于CAS。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.4 Watcher--数据变更通知</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper使用Watcher机制实现分布式数据的发布/订阅功能。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161122094930487-367521759.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper的Watcher机制主要包括<strong>客户端线程、客户端WatcherManager、Zookeeper服务器</strong>三部分。客户端在向Zookeeper服务器注册的同时，会将Watcher对象存储在客户端的WatcherManager当中。当Zookeeper服务器触发Watcher事件后，会向客户端发送通知，客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 ACL--保障数据的安全</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper内部存储了分布式系统运行时状态的元数据，这些元数据会直接影响基于Zookeeper进行构造的分布式系统的运行状态，如何保障系统中数据的安全，从而避免因误操作而带来的数据随意变更而导致的数据库异常十分重要，Zookeeper提供了一套完善的ACL权限控制机制来保障数据的安全。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　我们可以从三个方面来理解ACL机制：<strong>权限模式（Scheme）、授权对象（ID）、权限（Permission）</strong>，通常使用"<strong>scheme:id:permission</strong>"来标识一个有效的ACL信息。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>权限模式</strong>用来确定权限验证过程中使用的检验策略，有如下四种模式：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1.&nbsp;<strong>IP</strong>，通过IP地址粒度来进行权限控制，如"ip:192.168.0.110"表示权限控制针对该IP地址，同时IP模式可以支持按照网段方式进行配置，如"ip:192.168.0.1/24"表示针对192.168.0.*这个网段进行权限控制。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2.&nbsp;<strong>Digest</strong>，使用"username:password"形式的权限标识来进行权限配置，便于区分不同应用来进行权限控制。Zookeeper会对其进行SHA-1加密和BASE64编码。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3.&nbsp;<strong>World</strong>，最为开放的权限控制模式，数据节点的访问权限对所有用户开放。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4.&nbsp;<strong>Super</strong>，超级用户，是一种特殊的Digest模式，超级用户可以对任意Zookeeper上的数据节点进行任何操作。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>授权对象</strong>是指权限赋予的用户或一个指定实体，如IP地址或机器等。不同的权限模式通常有不同的授权对象。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>权限</strong>是指通过权限检查可以被允许执行的操作，Zookeeper对所有数据的操作权限分为<strong>CREATE（节点创建权限）、DELETE（节点删除权限）、READ（节点读取权限）、WRITE（节点更新权限）、ADMIN（节点管理权限）</strong>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>三、总结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　本篇博客介绍了Zookeeper中的系统模型，系统模型的五个部分是Zookeeper提供一系列服务的基础，之后笔者会结合源码进行相应分析。谢谢各位园友观看~　　</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><div style="margin-top: 100px;"></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432182.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 21:14 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432182.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper应用场景</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432181.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 13:03:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432181.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432181.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432181.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432181.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432181.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6036548.html<br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>一、前言</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在上一篇博客已经介绍了Zookeeper开源客户端的简单实用，本篇讲解Zookeeper的应用场景。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>二、典型应用场景</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper是一个高可用的分布式数据管理和协调框架，并且能够很好的保证分布式环境中数据的一致性。在越来越多的分布式系统（Hadoop、HBase、Kafka）中，Zookeeper都作为核心组件使用。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 数据发布/订阅</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　数据发布/订阅系统，即配置中心。需要发布者将数据发布到Zookeeper的节点上，供订阅者进行数据订阅，进而达到动态获取数据的目的，实现配置信息的集中式管理和数据的动态更新。发布/订阅一般有两种设计模式：推模式和拉模式，服务端主动将数据更新发送给所有订阅的客户端称为推模式；客户端主动请求获取最新数据称为拉模式，Zookeeper采用了推拉相结合的模式，客户端向服务端注册自己需要关注的节点，一旦该节点数据发生变更，那么服务端就会向相应的客户端推送Watcher事件通知，客户端接收到此通知后，主动到服务端获取最新的数据。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　若将配置信息存放到Zookeeper上进行集中管理，在通常情况下，应用在启动时会主动到Zookeeper服务端上进行一次配置信息的获取，同时，在指定节点上注册一个Watcher监听，这样在配置信息发生变更，服务端都会实时通知所有订阅的客户端，从而达到实时获取最新配置的目的。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 负载均衡</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　负载均衡是一种相当常见的计算机网络技术，用来对多个计算机、网络连接、CPU、磁盘驱动或其他资源进行分配负载，以达到优化资源使用、最大化吞吐率、最小化响应时间和避免过载的目的。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　使用Zookeeper实现动态DNS服务</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>&#183; 域名配置</strong>，首先在Zookeeper上创建一个节点来进行域名配置，如DDNS/app1/server.app1.company1.com。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>&#183; 域名解析</strong>，应用首先从域名节点中获取IP地址和端口的配置，进行自行解析。同时，应用程序还会在域名节点上注册一个数据变更Watcher监听，以便及时收到域名变更的通知。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>&#183; 域名变更</strong>，若发生IP或端口号变更，此时需要进行域名变更操作，此时，只需要对指定的域名节点进行更新操作，Zookeeper就会向订阅的客户端发送这个事件通知，客户端之后就再次进行域名配置的获取。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 命名服务</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　命名服务是分步实现系统中较为常见的一类场景，分布式系统中，被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等，通过命名服务，客户端可以根据指定名字来获取资源的实体、服务地址和提供者的信息。Zookeeper也可帮助应用系统通过资源引用的方式来实现对资源的定位和使用，广义上的命名服务的资源定位都不是真正意义上的实体资源，在分布式环境中，上层应用仅仅需要一个全局唯一的名字。Zookeeper可以实现一套分布式全局唯一ID的分配机制。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161111191903983-1360060273.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　通过调用Zookeeper节点创建的API接口就可以创建一个顺序节点，并且在API返回值中会返回这个节点的完整名字，利用此特性，可以生成全局ID，其步骤如下</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 客户端根据任务类型，在指定类型的任务下通过调用接口创建一个顺序节点，如"job-"。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 创建完成后，会返回一个完整的节点名，如"job-00000001"。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 客户端拼接type类型和返回值后，就可以作为全局唯一ID了，如"type2-job-00000001"。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.4 分布式协调/通知</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper中特有的Watcher注册于异步通知机制，能够很好地实现分布式环境下不同机器，甚至不同系统之间的协调与通知，从而实现对数据变更的实时处理。通常的做法是不同的客户端都对Zookeeper上的同一个数据节点进行Watcher注册，监听数据节点的变化（包括节点本身和子节点），若数据节点发生变化，那么所有订阅的客户端都能够接收到相应的Watcher通知，并作出相应处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　MySQL数据复制总线是一个实时的数据复制框架，用于在不同的MySQL数据库实例之间进行异步数据复制和数据变化通知，整个系统由MySQL数据库集群、消息队列系统、任务管理监控平台、Zookeeper集群等组件共同构成的一个包含生产者、复制管道、数据消费等部分的数据总线系统。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161111202330842-23526575.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper主要负责进行分布式协调工作，在具体的实现上，根据功能将数据复制组件划分为三个模块：Core（实现数据复制核心逻辑，将数据复制封装成管道，并抽象出生产者和消费者概念）、Server（启动和停止复制任务）、Monitor（监控任务的运行状态，若数据复制期间发生异常或出现故障则进行告警）</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161111203053592-27696266.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　每个模块作为独立的进程运行在服务端，运行时的数据和配置信息均保存在Zookeeper上。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161111205731186-442164523.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　&#9312;&nbsp;<strong>任务创建</strong>，Core进程启动时，首先向/mysql_replicator/tasks节点注册任务，如创建一个子节点/mysql_replicator/tasks/copy_hot/item，若注册过程中发现该子节点已经存在，说明已经有其他Task机器注册了该任务，因此其自身不需要再创建该节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;&nbsp;<strong>任务热备份</strong>，为了应对任务故障或者复制任务所在主机故障，复制组件采用"热备份"的容灾方式，即将同一个复制任务部署在不同的主机上，主备任务机通过Zookeeper互相检测运行监控状况。无论在第一步是否创建了任务节点，每台机器都需要在/mysql_replicator/tasks/copy_hot_item/instrances节点上将自己的主机名注册上去，节点类型为临时顺序节点，在完成子节点创建后，每天任务机器都可以获取到自己创建的节点名及所有子节点列表，然后通过对比判断自己是否是所有子节点中序号最小的，若是，则将自己运行状态设置为RUNNING，其他机器设置为STANDBY，这种策略称为小序号优先策略。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314;&nbsp;<strong>热备切换</strong>，完成运行状态的标示后，其中标记为RUNNING的客户端机器进行正常的数据复制，而标记为STANDBY的机器则进入待命状态，一旦RUNNING机器出现故障，那么所有标记为STANDBY的机器再次按照小序号优先策略来选出RUNNIG机器运行（STANDY机器需要在/mysql_replicator/tasks/copy_hot_item/instances节点上注册一个子节点列表变更监听，RUNNING机器宕机与Zookeeper断开连接后，对应的节点也会消失，于是所有客户端收到通知，进行新一轮选举）。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315;&nbsp;<strong>记录执行状态</strong>，RUNNING机器需要将运行时的上下文状态保留给STANDBY机器。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9316;<strong>&nbsp;控制台协调</strong>，Server的主要工作就是进行任务控制，通过Zookeeper来对不同任务进行控制和协调，Server会将每个复制任务对应生产者的元数据及消费者的相关信息以配置的形式写入任务节点/mysql_replicator/tasks/copy_hot_item中去，以便该任务的所有任务机器都能够共享复制任务的配置。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在上述热备份方案中，针对一个任务，都会至少分配两台任务机器来进行热备份（RUNNING和STANDBY、即主备机器），若需要MySQL实例需要进行数据复制，那么需要消耗太多机器。此时，需要使用冷备份方案，其对所有任务进行分组。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161111211128483-761063276.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Core进程被配置了所属组（Group），即若一个Core进程被标记了group1，那么在Core进程启动后，会到对应的Zookeeper group1节点下面获取所有的Task列表，假如找到任务"copy_hot_item"之后，就会遍历这个Task列表的instances节点，但凡还没有子节点，则创建一个临时的顺序节点如/mysql_replicator/task-groups/group1/copy_hot_item/instances/[Hostname]-1，当然，在这个过程中，其他Core进程也会在这个instances节点下创建类似的子节点，按照"小序号优先"策略确定RUNNING，不同的是，其他Core进程会自动删除自己创建的子节点，然后遍历下一个Task节点，这样的过程称为冷备份扫描，这样，所有的Core进程在扫描周期内不断地对相应的Group下来的Task进行冷备份。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在绝大多数分布式系统中，系统机器间的通信无外乎<span style="line-height: 1.8; color: #ff0000;"><strong>心跳检测</strong></span>、<span style="line-height: 1.8; color: #ff0000;"><strong>工作进度汇报</strong></span>和<span style="line-height: 1.8; color: #ff0000;"><strong>系统调度</strong></span>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312;&nbsp;<strong>心跳检测</strong>，不同机器间需要检测到彼此是否在正常运行，可以使用Zookeeper实现机器间的心跳检测，基于其临时节点特性（临时节点的生存周期是客户端会话，客户端若当即后，其临时节点自然不再存在），可以让不同机器都在Zookeeper的一个指定节点下创建临时子节点，不同的机器之间可以根据这个临时子节点来判断对应的客户端机器是否存活。通过Zookeeper可以大大减少系统耦合。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;&nbsp;<strong>工作进度汇报</strong>，通常任务被分发到不同机器后，需要实时地将自己的任务执行进度汇报给分发系统，可以在Zookeeper上选择一个节点，每个任务客户端都在这个节点下面创建临时子节点，这样不仅可以判断机器是否存活，同时各个机器可以将自己的任务执行进度写到该临时节点中去，以便中心系统能够实时获取任务的执行进度。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314;&nbsp;<strong>系统调度</strong>，Zookeeper能够实现如下系统调度模式：分布式系统由控制台和一些客户端系统两部分构成，控制台的职责就是需要将一些指令信息发送给所有的客户端，以控制他们进行相应的业务逻辑，后台管理人员在控制台上做一些操作，实际上就是修改Zookeeper上某些节点的数据，Zookeeper可以把数据变更以时间通知的形式发送给订阅客户端。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 集群管理</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Zookeeper的两大特性：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><strong>　　&#183; 客户端如果对Zookeeper的数据节点注册Watcher监听，那么当该数据及诶单内容或是其子节点列表发生变更时，Zookeeper服务器就会向订阅的客户端发送变更通知。</strong></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><strong>　　&#183; 对在Zookeeper上创建的临时节点，一旦客户端与服务器之间的会话失效，那么临时节点也会被自动删除。</strong></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　利用其两大特性，可以实现集群机器存活监控系统，若监控系统在/clusterServers节点上注册一个Watcher监听，那么但凡进行动态添加机器的操作，就会在/clusterServers节点下创建一个临时节点：/clusterServers/[Hostname]，这样，监控系统就能够实时监测机器的变动情况。下面通过分布式日志收集系统的典型应用来学习Zookeeper如何实现集群管理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　分布式日志收集系统的核心工作就是收集分布在不同机器上的系统日志，在典型的日志系统架构设计中，整个日志系统会把所有需要收集的日志机器分为多个组别，每个组别对应一个收集器，这个收集器其实就是一个后台机器，用于收集日志，对于大规模的分布式日志收集系统场景，通常需要解决两个问题：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>　　&#183; 变化的日志源机器</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>　　&#183; 变化的收集器机器</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　无论是日志源机器还是收集器机器的变更，最终都可以归结为如何快速、合理、动态地为每个收集器分配对应的日志源机器。使用Zookeeper的场景步骤如下</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312;&nbsp;<strong>注册收集器机器</strong>，在Zookeeper上创建一个节点作为收集器的根节点，例如/logs/collector的收集器节点，每个收集器机器启动时都会在收集器节点下创建自己的节点，如/logs/collector/[Hostname]</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112085605967-1265109617.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;&nbsp;<strong>任务分发</strong>，所有收集器机器都创建完对应节点后，系统根据收集器节点下子节点的个数，将所有日志源机器分成对应的若干组，然后将分组后的机器列表分别写到这些收集器机器创建的子节点，如/logs/collector/host1上去。这样，收集器机器就能够根据自己对应的收集器节点上获取日志源机器列表，进而开始进行日志收集工作。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314;&nbsp;<strong>状态汇报</strong>，完成任务分发后，机器随时会宕机，所以需要有一个收集器的状态汇报机制，每个收集器机器上创建完节点后，还需要再对应子节点上创建一个状态子节点，如/logs/collector/host/status，每个收集器机器都需要定期向该结点写入自己的状态信息，这可看做是心跳检测机制，通常收集器机器都会写入日志收集状态信息，日志系统通过判断状态子节点最后的更新时间来确定收集器机器是否存活。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315;&nbsp;<strong>动态分配</strong>，若收集器机器宕机，则需要动态进行收集任务的分配，收集系统运行过程中关注/logs/collector节点下所有子节点的变更，一旦有机器停止汇报或有新机器加入，就开始进行任务的重新分配，此时通常由两种做法：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;"><strong>&#183; 全局动态分配</strong></span>，当收集器机器宕机或有新的机器加入，系统根据新的收集器机器列表，立即对所有的日志源机器重新进行一次分组，然后将其分配给剩下的收集器机器。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;"><strong>&#183; 局部动态分配</strong></span>，每个收集器机器在汇报自己日志收集状态的同时，也会把自己的负载汇报上去，如果一个机器宕机了，那么日志系统就会把之前分配给这个机器的任务重新分配到那些负载较低的机器，同样，如果有新机器加入，会从那些负载高的机器上转移一部分任务给新机器。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　上述步骤已经完整的说明了整个日志收集系统的工作流程，其中有两点注意事项。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312;<strong>节点类型</strong>，在/logs/collector节点下创建临时节点可以很好的判断机器是否存活，但是，若机器挂了，其节点会被删除，记录在节点上的日志源机器列表也被清除，所以需要选择持久节点来标识每一台机器，同时在节点下分别创建/logs/collector/[Hostname]/status节点来表征每一个收集器机器的状态，这样，既能实现对所有机器的监控，同时机器挂掉后，依然能够将分配任务还原。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　<strong><span style="line-height: 1.8; color: #ff0000;">　</span></strong>&#9313;&nbsp;<strong>日志系统节点监听</strong>，若采用Watcher机制，那么通知的消息量的网络开销非常大，需要采用日志系统主动轮询收集器节点的策略，这样可以节省网络流量，但是存在一定的延时。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.6 Master选举</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在分布式系统中，Master往往用来协调集群中其他系统单元，具有对分布式系统状态变更的决定权，如在读写分离的应用场景中，客户端的写请求往往是由Master来处理，或者其常常处理一些复杂的逻辑并将处理结果同步给其他系统单元。利用Zookeeper的强一致性，能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性，即Zookeeper将会保证客户端无法重复创建一个已经存在的数据节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　首先创建/master_election/2016-11-12节点，客户端集群每天会定时往该节点下创建临时节点，如/master_election/2016-11-12/binding，这个过程中，只有一个客户端能够成功创建，此时其变成master，其他节点都会在节点/master_election/2016-11-12上注册一个子节点变更的Watcher，用于监控当前的Master机器是否存活，一旦发现当前Master挂了，其余客户端将会重新进行Master选举。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112093212483-1690012026.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　<span style="line-height: 1.8; color: #ff0000;">　2.7 分布式锁</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　分布式锁用于控制分布式系统之间同步访问共享资源的一种方式，可以保证不同系统访问一个或一组资源时的一致性，主要分为排它锁和共享锁。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;"><strong>排它锁又称为写锁或独占锁</strong></span>，若事务T1对数据对象O1加上了排它锁，那么在整个加锁期间，只允许事务T1对O1进行读取和更新操作，其他任何事务都不能再对这个数据对象进行任何类型的操作，直到T1释放了排它锁。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112100514577-471030324.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　&#9312;&nbsp;<strong>获取锁</strong>，在需要获取排它锁时，所有客户端通过调用接口，在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。Zookeeper可以保证只有一个客户端能够创建成功，没有成功的客户端需要注册/exclusive_lock节点监听。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;&nbsp;<strong>释放锁</strong>，当获取锁的客户端宕机或者正常完成业务逻辑都会导致临时节点的删除，此时，所有在/exclusive_lock节点上注册监听的客户端都会收到通知，可以重新发起分布式锁获取。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong><span style="line-height: 1.8; color: #ff0000;">共享锁又称为读锁</span></strong>，若事务T1对数据对象O1加上共享锁，那么当前事务只能对O1进行读取操作，其他事务也只能对这个数据对象加共享锁，直到该数据对象上的所有共享锁都被释放。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112101550327-899695174.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　&#9312;&nbsp;<strong>获取锁</strong>，在需要获取共享锁时，所有客户端都会到/shared_lock下面创建一个临时顺序节点，如果是读请求，那么就创建例如/shared_lock/host1-R-00000001的节点，如果是写请求，那么就创建例如/shared_lock/host2-W-00000002的节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;<strong>&nbsp;判断读写顺序</strong>，不同事务可以同时对一个数据对象进行读写操作，而更新操作必须在当前没有任何事务进行读写情况下进行，通过Zookeeper来确定分布式读写顺序，大致分为四步。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　　　1. 创建完节点后，获取/shared_lock节点下所有子节点，并对该节点变更注册监听。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　　　2. 确定自己的节点序号在所有子节点中的顺序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　　　3. 对于读请求：若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求，那么表明自己已经成功获取到共享锁，同时开始执行读取逻辑，若有写请求，则需要等待。对于写请求：若自己不是序号最小的子节点，那么需要等待。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　　　4. 接收到Watcher通知后，重复步骤1。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314;<strong>&nbsp;释放锁</strong>，其释放锁的流程与独占锁一致。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　上述共享锁的实现方案，可以满足一般分布式集群竞争锁的需求，但是如果机器规模扩大会出现一些问题，下面着重分析判断读写顺序的步骤3。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112102635827-1113002930.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　针对如上图所示的情况进行分析</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. host1首先进行读操作，完成后将节点/shared_lock/host1-R-00000001删除。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 余下4台机器均收到这个节点移除的通知，然后重新从/shared_lock节点上获取一份新的子节点列表。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 每台机器判断自己的读写顺序，其中host2检测到自己序号最小，于是进行写操作，余下的机器则继续等待。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4. 继续...</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　可以看到，host1客户端在移除自己的共享锁后，Zookeeper发送了子节点更变Watcher通知给所有机器，然而除了给host2产生影响外，对其他机器没有任何作用。大量的Watcher通知和子节点列表获取两个操作会重复运行，这样会造成系能鞥影响和网络开销，更为严重的是，如果同一时间有多个节点对应的客户端完成事务或事务中断引起节点小时，Zookeeper服务器就会在短时间内向其他所有客户端发送大量的事件通知，这就是所谓的<span style="line-height: 1.8; color: #ff0000;"><strong>羊群效应</strong></span>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　可以有如下改动来避免羊群效应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 客户端调用getChildren接口获取所有已经创建的子节点列表（不注册任何Watcher）。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 如果无法获取共享锁，就调用exist接口来对比自己小的节点注册Watcher。对于读请求：向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求：向比自己序号小的最后一个节点注册Watcher监听。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4. 等待Watcher通知，继续进入步骤2。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　此方案改动主要在于：每个锁竞争者，只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.8 分布式队列</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　分布式队列可以简单分为<span style="line-height: 1.8; color: #ff0000;"><strong>先入先出队列模型</strong></span>和<span style="line-height: 1.8; color: #ff0000;"><strong>等待队列元素聚集后统一安排处理执行的Barrier模型</strong></span>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312;&nbsp;<strong>FIFO先入先出</strong>，先进入队列的请求操作先完成后，才会开始处理后面的请求。FIFO队列就类似于全写的共享模型，所有客户端都会到/queue_fifo这个节点下创建一个临时节点，如/queue_fifo/host1-00000001。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112104959530-1061396244.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　创建完节点后，按照如下步骤执行。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 通过调用getChildren接口来获取/queue_fifo节点的所有子节点，即获取队列中所有的元素。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 确定自己的节点序号在所有子节点中的顺序。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 如果自己的序号不是最小，那么需要等待，同时向比自己序号小的最后一个节点注册Watcher监听。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4. 接收到Watcher通知后，重复步骤1。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313;<strong>&nbsp;Barrier分布式屏障</strong>，最终的合并计算需要基于很多并行计算的子结果来进行，开始时，/queue_barrier节点已经默认存在，并且将结点数据内容赋值为数字n来代表Barrier值，之后，所有客户端都会到/queue_barrier节点下创建一个临时节点，例如/queue_barrier/host1。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201611/616953-20161112105044686-1718104359.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　创建完节点后，按照如下步骤执行。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. 通过调用getData接口获取/queue_barrier节点的数据内容，如10。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 通过调用getChildren接口获取/queue_barrier节点下的所有子节点，同时注册对子节点变更的Watcher监听。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　3. 统计子节点的个数。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　4. 如果子节点个数还不足10个，那么需要等待。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　5. 接受到Wacher通知后，重复步骤3。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>三、总结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　本篇博客讲解了如何利用Zookeeper的特性来完成典型应用，展示了Zookeeper在解决分布式问题上的强大作用，基于Zookeeper对分布式数据一致性的保证及其特性，开发人员能够构建出自己的分布式系统。也谢谢各位园友的观看~</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><div style="margin-top: 100px;"></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432181.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 21:03 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432181.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Zookeeper与Paxos</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432178.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 07:59:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432178.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432178.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432178.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432178.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432178.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6012777.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong><span style="line-height: 1.5;">一、前言</span></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在学习了Paxos在Chubby中的应用后，接下来学习Paxos在开源软件Zookeeper中的应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、Zookeeper</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper是一个开源的分布式协调服务，其设计目标是将那些复杂的且容易出错的分布式一致性服务封装起来，构成一个高效可靠的原语集，并以一些列简单的接口提供给用户使用。其是一个典型的分布式数据一致性的解决方案，分布式应用程序可以基于它实现诸如数据发布/发布、负载均衡、命名服务、分布式协调/通知、集群管理、Master选举、分布式锁和分布式队列等功能。其可以保证如下分布式一致性特性。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312;&nbsp;<strong>顺序一致性</strong>，从同一个客户端发起的事务请求，最终将会严格地按照其发起顺序被应用到Zookeeper中去。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313;&nbsp;<strong>原子性</strong>，所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的，即整个集群要么都成功应用了某个事务，要么都没有应用。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314;&nbsp;<strong>单一视图</strong>，无论客户端连接的是哪个Zookeeper服务器，其看到的服务端数据模型都是一致的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9315;&nbsp;<strong>可靠性</strong>，一旦服务端成功地应用了一个事务，并完成对客户端的响应，那么该事务所引起的服务端状态变更将会一直被保留，除非有另一个事务对其进行了变更。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9316;&nbsp;<strong>实时性</strong>，Zookeeper保证在一定的时间段内，客户端最终一定能够从服务端上读取到最新的数据状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2.1 设计目标</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper致力于提供一个高性能、高可用、且具有严格的顺序访问控制能力（主要是写操作的严格顺序性）的分布式协调服务，其具有如下的设计目标。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>简单的数据模型</strong></span>，Zookeeper使得分布式程序能够通过一个共享的树形结构的名字空间来进行相互协调，即Zookeeper服务器内存中的数据模型由一系列被称为ZNode的数据节点组成，<strong>Zookeeper将全量的数据存储在内存中，以此来提高服务器吞吐、减少延迟的目的</strong>。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>可构建集群</strong></span>，一个Zookeeper集群通常由一组机器构成，组成Zookeeper集群的而每台机器都会在内存中维护当前服务器状态，并且每台机器之间都相互通信。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314;&nbsp;<strong><span style="line-height: 1.8; color: #ff0000;">顺序访问</span></strong>，对于来自客户端的每个更新请求，Zookeeper都会分配一个全局唯一的递增编号，这个编号反映了所有事务操作的先后顺序。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9315;&nbsp;<strong><span style="line-height: 1.8; color: #ff0000;">高性能</span></strong>，Zookeeper将全量数据存储在内存中，并直接服务于客户端的所有非事务请求，因此它尤其适用于以读操作为主的应用场景。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>2.2 基本概念</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>集群角色</strong></span>，最典型的集群就是Master/Slave模式（主备模式），此情况下把所有能够处理写操作的机器称为Master机器，把所有通过异步复制方式获取最新数据，并提供读服务的机器为Slave机器。Zookeeper引入了Leader、Follower、Observer三种角色，Zookeeper集群中的所有机器通过Leaser选举过程来选定一台被称为Leader的机器，Leader服务器为客户端提供写服务，Follower和Observer提供读服务，但是<strong>Observer不参与Leader选举过程</strong>，<strong>不参与写操作</strong>的<strong>过半写成功</strong>策略，Observer可以在不影响写性能的情况下提升集群的性能。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>会话</strong></span>，指客户端会话，<strong>一个客户端连接是指客户端和服务端之间的一个TCP长连接</strong>，Zookeeper对外的服务端口默认为2181，客户端启动的时候，首先会与服务器建立一个TCP连接，从第一次连接建立开始，客户端会话的生命周期也开始了，通过这个连接，客户端能够心跳检测与服务器保持有效的会话，也能够向Zookeeper服务器发送请求并接受响应，同时还能够通过该连接接受来自服务器的Watch事件通知。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314;&nbsp;<strong><span style="line-height: 1.8; color: #ff0000;">数据节点</span></strong>，第一类指构成集群的机器，称为<strong>机器节点</strong>，第二类是指数据模型中的<strong>数据单元</strong>，称为数据节点-Znode，Zookeeper将<strong>所有数据存储在内存中</strong>，数据模型是一棵树，由斜杠/进行分割的路径，就是一个ZNode，如/foo/path1，每个ZNode都会保存自己的数据内存，同时还会保存一些列属性信息。ZNode分为<strong>持久节点</strong>和<strong>临时节点</strong>两类，持久节点是指一旦这个ZNode被创建了，<strong>除非主动进行ZNode的移除操作，否则这个ZNode将一直保存在Zookeeper上</strong>，而<strong>临时节点的生命周期和客户端会话绑定，一旦客户端会话失效，那么这个客户端创建的所有临时节点都会被移除</strong>。另外，Zookeeper还允许用户为每个节点添加一个特殊的属性：<strong>SEQUENTIAL</strong>。一旦节点被标记上这个属性，那么在这个节点被创建的时候，Zookeeper会自动在其节点后面追加一个整形数字，其是由父节点维护的自增数字。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9315;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>版本</strong></span>，对于每个ZNode，Zookeeper都会为其维护一个叫作Stat的数据结构，Stat记录了这个ZNode的三个数据版本，分别是version（当前ZNode的版本）、cversion（当前ZNode子节点的版本）、aversion（当前ZNode的ACL版本）。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9316;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>Watcher</strong></span>，Zookeeper允许用户在指定节点上注册一些Watcher，并且在一些特定事件触发的时候，Zookeeper服务端会将事件通知到感兴趣的客户端。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9317;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>ACL</strong></span>，Zookeeper采用ACL（Access Control Lists）策略来进行权限控制，其定义了如下五种权限：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; CREATE：创建子节点的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; READ：获取节点数据和子节点列表的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; WRITE：更新节点数据的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; DELETE：删除子节点的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　　　&#183; ADMIN：设置节点ACL的权限。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<strong>　2.3 ZAB协议</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Zookeeper使用了Zookeeper Atomic Broadcast（ZAB，Zookeeper原子消息广播协议）的协议作为其数据一致性的核心算法。ZAB协议是为Zookeeper专门设计的一种<strong>支持崩溃恢复的原子广播协议。</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>　　</strong>Zookeeper依赖ZAB协议来实现分布式数据的一致性，基于该协议，Zookeeper实现了一种主备模式的系统架构来保持集群中各副本之间的数据的一致性，即其使用一个单一的诸进程来接收并处理客户端的所有事务请求，并采用ZAB的原子广播协议，将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程中，ZAB协议的主备模型架构保证了同一时刻集群中只能够有一个主进程来广播服务器的状态变更，因此能够很好地处理客户端大量的并发请求。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ZAB协议的核心是定义了对于那些会改变Zookeeper服务器数据状态的事务请求的处理方式，即：<span style="line-height: 1.8; color: #ff0000;"><strong>所有事务请求必须由一个全局唯一的服务器来协调处理</strong></span>，这样的服务器被称为Leader服务器，余下的服务器则称为Follower服务器，Leader服务器负责将一个客户端事务请求转化成一个事务Proposal（提议），并将该Proposal分发给集群中所有的Follower服务器，之后Leader服务器需要等待所有Follower服务器的反馈，一旦超过半数的Follower服务器进行了正确的反馈后，那么Leader就会再次向所有的Follower服务器分发Commit消息，要求其将前一个Proposal进行提交。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ZAB一些包括两种基本的模式：<span style="line-height: 1.8; color: #ff0000;"><strong>崩溃恢复</strong></span>和<span style="line-height: 1.8; color: #ff0000;"><strong>消息广播</strong></span>。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当整个服务框架启动过程中或Leader服务器出现网络中断、崩溃退出与重启等异常情况时，ZAB协议就会进入恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器，同时集群中已经有过半的机器与该Leader服务器完成了状态同步之后，ZAB协议就会退出恢复模式，状态同步时指数据同步，用来保证集群在过半的机器能够和Leader服务器的数据状态保持一致。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当集群中已经有过半的Follower服务器完成了和Leader服务器的状态同步，那么整个服务框架就可以进入<strong>消息广播模式，</strong>当一台同样遵守ZAB协议的服务器启动后加入到集群中，如果此时集群中已经存在一个Leader服务器在负责进行消息广播，那么加入的服务器就会自觉地进入<strong>数据恢复模式：找到Leader所在的服务器，并与其进行数据同步，然后一起参与到消息广播流程中去。</strong>Zookeeper只允许唯一的一个Leader服务器来进行事务请求的处理，Leader服务器在接收到客户端的事务请求后，会生成对应的事务提议并发起一轮广播协议，而如果集群中的其他机器收到客户端的事务请求后，那么这些非Leader服务器会首先将这个事务请求转发给Leader服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当Leader服务器出现崩溃或者机器重启、集群中已经不存在过半的服务器与Leader服务器保持正常通信时，那么在重新开始新的一轮的原子广播事务操作之前，所有进程首先会使用崩溃恢复协议来使彼此到达一致状态，于是整个ZAB流程就会从消息广播模式进入到崩溃恢复模式。一个机器要成为新的Leader，必须获得过半机器的支持，同时由于每个机器都有可能会崩溃，因此，ZAB协议运行过程中，前后会出现多个Leader，并且每台机器也有可能会多次成为Leader，进入崩溃恢复模式后，只要集群中存在过半的服务器能够彼此进行正常通信，那么就可以产生一个新的Leader并再次进入消息广播模式。如一个由三台机器组成的ZAB服务，通常由一个Leader、2个Follower服务器组成，某一个时刻，加入其中一个Follower挂了，整个ZAB集群是不会中断服务的。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>消息广播</strong></span>，ZAB协议的消息广播过程使用原子广播协议，类似于一个二阶段提交过程，针对客户端的事务请求，Leader服务器会为其生成对应的事务Proposal，并将其发送给集群中其余所有的机器，然后再分别收集各自的选票，最后进行事务提交。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161031193240018-708457999.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在ZAB的二阶段提交过程中，移除了中断逻辑，所有的Follower服务器要么正常反馈Leader提出的事务Proposal，要么就抛弃Leader服务器，同时，ZAB协议将二阶段提交中的中断逻辑移除意味着我们可以在过半的Follower服务器已经反馈Ack之后就开始提交事务Proposal，而不需要等待集群中所有的Follower服务器都反馈响应，但是，在这种简化的二阶段提交模型下，无法处理Leader服务器崩溃退出而带来的数据不一致问题，因此ZAB采用了崩溃恢复模式来解决此问题，另外，整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的，因此能够很容易保证消息广播过程中消息接受与发送的顺序性。再整个消息广播过程中，Leader服务器会为每个事务请求生成对应的Proposal来进行广播，并且在广播事务Proposal之前，Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID，称之为事务ID（ZXID），由于ZAB协议需要保证每个消息严格的因果关系，因此必须将每个事务Proposal按照其ZXID的先后顺序来进行排序和处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313;<span style="line-height: 1.8; color: #ff0000;"><strong>&nbsp;崩溃恢复</strong></span>，在Leader服务器出现崩溃，或者由于网络原因导致Leader服务器失去了与过半Follower的联系，那么就会进入崩溃恢复模式，在ZAB协议中，为了保证程序的正确运行，整个恢复过程结束后需要选举出一个新的Leader服务器，因此，ZAB协议需要一个高效且可靠的Leader选举算法，从而保证能够快速地选举出新的Leader，同时，Leader选举算法不仅仅需要让Leader自身知道已经被选举为Leader，同时还需要让集群中的所有其他机器也能够快速地感知到选举产生的新的Leader服务器。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>基本特性</strong></span>，ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功，那么应该在所有的机器上都被处理成功，哪怕机器出现故障崩溃。<strong>ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有服务器都提交</strong>，假设一个事务在Leader服务器上被提交了，并且已经得到了过半Follower服务器的Ack反馈，但是在它Commit消息发送给所有Follower机器之前，Leader服务挂了。如下图所示</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161031195828690-138896834.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在集群正常运行过程中的某一个时刻，Server1是Leader服务器，其先后广播了P1、P2、C1、P3、C2（C2是Commit Of Proposal2的缩写），其中，当Leader服务器发出C2后就立即崩溃退出了，针对这种情况，ZAB协议就需要确保事务Proposal2最终能够在所有的服务器上都被提交成功，否则将出现不一致。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务</strong>。如果在崩溃恢复过程中出现一个需要被丢弃的提议，那么在崩溃恢复结束后需要跳过该事务Proposal，如下图所示</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161031200533846-1367154177.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" />　　假设初始的Leader服务器Server1在提出一个事务Proposal3之后就崩溃退出了，从而导致集群中的其他服务器都没有收到这个事务Proposal，于是，当Server1恢复过来再次加入到集群中的时候，ZAB协议需要确保丢弃Proposal3这个事务。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在上述的崩溃恢复过程中需要处理的特殊情况，就决定了ZAB协议必须设计这样的Leader选举算法：能够确保提交已经被Leader提交的事务的Proposal，同时丢弃已经被跳过的事务Proposal。如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号（ZXID最大）的事务Proposal，那么就可以保证这个新选举出来的Leader一定具有所有已经提交的提议，更为重要的是如果让具有最高编号事务的Proposal机器称为Leader，就可以省去Leader服务器查询Proposal的提交和丢弃工作这一步骤了。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9315;&nbsp;<span style="line-height: 1.8; color: #ff0000;"><strong>数据同步</strong></span>，完成Leader选举后，在正式开始工作前，Leader服务器首先会确认日志中的所有Proposal是否都已经被集群中的过半机器提交了，即是否完成了数据同步。Leader服务器需要确所有的Follower服务器都能够接收到每一条事务Proposal，并且能够正确地将所有已经提交了的事务Proposal应用到内存数据库中。Leader服务器会为每个Follower服务器维护一个队列，并将那些没有被各Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器，并在每一个Proposal消息后面紧接着再发送一个Commit消息，以表示该事务已经被提交，等到Follower服务器将所有其尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库后，Leader服务器就会将该Follower服务器加入到真正的可用Follower列表并开始之后的其他流程。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　下面分析ZAB协议如何处理需要丢弃的事务Proposal的，ZXID是一个64位的数字，其中32位可以看做是一个简单的单调递增的计数器，针对客户端的每一个事务请求，Leader服务器在产生一个新的事务Proposal时，都会对该计数器进行加1操作，而高32位则代表了Leader周期epoch的编号，每当选举产生一个新的Leader时，就会从这个Leader上取出其本地日志中最大事务Proposal的ZXID，并解析出epoch值，然后加1，之后以该编号作为新的epoch，低32位则置为0来开始生成新的ZXID，ZAB协议通过epoch号来区分Leader周期变化的策略，能够有效地避免不同的Leader服务器错误地使用不同的ZXID编号提出不一样的事务Proposal的异常情况。<strong>当一个包含了上一个Leader周期中尚未提交过的事务Proposal的服务器启动时，其肯定无法成为Leader，因为当前集群中一定包含了一个Quorum（过半）集合，该集合中的机器一定包含了更高epoch的事务的Proposal，因此这台机器的事务Proposal并非最高，也就无法成为Leader。</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>　　<strong>2.4 ZAB协议原理</strong></strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>　　</strong>ZAB主要包括消息广播和崩溃恢复两个过程，进一步可以分为三个阶段，分别是发现（Discovery）、同步（Synchronization）、广播（Broadcast）阶段。ZAB的每一个分布式进程会循环执行这三个阶段，称为主进程周期。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 发现</strong>，选举产生PL(prospective leader)，PL收集Follower epoch(cepoch)，根据Follower的反馈，PL产生newepoch(每次选举产生新Leader的同时产生新epoch)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 同步</strong>，PL补齐相比Follower多数派缺失的状态、之后各Follower再补齐相比PL缺失的状态，PL和Follower完成状态同步后PL变为正式Leader(established leader)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<strong>&#183; 广播</strong>，Leader处理客户端的写操作，并将状态变更广播至Follower，Follower多数派通过之后Leader发起将状态变更落地(deliver/commit)。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　在正常运行过程中，ZAB协议会一直运行于阶段三来反复进行消息广播流程，如果出现崩溃或其他原因导致Leader缺失，那么此时ZAB协议会再次进入发现阶段，选举新的Leader。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.4.1 运行分析</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　每个进程都有可能处于如下三种状态之一</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#183; LOOKING：Leader选举阶段。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#183; FOLLOWING：Follower服务器和Leader服务器保持同步状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#183; LEADING：Leader服务器作为主进程领导状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　所有进程初始状态都是LOOKING状态，此时不存在Leader，此时，进程会试图选举出一个新的Leader，之后，如果进程发现已经选举出新的Leader了，那么它就会切换到FOLLOWING状态，并开始和Leader保持同步，处于FOLLOWING状态的进程称为Follower，LEADING状态的进程称为Leader，当Leader崩溃或放弃领导地位时，其余的Follower进程就会转换到LOOKING状态开始新一轮的Leader选举。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　一个Follower只能和一个Leader保持同步，Leader进程和所有与所有的Follower进程之间都通过心跳检测机制来感知彼此的情况。若Leader能够在超时时间内正常收到心跳检测，那么Follower就会一直与该Leader保持连接，而如果在指定时间内Leader无法从过半的Follower进程那里接收到心跳检测，或者TCP连接断开，那么Leader会放弃当前周期的领导，比你转换到LOOKING状态，其他的Follower也会选择放弃这个Leader，同时转换到LOOKING状态，之后会进行新一轮的Leader选举，并在选举产生新的Leader之后开始新的一轮主进程周期。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 ZAB与Paxos的联系和区别</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　联系：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312; 都存在一个类似于Leader进程的角色，由其负责协调多个Follower进程的运行。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313; Leader进程都会等待超过半数的Follower做出正确的反馈后，才会将一个提议进行提交。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314; 在ZAB协议中，每个Proposal中都包含了一个epoch值，用来代表当前的Leader周期，在Paxos算法中，同样存在这样的一个标识，名字为Ballot。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　区别：</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　Paxos算法中，新选举产生的主进程会进行两个阶段的工作，第一阶段称为读阶段，新的主进程和其他进程通信来收集主进程提出的提议，并将它们提交。第二阶段称为写阶段，当前主进程开始提出自己的提议。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ZAB协议在Paxos基础上添加了同步阶段，此时，新的Leader会确保存在过半的Follower已经提交了之前的Leader周期中的所有事务Proposal。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　ZAB协议主要用于构建一个高可用的分布式数据主备系统，而Paxos算法则用于构建一个分布式的一致性状态机系统。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　此部分也还是理论知识偏多，学好理论之后再看代码应该会更快速一些，也谢谢各位园友的观看~　</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432178.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 15:59 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432178.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】一致性协议</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432177.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 07:39:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432177.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432177.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432177.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432177.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432177.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6001278.html<br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>一、前言</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　继续前面的学习，这篇我们来学习在分布式系统中最重要的一块，一致性协议，其中就包括了大名鼎鼎的Paxos算法。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>二、2PC与3PC</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在分布式系统中，每一个机器节点虽然能够明确知道自己在进行事务操作过程中的结果是成功或是失败，但是却无法直接获取到其他分布式节点的操作结果，因此，当一个事务操作需要跨越多个分布式节点的时候，为了保持事务处理的ACID的特性，需要引入协调者的组件来统一调度所有分布式节点的执行逻辑，而被调度的节点则被称为参与者，协调者负责调度参与者的行为并最终决定这些参与者是否要把事务真正进行提交，基于这个思想，衍生出了二阶段提交和三阶段提交两种协议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 2PC</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2PC为Two-Phase Commit的简写，为二阶段提交协议将事务的提交过程分成了两个阶段来进行处理，并执行如下流程：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>阶段一：提交事务请求</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 事务询问，协调者向所有的参与者发送事务内容，询问是否可以执行事务提交操作，并开始等待各参与者的响应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 执行事务，各参与者节点执行事务操作（已经执行），并将Undo和Redo信息记入事务日志中。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 各参与者向协调者反馈事务询问的响应，如果参与者成功执行了事务操作，那么就反馈给协调者Yes响应，表示事务可以执行；如果参与者没有成功执行事务，那么就反馈给协调者No响应，表示事务不可以执行。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　第一阶段近似于是协调者组织各参与者对一次事务操作的投票表态的过程，因此二阶段提交协议的阶段一也被称为投票阶段。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>阶段二：执行事务提交</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作，正常情况包含如下两种可能：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1.&nbsp;<span style="line-height: 1.8; color: #ff0000;">执行事务提交</span>，假如协调者从所有的参与者获得的反馈都是Yes响应，那么就会执行事务提交。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送提交请求，协调者向所有参与者节点发出Commit请求。&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 事务提交，参与者接收到Commit请求后，会正式执行事务提交操作，并在完成提交之后释放在整个事务执行期间占用的事务资源。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 反馈事务提交结果，参与者在完成事务提交之后，向协调者发送Ack消息。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315; 完成事务，协调者接收到所有参与者反馈的Ack消息后，完成事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2.&nbsp;<span style="line-height: 1.8; color: #ff0000;">中断事务</span>，假如任意一个参与者向协调者反馈了No响应，或者在等待超时之后，协调者尚无法接收到参与者的反馈响应，就会中断事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送回滚请求，协调者向所有参与者节点发出Rollback请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 事务回滚，参与者接收到Rollback请求后，会利用其在阶段一中记录的Undo信息来执行事务回滚，并在完成回滚之后释放在整个事务执行期间占用的资源。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 反馈事务回滚结果，参与者在完成事务回滚后，向协调者发送Ack消息。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315; 中断事务，协调者接收所有参与者反馈的Ack消息后，完成事务中断。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　二阶段提交协议的优点：原理简单，实现方便。缺点：同步阻塞，单点问题，数据不一致，太过保守。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>同步阻塞</strong>：在二阶段提交的执行过程中，所有参与该事务操作的逻辑都处于阻塞状态，即当参与者占有公共资源时，其他节点访问公共资源不得不处于阻塞状态。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>单点问题</strong>：若协调器出现问题，那么整个二阶段提交流程将无法运转，若协调者是在阶段二中出现问题时，那么其他参与者将会一直处于锁定事务资源的状态中，而无法继续完成事务操作。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>数据不一致</strong>：在二阶段的阶段二，执行事务提交的时候，当协调者向所有的参与者发送Commit请求之后，发生了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩溃，导致最终只有部分参与者收到了Commit请求，于是会出现数据不一致的现象。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>太过保守</strong>：在进行事务提交询问的过程中，参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话，此时协调者只能依靠自身的超时机制来判断是否需要中断事务，这样的策略过于保守，即没有完善的容错机制，任意一个结点的失败都会导致整个事务的失败。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 3PC</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　三阶段提交，将二阶段提交协议的提交事务请求过程分为CanCommit、PreCommit、doCommit三个阶段组成的事务处理协议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161026193835781-416773789.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>阶段一：canCommit</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 事务询问，协调者向所有的参与者发送一个包含事务内容的canCommit请求，询问是否可以执行事务提交操作，并开始等待各参与者的响应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 各参与者向协调者反馈事务询问的响应，参与者在接收到来自协调者的canCommit请求后，正常情况下，如果自身认为可以顺利执行事务，则反馈Yes响应，并进入预备状态，否则反馈No响应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>阶段二：preCommit</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　该阶段会根据反馈情况决定是否可以进行事务preCommit操作，正常情况下，包含如下两种可能：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　执行事务预提交，假如所有参与反馈的都是Yes，那么就会执行事务预提交。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送预提交请求，协调者向所有参与者节点发出preCommit请求，并进入prepared阶段。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 事务预提交，参与者接收到preCommit请求后，会执行事务操作，并将Undo和Redo信息记录到事务日志中。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 各参与者向协调者反馈事务执行的响应，若参与者成功执行了事务操作，那么反馈Ack，同时等待最终的指令：提交（commit）或终止（abort）。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　中断事务，若任一参与反馈了No响应，或者在等待超时后，协调者尚无法接收到所有参与者反馈，则中断事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送中断请求，协调者向所有参与者发出abort请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 中断事务，无论是收到来自协调者的abort请求或者等待协调者请求过程中超时，参与者都会中断事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong>阶段三：doCommit</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　该阶段会进行真正的事务提交，也会存在如下情况。　　</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">1. 执行提交</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送提交请求，进入这一阶段，若协调者处于正常工作状态，并且他接收到了来自所有参与者的Ack响应，那么他将从预提交状态转化为提交状态，并向所有的参与者发送doCommit请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 事务提交，参与者接收到doCommit请求后，会正式执行事务提交操作，并在完成提交之后释放整个事务执行过程中占用的事务资源。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 反馈事务提交结果，参与者在完成事务提交后，向协调者发送Ack响应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315; 完成事务，协调者接收到所有参与者反馈的Ack消息后，完成事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2. 中断事务</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 发送中断请求，协调者向所有的参与者节点发送abort请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 事务回滚，参与者收到abort请求后，会根据记录的Undo信息来执行事务回滚，并在完成回滚之后释放整个事务执行期间占用的资源。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 反馈事务回滚结果，参与者在完成事务回滚后，向协调者发送Ack消息。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315; 中断事务，协调者接收到所有参与者反馈的Ack消息后，中断事务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　三阶段提交协议降低了参与者的阻塞范围，能够在发生单点故障后继续达成一致。但是其可能还是会发生数据不一致问题。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>三、Paxos算法</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Paxos算法是一种基于消息传递且具有高度容错特性的一致性算法，其需要解决的问题就是如何在一个可能发生异常的分布式系统中，快速且正确地在集群内部对某个数据的值达成一致，并且保证不论发生以上任何异常，都不会破坏整个系统的一致性。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　和2PC类似，Paxos先把节点分成两类，发起提议(proposal)的一方为proposer，参与决议的一方为acceptor。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在没有失败和消息丢失的情况下，假如只有一个提议被提出的情况，如何确定一个提议，做到如下就可以保证</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong><span style="line-height: 1.8; color: #ff0000;">P1:一个acceptor必须接受它收到的第一个提议。</span></strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　P1会引入一个问题，若果多个提议被不同的proposer同时提出，这可能会导致虽然每个acceptor都批准了它收到的第一个提议，但是没有一个提议是由多数acceptor都接受的，因此无法确定一个提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161026202212796-47122031.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　上图无法确定一个提议。即使只有两个提议被提出，如果每个提议都被差不多一半的acceptor批准了，此时也可能无法确定哪个提议，如下图所示</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161026202831765-739081816.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　如上图所示，若acceptor5出现故障，则无法确定哪个提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在P1的基础上，增加如下条件：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>　　a. proposer发起的每项提议分别用一个ID标识，提议的组成因此变为(ID, value)</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>　　b.&nbsp;若确定一个提议，需要由半数以上的acceptor接受，当某个提议被半数以上的acceptor接受后，我们就认为该提议就被确定了。</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　我们约定后面发起的提议的ID比前面提议的ID大，并假设可以有多项提议被确定，为做到确定并只确定一个值acceptor要做到以下这点：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;"><strong>P2：如果一项值为v的提议被确定，那么后续只确定值为v的提议。</strong></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　由于一项提议被确定(chosen)前必须先被多数派acceptor接受(accepted)，为实现P2，实质上acceptor需要做到：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;"><strong>P2a：如果一项值为v的提议被确定，那么acceptor后续只接受值为v的提议。</strong></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161026204547015-1218968410.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　如上图所示，在acceptor1没有收到任何提议的情况下，其他4个acceptor已经批准了来自proposer2的提议[M0,V1]，而此时，proposer1产生了一个具有其他value值的，编号更高的提议[M1,V2]，并发送给了acceptor1，根据P1，就需要接受该提议，但是这与P2a矛盾，因此如果要同时满足P1和P2a，需要进入如下强化</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<strong><span style="line-height: 1.8; color: #ff0000;">P2b：如果一项值为v的提议被确定，那么proposer后续只发起值为v的提议。</span></strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　P2b约束的是提议被确定(chosen)后proposer的行为，我们更关心提议被确定前proposer应该怎么做。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>　　<span style="line-height: 1.8; color: #ff0000;">P2c：对于提议(n,v)，acceptor的多数派S中，如果存在acceptor最近一次(即ID值最大)接受的提议的值为v'，那么要求v = v'；否则v可为任意值。</span></strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">3.1 proposer生成提议</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在proposer产生一个编号为Mn的提议时，必须要知道当前某一个将要或已经被半数以上acceptor接受的编号小于Mn但为最大编号的提议，并且，proposer会要求所有的acceptor都不要再接受任何编号小于Mn的提议，这也就是如下提议生成算法。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　1. proposer选择一个新的提议编号为Mn，然后向某个acceptor集合的成员发送请求，要求该集合中的acceptor做出如下回应。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 向proposer承诺，保证不再接受任何编号小于Mn的提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 如果acceptor已经接受过任何提议，那么其就向proposer反馈当前该acceptor已经接受的编号小于Mn但为最大编号的那个提议的值。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　我们将请求称为编号Mn的提议的Prepare请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　2. 如果proposer收到了来自半数以上的acceptor的响应结果，那么它就可以产生编号为Mn、Value值为Vn的提议，这里的Vn是所有响应中编号最大的提议Value的值，当然，如果半数以上的acceptor都没有接受过任何提议，即响应中不包含任何提议，那么此时Vn值就可以由proposer任意选择。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在确定了proposer的提议后，proposer就会将该提议再次发送给某个acceptor集合，并期望获得它们的接受，此请求称为accept请求，此时接受accept请求的acceptor集合不一定是之前响应prepare请求的acceptor集合。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">3.2 acceptor接受提议</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　一个acceptor可能会收到来自proposer的两种请求，分别是prepare请求和accept请求，对这两类请求作出响应的条件分别如下</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　prepare请求：acceptor可以在任何时候响应一个prepare请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　accept请求：在不违背accept现有承诺的前提下，可以任意响应accept请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　因此，对acceptor逻辑处理的约束条件，大体可以定义如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　P1a：一个acceptor只要尚未响应过任何编号大于Mn的prepare请求，那么它就可以接受这个编号为Mn的提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">3.3 算法描述</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　阶段一：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; proposer选择一个提议编号Mn，然后向acceptor的某个超过半数的子集成员发送编号为Mn的prepare请求。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 如果一个acceptor收到编号为Mn的prepare请求，且编号Mn大于该acceptor已经响应的所有prepare请求的编号，那么它就会将它已经接受过的最大编号的提议作为响应反馈给proposer，同时该acceptor承诺不会再接受任何编号小于Mn的提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　阶段二：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 如果proposer收到来自半数以上的acceptor对于其发出的编号为Mn的prepare请求响应，那么它就会发送一个针对[Mn,Vn]提议的accept请求给acceptor，注意，Vn的值就是收到的响应中编号最大的提议的值，如果响应中不包含任何提议，那么它就是任意值。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 如果acceptor收到这个针对[Mn,Vn]提议的accept请求，只要该accept尚未对编号大于Mn的prepare请求作出响应，它就可以接受这个提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">3.4 提议的获取</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　使learner获取提议，有如下方案</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 一旦acceptor接受了一个提议，就将该提议发送给所有的learner，通信开销很大。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 让所有的acceptor将它们对提议的接受情况，统一发送给一个特定的learner（主learner），当该learner被通知一个提议已经被确定时，它就负责通知其他的learner。主learner可能会出现单点故障。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 将主learner范围扩大至一个特定的learner集合，该集合中的每个learner都可以在一个提议被选定后通知所有其他的learner，集合learner越多，越可靠，但是通信开销越大。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">3.5 选取主proposer保证算法的活性</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　假设存在如下的极端情况，有两个proposer依次提出了一系列编号递增的提议，但是最终都无法被确定，具体流程如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　proposer1提出了编号为M1的提议，然后完成了上述的第一阶段，与此同时，proposer2提出了编号为M2的提议，同样完成了第一阶段，于是acceptor承诺不再接受编号小于M2的提议，因此，当proposer1进入阶段二时，其发出的accept请求会被acceptor忽略，于是proposer1又进入第一阶段并提出了编号为M3的提议，这导致proposer2的accept请求被忽略，一次类推，提议的确定过程将陷入死循环。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　为了保证Paxos算法的活性，就必须选择一个主proposer，并规定只有主proposer才能提出提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>四、总结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　本篇分析了一致性协议，并且从理论上着重分析了Paxos算法，理解了其含义，也谢谢各位园友的观看~</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">&nbsp;</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">参考链接：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　http://www.cnblogs.com/bangerlee/p/5655754.html</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　http://flychao88.iteye.com/blog/2262326</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　http://www.tudou.com/programs/view/e8zM8dAL6hM/</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><div style="margin-top: 100px;"></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432177.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 15:39 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432177.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】Chubby与Paxos</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432176.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 07:38:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432176.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432176.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432176.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432176.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432176.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/6005806.html<br /><br /><div id="cnblogs_post_body" style="margin-bottom: 20px; word-break: break-word; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>一、前言</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在上一篇理解了Paxos算法的理论基础后，接下来看看Paxos算法在工程中的应用。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>二、Chubby</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Chubby是一个面向松耦合分布式系统的锁服务，GFS（Google File System）和Big Table等大型系统都是用它来解决分布式协作、元数据存储和Master选举等一些列与分布式锁服务相关的问题。Chubby的底层一致性实现就是以Paxos算法为基础，Chubby提供了粗粒度的分布式锁服务，开发人员直接调用Chubby的锁服务接口即可实现分布式系统中多个进程之间粗粒度的同控制，从而保证分布式数据的一致性。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.1 设计目标</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Chubby被设计成为一个<strong>需要访问中心化的分布式锁服务</strong>。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 对上层应用程序的侵入性更小，使用一个分布式锁服务的接口方式对上层应用程序的侵入性更小，应用程序只需调用相应的接口即可使用分布式一致性特性，并且更易于保持系统已有的程序结构和网络通信模式。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 便于提供数据的发布与订阅，在Chubby进行Master选举时，需要使用一种广播结果的机制来向所有客户端公布当前Master服务器，这意味着Chubby应该允许其客户端在服务器上进行少量数据的存储和读取（存储主Master地址等信息），也就是对小文件的读写操作。数据的发布与订阅功能和锁服务在分布式一致性特性上是相通的。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; 开发人员对基于锁的接口更为熟悉，Chubby提供了一套近乎和单机锁机制一直的分布式锁服务接口。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9315; 更便捷地构建更可靠的服务，Chubby中通常使用5台服务器来组成一个集群单元（Cell），根据Quorum机制（在一个由若干个机器组成的集群中，在一个数据项值的选定过程中，要求集群中过半的机器达成一致），只要整个集群中有3台服务器是正常运行的，那么整个集群就可以对外提供正常的服务。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9316; 提供一个完整的、独立的分布式锁服务，Chubby对于上层应用程序的侵入性特别低，对于Master选举同时将Master信息等级并广播的场景，应用程序只需要向Chubby请求一个锁，并且在获得锁之后向相应的锁文件写入Master信息即可，其余的客户端就可以通过读取这个锁文件来获取Master信息。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9317; 提供粗粒度的锁服务，Chubby针对的应用场景是客户端获得锁之后会进行长时间持有（数小时或数天），而非用于短暂获取锁的场景。当锁服务短暂失效时（服务器宕机），Chubby需要保持所有锁的持有状态，以避免持有锁的客户端出现问题。而细粒度锁通常设计为为锁服务一旦失效就释放所有锁，因为其持有时间很短，所以其放弃锁带来的代价较小。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9318; 高可用、高可靠，对于一个由5太机器组成的集群而言，只要保证3台正常运行的机器，整个集群对外服务就能保持可用，另外，由于Chubby支持通过小文件读写服务的方式来进行Master选举结果的发布与订阅，因此在Chubby的实际应用中，必须能够支撑成百上千个Chubby客户端对同一个文件进行监控和读取。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9319; 提供时间通知机制，Chubby客户端需要实时地感知到Master的变化情况，这可以通过让你客户端反复轮询来实现，但是在客户端规模不断增大的情况下，客户端主动轮询的实时性效果并不理想，且对服务器性能和网络带宽压力都非常大，因此，Chubby需要有能力将服务端的数据变化情况以时间的形式通知到所有订阅的客户端。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　<span style="line-height: 1.8; color: #ff0000;">2.2 技术架构</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Chubby的整个系统结构主要由服务端和客户端两部分组成，客户端通过RPC调用和服务端进行通信，如下图所示。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161027204317562-1654005264.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　一个典型的Chubby集群（Chubby Cell），通常由5台服务器组成，这些副本服务器采用Paxos协议，通过投票的方式来选举产生一个获得过半投票的服务器作为Master，一旦成为Master，Chubby就会保证在一段时间内不会再有其他服务器成为Master，这段时期称为Master租期（Master Lease），在运行过程中，Master服务器会通过不断续租的方式来延长Master租期，而如果Master服务器出现故障，那么余下的服务器会进行新一轮的Master选举，最终产生新的Master服务器，开始新的Master租期。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　集群中的每个服务器都维护着一份服务端数据库的副本，但在实际运行过程中，<span style="line-height: 1.8; color: #ff0000;">只有Master服务器才能对数据库进行写操作，而其他服务器都是使用Paxos协议从Master服务器上同步数据库数据的更新。</span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;">　　<span style="line-height: 1.8; color: #000000;">Chubby客户端通过向记录有Chubby服务端机器列表的DNS来请求获取所有的Chubby服务器列表，然后逐一发起请求询问该服务器是否是Master，在这个询问过程中，那些非Master的服务器，则会将当前Master所在的服务器标志反馈给客户端，这样客户端就能很快速的定位到Master服务器了。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　只要该Master服务器正常运行，那么客户端就会将所有的请求都发送到该Master服务器上，针对写请求，Master会采用一致性协议将其广播给集群中所有的副本服务器，并且在过半的服</span></span><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">务器接受了该写请求后，再响应给客户端正确的应答，而对于读请求，则不需要在集群内部进行广播处理，直接由Master服务器单独处理即可。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　若该Master服务器发生故障，那么集群中的其他服务器会在Master租期到期后，重新开启新的一轮Master选举，通常一次Master选举大概花费几秒钟的时间，而如果其他副本服务器崩溃，那么整个集群继续工作，该崩溃的服务器会在恢复之后自动加入到Chubby集群中去，新加入的服务器首先需要同步Chubby最新的数据库数据，完成数据库同步之后，新的服务器就可以加入到正常的Paxos运作流程中与其他服务器副本一起协同工作。若崩溃后几小时后仍无法恢复工作，那么需要加入新的机器，同时更新DNS列表（新IP代替旧IP），Master服务器会周期性地轮询DNS列表，因此会很快感知服务器地址的变更，然后Master就会将集群数据库中的地址列表做相应的变更，集群内部的其他副本服务器通过复制方式就可以获取到最新的服务器地址列表了。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.3 目录与文件</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　</span></span>Chubby对外提供了一套与Unix文件系统非常相近但是更简单的访问接口。Chubby的数据结构可以看作是一个由文件和目录组成的树，其中每一个节点都可以表示为一个使用斜杠分割的字符串，典型的节点路径表示如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　/ls/foo/wombat/pouch</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　其中，ls是所有Chubby节点所共有的前缀，代表着锁服务，是Lock Service的缩写；foo则指定了Chubby集群的名字，从DNS可以查询到由一个或多个服务器组成该Chubby集群；剩余部分的路径/wombat/pouch则是一个真正包含业务含义的节点名字，由Chubby服务器内部解析并定位到数据节点。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Chubby的命名空间，包括文件和目录，我们称之为节点（Nodes，我们以数据节点来泛指Chubby的文件或目录）。在同一个Chubby集群数据库中，每一个节点都是全局唯一的。和Unix系统一样，每个目录都可以包含一系列的子文件和子目录列表，而每个文件中则会包含文件内容。Chubby没有符号链接和硬连接的概念。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　Chubby上的每个数据节点都分为<span style="line-height: 1.8; color: #ff0000;">持久节点</span>和<span style="line-height: 1.8; color: #ff0000;">临时节点</span>两大类，其中持久节点需要显式地调用接口API来进行删除，<strong>而临时节点则会在其对应的客户端会话失效后被自动删除</strong>。也就是说，临时节点的生命周期和客户端会话绑定，如果该临时节点对应的文件没有被任何客户端打开的话，那么它就会被删除掉。因此，临时节点通常可以用来进行客户端会话有效性的判断依据。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Chubby的每个数据节点都包含了少量的元数据信息，其中包括用于权限控制的访问控制列表（ACL）信息，同时每个节点的元数据还包括4个单调递增的64位标号：</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9312;&nbsp;<strong>实例标号</strong>，用于标识创建该数据节点的顺序，节点的创建顺序不同，其实例编号也不相同，可以通过实例编号来识别两个名字相同的数据节点是否是同一个数据节点，因为创建时间晚的数据节点，其实例编号必定大于任意先前创建的同名节点。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9313;&nbsp;<strong>文件内容编号（针对文件）</strong>，用于标识文件内容的变化情况，该编号会在文件内容被写入时增加。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9314;&nbsp;<strong>锁编号</strong>，用于标识节点锁状态的变更情况，该编号会在节点锁从自由状态转化到被持有状态时增加。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9315;<strong>&nbsp;ACL编号</strong>，用于标识节点的ACL信息变更情况，该编号会在节点的ACL配置信息被写入时增加。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　<span style="line-height: 1.8; color: #ff0000;">　2.4 锁和锁序列器</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　分布式环境中锁机制十分复杂，消息的延迟或是乱序都有可能引起锁的失效，如客户端C1获得互斥锁L后发出请求R，但请求R迟迟没有到达服务端（网络延迟或消息重发等），这时应用程序会认为该客户端进程已经失败，于是为另一个客户端C2分配锁L，然后在发送请求R，并成功应用到了服务器上。然而，之前的请求R经过一波三折后也到达了服务器端，此时，它可能不瘦任何锁控制的情况下被服务端处理，而覆盖了客户端C2的操作，也是导致了数据不一致问题。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　在Chubby中，任意一个数据节点均可被当做一个读写锁来使用：一种是单个客户端排他（写）模式持有这个锁，另一种则是任意数目的客户端以共享（读）模式持有这个锁，Chubby放弃了严格的强制锁，客户端可以在没有获取任何锁的情况下访问Chubby的文件。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Chubby采用了<strong>锁延迟</strong>和<strong>锁序列器</strong>两种策略来解决上述由于消息延迟和重排序引起的分布式锁问题，对于锁延迟而言，如果一个客户端以正常的方式主动释放了一个锁，那么Chubby服务端将会允许其他客户端能够立即获取到该锁；如果锁是以异常情况被释放的话，那么Chubby服务器会为该锁保留一定的时间，这称之为锁延迟，这段时间内，其他客户端无法获取该锁，其可以很好的防止一些客户端由于网络闪断的原因而与服务器暂时断开的场景。对于锁序列器而言，其需要Chubby的上层应用配合在代码中加入相应的修改逻辑，任何时候，锁的持有者都可以向Chubby请求一个<strong>锁序列器</strong>，其包括<strong>锁的名字、锁模式（排他或共享）、锁序号</strong>，当客户端应用程序在进行一些需要锁机制保护的操作时，可以将该锁序列器一并发送给服务端，服务端收到该请求后，会首先检测该序列器是否有效，以及检查客户端是否处于恰当的锁模式，如果没有通过检查，那么服务端就会拒绝该客户端的请求。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.5 事件通知机制</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　为了避免大量客户端轮询Chubby服务端状态所带来的压力，Chubby提供了事件通知机制，客户端可以向服务端注册事件通知，当触发这些事件时，服务端就会向客户端发送对应的时间通知，消息通知都是通过异步的方式发送给客户端的。常见的事件如下</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9312; 文件内容变更 &#9313; 节点删除 &#9314; 子节点新增、删除 &#9315; Master服务器转移</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.6 缓存</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　其是为了减少客户端与服务端之间的频繁的读请求对服务端的压力设计的，会在客户端对文件内容和元数据信息进行缓存，虽然缓存提高了系统的整体性能，但是其也带来了一定复杂性，如如何保证缓存的一致性。其通过租期机制来保证缓存的一致性。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　缓存的生命周期和Master租期机制紧密相关，Master会维护每个客户端的数据缓存情况，并通过向客户端发送过期信息的方式来保证客户端数据的一致性，在此机制下，Chubby能够保证客户端要么能够从缓存中访问到一致的数据，要么访问出错，而一定不会访问到不一致的数据。具体的讲，每个客户端的缓存都有一个租期，一旦该租期到期，客户端就需要向服务端续订租期以继续维持缓存的有效性，当文件数据或元数据被修改时，Chubby服务端首先会阻塞该修改操作，然后由Master向所有可能缓存了该数据的客户端发送缓存过期信号，使其缓存失效，等到Master在接收到所有相关客户端针对该过期信号的应答（客户端明确要求更新缓存或客户端允许缓存租期过期）后，再进行之前的修改操作。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.7 会话</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Chubby客户端和服务端之间通过创建一个TCP连接来进行所有的网络通信操作，这称之为会话，会话存在生命周期，存在超时时间，在超时时间内，客户端和服务端之间可以通过心跳检测来保持会话的活性，以使会话周期得到延续，这个过程称为KeepAlive（会话激活），如果能够成功地通过KeepAlive过程将Chubby会话一直延续下去，那么客户端创建的句柄、锁、缓存数据等将仍然有效。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.8 KeepAlive请求</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Master服务端在收到客户端的KeepAlive请求时，首先会将该请求阻塞住，并等到该客户端的当前会话租期即将过期时，才为其续租该客户端的会话租期，之后再向客户端响应这个KeepAlive请求，并同时将最新的会话租期超时时间反馈给客户端，在正常情况下，每个客户端总是会有一个KeepAlive请求阻塞在Master服务器上。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.9 会话超时</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　客户端也会维持一个和Master端近似相同（由于KeepAlive响应在网络传输过程中会花费一定的时间、Master服务端和客户端存在时钟不一致的现象）的会话租期。客户端在运行过程中，按照本地的会话租期超时时间，检测到其会话租期已经过期却尚未收到Master的KeepAlive响应，此时，它将无法确定Master服务端是否已经中止了当前会话，这个时候客户端处于危险状态，此时，客户端会清空其本地缓存并将其标记为不可用，同时客户端还会等待一个被称作宽限期的时间周期，默认为45秒，若在宽限期到期前，客户端与服务端之间成功地进行了KeepAlive，那么客户端就会再次开启本地缓存，否则，客户端就会认为当前会话已经过期了，从而终止本次会话。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　在客户端进入危险状态时，客户端会通过一个&#8220;jeopardy&#8221;事件来通知上层应用程序，如果恢复正常，客户端同样会以一个&#8220;safe&#8221;事件来通知应用程序可以继续正常运行，但如果客户端最终没能从危险状态中恢复过来，那么客户端会以一个&#8220;expired&#8221;事件来通知应用程序当前Chubby会话已经超时。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　<span style="line-height: 1.8; color: #ff0000;">2.10 Master故障恢复</span></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Master服务端会运行着会话租期计时器，用来管理所有的会话的生命周期，如果Master在运行过程中出现故障，那么该计时器就会停止，直到新的Master选举产生后，计时器才会继续计时，即从旧的Master崩溃到新的Master选举产生所花费的时间将不计入会话超时的计算中，这就等价于延长了客户端的会话租期，如果Master在短时间内就选举产生了，那么客户端就可以在本地会话租期过期前与其创建连接，而如果Master的选举花费了较长的时间，就会导致客户端只能清空本地的缓存而进入宽限期进行等待，由于宽限期的存在，使得会话能够很好地在服务端Master转化的过程中得到维持，整个Master的故障恢复过程中服务端和客户端的交互情况如下</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161028090848437-843934438.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　如上图所示，一开始在旧的Master服务器上维持了一个会话租期lease M1，在客户端上维持对应的lease C1，同时客户端的KeepAlive请求1一直被Master服务器阻塞着。一段时间后，Master向客户端反馈了KeepAlive响应2，同时开始了新的会话租期lease M2，而客户端在接收到该KeepAlive响应后，立即发送了新的KeepAlive请求3，并同时也开始了新的会话租期lease C2。至此，客户端和服务端的交互都是正常的，随后，Master发生了故障，从而无法反馈客户端的KeepAlive请求3，在此过程中，客户端检测到会话租期lease C2已经过期，它会清空本地缓存并进入宽限期，这段时间内，客户端无法确定Master上的会话周期是否也已经过期，因此它不会销毁它的本地会话，而是将所有应用程序对它的API调用都阻塞住，以避免出现数据不一致的现象。同时，在客户端宽限期开始时，客户端会向上层应用程序发送一个&#8220;jeopardy&#8221;事件，一段时间后，服务器开始选举产生新的Master，并为该客户端初始化了新的会话租期lease M3，当客户端向新的Master发送KeepAlive请求4时，Master检测到该客户端的Master周期号已经过期，因此会在KeepAlive响应5中拒绝这个客户端请求，并将最新的Master周期号发送给客户端，之后，客户端会携带上最新的Master周期号，再次发送KeepAlive请求6给Master，最终，整个客户端和服务端之间的会话就会再次回复正常。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　在Master转化的这段时间内，只要客户端的宽限足够长，那么客户端应用程序就可以在没有任何察觉的情况下，实现Chubby的故障恢复，但如果客户端的宽限期设置得比较短，那么Chubby客户端就会丢弃当前会话，并将这个异常情况通知给上层应用程序。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　一旦客户端与新的Master建立上连接之后，客户端和Master之间会通过互相配合来实现对故障的平滑恢复，新的Master会设法将上一个Master服务器的内存状态构建出来，同时，由于本地数据库记录了每个客户端的会话信息，以及持有的锁和临时文件等信息，因此Chubby会通过读取本地磁盘上的数据来恢复一部分状态。一个新的Master服务器选举之后，会进行如下处理。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9312; 确定Master周期。Master周期用来唯一标识一个Chubby集群的Master统治轮次，以便区分不同的Master，只要发生Master重新选举，就一定会产生新的Master周期，即使选举前后Master是同一台机器。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9313; 新的Master能够对客户端的Master寻址请求进行响应，但是不会立即开始处理客户端会话相关的请求操作。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9314; Master根据本地数据库中存储的会话和锁信息来构建服务器的内存状态。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9315; 至此，Master已经能够处理客户端的KeepAlive请求，但仍然无法处理其他会话相关的操作。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9316; Master会发送一个Master故障切换事件给每一个会话，客户端接收到这个事件后，会清空它的本地缓存，并警告上层应用程序可能已经丢失了别的事件，之后再向Master反馈应答。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9317; 此时，Master会一直等待客户端的应答，直到每一个会话都应答了这个切换事件。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9318; Master接收了所有客户端的应答之后，就能够开始处理所有的请求操作。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9319;若客户端使用了一个故障切换之间创建的句柄，Master会重新为其创建这个句柄的内存对象，并执行调用，如果该句柄在之前的Master周期中就已经被关闭了，那么它就不能在这个Master周期内再次被重建了，这一机制就确保了由于网络原因使得Master接收到那些延迟或重发的网络数据包也不会错误的重建一个已经关闭的句柄。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">三、Paxos协议实现</span></span></strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Chubby服务端的基本架构大致分为三层</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9312; 最底层是容错日志系统（Fault-Tolerant Log），通过Paxos算法能够保证集群所有机器上的日志完全一致，同时具备较好的容错性。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9313; 日志层之上是Key-Value类型的容错数据库（Fault-Tolerant DB），其通过下层的日志来保证一致性和容错性。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　&#9314; 存储层之上的就是Chubby对外提供的分布式锁服务和小文件存储服务。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;"><img src="http://images2015.cnblogs.com/blog/616953/201610/616953-20161028100452109-903947609.png" alt="" style="border: 0px; max-width: 900px; display: block; margin-left: auto; margin-right: auto;" /></span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><span style="line-height: 1.8; color: #ff0000;"><span style="line-height: 1.8; color: #000000;">　　Paxos算法用于保证集群内各个副本节点的日志能够保持一致，Chubby事务日志（Transaction Log）中的每一个Value对应Paxos算法中的一个Instance（对应Proposer），由于Chubby需要对外提供不断的服务，因此事务日志会无限增长，于是在整个Chubby运行过程中，会存在多个Paxos Instance，同时，Chubby会为每个Paxos Instance都按序分配一个全局唯一的Instance编号，并将其顺序写入到事务日志中去。</span></span></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在多Paxos Instance的模式下，为了提升算法执行的性能，就必须选举出一个副本作为Paxos算法的主节点，以避免因为每一个Paxos Instance都提出提议而陷入多个Paxos Round并存的情况，同时，Paxos会保证在Master重启或出现故障而进行切换的时候，允许出现短暂的多个Master共存却不影响副本之间的一致性。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在Paxos中，每一个Paxos Instance都需要进行一轮或多轮的Prepare-&gt;Promise-&gt;Propose-&gt;Accept这样完整的二阶段请求过程来完成对一个提议值的选定，为了保证正确性的前提下尽可能地提高算法运行性能，可以让多个Instance共用一套序号分配机制，并将Prepare-&gt;Promise合并为一个阶段。具体做法如下：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9312; 当某个副本节点通过选举成为Master后，就会使用新分配的编号N来广播一个Prepare消息，该Prepare消息会被所有未达成一致的Instance和目前还未开始的Instance共用。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9313; 当Acceptor接收到Prepare消息后，必须对多个Instance同时做出回应，这通常可以通过将反馈信息封装在一个数据包中来实现，假设最多允许K个Instance同时进行提议值的选定，那么：</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　-当前之多存在K个未达成一致的Instance，将这些未决的Instance各自最后接受的提议值封装进一个数据包，并作为Promise消息返回。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　-同时，判断N是否大于当前Acceptor的highestPromisedNum值（当前已经接受的最大的提议编号值），如果大于，那么就标记这些未决Instance和所有未来的Instance的highestPromisedNum的值为N，这样，这些未决Instance和所有未来Instance都不能再接受任何编号小于N的提议。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　&#9314; Master对所有未决Instance和所有未来Instance分别执行Propose-&gt;Accept阶段的处理，如果Master能够一直稳定运行的话，那么在接下来的算法运行过程中，就不再需要进行Prepare-&gt;Promise处理了。但是，一旦Master发现Acceptor返回了一个Reject消息，说明集群中存在另一个Master并且试图使用更大的提议编号发送了Prepare消息，此时，当前Master就需要重新分配新的提议编号并再次进行Prepare-&gt;Promise阶段的处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　利用上述改进的Paxos算法，在Master稳定运行的情况下，只需要使用同一个编号来依次执行每一个Instance的Promise-&gt;Accept阶段处理。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在集群的某台机器在宕机重启后，为了恢复机器的状态，最简单的办法就是将已记录的所有日志重新执行一遍，但是如果机器上的日志已经很多，则耗时长，因此需要定期对状态机数据做一个数据快照并将其存入磁盘，然后就可以将数据快照点之前的事务日志清除。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　在恢复过程中，会出现磁盘未损坏和损坏两种情况，若未损坏，则通过磁盘上保存的数据库快照和事务日志就可以恢复到之前的某个时间点的状态，之后再向集群中其他正常运行的副本节点索取宕机后缺失的部分数据变更记录即可；若磁盘损坏，就笑从其他副本节点索取全部的状态数据。</p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;"><strong>四、总结</strong></p><p style="line-height: 1.8; margin-top: 10px; margin-bottom: 10px;">　　本部分详细介绍了Chubby系统，并且对Paxos在Chubby中的使用也做了较为详细的介绍，后面我们将会介绍开源分布式系统Zookeeper与Paxos的联系。本部分理论知识较强（文字较多），需要慢慢研究才能理解其中的含义，谢谢各位园友的观看~</p></div><div id="MySignature" style="color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; line-height: 25.2px; background-color: #ffffff;"><div style="margin-top: 100px;"></div></div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432176.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 15:38 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432176.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>可靠分布式系统基础：paxos的直观解释</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432175.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 07:20:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432175.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432175.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432175.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432175.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432175.html</trackback:ping><description><![CDATA[<a href="/Files/jinfeng_wang/paxos.pdf">/Files/jinfeng_wang/paxos.pdf<br /><br /></a><div>http://drmingdrmer.github.io/pdf/paxos-slide/paxos.html<br /><br /></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432175.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 15:20 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432175.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>分布式理论之一：Paxos算法的通俗理解</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432173.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 06:43:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432173.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432173.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432173.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432173.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432173.html</trackback:ping><description><![CDATA[<div>http://www.mamicode.com/info-detail-198650.html<br /><br /><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">维基的简介：Paxos算法是莱斯利&#183;兰伯特（Leslie Lamport，就是 LaTeX 中的"La"，此人现在在微软研究院）于1990年提出的一种基于消息传递且具有高度容错特性的一致性算法。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">Paxos算法目前在Google的Chubby、MegaStore、Spanner等系统中得到了应用，Hadoop中的ZooKeeper也使用了Paxos算法，在上面的各个系统中，使用的算法与Lamport提出的原始Paxos并不完全一样，这个以后再慢慢分析。本博文的目的是，如何让一个小白在半个小时之内理解Paxos算法的思想。小白可能对数学不感兴趣，对分布式的复杂理论不熟悉，只是一个入门级程序员。之所以想写这篇博文，是因为自己看了网上很多介绍Paxos算法的文章，以及博客，<span style="padding: 0px;">包括Lamport的论文，</span>感觉还是难以理解，大多过于复杂，本人一直认为，复杂高深的理论背后一定基于一些通用的规律，而这些通用的规律在生活中其实我们早就遇到过，甚至用过。所以，我们先忽略Paxos算法本身，从生活中的小事开始谈起。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">&nbsp;假如有一群驴友要决定中秋节去旅游，这群驴友分布在全国各地，假定一共25个人，分别在不同的省，要决定到底去拉萨、昆明、三亚等等哪个地点（会合时间中秋节已经定了，此时需要决定旅游地）。最直接的方式当然就是建一个QQ群，大家都在里面投票，按照少数服从多数的原则。这种方式类似于&#8220;共享内存&#8221;实现的一致性，实现起来简单，但Paxos算法不是这种场景，因为Paxos算法认为这种方式有一个很大的问题，就是QQ服务器挂掉怎么办？Paxos的原则是容错性一定要很强。所以，Paxos的场景类似于这25个人相互之间只能发短信，为了这件事情能够达成一致，这25个人找了另外的5个人（当然这5个人可以从25个人中选，这里为了描述方便，就单拿出另外5个人），比如北京、上海、广州、深圳、成都的5个人，25个人都给他们发短信，告诉自己倾向的旅游地。这5个人相互之间可以并不通信，只接受25个人发过来的短信。这25个人我们称为驴友，那5个人称为队长。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">先来看驴友的逻辑。驴友可以给任意5个队长都发短信，发短信的过程分为两个步骤：</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">第一步（申请阶段）：询问5个队长，试图与队长沟通旅游地。因为每个队长一直会收到不同驴友的短信，不能跟多个驴友一起沟通，在任何时刻只能跟一个驴友沟通，按照什么原则才能做到公平公正公开呢？这些短信都带有发送时间，队长采用的原则是同意与短信发送时间最新的驴友沟通，如果出现了更新的短信，则与<span style="padding: 0px;">短信</span>更新的驴友沟通。总之，作为一个有话语权的人，只有时刻保持倾听最新的呼声，才能做出最明智的选择。在驴友发出短信后，等着队长回复。某些队长可能会回复说，你这条短信太老了，我不与你沟通；有些队长则可能返回说，你的短信是我收到的最新的，我同意跟你沟通。对于后面这些队长，还得返回自己决定的旅游地。关于队长是怎么决定旅游地的，后面再说。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">对于驴友来说，第一步必须至少有半数以上队长都同意沟通了，才能进入下一步。否则，你连沟通的资格都没有，一直在那儿狂发吧。你发的短信更新，你获得沟通权的可能性才更大。。。。。。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">因为至少有半数以上队长（也就是3个队长以上）同意，你才能与队长们进行实质性的沟通，也就是进入第二步；而队长在任何时候只能跟1个驴友沟通，所以，在任何时候，不可能出现两个驴友都达到了这个状态。。。当然，你可以通过狂发短信把沟通权抢了。。。。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">对于获得沟通权的那个驴友（称为A），那些队长会给他发送他们自己决定的旅游地（也可能都还没有决定）。可以看出，各个队长是自己决定旅游地的，队长之间无需沟通。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">第二步（沟通阶段）：这个幸运的驴友收到了队长们给他发的旅游地，可能有几种情况：</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">第一种情况：跟A沟通的队长们（不一定是全部5个队长，但是半数以上）全部都还没有决定到底去那儿旅游，此时驴友A心花怒放，给这些队长发第二条短信，告诉他们自己希望的旅游地（比如马尔代夫）；</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">可能会收到两种结果：一是半数以上队长都同意了，于是表明A建议的马尔代夫被半数以上<span style="padding: 0px;">队长</span>都同意了，整个决定过程完毕了，其它驴友迟早会知道这个消息的，A先去收拾东西准备去马尔代夫；除此之外，表明失败。可能队长出故障了，比如某个队长在跟女朋友打电话等等，也可能被其它驴友抢占沟通权了（因为队长喜新厌旧嘛，只有要更新的驴友给自己发短信，自己就与新人沟通，A的建议队长不同意）等等。不管怎么说，苦逼的A还得重新从第一步开始，重新给队长们发短信申请。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">第二种情况：至少有一个队长已经决定旅游地了，A可能会收到来自不同队长决定的多个旅游地，这些旅游地是不同队长跟不同驴友在不同时间上做出的决定，那么，A会先看一下，是不是有的旅游地已经被半数以上队长同意了（比如3个队长都同意去三亚，1个同意去昆明，另外一个没搭理A），如果出现了这种情况，那就别扯了，说明整个决定过程已经达成一致了，收拾收拾准备去三亚吧，结束了；如果都没有达到半数以上（比如1个同意去昆明，1个同意去三亚，2个同意去拉萨，1个没搭理我），A作为一个高素质驴友，也不按照自己的意愿乱来了（Paxos的关键所在，后者认同前者，否则整个决定过程永无止境），虽然自己原来可能想去马尔代夫等等。就给队长们发第二条短信的时候，告诉他们自己希望的旅游地，就是自己收到的那堆旅游地中最新决定的那个。（比如，去昆明那个是北京那个队长前1分钟决定的，去三亚的决定是上海那个队长1个小时之前做出来的，于是顶昆明）。驴友A的想法是，既然有队长已经做决定了，那我就干脆顶最新那个决定。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">从上面的逻辑可以看出，一旦某个时刻有半数以上队长同意了某个地点比如昆明，紧跟着后面的驴友B继续发短信时，如果获得沟通权，因为半数以上队长都同意与B沟通了，B必然会收到至少一个队长给他发的<span style="padding: 0px;">昆明</span>这个结果，B于是会顶这个最新地点，不会更改，因为后面的驴友都会顶昆明，因此同意昆明的队长越来越多，最终必然达成一致。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">看完了驴友的逻辑，那么队长的逻辑是什么呢？</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">队长的逻辑比较简单。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">在申请阶段，队长只会选择与最新发申请短信的驴友沟通，队长知道自己接收到最新短信的时间，对于更老的短信，队长不会搭理；队长同意沟通了的话，会把自己决定的旅游地（或者还没决定这一信息）发给驴友。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">在沟通阶段，驴友C会把自己希望的旅游地发过来（同时会附加上自己申请短信的时间，比如3分钟前），所以队长要检查一下，如果这个时间（3分钟前）确实是当前自己最新接收到申请短信的时间（说明这段时间没有驴友要跟自己沟通），那么，队长就同意驴友C的这个旅游地了（比如昆明，哪怕自己1个小时前已经做过去三亚的决定，谁让C更新呢，于是更新为昆明）；如果不是最新的，说明这3分钟内又有其它驴友D跟自己申请了，因为自己是个喜新厌旧的家伙，同意与D沟通了，所以驴友C的决定自己不会同意，等着D一会儿要发过来的决定吧。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;">&nbsp;</p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">Paxos的基本思想大致就是上面的过程。让我们来对应一下。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">Paxos主要用于保证分布式存储中副本（或者状态）的一致性。副本要保持一致，那么，所有副本的更新序列就要保持一致。因为数据的增删改查操作一般都存在多个客户端并发操作，到底哪个客户端先做，哪个客户端后做，这就是更新顺序。如果不是分布式，那么可以利用加锁的方法，谁先申请到锁，谁就先操作。但是在分布式条件下，存在多个副本，如果依赖申请锁+副本同步更新完毕再释放锁，那么需要有分配锁的这么一个节点（如果是多个锁分配节点，那么又出现分布式锁管理的需求，把锁给哪一个客户端又成为一个难点），这个节点又成为单点，岂不是可靠性不行了，失去了分布式多副本的意义，同时性能也很差，另外，还会出现死锁等情况。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">所以，说来说去，只有解决分布式条件下的一致性问题，似乎才能解决本质问题。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">如上面的例子，Paxos解决这一问题利用的是选举，少数服从多数的思想，只要2N+1个节点中，有N个以上同意了某个决定，则认为系统达到了一致，并且按照Paxos原则，最终理论上也达到了一致，不会再改变。这样的话，客户端不必与所有服务器通信，选择与大部分通信即可；也无需服务器都全部处于工作状态，有一些服务器挂掉，只有保证半数以上存活着，整个过程也能持续下去，容错性相当好。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">Paxos中的Acceptor就相当于上面的队长，Proposer就相当于上面的驴友，epoch编号就相当于例子中申请短信的发送时间。关于Paxos的正式描述已经很多了，这里就不复述了，关于Paxos正确性的证明，因为比较复杂，以后有时间再分析。另外，Paxos最消耗时间的地方就在于需要半数以上同意沟通了才能进入第二步，试想一下，一开始，所有驴友就给队长狂发短信，每个队长收到的最新短信的是不同驴友，这样，就难以达到半数以上都同意与某个驴友沟通的状态，为了减小这个时间，Paxos还有Fast Paxos的改进等等，有空再分析。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">倒是有一些问题可以思考一下：在Paxos之前，或者说除了Chubby，ZooKeeper这些系统，其它分布式系统同样面临这样的一致性问题，比如<span style="padding: 0px;">HDFS</span>、分布式数据库、<span style="padding: 0px;">Amazon的Dynamo</span>等等，解决思路又不同，有空再进行对比分析。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">最后谈谈一致性这个名词。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">关于Paxos说的一致性，个人理解是指冗余副本（或状态等，但都是因为存在冗余）的一致性。这与<span style="padding: 0px;">关系型数据库中ACID的一致性说的不是一个东西。<span style="padding: 0px;"><span style="padding: 0px;">在关系数据库里，可以连副本都没有，何谈副本的一致性？按照经典定义，</span></span>ACID中的C指的是在一个事务中，事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。那么，什么又是一致性状态呢，这跟业务约束有关系，比如经典的转账事务，事务处理完毕后，不能出现一个账户钱被扣了，另一个<span style="padding: 0px;"><span style="padding: 0px;">账户的</span></span>钱没有增加的情况，如果两者加起来的钱还是等于转账前的钱，那么就是一致性状态。</span></span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">从很多博文来看，对这两种一致性往往混淆起来。另外，CAP原则里面所说的一致性，个人认为是指副本一致性，与Paxos里面的一致性接近。都是处理&#8220;因为冗余数据的存在而需要保证多个副本保持一致&#8221;的问题，NoSQL放弃的强一致性也是指副本一致性，最终一致性也是指副本达到完全相同存在一定延时。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">当然，如果数据库本身是分布式的，且存在冗余副本，则除了解决事务在业务逻辑上的一致性问题外，同时需要解决副本一致性问题，此时可以利用Paxos协议。<span style="padding: 0px;">但解决了副本一致性问题，还不能完全解决业务逻辑一致性；</span>如果是分布式数据库，但并不存在副本的情况，<span style="padding: 0px;">事务的一致性需要根据业务约束进行设计。</span></span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">另外，谈到Paxos时，还会涉及到拜占庭将军问题，它指的是在存在消息丢失的不可靠信道上试图通过消息传递的方式达到一致性是不可能的。Paxos本身就是利用消息传递方式解决一致性问题的，所以它的假定是信道必须可靠，这里的可靠，主要指消息不会被篡改。消息丢失是允许的。</span></p><p style="font-family: Arial, Helvetica, sans-serif; padding: 0px; color: #3f3f3f; line-height: 30px; background-color: #ffffff;"><span style="padding: 0px; font-size: 18px;">关于一致性，关于事务的ACID，CAP，NoSQL等等问题，以后再详细分析。</span></p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432173.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 14:43 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432173.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一篇文章带你了解Paxos算法</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432172.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 06:27:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432172.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432172.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432172.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432172.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432172.html</trackback:ping><description><![CDATA[<div>http://dockone.io/article/640<br /><br /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">【编者的话】本文是Quora上关于Paxos算法的回答，两位答者分别从不同的角度描述Paxos算法。Vineet Gupta的回答细致入微，更偏向理论。Russell Cohen用具体的例子讲解Paxos算法，相辅相成。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>Vineet Gupta的回答</h3><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">有很多关于一致性（consensus）问题的解决方案，而这些解决方案中，我认为Paxos相对来说很好理解。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">『达成一致性』最简单的例子就是结婚誓词：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">&#8220;你愿意.......&#8221;（男：）&#8220;我愿意！&#8221;（女：）&#8220;我愿意！&#8221;</li><li style="box-sizing: border-box; line-height: 25px;">&#8220;现在我宣布你们...&#8221;</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">现在假设婚姻并非只有两个人，就像罗伯特&#183;乔丹的《时间之轮》中的</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><a href="https://en.wikipedia.org/wiki/The_Wheel_of_Time" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">艾尔</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">民俗：一位或者更多的艾尔女人可以成为</span><a href="http://wot.wikia.com/wiki/First-sisters#first-sister" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">first-sisters</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">，而男人要么把她们全部娶回家要么一个也不娶。在艾尔人眼中，婚姻誓词也许是下面这样的：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">&#8220;你愿意...?&#8221;（男：）&#8220;我愿意！&#8221;（女1：）&#8220;我愿意！&#8221;（女2：）&#8220;我愿意！&#8221;...</li><li style="box-sizing: border-box; line-height: 25px;">&#8220;现在我宣布你们...&#8221;</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">如果任何一个将会成为配偶的艾尔人不回应&#8220;我愿意&#8221;，那这场婚礼将无法继续。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">计算机科学家把上面的现象称为</span><a href="https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">二阶段提交</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>二阶段提交（2PC）</h3><ol style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;"><span style="box-sizing: border-box; font-weight: 700; color: #333333;">表决阶段</span>。在表决阶段，协调者将通知事务参与者准备提交或取消事务，然后进入表决过程。在表决过程中，参与者将告知协调者自己的决策：同意（事务参与者本地作业执行成功）或取消（本地作业执行故障）。</li><li style="box-sizing: border-box; line-height: 25px;"><span style="box-sizing: border-box; font-weight: 700; color: #333333;">提交阶段</span>。在该阶段，协调者将基于第一个阶段的投票结果进行决策：提交或取消。当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务，否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行响应的操作。</li></ol><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">要注意的是票只能投给（协调器）建议的值，每个节点只能说是或否。节点不能再提出一个可供选择的值。如果一个节点想提出自己的 值，它需要开始它自己的2PC。算法很简单，由节点来决定某一个节点提出的值。它也不是非常低效的，对于N个节点，将交换3N条消息。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">但是如果一个节点崩溃了会发生什么？例如，假设在阶段一协调器将提议的值发送给部分节点（并非全部）后，然后协调器就挂掉了。（这是会发生什么？）&nbsp;</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">现在一些节点已经开始了2PC循环，而另一些节点没有意识到2PC循环已经发生。那些已经开始2PC循环的节点被阻塞在等待下一个阶段上。</li><li style="box-sizing: border-box; line-height: 25px;">在我们的场景中，已经投票的资源管理单元也可能不得不锁定一些资源，这样资源管理不会因为等待协调器恢复并启动阶段二而耗尽时间。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">如果阶段二中的协调器未能把所有的提交信息发送给全部节点而只是部分节点后就崩溃了，相似的问题依然存在。我们可以让另一个节点接管协调器职责观察时间超时来解决部分存在的问题。这个节点会和其它节点取得联系并获得它们的投票情况（要求它们必须投票）以及推动事务完成，但这一过程中进一步参与者可能发生故障，同时事务可能永远不会恢复。我们的底线-在节点故障的情况下2PC无法可靠地操作。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>三阶段提交（3PC）</h3><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2PC的关键问题是，万一协调器挂掉了，没有节点具备足够的信息来完成整个协议。这一点可以通过添加额外的步骤来解决：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">1. 阶段1和（2PC）相同-协调器给所有的节点发送一个值。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2. 阶段2-新的步骤-一旦从上一步中所有节点都接收到&#8220;是&#8221;，协调器发送出&#8220;准备提交&#8221;消息。我们期望的是节点能够执行可以撤消的工作，而且没有什么是不能撤消的。每一个节点向协调器确认它已经接收到&#8220;准备提交&#8221;消息了。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 阶段3-类似2PC中阶段二-如果协调器从所有节点接收到关于&#8220;准备提交&#8221;的确认信息，它就继续工作，将投票的结果发送给所有的节点要求他们提交。但是，如果所有节点都不确认，协调器会终止事务。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">现在如果协调器在任何时刻崩溃，任何参与者都可以接管协调器的角色并查询来自其它节点的状态。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">如果任何资源管理向恢复节点报告说它并没有接收到&#8220;准备提交&#8221;的信息，恢复节点便知道事务没有提交给任何资源管理。现在要么事务被悲观地终止，要么协议实例重新运行。</li><li style="box-sizing: border-box; line-height: 25px;">如果有一个管理单元提交事务后崩溃了，我们知道的是，其它每一个资源管理单元可能会收到&#8220;准备提交&#8221;的确认信息，否则协调器不会进入提交阶段。所以协调器是可以进入到最后一个阶段的。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">因此3PC能够很好地工作尽管有节点故障。这是以在N个节点添加一个新的步骤为代价的，它导致了较高的延时。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3PC在网络分区情况下存在不足。假设所有收到&#8220;准备提交&#8221;信息的资源管理都在分区的一侧，其余的都在另一边。现在这会导致每个分区都选择一个恢复节点，这两个恢复节点要么提交事务，要么终止事务。一旦网络分区被移除，系统会得到不一致的状态。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>Paxos-为什么麻烦？</h3><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">先说重要的事-考虑下3PC，我们还需要更好的算法吗？3PC唯一的问题是网络分区，真的吗？首先，我们假设网络分区是唯一的问题（并不是，下面我们会看到）。在网络分区的情况下，正确性真的是值得解决的问题吗？今天，在云计算和互联网规模的服务下，其中的节点有可能在大陆的不同侧或者跨越了大洋，我们确实需要分区容错算法。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">第二点是网络分区并不是唯一的问题。当我们解决了单个节点永久故障的情况时，更一般的情况是，节点崩溃了，接着它重新恢复并且从崩溃的地方工作。这种</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">故障-恢复模式</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">也可以描述一个异步网络模型，这种网络模型中一个节点用来响应消息的时间是没有上限的，因此你不能假设一个节点死了-也许它仅仅是响应缓慢或者是网络缓慢。在这种模型中你不能判断超时。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3PC是</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">故障-停止</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">容错，而不是</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">故障-恢复</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">容错。不幸的是现实生活中需要的是</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">故障-恢复</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">容错，因此我们需要更一般的解决方案。而这正是Paxos的用武之地。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>Paxos-它如何运行?</h3><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos中的基本步骤和2PC很像：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">选择一个节点作为领导者/提议者。</li><li style="box-sizing: border-box; line-height: 25px;">领导者选择一个值并将它发给所有节点（Paxos中被称为接收者），（这个值）封装在<code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; background-color: #f9f2f4;">接收-请求</code>消息中，接收者可以回复拒绝或接受。</li><li style="box-sizing: border-box; line-height: 25px;">一旦多数节点都接受，共识就达成了同时协调器向所有节点广播<code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; background-color: #f9f2f4;">提交</code>信息。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos与2PC主要不同点在于Paxos不要求所有节点都要同意（才会提交事务），只要大部分节点同意就行。这是一个有趣的想法因为在两个独立的多数节点中至少存在一个正常工作的节点。这确保在给定的循环中，如果大部分节点赞同给定的值，任何随后努力提出一个新值的节点将会获得来自其它节点的值而且这个节点也只会赞同那个值（不会再提出自己的值了）。这也意味着Paxos算法不会阻塞即使一半的节点无法回复消息。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">当然也可能发生是领导节点自己故障了。为了解决这个问题，Paoxs在给定的时间点不会只向一个领导节点授权。它允许任何节点（在领导节点故障时）成为领导者并协调事务。这也自然意味着在特定情况下至少存在一个节点能称为领导者。在上述情况下很可能存在两个领导者并且他们发送了不同的值。所以如何达成共识呢？为了在这样的设置下达成共识，Paxos介绍了两种机制：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;"><span style="box-sizing: border-box; font-weight: 700; color: #333333;">给领导者指定顺序</span>。这让每个节点能够区分当前领导者和旧的领导者，它阻止旧领导者（或许刚从旧的故障中恢复）扰乱已经达成的共识。</li><li style="box-sizing: border-box; line-height: 25px;"><span style="box-sizing: border-box; font-weight: 700; color: #333333;">限制领导者对值的选择</span>。一旦就某个值`达成了共识，Paxos强制将来的领导只能选择相同的值确保共识能延续下去。这一点是这样实现的-接收节点发送它赞同的最新的值和它收到的领导者的序列号（来实现上一点）。新的领导者可以从接受者发送的值中选择一个，万一没有任何接收节点发送值，领导者也可以选择自己的值。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>协议步骤：</h3><h4>准备阶段：</h4><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">一个节点被选为领导者并且选择序列号x和值v创建提议P1(x,v)。领导者把P1发送给接收者并等待大部分节点响应。</li><li style="box-sizing: border-box; line-height: 25px;">接收者一旦接收到提议P1(x,v)会做下面的事：<ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; list-style: disc; line-height: 30px;"><li style="box-sizing: border-box; line-height: 25px;">如果是接收者第一次收到提议而且它选择赞同，回复&#8220;赞同&#8221;-这是接收者的承诺，它将承诺拒绝将来所有小于x的提议请求。</li>- 如果接收者已经赞同了提议：<li style="box-sizing: border-box; line-height: 25px;">比较x和接收者赞同的提议的最高序列号，称为P2(y,v2)</li><li style="box-sizing: border-box; line-height: 25px;">若x&lt;y,回应&#8220;拒绝&#8221;以及y的值</li>- 若x&gt;y,回应&#8220;赞同&#8221;以及P2(y,v2)</ul></li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h4>接受阶段</h4><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 如果大部分接收节点未能回应或者回应&#8220;拒绝&#8221;，领导者放弃这次协议并重新开始。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 如果大部分接收节点回应&#8220;赞同&#8221;，领导者也会接受大部分节点接受的协议的值。领导者选择这些值的任意一个并发送</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">接受请求</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">以及提议序列号和值。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 当接收者收到</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">接受请求</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">消息，它只在下面两种情况符合时发送&#8220;接受&#8221;信息，否则发送&#8220;拒绝&#8221;：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 值和之前接受的提议中的任一值相同</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 序列号和接收者赞同的最高提议序列号相同</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">- 如果领导者没有从大部分节点那接收到&#8220;接受&#8221;消息，它会放弃这次提议重新开始。但是如果接收到了大部分的&#8220;接受&#8221;消息，深思熟虑后协议也可能被终止。作为优化，领导者要给其它节点发送&#8220;提交&#8221;信息。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>Paxos对故障的处理</h3><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">如果Paxos算法中我们一次只授权唯一一个领导者，同时授权小部分节点，那样会发生什么？所有节点都要投票吗？是的，（这时）我们使用2PC。</span><a href="http://research.microsoft.com/pubs/64636/tr-2003-96.pdf" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">2PC是Paxos中的特例</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">正如人们所看到的，在故障容错上Paxos要优于2PC：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;">领导者故障了-另一位（新的）领导者可以通过发出自己的协议来接管协议。</li><li style="box-sizing: border-box; line-height: 25px;">原先的（故障）领导者恢复后-两个领导者可以同时存在，这归功于下面的规则：只赞同更高序列号的协议以及只提交以前接受的值。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos也比3PC更加容错。具体地讲，Paxos是分区容错3PC不是。在3PC中，如果两个分区分别赞同一个值，当分区合并的时候，会留给你一个不一致的状态。在Paxos中，大部分情况下这不会发生。除非某个分区占绝大多数，否则不会达成共识。如果某个分区占绝大多数并达成一个共识，那值就需要被其它分区中的节点接受。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos存在的一个问题是，两个领导者因为处于不同的分区导致不能互相观察，他们会努力向另一方投标，发布一个比先前协议有更高序列号的协议。这可能导致Paxos无法终止。最终这两个互相观察的领导者必须有一个需要做出退让。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">这样做是安全和活性间的折中。Paxos是安全的算法-一旦达成共识，赞同的值不会改变。但是，Paxos不能保证处在工作状态-Paxos在稀少的情况下可能不被终止。事实上一个异步一致算法不能被保证既安全又处于</span><a href="http://dockone.io/" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">活动状态</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">。我们称这为</span><a href="http://www.podc.org/influential/2001-influential-paper/" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">FLP不可能结果</a><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h3>拓展阅读</h3><ul style="box-sizing: border-box; margin: 0px; padding: 0px 0px 0px 32px; line-height: 30px; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; background-color: #ffffff;"><li style="box-sizing: border-box; line-height: 25px;"><a href="http://www.amazon.com/Principles-Transaction-Processing-Kaufmann-Management/dp/1558606238" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">Principles of Transaction Processing</a>，第八章提供了2PC的详细概述。</li><li style="box-sizing: border-box; line-height: 25px;"><a href="http://www.cs.cornell.edu/courses/cs614/2004sp/papers/Ske81.pdf" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">Non-blocking Commit Protocols</a>-Dale Skeen著作的描述3PC的原始论文</li><li style="box-sizing: border-box; line-height: 25px;"><a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">The Part-time Parliament</a>-Lamport的关于Paxos的原始论文。它用议会类比，当论文发表时人们发现很难回到过去的议会（译者注：议会的时代和论文发表的时代差距很远，人们很难理解过去的议会，所以也难于理解这篇论文）。</li><li style="box-sizing: border-box; line-height: 25px;"><a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">Paxos Made Simple</a>-Lamport重写的论文，去掉了议会类比。虽然简单，你也有可能错过论文的（核心），需要多次阅读来理解论文核心思想。</li><li style="box-sizing: border-box; line-height: 25px;"><a href="http://research.google.com/pubs/pub33002.html" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">Paxos Made Live</a>-谷歌关于他们Paxos算法实现的描述。是关于Paxos的最具可读性的论文。</li></ul><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><h1>Russell Cohen的回答</h1><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">本文提供了一个非常清晰的解释和证明：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><a href="http://nil.csail.mit.edu/6.824/2015/papers/paxos-simple.pdf" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;"></a><a href="http://nil.csail.mit.edu/6.824..." rel="nofollow" target="_blank" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background: 0px 0px #ffffff;">http://nil.csail.mit.edu/6.824...</a><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">我将努力提供一个清晰的总结：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos算法的目的是帮助一些同等的节点就一个值达成一致的协议。Paxos保证如果一个节点相信一个值已经被多数节点赞同，多数节点就再也不会赞同其它值。这个协议被设计使得任何共识必须得到大部分节点的认可。任何将来试图达成共识的尝试，如果成功了也必须经过至少一个节点的认可。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">因此，任何已经做出决定的节点必须和多数中的一个节点通信。协议保证节点将能从多数节点赞同的值得到收获。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">下面讲述它是如何运作的：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">假设我们有3个节点：A、B和C。A将提出一个值"Foo"</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">Paxos将分3个阶段执行。在每个阶段，必须有多数节点达成一致。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">首先，我们来看准备阶段。节点A发送准备请求给A、B、C。Paxos根据序列号来担保。</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">准备请求</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">要求节点承诺：&#8220;我永远不会接受序列号比</span><code style="box-sizing: border-box; font-family: 'Courier New', Courier, monospace; padding: 0px 5px; color: #c7254e; border-radius: 4px; display: inline-block; margin: 0px; line-height: 25.2px; background-color: #f9f2f4;">准备请求</code><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">小的提议。&#8221;（被询问的节点）回复一个它们之前赞同的值（如果有的话）。（这一点非常重要）：节点A必须回应它接收到的最高序列号的值。这一行为提供了保证：先前得到的赞同的值将被保留。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">接下来我们进入到接受阶段。A发送一条</span><span style="box-sizing: border-box; font-weight: 700; color: #333333; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">接受请求</span><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">给A，B和C。接受请求表示：&#8220;你接受Foo吗？&#8221;。如果伴随的序列号不小于节点先前已经承诺过的序列号或者是节点先前接受的请求的序列号，那么节点将接受新的值和序列号。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">如果节点A从多数节点接收到的是&#8220;接受&#8221;，(Foo)这个值就被确定下来。Paxos循环也会终止不再询问新的值。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">阶段三不是绝对必须的，但是如果在生产环境中实现Paxos算法，阶段三会是重要的优化。A收到多数节点&#8220;接受&#8221;消息后，它发送&#8220;已经确定值&#8221;消息给A，B和C。这条消息让所有节点知道已经选好了值，这会加快决定值的过程结束。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">如果没有这个消息，其它节点将继续提出新的值来了解协议。在准备阶段，它们不断从预先约定的值中学习，一旦它们从协议得到结论，节点就会确认这个协议。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">（为了便于理解）我掩盖了一些关键的问题：</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">1. 所有的序列号必须单调递增，而且不同节点序列号都不同。也就是说，A、B不能用同一个序列号k发送信息。协议要求所有发送的消息必须包含序列号。在准备阶段每个节点都要追踪它遇到的最高接受请求和它承诺的（序列号）最高的值。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2. 故障情况。一次Paxos循环中很可能失败，万一失败了，一个节点会用更高的序列号发起新的提议。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 终止情况。我所描述的Paxos版本不一定出现终止故障。对于正常的终止情况，（我描述的）Paxos算法还需要一些调整。</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="box-sizing: border-box; font-weight: 700; color: #333333; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">原文连接：<a href="http://www.quora.com/Distributed-Systems/What-is-a-simple-explanation-of-the-Paxos-algorithm" style="box-sizing: border-box; color: #155faa; text-decoration: none; cursor: pointer; background: 0px 0px;">Distributed Systems: What is a simple explanation of the Paxos algorithm?</a>（翻译：adolphlwq）</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">=========================================</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="box-sizing: border-box; font-weight: 700; color: #333333; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">译者介绍</span><br style="box-sizing: border-box; color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;" /><span style="box-sizing: border-box; font-weight: 700; color: #333333; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">adolphlwq</span><span style="color: #666666; font-family: 'Helvetica Neue', STHeiti, 'Microsoft YaHei', Helvetica, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">，南京信息工程大学本科大四学生，对Docker充满兴趣，喜欢运动。现在正努力充电希望快速进步。</span></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432172.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 14:27 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432172.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>以两军问题为背景来演绎Basic Paxos</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432171.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 05:32:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432171.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432171.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432171.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432171.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432171.html</trackback:ping><description><![CDATA[<div>http://iunknown.iteye.com/blog/2246484?from=message&amp;isappinstalled=0<br /><br /><h1><span style="font-family: 楷体;">背景</span></h1><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">在计算机通信理论中，有一个著名的两军问题(</span><span style="font-size: 14px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 1.5;">two-army problem</span><span style="font-family: 楷体;">)，讲述通信的双方通过</span><span style="font-size: 14pt; text-indent: 28pt;">ACK</span><span style="font-family: 楷体;">来达成共识，永远会有一个在途的</span><span style="font-size: 14pt; text-indent: 28pt;">ACK</span><span style="font-family: 楷体;">需要进行确认，因此无法达成共识。</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">两军问题和</span>Basic Paxos<span style="font-family: 楷体;">非常相似</span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;<span style="font-family: 楷体;">通信的各方需要达成共识；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;<span style="font-family: 楷体;">通信的各方仅需要达成一个共识；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">假设的前提是信道不稳定，有丢包、延迟或者重放，但消息不会被篡改。</span></span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">Basic Paxos<span style="font-family: 楷体;">最早以希腊议会的背景来讲解，但普通人不理解希腊议会的运作模式，因此看</span>Basic Paxos<span style="font-family: 楷体;">的论文会比较难理解。两军问题的背景大家更熟悉，因此尝试用这个背景来演绎一下</span>Basic Paxos<span style="font-family: 楷体;">。</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">为了配合</span>Basic Paxos<span style="font-family: 楷体;">的多数派概念，把两军改为</span>3<span style="font-family: 楷体;">军；同时假设了将军和参谋的角色。</span></p><h1><span style="font-family: 楷体;">假设的</span>3<span style="font-family: 楷体;">军问题</span></h1><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;1<span style="font-family: 楷体;">支红军在山谷里扎营，在周围的山坡上驻扎着</span>3<span style="font-family: 楷体;">支蓝军；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;<span style="font-family: 楷体;">红军比任意</span>1<span style="font-family: 楷体;">支蓝军都要强大；如果</span>1<span style="font-family: 楷体;">支蓝军单独作战，红军胜；如果</span>2<span style="font-family: 楷体;">支或以上蓝军同时进攻，蓝军胜；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">三支蓝军需要同步他们的进攻时间；但他们惟一的通信媒介是派通信兵步行进入山谷，在那里他们可能被俘虏，从而将信息丢失；或者为了避免被俘虏，可能在山谷停留很长时间；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">4）&nbsp;<span style="font-family: 楷体;">每支军队有</span>1<span style="font-family: 楷体;">个参谋负责提议进攻时间；每支军队也有</span>1<span style="font-family: 楷体;">个将军批准参谋提出的进攻时间；很明显，</span>1<span style="font-family: 楷体;">个参谋提出的进攻时间需要获得至少</span>2<span style="font-family: 楷体;">个将军的批准才有意义；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">5）&nbsp;<span style="font-family: 楷体;">问题：是否存在一个协议，能够使得蓝军同步他们的进攻时间？</span></span></p><p style="margin: 0px; padding: 0px; font-size: 10.5pt; text-align: justify; font-family: Calibri, sans-serif; line-height: normal; text-indent: 0.1pt; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">接下来以两个假设的场景来演绎</span>BasicPaxos<span style="font-family: 楷体;">；参谋和将军需要遵循一些基本的规则</span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;<span style="font-family: 楷体;">参谋以两阶段提交（</span>prepare/commit<span style="font-family: 楷体;">）的方式来发起提议，在</span>prepare<span style="font-family: 楷体;">阶段需要给出一个编号；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;<span style="font-family: 楷体;">在</span>prepare<span style="font-family: 楷体;">阶段产生冲突，将军以编号大小来裁决，编号大的参谋胜出；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">参谋在</span>prepare<span style="font-family: 楷体;">阶段如果收到了将军返回的已接受进攻时间，在</span>commit<span style="font-family: 楷体;">阶段必须使用这个返回的进攻时间；</span></span></p><h1><span style="font-family: 楷体;">两个参谋先后提议的场景</span></h1><p style="margin: 0px; padding: 0px; font-size: 10.5pt; text-align: justify; font-family: Calibri, sans-serif; line-height: normal; text-indent: 0.1pt; background-color: #ffffff;"><br /><img alt="" src="http://dl2.iteye.com/upload/attachment/0112/0263/544b1fa2-eeb5-3434-8f0a-d436a5525500.png" title="点击查看原始大小图片" width="700" height="330" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer;" /><br />&nbsp;</p><p style="margin: 0px; padding: 0px; font-size: 10.5pt; text-align: justify; font-family: Calibri, sans-serif; line-height: normal; text-indent: 0.1pt; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">发起提议，派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;3<span style="font-family: 楷体;">个将军收到参谋</span>1<span style="font-family: 楷体;">的提议，由于之前还没有保存任何编号，因此把（编号</span>1<span style="font-family: 楷体;">）保存下来，避免遗忘；同时让通信兵带信回去，内容为（</span>ok<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的回复，再次派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">4）&nbsp;3<span style="font-family: 楷体;">个将军收到参谋</span>1<span style="font-family: 楷体;">的时间，把（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）保存下来，避免遗忘；同时让通信兵带信回去，内容为（</span>Accepted<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">5）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的（</span>Accepted<span style="font-family: 楷体;">）内容，确认进攻时间已经被大家接收；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-size: 10.5pt; text-align: justify; font-family: Calibri, sans-serif; line-height: normal; text-indent: 0.1pt; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">6）&nbsp;<span style="font-family: 楷体;">参谋</span>2<span style="font-family: 楷体;">发起提议，派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">7）&nbsp;3<span style="font-family: 楷体;">个将军收到参谋</span>2<span style="font-family: 楷体;">的提议，由于（编号</span>2<span style="font-family: 楷体;">）比（编号</span>1<span style="font-family: 楷体;">）大，因此把（编号</span>2<span style="font-family: 楷体;">）保存下来，避免遗忘；又由于之前已经接受参谋</span>1<span style="font-family: 楷体;">的提议，因此让通信兵带信回去，内容为（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">8）&nbsp;<span style="font-family: 楷体;">参谋</span>2<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的回复，由于回复中带来了已接受的参谋</span>1<span style="font-family: 楷体;">的提议内容，参谋</span>2<span style="font-family: 楷体;">因此不再提出新的进攻时间，接受参谋</span>1<span style="font-family: 楷体;">提出的时间；</span></span></p><h1><span style="font-family: 楷体;">两个参谋交叉提议的场景</span></h1><p style="margin: 0px; padding: 0px; font-size: 10.5pt; text-align: justify; font-family: Calibri, sans-serif; line-height: normal; text-indent: 0.1pt; background-color: #ffffff;"><br /><img alt="" src="http://dl2.iteye.com/upload/attachment/0112/0265/5c583950-a182-3f2e-89ef-c12bf7662162.png" title="点击查看原始大小图片" width="700" height="291" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer;" /><br />&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">发起提议，派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;3<span style="font-family: 楷体;">个将军的情况如下</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">a)&nbsp;<span style="font-family: 楷体;">将军</span>1<span style="font-family: 楷体;">和将军</span>2<span style="font-family: 楷体;">收到参谋</span>1<span style="font-family: 楷体;">的提议，将军</span>1<span style="font-family: 楷体;">和将军</span>2<span style="font-family: 楷体;">把（编号</span>1<span style="font-family: 楷体;">）记录下来，如果有其他参谋提出更小的编号，将被拒绝；同时让通信兵带信回去，内容为（</span>ok<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">b)&nbsp;<span style="font-family: 楷体;">负责通知将军</span>3<span style="font-family: 楷体;">的通信兵被抓，因此将军</span>3<span style="font-family: 楷体;">没收到参谋</span>1<span style="font-family: 楷体;">的提议；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">参谋</span>2<span style="font-family: 楷体;">在同一时间也发起了提议，派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">4）&nbsp;3<span style="font-family: 楷体;">个将军的情况如下</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">a)&nbsp;<span style="font-family: 楷体;">将军</span>2<span style="font-family: 楷体;">和将军</span>3<span style="font-family: 楷体;">收到参谋</span>2<span style="font-family: 楷体;">的提议，将军</span>2<span style="font-family: 楷体;">和将军</span>3<span style="font-family: 楷体;">把（编号</span>2<span style="font-family: 楷体;">）记录下来，如果有其他参谋提出更小的编号，将被拒绝；同时让通信兵带信回去，内容为（</span>ok<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">b)&nbsp;<span style="font-family: 楷体;">负责通知将军</span>1<span style="font-family: 楷体;">的通信兵被抓，因此将军</span>1<span style="font-family: 楷体;">没收到参谋</span>2<span style="font-family: 楷体;">的提议；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">5）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的回复，再次派通信兵带信给有答复的</span>2<span style="font-family: 楷体;">个将军，内容为（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">6）&nbsp;2<span style="font-family: 楷体;">个将军的情况如下</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">a)&nbsp;<span style="font-family: 楷体;">将军</span>1<span style="font-family: 楷体;">收到了（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">），和自己保存的编号相同，因此把（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）保存下来；同时让通信兵带信回去，内容为（</span>Accepted<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">b)&nbsp;<span style="font-family: 楷体;">将军</span>2<span style="font-family: 楷体;">收到了（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">），由于（编号</span>1<span style="font-family: 楷体;">）小于已经保存的（编号</span>2<span style="font-family: 楷体;">），因此让通信兵带信回去，内容为（</span>Rejected<span style="font-family: 楷体;">，编号</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">7）&nbsp;<span style="font-family: 楷体;">参谋</span>2<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的回复，再次派通信兵带信给有答复的</span>2<span style="font-family: 楷体;">个将军，内容为（编号</span>2<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">8）&nbsp;<span style="font-family: 楷体;">将军</span>2<span style="font-family: 楷体;">和将军</span>3<span style="font-family: 楷体;">收到了（编号</span>2<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">），和自己保存的编号相同，因此把（编号</span>2<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">）保存下来，同时让通信兵带信回去，内容为（</span>Accepted<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">9）&nbsp;<span style="font-family: 楷体;">参谋</span>2<span style="font-family: 楷体;">收到至少</span>2<span style="font-family: 楷体;">个将军的（</span>Accepted<span style="font-family: 楷体;">）内容，确认进攻时间已经被多数派接受；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">10）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">只收到了</span>1<span style="font-family: 楷体;">个将军的（</span>Accepted<span style="font-family: 楷体;">）内容，同时收到一个（</span>Rejected<span style="font-family: 楷体;">，编号</span>2<span style="font-family: 楷体;">）；参谋</span>1<span style="font-family: 楷体;">重新发起提议，派通信兵带信给</span>3<span style="font-family: 楷体;">个将军，内容为（编号</span>3<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">11）&nbsp;3<span style="font-family: 楷体;">个将军的情况如下</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">a)&nbsp;<span style="font-family: 楷体;">将军</span>1<span style="font-family: 楷体;">收到参谋</span>1<span style="font-family: 楷体;">的提议，由于（编号</span>3<span style="font-family: 楷体;">）大于之前保存的（编号</span>1<span style="font-family: 楷体;">），因此把（编号</span>3<span style="font-family: 楷体;">）保存下来；由于将军</span>1<span style="font-family: 楷体;">已经接受参谋</span>1<span style="font-family: 楷体;">前一次的提议，因此让通信兵带信回去，内容为（编号</span>1<span style="font-family: 楷体;">，进攻时间</span>1<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">b)&nbsp;<span style="font-family: 楷体;">将军</span>2<span style="font-family: 楷体;">收到参谋</span>1<span style="font-family: 楷体;">的提议，由于（编号</span>3<span style="font-family: 楷体;">）大于之前保存的（编号</span>2<span style="font-family: 楷体;">），因此把（编号</span>3<span style="font-family: 楷体;">）保存下来；由于将军</span>2<span style="font-family: 楷体;">已经接受参谋</span>2<span style="font-family: 楷体;">的提议，因此让通信兵带信回去，内容为（编号</span>2<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">c)&nbsp;<span style="font-family: 楷体;">负责通知将军</span>3<span style="font-family: 楷体;">的通信兵被抓，因此将军</span>3<span style="font-family: 楷体;">没收到参谋</span>1<span style="font-family: 楷体;">的提议；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">12）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">收到了至少</span>2<span style="font-family: 楷体;">个将军的回复，比较两个回复的编号大小，选择大编号对应的进攻时间作为最新的提议；参谋</span>1<span style="font-family: 楷体;">再次派通信兵带信给有答复的</span>2<span style="font-family: 楷体;">个将军，内容为（编号</span>3<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">13）&nbsp;<span style="font-family: 楷体;">将军</span>1<span style="font-family: 楷体;">和将军</span>2<span style="font-family: 楷体;">收到了（编号</span>3<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">），和自己保存的编号相同，因此保存（编号</span>3<span style="font-family: 楷体;">，进攻时间</span>2<span style="font-family: 楷体;">），同时让通信兵带信回去，内容为（</span>Accepted<span style="font-family: 楷体;">）；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">14）&nbsp;<span style="font-family: 楷体;">参谋</span>1<span style="font-family: 楷体;">收到了至少</span>2<span style="font-family: 楷体;">个将军的（</span>accepted<span style="font-family: 楷体;">）内容，确认进攻时间已经被多数派接受；</span></span></p><h1><span style="font-family: 楷体;">小结</span></h1><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">BasicPaxos<span style="font-family: 楷体;">算法难理解，除了讲故事的背景不熟悉之外，还有以下几点</span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">1）&nbsp;<span style="font-family: 楷体;">参与的各方并不是要针锋相对，拼个你死我活；而是要合作共赢，最终达成一个共识；当大家讲起投票的时候，往往第一反应是要针锋相对，没想到是要合作共赢；很明显可以想到，在第二个场景下，如果参谋</span>1<span style="font-family: 楷体;">为了逞英雄，强行要提交他提出的进攻时间</span>1<span style="font-family: 楷体;">，那么最终是无法达成一个共识的；这里的点就在于参谋</span>1<span style="font-family: 楷体;">违反了规则，相当于产生了拜占庭错误；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">2）&nbsp;<span style="font-family: 楷体;">常规的通信协议设计，对于写操作，通常都是只返回成功和失败的状态，不会返回更多的东西；但</span>BasicPaxos<span style="font-family: 楷体;">的</span>prepare<span style="font-family: 楷体;">和</span>commit<span style="font-family: 楷体;">，将军除了返回成功还是失败的状态之外，还会把之前已经发生的一些状态带回给参谋，这个和常规的通信协议是不同的；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">3）&nbsp;<span style="font-family: 楷体;">在两军问题的背景下，其实知道进攻时间被至少</span>2<span style="font-family: 楷体;">个将军接受的是参谋，而不是将军；在&#8220;两个参谋交叉提议的场景&#8221;下，当参谋</span>1<span style="font-family: 楷体;">没有做第</span>2<span style="font-family: 楷体;">次</span>prepare<span style="font-family: 楷体;">之前，将军</span>1<span style="font-family: 楷体;">记录的其实是一个错误的进攻时间；理论上来说，任何一个将军在任何一个时刻都无法判断自己不是处在将军</span>1<span style="font-family: 楷体;">的场景下；因此</span>BasicPaxos<span style="font-family: 楷体;">在</span>3<span style="font-family: 楷体;">个蓝军组成的系统中达成了一个共识，但并没有为每个将军明确了共识；</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 30px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">4）&nbsp;<span style="font-family: 楷体;">本文的两个场景都以&#8220;两个参谋&#8221;来讲，这里的&#8220;两个参谋&#8221;可能是真的两个不同的参谋，也可能是同一个参谋因为某种原因先后做了多次提议；对应分布式系统的场景</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">a）&nbsp;<span style="font-family: 楷体;">真的有两个并发的</span>client</span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">b）&nbsp;<span style="font-family: 楷体;">两个</span>client<span style="font-family: 楷体;">一先一后；第一个</span>client<span style="font-family: 楷体;">执行到某个步骤因为某种原因停止了；过了一段时间，另外一个</span>client<span style="font-family: 楷体;">接着操作同一个数据</span></span></p><p style="margin: 0px; padding: 0px 0px 0px 60px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 18px;">c）&nbsp;<span style="font-family: 楷体;">同一个</span>client<span style="font-family: 楷体;">重试；第一次执行到某一步骤因为某种原因停止了，立即或者稍后进行了重试</span></span></p><h1><span style="font-family: 楷体;">后记</span></h1><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">写这篇文章的时候，参考了以下两篇文章。</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">Paxos<span style="font-family: 楷体;">算法细节详解</span>(<span style="font-family: 楷体;">一</span>)--<span style="font-family: 楷体;">通过现实世界描述算法</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><a href="http://www.cnblogs.com/endsock/p/3480093.html" style="color: #954f72;">http://www.cnblogs.com/endsock/p/3480093.html</a></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">第一篇文章用了我们喜闻乐见的背景，大部分内容非常容易理解，尤其是用比特币来映射编号，非常贴切；只是对于</span>proposer-1<span style="font-family: 楷体;">小姐最后的</span>&#8220;<span style="font-family: 楷体;">背叛</span>&#8221;<span style="font-family: 楷体;">会有点违反常识。看完这个故事之后就一直在想更贴切的背景。在两军问题中，蓝军各方是要合作达成一个共识；对于参谋来说，获得了前一个参谋的提议就接受，而不再提出自己的提议是符合逻辑的，这个和</span>paxos<span style="font-family: 楷体;">也更加吻合。在实际的分布式系统中，如果遇到冲突，涉及的各方也不是要针锋相对争个你死我活，想要的只是能发现冲突，只有一方成功、或者全部失败都无所谓，只要能保证数据一致就行。</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><span style="font-family: 楷体;">以两军问题为背景，在提议编号上找不到合适的映射点，比较生硬，这一点不如第一遍文章中的故事。</span></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">Question 7: The Two Generals&#8217; Problem of reaching consensus on when to attack is unsolvable, how come it&#8217;s possible to have consensus with Paxos?</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;"><a href="http://bogdan.pistol.gg/2014/10/20/paxos-algorithm-explained-part-2-insights/#q7" style="color: #954f72;">http://bogdan.pistol.gg/2014/10/20/paxos-algorithm-explained-part-2-insights/#q7</a></p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; text-align: justify; text-indent: 28pt; font-size: 14pt; font-family: Calibri, sans-serif; line-height: normal; background-color: #ffffff;">paxos<span style="font-family: 楷体;">最终仍然无法解决两军问题，即使是扩展到</span>3<span style="font-family: 楷体;">军也是无法解决的。在</span>3<span style="font-family: 楷体;">军背景下，按</span>paxos<span style="font-family: 楷体;">算法的定义，最后是达成了一个共同的进攻时间，</span>3<span style="font-family: 楷体;">军中的任何一方都可以通过</span>paxos<span style="font-family: 楷体;">算法读取出这个进攻时间。但</span>3<span style="font-family: 楷体;">军怎么知道在什么时候去读取、其他人是否已经读取，这是一个和两军问题同样的问题；同时由于通信兵可能无限延迟，可能部分蓝军在进攻时间之前读取到了，部分蓝军可能在进攻时间之后才读取到，所以两军最终还是无解的。第二篇参考文章中也详细描述了这些问题。所以写</span>paxos<span style="font-family: 楷体;">和两军问题，不是说</span>paxos<span style="font-family: 楷体;">解决了两军问题，只是借用两军问题的背景来演绎</span>paxos<span style="font-family: 楷体;">。</span></p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432171.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 13:32 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432171.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Paxos分析</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432170.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Mon, 26 Dec 2016 05:26:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432170.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432170.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432170.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432170.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432170.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/endsock/p/3480093.html<br /><br /><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">最近研究paxos算法，看了许多相关的文章，概念还是很模糊，觉得还是没有掌握paxos算法的精髓，所以花了3天时间分析了libpaxos3的所有代码，此代码可以从<a href="https://bitbucket.org/sciascid/libpaxos" style="margin: 0px; padding: 0px; color: #000000;">https://bitbucket.org/sciascid/libpaxos</a>&nbsp;下载。对paxos算法有初步了解之后，再看此文的效果会更好；如果你也想分析libpaxos3的话，此文应该会对你有不小帮助；关于paxos的历史这里不多做介绍，关于描述paxos算法写的最好的一篇文章应该就是维基百科了，地址戳这里：<a href="http://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95" style="margin: 0px; padding: 0px; color: #000000;">http://zh.wikipedia.org/zh-cn/Paxos%E7%AE%97%E6%B3%95</a></span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">在paxos算法中，分为4种角色：</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp; Proposer ：提议者</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp; Acceptor：决策者</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp; Client：产生议题者</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp; Learner：最终决策学习者</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">上面4种角色中，提议者和决策者是很重要的，其他的2个角色在整个算法中应该算做打酱油的，Proposer就像Client的使者，由Proposer使者拿着Client的议题去向Acceptor提议，让Acceptor来决策。这里上面出现了个新名词：最终决策。现在来系统的介绍一下paxos算法中所有的行为：</span></p><ol style="margin: 0px; padding: 0px 0px 0px 40px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">Proposer提出议题</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">Acceptor初步接受 或者 Acceptor初步不接受</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">如果上一步Acceptor初步接受则Proposer再次向Acceptor确认是否最终接受</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">Acceptor 最终接受 或者Acceptor 最终不接受</span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">上面Learner最终学习的目标是Acceptor们最终接受了什么议题？注意，这里是向所有Acceptor学习，如果有多数派个Acceptor最终接受了某提议，那就得到了最终的结果，算法的目的就达到了。画一幅图来更加直观：</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp;<img src="http://images.cnitblog.com/blog/38637/201312/18123808-b860c61793e247919ee3787ead3430ef.png" alt="" style="margin: 0px; padding: 0px; border: 0px; max-width: 660px;" /></span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">为什么需要3个Acceptor？因为Acceptor必须是最少大于等于3个，并且必须是奇数个，因为要形成多数派嘛，如果是偶数个，比如4个，2个接受2个不接受，各执己见，没法搞下去了。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">为什么是3个Proposer？ 其实无所谓是多少个了，1~n 都可以的；如果是1个proposer，毫无竞争压力，很顺利的完成2阶段提交，Acceptor们最终批准了事。如果是多个proposer就比较复杂了，请继续看。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">上面的图中是画了很多节点的，每个节点需要一台机器么？答案是不需要的，上面的图是逻辑图，物理中，可以将Acceptor和Proposer以及Client放到一台机器上，只是使用了不同的端口号罢了，Acceptor们启动不同端口的TCP监听，Proposer来主动连接即可；完全可以将Client、Proposer、Acceptor、Learner合并到一个程序里面；这里举一个例子：比如开发一个JOB程序，JOB程序部署在多台服务器上(数量为奇数)，这些JOB有可能同时处理一项任务，现在使用paxos算法让这些JOB自己来商量由谁(哪台机器)来处理这项任务，这样JOB程序里就需要包含Client、Proposer、Acceptor、Learner这4大功能，并且需要配置其他JOB服务器的IP地址。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">再举一个例子，zookeeper常常用来做分布式事务锁。Zookeeper所使用的zad协议也是类似paxos协议的。所有分布式自协商一致性算法都是paxos算法的简化或者变种。Client是使用zookeeper服务的机器，Zookeeper自身包含了Acceptor, Proposer, Learner。Zookeeper领导选举就是paxos过程，还有Client对Zookeeper写Znode时，也是要进行Paxos过程的，因为不同Client可能连接不同的Zookeeper服务器来写Znode，到底哪个Client才能写成功？需要依靠Zookeeper的paxos保证一致性，写成功Znode的Client自然就是被最终接受了，Znode包含了写入Client的IP与端口，其他的Client也可以读取到这个Znode来进行Learner。也就是说在Zookeeper自身包含了Learner(因为Zookeeper为了保证自身的一致性而会进行领导选举，所以需要有Learner的内部机制，多个Zookeeper服务器之间需要知道现在谁是领导了)，Client端也可以Learner，Learner是广义的。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;">&nbsp;</p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">现在通过一则故事来学习paxos的算法的流程(2阶段提交)，有2个Client(老板，老板之间是竞争关系)和3个Acceptor(政府官员)：</span></p><ol style="margin: 0px; padding: 0px 0px 0px 40px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">现在需要对一项议题来进行paxos过程，议题是&#8220;A项目我要中标！&#8221;，这里的&#8220;我&#8221;指每个带着他的秘书Proposer的Client老板。</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">Proposer当然听老板的话了，赶紧带着议题和现金去找Acceptor政府官员。</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">作为政府官员，当然想谁给的钱多就把项目给谁。</span></li><li style="margin: 0px 0px 1em; padding: 0px; list-style: decimal;"><span style="margin: 0px; padding: 0px; font-size: 15px;">Proposer-1小姐带着现金<span style="margin: 0px; padding: 0px; color: #ff0000;">同时</span>找到了Acceptor-1~Acceptor-3官员，1与2号官员分别收取了10比特币，找到第3号官员时，没想到遭到了3号官员的鄙视，3号官员告诉她，Proposer-2给了11比特币。不过没关系，Proposer-1已经得到了1,2两个官员的认可，形成了多数派(如果没有形成多数派，Proposer-1会去银行提款在来找官员们给每人20比特币，这个过程一直重复每次+10比特币，直到多数派的形成)，满意的找老板复命去了，但是此时Proposer-2保镖找到了1,2号官员，分别给了他们11比特币，1,2号官员的态度立刻转变，都说Proposer-2的老板懂事，这下子Proposer-2放心了，搞定了3个官员，找老板复命去了，当然这个过程是第一阶段提交，只是官员们初步接受贿赂而已。故事中的比特币是编号，议题是value。</span></li></ol><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">　　　　<span style="margin: 0px; padding: 0px; color: #ff0000;">这个过程保证了在某一时刻，某一个proposer的议题会形成一个多数派进行初步支持；</span></span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">&nbsp;===============华丽的分割线，第一阶段结束================</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">　　5.　现在进入第二阶段提交，现在proposer-1小姐使用分身术(多线程并发)分了3个自己分别去找3位官员，最先找到了1号官员签合同，遭到了1号官员的鄙视，1号官员告诉他proposer-2先生给了他11比特币，因为上一条规则的性质proposer-1小姐知道proposer-2第一阶段在她之后又形成了多数派(至少有2位官员的赃款被更新了);此时她赶紧去提款准备重新贿赂这3个官员(重新进入第一阶段)，每人20比特币。刚给1号官员20比特币， 1号官员很高兴初步接受了议题，还没来得及见到2,3号官员的时候</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">这时proposer-2先生也使用分身术分别找3位官员(注意这里是proposer-2的第二阶段)，被第1号官员拒绝了告诉他收到了20比特币，第2,3号官员顺利签了合同，这时2，3号官员记录client-2老板用了11比特币中标，因为形成了多数派，所以最终接受了Client2老板中标这个议题，对于proposer-2先生已经出色的完成了工作；</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">这时proposer-1小姐找到了2号官员，官员告诉她合同已经签了，将合同给她看，proposer-1小姐是一个没有什么职业操守的聪明人，觉得跟Client1老板混没什么前途，所以将自己的议题修改为&#8220;Client2老板中标&#8221;，并且给了2号官员20比特币，这样形成了一个多数派。顺利的再次进入第二阶段。由于此时没有人竞争了，顺利的找3位官员签合同，3位官员看到议题与上次一次的合同是一致的，所以最终接受了，形成了多数派，proposer-1小姐跳槽到Client2老板的公司去了。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">===============华丽的分割线，第二阶段结束===============</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;"><span style="margin: 0px; padding: 0px; font-size: 15px;">　　Paxos过程结束了，这样，一致性得到了保证，算法运行到最后所有的proposer都投&#8220;client2中标&#8221;所有的acceptor都接受这个议题，也就是说在最初的第二阶段，议题是先入为主的，谁先占了先机，后面的proposer在第一阶段就会学习到这个议题而修改自己本身的议题，因为这样没职业操守，才能让一致性得到保证，这就是paxos算法的一个过程。原来paxos算法里的角色都是这样的不靠谱，不过没关系，结果靠谱就可以了。该算法就是为了追求结果的一致性。</span></p><p style="margin-top: 10px; margin-bottom: 10px; padding: 0px; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 12px; line-height: 21.6px; background-color: #ffffff;">&nbsp;</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432170.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-26 13:26 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/26/432170.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>【分布式】分布式架构</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/23/432169.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Fri, 23 Dec 2016 14:47:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/23/432169.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432169.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/23/432169.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432169.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432169.html</trackback:ping><description><![CDATA[<div>http://www.cnblogs.com/leesf456/p/5992377.html<br /><br /><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>一、前言</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>　　</strong>在大数据系统中，分布式系统已经成为一个无法避免的组件，如zookeeper已经成为了工业届的标准。所以对于大数据的研究，也必须要研究分布式系统的特点。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>二、集中式系统</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　由一台或多台计算机组成的中心节点，数据集中存储在这个中心节点中，并且整个系统的所有业务单元都集中部署在这个中心节点上，系统的所有功能均由其集中处理。其部署简单，不用考虑多个节点间的分布式协作问题。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>三、分布式系统</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　分布式系统是一个由硬件或软件组件分布在不同的网络计算机上，彼此之间仅仅通过消息传递进行通信和协调的系统。其拥有如下特点</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">3.1 分布性</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　分布式系统中的多台计算机都会在空间中随意分布，同时，机器的分布情况也会随时变动。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　3.2 对等性</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　分布式系统中的计算机没有主/从之分，既没有控制整个系统的主机，也没有被控制的从机，组成分布式系统的所有计算机节点都是对等的，<span style="line-height: 1.8; color: #ff0000;">副本</span>指的是分布式系统对数据和服务提供的一种冗余方式，为了对外提供高可用的服务，我们往往会对数据和服务进行副本处理。<span style="line-height: 1.8; color: #ff0000;">数据副本</span>是指在不同的节点上持久化同一份数据，当某一个节点上存储的数据丢失时，可以从副本上读取到该数据，这是解决分布式系统数据丢失问题最为有效的手段。<span style="line-height: 1.8; color: #ff0000;">服务副本</span>是只多个节点提供同样的服务，每个节点都有能力接受来自外部的请求并进行相应的处理。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　3.3 并发性</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　同一分布式系统中的多个节点，可能会并发地操作一些共享资源，诸如数据库或分布式存储等，如何高效地协调分布式并发操作也成为了分布式系统架构与设计中最大的挑战之一。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">3.4 缺乏全局时钟</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　典型的分布式系统由一系列在空间上随意分布的多个进程组成，具有明显的分布性，这些进程之间通过交换消息来进行互相通信，因此，在分布式系统中，很难定义两个时间究竟谁先谁后，原因就是因为分布式系统缺乏一个全局的时钟序列控制。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">3.5 故障总是会发生</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　组成分布式系统的所有计算机，都有可能发生任何形式的故障，任何在设计阶段考虑到的异常情况，一定会在系统实际运行中发生。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>四、分布式环境的问题</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">4.1 通信异常</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　从集中式到分布式，必然引入了网络因素，而由于网络本身的不可靠性，因此就引入了额外的问题。分布式系统各节点之间的网络通信能够正常进行，其延时也会远大于单机操作，在消息的收发过程中，消息丢失和消息延迟变得十分普遍。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">4.2 网络分区</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　当网络发生异常情况时，导致分布式系统中部分节点之间的网络延时不断增大，最终导致组成分布式胸的所有节点中，只有部分节点之间能够正常通信，而另一些节点则不能，这种现象称之为网络分区，当网络分区出现时，分布式系统会出现局部小集群，在极端情况下，这些局部小集群会独立完成原本需要整个分布式系统才能完成的功能，包括对数据的事务处理，这就对分布式一致性提出了非常大的挑战。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">4.3 三态</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　由于网络可能会出现各种各样的问题，因此分布式系统的每一次请求与响应，存在特有的三态概念：成功、失败、超时。当网络在异常情况下，可能会出现超时现象，通常由以下两种情况：1. 由于网络原因，该请求并没有被成功地发送到接收方，而是在发送过程就发生了消息丢失现象。2. 该请求成功的被接收方接受后，并进行了处理，但是在将响应反馈给发送方时，发生了消息丢失现象。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　<span style="line-height: 1.8; color: #ff0000;">　4.4 节点故障</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　节点故障是指组成分布式系统的服务器节点出现宕机或僵死现象，每个节点都有可能出现故障，并且煤炭都在发生。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>五、从ACID到CAP/BASE</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">5.1 ACID</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　事务是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行单元，狭义上的食物特指数据库事务。一方面，当多个应用程序并发访问数据库时，食物可以在这些应用程序之间提供一个隔离方法，以防止彼此的操作相互干扰，另一方面，食物为数据库操作序列提供了一个从失败中恢复到正常状态的方法，同时提供了数据库即使在宜昌状态下仍能保持数据一致性的方法。事务具有原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability），简称ACID。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312; 原子性，指事务必须是一个原子的操作序列单元，事务中包含的各项操作在一次执行过程中，只允许出现以下两种状态之一，全部成功执行，全部不执行。任何一项操作失败都将导致整个事务失败，同时其他已经被执行的操作都将被撤销并回滚，只有所有操作全部成功，整个事务才算是成功完成。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313; 一致性，指事务的执行不能破坏数据库数据的完整性和一致性，一个事务在执行之前和执行之后，数据库都必须处于一致性状态，即事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态，因此当数据库只包含成功事务提交的结果时，就能说数据库处于一致性状态，而如果数据库系统在运行过程中发生故障，有些事务尚未完成就被迫中断，这些未完成的事务对数据库所做的修改有一部分已写入物理数据库，这时数据库就处于一种不正确的状态，或者说是不一致的状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314; 隔离性，指在并发环境中，并发的事务是相互隔离的，一个事务的执行不能被其他事务干扰，即不同的事务并发操作相同的数据时，每个事务都有各自完整的数据空间，即一个事务内部的操作及使用的数据对其他并发事务是隔离的，并发执行的各个事务之间不能相互干扰。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9315; 持久性，指一个事务一旦提交，他对数据库中对应数据的状态变更就应该是永久的，即一旦某个事务成功结束，那么它对数据库所做的更新就必须被永久的保存下来，即使发生系统崩溃或者宕机故障，只要数据库能够重新启动，那么一定能够将其恢复到事务成功结束时的状态。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">5.2 分布式事务</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点上，通常一个分布式事务中会涉及对多个数据源或业务系统的操作。一个分布式事务可以看做是由多个分布式的操作序列组成，通常可以把这一系列分布式的操作序列称为子事务。由于在分布式事务中，各个子事务的执行是分布式的，因此要实现一种能够保证ACID特性的分布式事务处理系统就显得格外复杂。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">5.3 CAP</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　CAP理论告诉我们，一个分布式系统不可能同时满足一致性、可用性、分区容错性这三个基本需求，最多只能同时满足其中的两个。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312; 一致性，指数据在多个副本之间是否能够保持一致的特性，在一致性的需求下，当一个系统在数据一致的状态下执行更新操作后，应该保证系统的数据仍然处于一致状态。对于一个将数据副本分布在不同分布式节点上的系统来来说，如果对第一个结点的数据进行了更新操作并且成功后，却没有使得第二个节点上的数据得到相应的更新，于是在对第二个结点的数据进行读取操作时，获取的仍然是老数据（脏数据），这就是典型的分布式数据不一致的情况，在分布式系统中，如果能够做到针对一个数据项的更新操作执行成功后，所有的用户都可以读取到期最新的值，那么这样的系统就被认为具有强一致性。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313; 可用性，指系统提供的服务必须一直处于可用的状态，对于用户的每一操作请求总是能够在有限的时间内返回结果。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314; 分区容错性，分布式系统在遇到任何网络分区故障时，仍然需要能够保证对外提供满足一致性和可用性的服务，除非是整个网络环境都发生了故障。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　<span style="line-height: 1.8; color: #ff0000;">5.4 BASE</span></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　BASE是基本可用（Basically Available）、Soft state（弱状态）、Eventually consistent（最终一致性）三个短语的简写。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9312; 基本可用，指分布式系统在出现不可预知故障时，允许损失部分可用性，如响应时间上的损失或功能上的损失。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9313; 弱状态，也称为软状态，指允许系统中的二数据存在中间状态，并认为该中间状态的存在不会影响系统的整体可用性，即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　&#9314; 最终一致性，指系统中所有的数据副本，在经过一段时回见的同步后，最终能够达到一个一致的状态，因此最终一致性的本质是需要系统保证数据能够达到一致，而不需要实时保证系统数据的强一致性。</p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;"><strong>六、总结</strong></p><p style="line-height: 25.2px; margin-top: 10px; margin-bottom: 10px; color: #333333; font-family: Georgia, 'Times New Roman', Times, sans-serif; background-color: #ffffff;">　　这篇博文主要介绍了分布式的一些相关知识，更详细的知识之后会给出。谢谢各位园友的观看~</p></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432169.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-23 22:47 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/23/432169.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--7.无底洞问题(multiget hole)</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432157.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:19:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432157.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432157.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432157.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432157.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432157.html</trackback:ping><description><![CDATA[<h1>&nbsp;转载请注明出处哈:<a target="_blank" href="http://carlosfu.iteye.com/blog/2269678" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px; background-color: #ffffff;" /><h1>&nbsp;&nbsp;最近有点忙，一直没更新博客，继续坚持下去。<img alt="大笑" border="0" src="http://www.iteye.com/javascripts/tinymce/plugins/emotions/img/smiley-laughing.gif" title="大笑" style="border: 0px; font-size: 14px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 1.5;" /></h1><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 24px;">一、背景&nbsp;</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<span style="background-color: #ffff00;">&nbsp;1. 什么是缓存无底洞问题：</span></p><div style="border: 1px solid #cccccc; margin: 0px 5px 5px 15px; padding: 3px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background: #fafafa;">Facebook的工作人员反应2010年已达到3000个memcached节点，储存数千G的缓存。<br />他们发现一个问题--memcached的连接效率下降了，于是添加memcached节点，添加完之后，并没有好转。称为&#8220;无底洞&#8221;现象</div><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<span style="background-color: #ffff00;">2. 缓存无底洞产生的原因：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;键值数据库或者缓存系统，由于通常采用hash函数将key映射到对应的实例，造成key的分布与业务无关，但是由于数据量、访问量的需求，需要使用分布式后（无论是客户端一致性哈性、redis-cluster、codis），批量操作比如批量获取多个key(例如redis的mget操作)，通常需要从不同实例获取key值，相比于单机批量操作只涉及到一次网络操作，分布式批量操作会涉及到多次网络io。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;<img width="663" alt="" height="407" src="http://dl2.iteye.com/upload/attachment/0113/7336/d8e260f2-0b1a-3fc8-bc9d-78b4e19a2ad6.png" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;<img width="651" alt="" height="389" src="http://dl2.iteye.com/upload/attachment/0113/7334/557375ef-42de-34c7-befa-445129a74a6c.png" style="border: 0px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;&nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">3. 无底洞问题带来的危害：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (1) 客户端一次批量操作会涉及多次网络操作，也就意味着批量操作会随着实例的增多，耗时会不断增大。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (2) 服务端网络连接次数变多，对实例的性能也有一定影响。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<br /><span style="background-color: #ffff00;">4. 结论：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 用一句通俗的话总结：更多的机器不代表更多的性能，所谓&#8220;无底洞&#8221;就是说投入越多不一定产出越多。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;&nbsp;分布式又是不可以避免的，因为我们的网站访问量和数据量越来越大，一个实例根本坑不住，所以如何高效的在分布式缓存和存储批量获取数据是一个难点。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 24px;">二、哈希存储与顺序存储</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;在分布式存储产品中，哈希存储与顺序存储是两种重要的数据存储和分布方式，这两种方式不同也直接决定了批量获取数据的不同，所以这里需要对这两种数据的分布式方式进行简要说明：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;<span style="background-color: #ffff00;">1. hash分布：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;hash分布应用于大部分key-value系统中，例如memcache, redis-cluster, twemproxy，即使像mysql在分库分表时候，也经常会用user%100这样的方式。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;hash分布的主要作用是将key均匀的分布到各个机器，所以它的一个特点就是数据分散度较高，实现方式通常是hash(key)得到的整数再和分布式节点的某台机器做映射，以redis-cluster为例子：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;<img width="598" alt="" height="294" src="http://dl2.iteye.com/upload/attachment/0113/6187/1b8483c0-502e-3a3b-93ca-4bcf12b01e89.jpg" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;问题：和业务没什么关系，不支持范围查询。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;&nbsp;<span style="background-color: #ffff00;">2. 顺序分布</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<img alt="" src="http://dl2.iteye.com/upload/attachment/0113/6193/def09084-469c-3513-9dab-8e789a2c20c2.jpg" style="border: 0px; line-height: 1.5;" />&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">&nbsp;3. 两种分布方式的比较：</span></p><table style="font-size: 14px; border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 275px; width: 696px; background-color: #ffffff;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">分布方式</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">特点</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">典型产品</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">哈希分布</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1. 数据分散度高</p> <p style="margin: 0px; padding: 0px;">2.键值分布与业务无关</p> <p style="margin: 0px; padding: 0px;">3.无法顺序访问</p> <p style="margin: 0px; padding: 0px;">4.支持批量操作</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">一致性哈希memcache</p> <p style="margin: 0px; padding: 0px;">redisCluster</p> <p style="margin: 0px; padding: 0px;">其他缓存产品</p> </td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">顺序分布</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.数据分散度易倾斜</p> <p style="margin: 0px; padding: 0px;">2.键值分布与业务相关</p> <p style="margin: 0px; padding: 0px;">3.可以顺序访问</p> <p style="margin: 0px; padding: 0px;">4.支持批量操作</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">BigTable</p> <p style="margin: 0px; padding: 0px;">Hbase</p> </td></tr></tbody></table><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 24px;">三、分布式缓存/存储四种Mget解决方案</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">1. IO的优化思路：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (1) 命令本身的效率：例如sql优化，命令优化</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (2) 网络次数：减少通信次数</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (3)&nbsp;降低接入成本:长连/连接池,NIO等。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (4)&nbsp;IO访问合并:O(n)到O(1)过程:批量接口(mget),</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">2. &nbsp;如果只考虑减少网络次数的话，mget会有如下模型</span>：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><img width="666" alt="" height="411" src="http://dl2.iteye.com/upload/attachment/0113/6195/8dc22bf0-c55a-3a29-8285-4a11ef0c67be.jpg" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">3. 四种解决方案：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">(1).串行mget</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">将Mget操作(n个key)拆分为逐次执行N次get操作, 很明显这种操作时间复杂度较高，<span style="background-color: #ffcc00;">它的操作时间=n次网络时间+n次命令时间</span>，网络次数是n，很显然这种方案不是最优的，但是足够简单。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<img width="539" alt="" height="326" src="http://dl2.iteye.com/upload/attachment/0113/5649/734b8416-0dde-3ea5-bd85-6b19c058d8ee.png" style="border: 0px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">(2). 串行IO</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 将Mget操作(n个key)，利用已知的hash函数算出key对应的节点，这样就可以得到一个这样的关系：Map&lt;node, somekeys&gt;，也就是每个节点对应的一些keys</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;<span style="background-color: #ffcc00;">它的操作时间=node次网络时间+n次命令时间</span>，网络次数是node的个数，很明显这种方案比第一种要好很多，但是如果节点数足够多，还是有一定的性能问题。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><img alt="" src="http://dl2.iteye.com/upload/attachment/0113/5599/a6e24459-5bca-3c42-b555-97f3c7c2d4f7.png" title="点击查看原始大小图片" width="700" height="408" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">(3). 并行IO</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;此方案是将方案（2）中的最后一步，改为多线程执行，网络次数虽然还是nodes.size()，但网络时间变为o(1)，但是这种方案会增加编程的复杂度。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;<span style="background-color: #ffcc00;">它的操作时间=1次网络时间+n次命令时间</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<img alt="" src="http://dl2.iteye.com/upload/attachment/0113/5653/668355e5-34f7-30a2-aee3-b4eb8b8dae68.png" title="点击查看原始大小图片" width="700" height="385" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">(4). hash-tag实现。</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 第二节提到过，由于hash函数会造成key随机分配到各个节点，那么有没有一种方法能够强制一些key到指定节点到指定的节点呢?</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; redis提供了这样的功能，叫做hash-tag。什么意思呢？假如我们现在使用的是redis-cluster（10个redis节点组成），我们现在有1000个k-v，那么按照hash函数(crc16)规则，这1000个key会被打散到10个节点上，那么时间复杂度还是上述(1)~(3)</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;<img width="603" alt="" height="360" src="http://dl2.iteye.com/upload/attachment/0113/5655/0cc9ab10-39ec-3114-a82a-68cb7d5075e4.png" style="border: 0px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 那么我们能不能像使用单机redis一样，一次IO将所有的key取出来呢？hash-tag提供了这样的功能，如果将上述的key改为如下，也就是用大括号括起来相同的内容，那么这些key就会到指定的一个节点上。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;例如：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;<img width="614" alt="" height="328" src="http://dl2.iteye.com/upload/attachment/0113/5657/8b42b6fb-91d0-367b-b72d-fd01f81c78d4.png" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;</p><div id="" style="font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; width: 679px; margin-left: 9px; padding-right: 1px; padding-bottom: 1px; padding-left: 1px; word-break: break-all; word-wrap: break-word; line-height: 25.2px; background-color: #ffffff;"><div><div style="padding-right: 3px; padding-bottom: 3px; padding-left: 3px; margin: 0px; font-weight: bold;">Java代码&nbsp;&nbsp;<a title="收藏这段代码" style="color: #108ac6; text-decoration: underline;"><img src="http://carlosfu.iteye.com/images/icon_star.png" alt="收藏代码" style="border: 0px;" /></a></div></div><ol start="1" style="font-size: 1em; line-height: 1.4em; margin-left: 0px; padding-top: 2px; padding-bottom: 2px; border: 1px solid #d1d7dc; color: #2b91af;"><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">user1,user2,user3......user1000&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">{user}<span style="color: #c00000;">1</span>,{user}<span style="color: #c00000;">2</span>,{user}<span style="color: #c00000;">3</span>.......{user}<span style="color: #c00000;">1000</span>&nbsp;&nbsp;</li></ol></div><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">例如下图：<span style="line-height: 1.5; background-color: #ffcc00;">它的操作时间=1次网络时间+n次命令时间</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><img alt="" src="http://dl2.iteye.com/upload/attachment/0113/5603/b17e3697-9d98-39ae-9500-a4365a3b2c69.png" title="点击查看原始大小图片" width="700" height="327" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="background-color: #ffff00;">3. 四种批量操作解决方案对比：</span></p><table style="font-size: 14px; border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 275px; width: 696px; background-color: #ffffff;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">方案</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">优点</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">缺点</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">网络IO</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">串行mget</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.编程简单</p> <p style="margin: 0px; padding: 0px;">2.少量keys，性能满足要求</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">大量keys请求延迟严重</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">o(keys)</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">串行IO</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.编程简单</p> <p style="margin: 0px; padding: 0px;">2.少量节点，性能满足要求</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">大量node延迟严重</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">o(nodes)</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">并行IO</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.利用并行特性</p> <p style="margin: 0px; padding: 0px;">2.延迟取决于最慢的节点</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.编程复杂</p> <p style="margin: 0px; padding: 0px;">2.超时定位较难</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">o(max_slow(node))</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">hash tags</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">性能最高</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.tag-key业务维护成本较高</p> <p style="margin: 0px; padding: 0px;">2.tag分布容易出现数据倾斜</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">o(1)</td></tr></tbody></table><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 24px;">四、总结和建议</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 无底洞问题对资源和性能有一定影响，但是其实大部分系统不需要考虑这个问题，因为</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 1. 99%公司的数据和流量无法和facebook相比。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 2. redis/memcache的分布式集群通常来讲是按照项目组做隔离的，以我们经验来看一般不会超过50对主从。 &nbsp;&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 所以这里只是提供了一种优化的思路，开阔一下视野。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 24px;">五、参考文献</span></p><ol style="line-height: 1.4em; margin: 0px 0px 1.5em; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; background-color: #ffffff;"><li style="font-size: 1em; margin: 0px 0px 0.25em 30px; padding: 0px;"><a target="_blank" href="http://highscalability.com/blog/2009/10/26/facebooks-memcached-multiget-hole-more-machines-more-capacit.html" style="color: #108ac6; line-height: 1.5;">Facebook's Memcached Multiget Hole: More machines != More Capacity &nbsp;</a></li><li style="font-size: 1em; margin: 0px 0px 0.25em 30px; padding: 0px;"><a target="_blank" href="http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/05/getbulk.html" style="color: #108ac6; line-height: 1.5;">Multiget的无底洞问题</a></li><li style="font-size: 1em; margin: 0px 0px 0.25em 30px; padding: 0px;"><a target="_blank" href="http://www.cnblogs.com/zhengyun_ustc/p/multigethole.html" style="color: #108ac6; line-height: 1.5;">再说memcache的multiget hole（无底洞）</a></li><li></li></ol><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432157.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:19 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432157.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--6.缓存雪崩问题</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432156.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:16:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432156.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432156.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432156.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432156.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432156.html</trackback:ping><description><![CDATA[<h1>&nbsp;转载请注明出处哈:<a href="http://carlosfu.iteye.com/blog/2269678" target="_blank" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px; background-color: #ffffff;" /><h1>&nbsp;</h1><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;一、什么是缓存雪崩</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;从下图可以很清晰出什么是缓存雪崩：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; 1. 由于Cache层承载着大量请求，有效的保护了Storage层(通常认为此层抗压能力稍弱)，所以Storage的调用量实际很低，所以它很爽。<img title="大笑" alt="大笑" border="0" src="http://www.iteye.com/javascripts/tinymce/plugins/emotions/img/smiley-laughing.gif" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; 2. 但是，如果Cache层由于某些原因(宕机、cache服务挂了或者不响应了)整体crash掉了，也就意味着所有的请求都会达到Storage层，所有Storage的调用量会暴增，所以它有点扛不住了，甚至也会挂掉&nbsp;<img title="哭" alt="哭" border="0" src="http://www.iteye.com/javascripts/tinymce/plugins/emotions/img/smiley-cry.gif" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><div style="border: 1px solid #cccccc; margin: 0px 5px 5px 15px; padding: 3px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background: #fafafa;">雪崩问题在国外叫做：stampeding herd(奔逃的野牛)，指的的cache crash后，流量会像奔逃的野牛一样，打向后端</div><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;<img width="693" alt="" height="332" src="http://dl2.iteye.com/upload/attachment/0112/7317/bbbbce96-6dae-39c2-93da-35be15ff0b1e.png" style="border: 0px; line-height: 25.1875px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 二、 缓存雪崩的危害</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;雪崩的危害显而易见，通常来讲可能很久以前storage已经扛不住大量请求了，于是加了cache层，所以雪崩会使得storage压力山大，甚至是挂掉。&nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 三、如何预防缓存雪崩</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 1. 保证Cache服务高可用性：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp; 和飞机都有多个引擎一样，如果我们的cache也是高可用的，即使个别实例挂掉了，影响不会很大（主从切换或者可能会有部分流量到了后端），实现自动化运维。例如：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;memcache的一致性hash：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;<img width="494" alt="" height="372" src="http://dl2.iteye.com/upload/attachment/0112/7325/d7765861-722a-3d5e-8597-d6ad3b37debf.png" style="border: 0px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;redis的sentinel和cluster机制：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;<img alt="" src="http://dl2.iteye.com/upload/attachment/0112/7327/672597df-b788-322b-b79c-0bc43d5900b2.png" style="border: 0px; line-height: 1.5;" /><img width="505" alt="" height="481" src="http://dl2.iteye.com/upload/attachment/0114/6587/954a00a3-b048-34c4-b0d2-7c017ccf5108.jpg" style="border: 0px; color: #333333; font-family: Arial, sans-serif; font-size: 20px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;有关memcache和redis的高可用方案，之后会有文章进行介绍。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 2. 依赖隔离组件为后端限流：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; 其实无论是cache或者是mysql, hbase, 甚至别人的API，都会出现问题，我们可以将这些视同为资源，作为并发量较大的系统，假如有一个资源不可访问了，即使设置了超时时间，依然会hang住所有线程，造成其他资源和接口也不可以访问。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; 相信大家一定遇到过这样的页面：这些应该就是淘宝的降级策略。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;<img width="206" alt="" height="308" src="http://dl2.iteye.com/upload/attachment/0112/7337/2768b579-a96c-3082-92db-2f03e7c56cae.jpg" style="border: 0px;" /><br />&nbsp; &nbsp; &nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;降级在高并发系统中是非常正常的：比如推荐服务中，很多都是个性化的需求，假如个性化需求不能提供服务了，可以降级补充热点数据，不至于造成前端页面是个大空白（开了天窗了）</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;在实际项目中，我们对重要的资源都进行隔离，比如hbase, elasticsearch, zookeeper, redis，别人的api(可能是http, rpc)，让每种资源都单独运行在自己的线程池中，即使资源出现了问题，对其他服务没有影响。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;但是线程池如何管理，比如如何关闭资源池，开启资源池，资源池阀值管理，这些做起来还是相当麻烦的，幸好netfilx公司提供了一个很牛逼的工具：hystrix，可以做各种资源的线程池隔离。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp; 有关hystrix的详细介绍可以参考：<a href="http://hot66hot.iteye.com/blog/2155036" target="_blank" style="color: #108ac6;">http://hot66hot.iteye.com/blog/2155036</a></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp; hystrix附图：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;<img width="640" alt="" height="582" src="http://dl2.iteye.com/upload/attachment/0103/1037/887e7862-578a-3616-a15c-1ef1cb62f3c4.png" style="border: 0px;" /><img width="640" alt="" height="582" src="http://dl2.iteye.com/upload/attachment/0103/1039/3bd6d0be-9ce5-35c2-bbd9-3493671b45d5.png" style="border: 0px;" /><img width="640" alt="" height="583" src="http://dl2.iteye.com/upload/attachment/0103/1041/22f20da1-4096-314c-8c9f-5728251c46af.png" style="border: 0px;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 提前演练：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;在项目上线前，通过演练，观察cache crash后，整体系统和storage的负载, 提前做好预案。 &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;<br /><img alt="" src="http://dl2.iteye.com/upload/attachment/0112/7341/d5e5ce3a-3b14-3138-8203-4554db8d0a22.jpg" style="border: 0px;" /><br />&nbsp;</p><div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432156.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:16 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432156.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--5.缓存穿透问题</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432155.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:14:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432155.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432155.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432155.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432155.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432155.html</trackback:ping><description><![CDATA[<div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><h1>转载请注明出处哈:<a href="http://carlosfu.iteye.com/blog/2269678" target="_blank" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px;" /><h1><strong style="line-height: 20px; font-size: 14px;">&nbsp;</strong></h1><span style="font-size: 16px; background-color: #ffff00;">&nbsp;&nbsp;一.&nbsp;缓存穿透&nbsp;（请求数据缓存大量不命中）：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 缓存穿透是指查询一个一定不存在的数据，由于缓存不命中，并且出于容错考虑，&nbsp;如果从存储层查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到存储层去查询，失去了缓存的意义。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 例如：下图是一个比较典型的cache-storage架构，cache(例如memcache, redis等等) + storage(例如mysql, hbase等等)架构，查一个压根就不存在的值, 如果不做兼容，永远会查询storage。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><img src="http://dl2.iteye.com/upload/attachment/0112/2335/480d9fab-3c9a-34b5-97c9-8833f4efbf47.png" height="396" alt="" width="325" style="border: 0px; font-size: 12px; color: #003366;" /></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><img alt="" style="border: 0px; display: inline-block; margin-top: 8px; max-width: 800px; background-color: inherit;" /></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px; background-color: #ffff00;">二. 危害：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp;对底层数据源(mysql, hbase, http接口, rpc调用等等)压力过大，有些底层数据源不具备高并发性。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp;例如mysql一般来说单台能够扛1000-QPS就已经很不错了（别说你的查询都是select * from table where id=xx 以及你的机器多么牛逼，那就有点矫情了）</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp;例如他人提供的一个抗压性很差的http接口，可能穿透会击溃他的服务。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;<img src="http://dl2.iteye.com/upload/attachment/0112/2422/fece197e-52fe-3c0f-a9e3-c38849bfc585.gif" height="67" alt="" width="69" style="border: 0px;" /></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px; background-color: #ffff00;">三. 如何发现：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp;我们可以分别记录cache命中数, storage命中数，以及总调用量，如果发现空命中（cache,storage都没有命中）较多，可能就会在缓存穿透问题。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp;注意：缓存本身的命中率（例如redis中的info提供了类似数字，只代表缓存本身）不代表storage和业务的命中率。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp;</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px; background-color: #ffff00;">四. 产生原因以及业务是否允许？</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 产生原因有很多：可能是代码本身或者数据存在的问题造成的，也很有可能是一些恶意攻击、爬虫等等（因为http读接口都是开放的）</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 业务是否允许：这个要看做的项目或者业务是否允许这种情况发生，比如做一些非实时的推荐系统，假如新用户来了，确实没有他的推荐数据（推荐数据通常是根据历史行为算出），这种业务是会发生穿透现象的，至于业务允不允许要具体问题具体分析了。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp;</div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px; background-color: #ffff00;">五. 解决方法：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">解决思路大致有两个，如下表。下面将分别说明</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><table style="border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 214px; width: 645px;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">解决缓存穿透</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">适用场景</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">维护成本</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">缓存空对象</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1. 数据命中不高</p> <p style="margin: 0px; padding: 0px;">2. 数据频繁变化实时性高</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.代码维护简单</p> <p style="margin: 0px; padding: 0px;">2.需要过多的缓存空间</p> <p style="margin: 0px; padding: 0px;">3. 数据不一致</p> </td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">bloomfilter或者压缩filter提前拦截</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1. 数据命中不高</p> <p style="margin: 0px; padding: 0px;">2. 数据相对固定实时性低</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1.代码维护复杂</p> <p style="margin: 0px; padding: 0px;">2.缓存空间占用少</p> </td></tr></tbody></table></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp; &nbsp; &nbsp;&nbsp;<span style="font-size: 16px;">&nbsp;1. 缓存空对象</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<img src="http://dl2.iteye.com/upload/attachment/0112/2340/ebf18dcb-6ed5-364b-b687-22946d279401.png" height="380" alt="" width="260" style="border: 0px;" /></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp; &nbsp; &nbsp; &nbsp;<span style="font-size: 16px;">&nbsp;(1). 定义：如上图所示，当第&#9313;步MISS后，仍然将空对象保留到Cache中（可能是保留几分钟或者一段时间，具体问题具体分析），下次新的Request（同一个key）将会从Cache中获取到数据，保护了后端的Storage。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; (2) 适用场景：数据命中不高，数据频繁变化实时性高（一些乱转业务）</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; (3) 维护成本：代码比较简单，但是有两个问题：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;第一是空值做了缓存，意味着缓存系统中存了更多的key-value，也就是需要更多空间（有人说空值没多少，但是架不住多啊），解决方法是我们可以设置一个较短的过期时间。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;第二是数据会有一段时间窗口的不一致，假如，Cache设置了5分钟过期，此时Storage确实有了这个数据的值，那此段时间就会出现数据不一致，解决方法是我们可以利用消息或者其他方式，清除掉Cache中的数据。</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; (4) 伪代码：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><div id="" style="font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; width: 679px; margin-left: 9px; padding-right: 1px; padding-bottom: 1px; padding-left: 1px; word-break: break-all; word-wrap: break-word; background-color: transparent;"><div><div style="padding-right: 3px; padding-bottom: 3px; padding-left: 3px; margin: 0px; font-weight: bold;">Java代码&nbsp;&nbsp;<a title="收藏这段代码" style="color: #108ac6; text-decoration: underline;"><img src="http://carlosfu.iteye.com/images/icon_star.png" alt="收藏代码" style="border: 0px;" /></a></div></div><ol start="1" style="font-size: 1em; line-height: 1.4em; margin-left: 0px; padding-top: 2px; padding-bottom: 2px; border: 1px solid #d1d7dc; color: #2b91af;"><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="color: #7f0055;">package</span>&nbsp;com.carlosfu.service;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="color: #7f0055;">import</span>&nbsp;org.apache.commons.lang.StringUtils;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="color: #7f0055;">import</span>&nbsp;com.carlosfu.cache.Cache;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="color: #7f0055;">import</span>&nbsp;com.carlosfu.storage.Storage;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">/**</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*&nbsp;某服务</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*&nbsp;</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*&nbsp;@author&nbsp;carlosfu</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*&nbsp;@Date&nbsp;2015-10-11</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*&nbsp;@Time&nbsp;下午6:28:46</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;*/</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="color: #7f0055;">public</span>&nbsp;<span style="color: #7f0055;">class</span>&nbsp;XXXService&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">/**</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;缓存</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">private</span>&nbsp;Cache&nbsp;cache&nbsp;=&nbsp;<span style="color: #7f0055;">new</span>&nbsp;Cache();&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">/**</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;存储</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">private</span>&nbsp;Storage&nbsp;storage&nbsp;=&nbsp;<span style="color: #7f0055;">new</span>&nbsp;Storage();&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">/**</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;模拟正常模式</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;key</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">public</span>&nbsp;String&nbsp;getNormal(String&nbsp;key)&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;从缓存中获取数据</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;cacheValue&nbsp;=&nbsp;cache.get(key);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;缓存为空</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">if</span>&nbsp;(StringUtils.isBlank(cacheValue))&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;从存储中获取</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;storageValue&nbsp;=&nbsp;storage.get(key);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;如果存储数据不为空,将存储的值设置到缓存</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">if</span>&nbsp;(StringUtils.isNotBlank(storageValue))&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.set(key,&nbsp;storageValue);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">return</span>&nbsp;storageValue;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #7f0055;">else</span>&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;缓存非空</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">return</span>&nbsp;cacheValue;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">/**</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;模拟防穿透模式</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;key</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return</span>&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;"><span style="width: auto; border-width: 0px; border-style: initial;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">public</span>&nbsp;String&nbsp;getPassThrough(String&nbsp;key)&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;从缓存中获取数据</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;cacheValue&nbsp;=&nbsp;cache.get(key);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;缓存为空</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">if</span>&nbsp;(StringUtils.isBlank(cacheValue))&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;从存储中获取</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;storageValue&nbsp;=&nbsp;storage.get(key);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.set(key,&nbsp;storageValue);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;如果存储数据为空，需要设置一个过期时间(300秒)</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">if</span>&nbsp;(StringUtils.isBlank(storageValue))&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cache.expire(key,&nbsp;<span style="color: #c00000;">60</span>&nbsp;*&nbsp;<span style="color: #c00000;">5</span>);&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">return</span>&nbsp;storageValue;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #7f0055;">else</span>&nbsp;{&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="width: auto; border-width: 0px; border-style: initial;">//&nbsp;缓存非空</span>&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #7f0055;">return</span>&nbsp;cacheValue;&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">}&nbsp;&nbsp;</li></ol></div>&nbsp;</div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">2. bloomfilter或者压缩filter(bitmap等等)提前拦截</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><img src="http://dl2.iteye.com/upload/attachment/0112/2344/28fd1304-fba6-3bdd-aab7-c8ff6cd7fcb6.png" alt="" style="border: 0px;" /></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><div style="line-height: 2.25;">&nbsp; &nbsp; &nbsp;&nbsp;<span style="font-size: 16px;">&nbsp; (1). 定义：如上图所示，在访问所有资源(cache, storage)之前，将存在的key用布隆过滤器提前保存起来，做第一层拦截,&nbsp;例如： 我们的推荐服务有4亿个用户uid, 我们会根据用户的历史行为进行推荐（非实时），所有的用户推荐数据放到hbase中，但是每天有许多新用户来到网站，这些用户在当天的访问就会穿透到hbase。为此我们每天4点对所有uid做一份布隆过滤器。如果布隆过滤器认为uid不存在，那么就不会访问hbase，在一定程度保护了hbase（减少30%左右）。</span></div><div style="line-height: 2.25;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<span style="background-color: #ffff00;">&nbsp; 注：有关布隆过滤器的相关知识，请自行查阅，有关guava中如何使用布隆过滤器，之后会系列文章给大家介绍。</span></span></div><div style="line-height: 2.25;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; (2) 适用场景：数据命中不高，数据相对固定实时性低（通常是数据集较大）</span></div><div style="line-height: 2.25;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; (3) 维护成本：代码维护复杂,&nbsp;缓存空间占用少</span></div><div style="line-height: 2.25;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 第一是空值做了缓存，意味着缓存系统中存了更多的key-value，也就是需要更多空间（有人说空值没多少，但是架不住多啊），解决方法是我们可以设置一个较短的过期时间。</span></div><div style="line-height: 2.25;"><span style="font-size: 16px;">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 第二是数据会有一段时间窗口的不一致，假如，Cache设置了5分钟过期，此时Storage确实有了这个数据的值，那此段时间就会出现数据不一致，解决方法是我们可以利用消息或者其他方式，清除掉Cache中的数据。</span></div></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px; background-color: #ffff00;">六、参考资料：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><div style="line-height: 2.25;"><span style="font-size: 16px;"><a href="http://www.xwuxin.com/?p=1938" style="color: #108ac6; cursor: pointer; background-color: inherit;">http://www.xwuxin.com/?p=1938</a></span></div><div style="line-height: 2.25;"><span style="font-size: 16px;"><a href="http://blog.jobbole.com/83439/" style="color: #108ac6; cursor: pointer; background-color: inherit;">http://blog.jobbole.com/83439/</a>&nbsp;（那些年我们一起追过的缓存写法）</span></div><div style="line-height: 2.25;"><span style="font-size: 16px;"><a href="http://www.tuicool.com/articles/7jMZFzj" style="color: #108ac6; cursor: pointer; background-color: inherit;">http://www.tuicool.com/articles/7jMZFzj</a></span></div></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;"><span style="font-size: 16px;">附图一张，单机负载，哈哈：</span></div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp;<img src="http://dl2.iteye.com/upload/attachment/0112/2420/9b0dd6d7-7521-30d2-a3f8-a17ea6f40309.png" alt="" title="点击查看原始大小图片" width="700" height="538" style="border: 0px; cursor: url(&quot;/images/magplus.gif&quot;), pointer;" />&nbsp;</div><div style="font-family: 微软雅黑; line-height: 2.25; background-color: #ffffff;">&nbsp;</div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432155.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:14 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432155.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--4.缓存的粒度控制</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432154.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:13:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432154.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432154.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432154.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432154.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432154.html</trackback:ping><description><![CDATA[<h1>转载请注明出处哈:<a href="http://carlosfu.iteye.com/blog/2269678" target="_blank" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px; background-color: #ffffff;" /><h1><strong style="line-height: 20px; font-size: 14px;">&nbsp;</strong>&nbsp;&nbsp;</h1><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 20px;">&nbsp;一、什么是缓存粒度</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 下面这个图是很多项目关于缓存使用最常用的一个抽象，那么我们假设storage层为mysql, cache层为redis。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;<img src="http://dl2.iteye.com/upload/attachment/0114/4088/9b296cc5-5751-3fee-a562-0ae981573c00.png" alt="" style="border: 0px; line-height: 1.5;" /></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 假如我现在需要对视频的信息做一个缓存，也就是需要对select * from video where id=?的每个id在redis里做一份缓存，这样cache层就可以帮助我抗住很多的访问量（注：这里不讨论一致性和架构等等问题，只讨论缓存的粒度问题）。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 我们假设视频表有100个属性（这个真有，有些人可能难以想象），那么问题来了，需要缓存什么维度呢，也就是有两种选择吧：</p><div id="" style="font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; width: 679px; margin-left: 9px; padding-right: 1px; padding-bottom: 1px; padding-left: 1px; word-break: break-all; word-wrap: break-word; line-height: 25.2px; background-color: #ffffff;"><div><div style="padding-right: 3px; padding-bottom: 3px; padding-left: 3px; margin: 0px; font-weight: bold;">Java代码&nbsp;&nbsp;<a title="收藏这段代码" style="color: #108ac6; text-decoration: underline;"><img src="http://carlosfu.iteye.com/images/icon_star.png" alt="收藏代码" style="border: 0px;" /></a></div></div><ol start="1" style="font-size: 1em; line-height: 1.4em; margin-left: 0px; padding-top: 2px; padding-bottom: 2px; border: 1px solid #d1d7dc; color: #2b91af;"><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">(<span style="color: #c00000;">1</span>)cache(id)=select&nbsp;*&nbsp;from&nbsp;video&nbsp;where&nbsp;id=#id&nbsp;&nbsp;</li><li style="font-size: 1em; margin-left: 38px; padding-right: 0px; border-left-width: 1px; border-left-color: #d1d7dc; line-height: 18px; background-color: #fafafa;">(<span style="color: #c00000;">2</span>)cache(id)=select&nbsp;importantColumn1,&nbsp;importantColumn2&nbsp;..&nbsp;importantColumnN&nbsp;from&nbsp;video&nbsp;where&nbsp;id=#id&nbsp;&nbsp;</li></ol></div><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 其实这个问题就是缓存粒度问题，我们在缓存设计应该佮预估和考虑呢？下面我们将从通用性、空间、代码维护三个角度进行说明。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 20px;">二、全部数据和部分数据比较</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">1. 两者的特点是显而易见的：</p><table style="font-size: 12px; border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 275px; width: 696px; background-color: #ffffff;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">数据类型</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">通用性</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">空间占用（内存空间 + 网络码率）</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">代码维护</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">全部数据</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">高</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">大</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">简单&nbsp;</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">部分数据</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">低</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">小</p> <p style="margin: 0px; padding: 0px;">&nbsp;</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">&nbsp;较为复杂</td></tr></tbody></table><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2. 通用性：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 如果单从通用性上看，全部数据是最优秀的，但是有个问题就是是否有必要缓存全部数据，认为以后会有这样的需求，但是从经验看除了非常重要的信息，那些不重要的字段基本不会在需求里出现，也就是说这种通用性 通常都是想象出来的。太多人觉得通用性是最重要的。vid拿一些基本信息，会想专辑明星。。要不要用通用性高的，于是加了全局的，通用性很重要，但是要想清楚。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 空间占用：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 很显然，缓存全部数据，会占用大量的内存，有人会说，不就费一点内存吗，能有多少钱？而且已经有人习惯了把缓存当做下水道来使用，什么都框框的往里面放，但是我这里要说内存并不是免费的，可以说是很珍贵的资源。<a href="http://instagram-engineering.tumblr.com/post/12202313862/storing-hundreds-of-millions-of-simple-key-value" target="_blank" style="color: #108ac6;">instagram21-&gt;4G</a>的例子就说明了这个道理，好的程序员可以帮助公司节约大量的资源。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 而且单个cache(id)也带来两个问题：序列化的开销和网络流量的开销（QPS,百倍），都是无容忽视的。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">4. 代码维护：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 代码维护性，全部数据的优势更加明显，而部分数据一旦要加新字段就会修改代码，而且还需要对原来的数据进行刷新。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 20px;">三、总结：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;缓存粒度问题是一个容易被忽视的问题，如果使用不当，可能会造成很多无用空间的浪费，可能会造成网络带宽的浪费，可能会造成代码通用性较差等情况，必须学会综合<span style="line-height: 1.5;">数据通用性、空间占用比、代码维护性 三点评估取舍</span><span style="line-height: 1.5;">因素权衡使用。</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><br /></p><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432154.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:13 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432154.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--2.是否真的需要缓存？</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432152.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:12:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432152.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432152.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432152.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432152.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432152.html</trackback:ping><description><![CDATA[<h1>转载请注明出处哈:<a href="http://carlosfu.iteye.com/blog/2269678" target="_blank" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px; background-color: #ffffff;" /><h1><strong style="line-height: 20px; font-size: 14px;">&nbsp;</strong></h1><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">一、缓存的成本和收益是什么：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 既然要讨论是否真的需要缓存这个问题，就要知道缓存带来的成本与收益（好处、坏处）是什么？</p><table style="font-size: 12px; border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 79px; width: 693px; background-color: #ffffff;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">&nbsp;</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">收益</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">成本</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">缓存 + 后端存储(资源)</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1. 加速读写</p> <p style="margin: 0px; padding: 0px;">2. 降低后端负载</p> </td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;"> <p style="margin: 0px; padding: 0px;">1. 数据不一致性</p> <p style="margin: 0px; padding: 0px;">2. 代码维护成本</p> <p style="margin: 0px; padding: 0px;">3. 架构复杂度</p> <p style="margin: 0px; padding: 0px;">&nbsp;</p> </td></tr></tbody></table><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 上面的表格应该清楚的表达了使用缓存后的收益和成本分别是什么。下面将进行详细的解析</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">二、缓存成本与收益详解：</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 1. 收益是很明显的，通常来说一个设计还不错的缓存系统，能够帮助你的业务实现加速读写，同时帮助降低了后端负载。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;(1) 加速读写：通常来说加速是明显的，因为缓存通常都是全内存的系统，而后端（可能是mysql、甚至是别人的HTTP, RPC接口）都有速度慢和抗压能力差的特性，通过缓存的使用可以有效的提高用户的访问速度同时优化了用户的体验。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;(2) 降低后端负载：通过缓存的添加，如果程序没有什么问题，在命中率还可以的情况下，可以帮助后端减少访问量和复杂计算(join、或者无法在优化的sql等)，在很大程度降低了后端的负载。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 2. 成本：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;(1) 数据不一致性：无论你的设计做的多么好，缓存数据与权威数据源（可以理解成真实或者后端数据源）一定存在着一定时间窗口的数据不一致性，这个时间窗口的大小可大可小，具体多大还要看一下你的业务允许多大时间窗口的不一致性。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;(2) 代码维护成本：加入缓存后，代码就会在原数据源基础上加入缓存的相关代码，例如原来只是一些sql, 现在要加入k-v缓存，必然增加了代码的维护成本。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;(3) 架构复杂度：加入缓存后，例如加入了redis-cluster，一般来说缓存不会像Mysql有专门的DBA，很有可能没有专职的管理人员，所以也增加了架构的复杂度和维护成本。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">三、如何选择？</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 如果当前系统的访问速度和访问量能够满足现有的要求，就不必增加缓存，其实像mysql并没有那么差，一台运行良好的Mysql，扛个QPS=1000没什么问题。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 如果要加入选择了缓存，一定要能给出足够的理由，不是为了简单的show技术和想当然，最好的方法就是用数据说话：加速比有多少、后端负载降低了多少。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">四、什么样的场景需要缓存？</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 在公司里，据我观察，无论怎么更新架构，使用各种新技术，但是80%的项目还是离不开SQL的，下面我们以SQL作为后端数据源、以Redis作为缓存层，说一下哪些场景是需要缓存的。</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">1、复杂开销大的计算、降低后端负载</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 以Mysql为例子，一些复杂的操作或者计算(例如大量联表操作、一些分组计算)，如果不加</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">缓存，大量流量将在这些复杂计算的执行。</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">2. 加速请求响应</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><span style="font-size: 16px;">&nbsp; &nbsp; 即使单条后端数据足够快（例如select * from table where id=?），那么依然可以利用redis/memcache将这些操作进行merge做优化(例如：cache(select * from table where id in(id1,id10....idK)))，从而优化整个IO链的相应时间。</span></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">附图一张：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;<img alt="" src="http://dl2.iteye.com/upload/attachment/0114/4222/7f341d15-8fcb-3432-a49a-93b87657e2d6.png" style="border: 0px; line-height: 1.5;" /></p><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432152.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:12 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432152.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>缓存系列文章--3.缓存常用更新策略对比(一致性)。</title><link>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432153.html</link><dc:creator>jinfeng_wang</dc:creator><author>jinfeng_wang</author><pubDate>Tue, 20 Dec 2016 09:12:00 GMT</pubDate><guid>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432153.html</guid><wfw:comment>http://www.blogjava.net/jinfeng_wang/comments/432153.html</wfw:comment><comments>http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432153.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/jinfeng_wang/comments/commentRss/432153.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/jinfeng_wang/services/trackbacks/432153.html</trackback:ping><description><![CDATA[<h1>转载请注明出处哈:<a href="http://carlosfu.iteye.com/blog/2269678" target="_blank" style="color: #108ac6; line-height: 1.5;">http://carlosfu.iteye.com/blog/2269678</a></h1><hr style="height: 1px; margin: 1.5em 10px; border-bottom-width: thin; border-bottom-color: black; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.1875px; background-color: #ffffff;" /><h1><strong style="line-height: 20px; font-size: 14px;">&nbsp;</strong><strong style="font-size: 14px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 1.5;">一、缓存的几种更新策略</strong></h1><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 从下面的表格看，缓存的更新策略大致分为三种，本文将从一致性和维护成本两个方面对于三种缓存更新策略进行简要说明，因为这些东西比较理论和抽象，如哪里说得不对，欢迎拍砖。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; 注：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (1) 一致性：缓存和真实数据源（例如mysql, hbase, elasticsearch等等）是否存在一段时间数据的不一致。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; (2) 维护成本: 开发人员的开发和维护成本。</p><table style="font-size: 14px; border-collapse: collapse; margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; height: 214px; width: 645px; background-color: #ffffff;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">策略</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">一致性</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">维护成本</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">LRU/LIRS/FIFO算法剔除</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">最差</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">低</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">超时剔除</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">较差</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">较低</td></tr><tr><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">主动更新</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">强</td><td style="font-size: 1em; white-space: pre-wrap; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">高</td></tr></tbody></table><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>二、</strong><strong>LRU/LFU/FIFO算法剔除</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">1. 使用场景：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 通常用于缓存使用量超过了预设的最大值时候（缓存空间不够），如何对现有的数据进行清理。例如FIFO会把最新进入缓存的数据清理出去, LRU会把最近最少使用的数据清理掉。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">例如：Memcache使用的是LRU，具体Memcache如何实现的，这里就不在赘述了，网上资料多的是。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">例如：Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的更新策略。</p><div style="margin: 10px 0px 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; background-color: #ffffff;"><table tablesorter"="" style="border-collapse: collapse; margin: 0px;"><tbody><tr style="background-color: #29c3f3;"><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">配置名</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">含义</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">默认值</td></tr><tr><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">maxmemory</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">最大可用内存</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">不使用该配置，也就对内存使用无限制</td></tr><tr><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">maxmemory-policy</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">内存不够时,淘汰策略</td><td style="font-size: 1em; border-style: solid; border-color: #dddddd; padding: 7px 10px; vertical-align: top;">volatile-lru</td></tr></tbody></table></div><ul style="margin: 10px 0px 0px; padding: 0px; color: #333333; font-family: Arial, sans-serif; line-height: 20px; background-color: #ffffff;"><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">volatile-lru -&gt; 用lru算法删除过期的键值</li><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">allkeys-lru -&gt; 用lru算法删除所有键值</li><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">volatile-random -&gt; 随机删除过期的键值</li><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">allkeys-random -&gt; 随机删除任何键值</li><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">volatile-ttl -&gt; 删除最近要到期的键值</li><li style="margin: 0px 0px 0.25em 30px; padding: 0px;">noeviction -&gt; 不删除键，只返回一个错误</li></ul><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2. 常用算法：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">这里不再赘述，常用的算法有如下几种：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">FIFO[first in first out]&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">LFU[Less Frequently Used]&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">LRU[Least Recently used]&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 一致性</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">可以想象，要清理哪些数据，不是由开发者决定（只能决定大致方向：策略算法），数据的一致性是最差的。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">4. 维护成本</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">这些算法不需要开发者自己来实现，通常只需要配置最大maxmemory和对应的策略即可。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">开发者只需要有这个东西，知道是什么意思，选择自己需要的算法，算法的实现是由缓存服务器实现的。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>三、超时剔除</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>1. 使用场景：</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp;</strong>就是我们通常做的缓存数据过期时间设置，例如redis和memcache都提供了expire这样的API，来设置K-V的过期时间。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;一般来说业务可以容忍一段时间内（例如一个小时），缓存数据和真实数据（例如：mysql, hbase等等）数据不一致（一般来说，缓存可以提高访问速度降低后端负载），那么我们可以对一个数据设置一定时间的过期时间，在数据过期后，再从真实数据源获取数据，重新放到缓存中，继续设置过期时间。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;例如： 一个视频的描述信息，我们可以容忍一个小时内数据不一致，但是涉及到钱的方面，如果不一致可想而知。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp;</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>2. 一致性：</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp;&nbsp;</strong>一段时间内（取决于过期时间）存在数据一致性问题，即缓存数据和真实数据源数据不一致。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>3. 维护成本</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp; &nbsp;</strong>&nbsp;用户的维护成本不是很高，只需要设置expire过期时间即可（前提是你的业务允许这段时间可能发生的数据不一致）。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>四、主动更新</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>1. 使用背景：</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp;</strong>业务对于数据的一致性要求很高，需要在真实数据更新后，立即更新缓存数据。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;具体做法：例如可以利用消息系统或者其他方式（比如数据库触发器，或者其他数据源的listener机制来完成）通知缓存更新。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">2. &nbsp;<strong style="line-height: 1.5;">一致性：</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong style="line-height: 1.5;">&nbsp; &nbsp;</strong>可以想象一致性最高（几乎接近强一致），但是有个问题：如果主动更新发生了问题，那么这条数据很可能很长时间不会更新了（所以可以结合超时剔除一起使用，下面最佳实践会说到）</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">3. 维护成本：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp;相当高，用户需要自己来完成更新（需要一定量的代码，从某种程度上加大了系统的复杂性），需要自己检查数据是否真的更新了之类的工作。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>五、最佳实践</strong></p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;"><strong>&nbsp; &nbsp;&nbsp;</strong>其实最佳实践就是组合使用：</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 1. 一般来说我们都需要配置超过最大缓存后的更新策略（例如：LRU）以及最大内存，这样可以保证系统可以继续运行（例如redis可能存在OOM问题）（极端情况下除外，数据一致性要求极高）。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp; &nbsp; 2. 一般来说我们需要把超时剔除和主动更新组合使用，那样即使主动更新出了问题，也能保证过期时间后，缓存就被清除了（不至于永远都是脏数据）。</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><p style="margin: 0px; padding: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2px; background-color: #ffffff;">&nbsp;</p><div></div><img src ="http://www.blogjava.net/jinfeng_wang/aggbug/432153.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/jinfeng_wang/" target="_blank">jinfeng_wang</a> 2016-12-20 17:12 <a href="http://www.blogjava.net/jinfeng_wang/archive/2016/12/20/432153.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>