分布式一致性协议Raft原理与实例

来源:http://m635674608.iteye.com/blog/2283621

1.Raft协议

1.1 Raft简介

Raft是由Stanford提出的一种更易理解的一致性算法,意在取代目前广为使用的Paxos算法。目前,在各种主流语言中都有了一些开源实现,比如本文中将使用的基于JGroups的Raft协议实现。关于Raft的原理,强烈推荐动画版Raft讲解。

1.2 Raft原理

在Raft中,每个结点会处于下面三种状态中的一种:

  • follower:所有结点都以follower的状态开始。如果没收到leader消息则会变成candidate状态
  • candidate:会向其他结点“拉选票”,如果得到大部分的票则成为leader。这个过程就叫做Leader选举(Leader Election)
  • leader:所有对系统的修改都会先经过leader。每个修改都会写一条日志(log entry)。leader收到修改请求后的过程如下,这个过程叫做日志复制(Log Replication): 
    1. 复制日志到所有follower结点(replicate entry)
    2. 大部分结点响应时才提交日志
    3. 通知所有follower结点日志已提交
    4. 所有follower也提交日志
    5. 现在整个系统处于一致的状态

1.2.1 Leader Election

当follower在选举超时时间(election timeout)内未收到leader的心跳消息(append entries),则变成candidate状态。为了避免选举冲突,这个超时时间是一个150~300ms之间的随机数

成为candidate的结点发起新的选举期(election term)去“拉选票”:

  1. 重置自己的计时器
  2. 投自己一票
  3. 发送 Request Vote消息

如果接收结点在新term内没有投过票那它就会投给此candidate,并重置它自己的选举超时时间。candidate拉到大部分选票就会成为leader,并定时发送心跳——Append Entries消息,去重置各个follower的计时器。当前Term会继续直到某个follower接收不到心跳并成为candidate。

如果不巧两个结点同时成为candidate都去“拉票”怎么办?这时会发生Splite Vote情况。两个结点可能都拉到了同样多的选票,难分胜负,选举失败,本term没有leader。之后又有计时器超时的follower会变成candidate,将term加一并开始新一轮的投票。

1.2.2 Log Replication

当发生改变时,leader会复制日志给follower结点,这也是通过Append Entries心跳消息完成的。前面已经列举了Log Replication的过程,这里就不重复了。

Raft能够正确地处理网络分区(“脑裂”)问题。假设A~E五个结点,B是leader。如果发生“脑裂”,A、B成为一个子分区,C、D、E成 为一个子分区。此时C、D、E会发生选举,选出C作为新term的leader。这样我们在两个子分区内就有了不同term的两个leader。这时如果 有客户端写A时,因为B无法复制日志到大部分follower所以日志处于uncommitted未提交状态。而同时另一个客户端对C的写操作却能够正确 完成,因为C是新的leader,它只知道D和E。

当网络通信恢复,B能够发送心跳给C、D、E了,却发现“改朝换代”了,因为C的term值更大,所以B自动降格为follower。然后A和B都回滚未提交的日志,并从新leader那里复制最新的日志。但这样是不是就会丢失更新?


2.JGroups-raft介绍

2.1 JGroups中的Raft

JGroups是Java里比较流行的网络通信框架,近期顺应潮流,它也推出了Raft基于JGroups的实现。简单试用了一下,还比较容易上 手,底层Raft的内部机制都被API屏蔽掉了。下面就通过一个分布式计数器的实例来学习一下Raft协议在JGroups中的实际用法。

Maven依赖如下:

Prettyprint代码   收藏代码
  1. "language-xml hljs  has-numbering">    "hljs-tag"><"hljs-title">dependency>  
  2.         "hljs-tag"><"hljs-title">groupId>org.jgroups"hljs-tag">"hljs-title">groupId>  
  3.         "hljs-tag"><"hljs-title">artifactId>jgroups-raft"hljs-tag">"hljs-title">artifactId>  
  4.         "hljs-tag"><"hljs-title">version>0.2"hljs-tag">"hljs-title">version>  
  5.     "hljs-tag">"hljs-title">dependency>  
  • 1
  • 2
  • 3
  • 4
  • 5

其实JGroups-raft的Jar包中已经自带了一个Counter的Demo,但仔细看了一下,有的地方写的有些麻烦,不太容易把握住Raft这根主线。所以这里就参照官方的例子,进行了简写,突出Raft协议的基本使用方法。JGroups-raft目前资料不多,InfoQ上的这篇文章很不错,还有官方文档。

2.2 核心API

使用JGroups-raft时,我们一般会实现两个接口:RAFT.RoleChange和StateMachine

  • 实现RAFT.RoleChange接口的方法能通知我们当前哪个结点是leader
  • 实现StateMachine执行要实现一致性的操作

典型单点服务实现方式就是:

Prettyprint代码   收藏代码
  1. "language-java hljs  has-numbering">JChannel ch = "hljs-keyword">null;  
  2. RaftHandle handle = "hljs-keyword">new RaftHandle(ch, "hljs-keyword">this);  
  3. handle.addRoleListener(role -> {  
  4.     "hljs-keyword">if(role == Role.Leader)  
  5.         "hljs-comment">// start singleton services  
  6.     "hljs-keyword">else  
  7.         "hljs-comment">// stop singleton services  
  8. });  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.3 默认配置

jgroups-raft.jar中已经带了一个raft.xml配置文件,作为实例程序我们可以直接使用它。

简要解释一下最核心的几个配置项,参照GitHub上的文档:

  • UDP:IP多播配置
  • raft.NO_DUPES:是否检测新加入结点的ID与老结点有重复
  • raft.ELECTION:选举超时时间的随机化范围
  • raft.RAFT所有Raft集群的成员必须在这里声明,也可以在运行时通过addServer/removeServer动态修改
  • raft.REDIRECT:是否转发请求给leader
  • raft.CLIENT:在哪个IP和端口上接收客户端请求
Prettyprint代码   收藏代码
  1. "language-xml hljs  has-numbering">"hljs-comment">  
  2.   
  3. "hljs-tag"><"hljs-title">config "hljs-attribute">xmlns="hljs-value">"urn:org:jgroups"  
  4.         "hljs-attribute">xmlns:xsi="hljs-value">"http://www.w3.org/2001/XMLSchema-instance"  
  5.         "hljs-attribute">xsi:schemaLocation="hljs-value">"urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd">  
  6.     "hljs-tag"><"hljs-title">UDP  
  7.          "hljs-attribute">mcast_addr="hljs-value">"228.5.5.5"  
  8.          "hljs-attribute">mcast_port="hljs-value">"${jgroups.udp.mcast_port:45588}"  
  9.          "hljs-attribute">... />  
  10.     ...  
  11.     "hljs-tag"><"hljs-title">raft.NO_DUPES/>  
  12.     "hljs-tag"><"hljs-title">raft.ELECTION "hljs-attribute">election_min_interval="hljs-value">"100" "hljs-attribute">election_max_interval="hljs-value">"500"/>  
  13.     "hljs-tag"><"hljs-title">raft.RAFT "hljs-attribute">members="hljs-value">"A,B,C" "hljs-attribute">raft_id="hljs-value">"${raft_id:undefined}"/>  
  14.     "hljs-tag"><"hljs-title">raft.REDIRECT/>  
  15.     "hljs-tag"><"hljs-title">raft.CLIENT "hljs-attribute">bind_addr="hljs-value">"0.0.0.0" />  
  16. "hljs-tag">"hljs-title">config>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

3.JGroups-raft实例

实例很简单,只有JGroupsRaftTest和CounterService两个类组成。JGroupsRaftTest是测试启动类,而CounterService就是利用Raft协议实现的分布式计数服务类。

3.1 JGroupsRaftTest

JGroupsRaftTest的职责主要有三个:

  • 创建Raft协议的JChannel
  • 创建CounterService
  • 循环读取用户输入

目前简单实现了几种操作包括:初始化计数器、加一、减一、读取计数器、查看Raft日志、做Raft快照(用于压缩日志文件)等。其中对计数器的操作,因为要与其他Raft成员进行分布式通信,所以当前集群必须要多于一个结点时才能进行操作。如果要支持单结点时的操作,需要做特殊处理

Prettyprint代码   收藏代码
  1. "language-java hljs  has-numbering">"hljs-keyword">import org.jgroups.JChannel;  
  2. "hljs-keyword">import org.jgroups.protocols.raft.RAFT;  
  3. "hljs-keyword">import org.jgroups.util.Util;  
  4.   
  5. "hljs-javadoc">/**  
  6.  * Test jgroups raft algorithm implementation.  
  7.  */  
  8. "hljs-keyword">public "hljs-class">"hljs-keyword">class "hljs-title">JGroupsRaftTest {  
  9.   
  10.     "hljs-keyword">private "hljs-keyword">static "hljs-keyword">final String CLUSTER_NAME = "hljs-string">"ctr-cluster";  
  11.     "hljs-keyword">private "hljs-keyword">static "hljs-keyword">final String COUNTER_NAME = "hljs-string">"counter";  
  12.     "hljs-keyword">private "hljs-keyword">static "hljs-keyword">final String RAFT_XML = "hljs-string">"raft.xml";  
  13.   
  14.     "hljs-keyword">public "hljs-keyword">static "hljs-keyword">void "hljs-title">main(String[] args) "hljs-keyword">throws Exception {  
  15.         JChannel ch = "hljs-keyword">new JChannel(RAFT_XML).name(args["hljs-number">0]);  
  16.         CounterService counter = "hljs-keyword">new CounterService(ch);  
  17.   
  18.         "hljs-keyword">try {  
  19.             doConnect(ch, CLUSTER_NAME);  
  20.             doLoop(ch, counter);  
  21.         } "hljs-keyword">finally {  
  22.             Util.close(ch);  
  23.         }  
  24.     }  
  25.   
  26.     "hljs-keyword">private "hljs-keyword">static "hljs-keyword">void "hljs-title">doConnect(JChannel ch, String clusterName) "hljs-keyword">throws Exception {  
  27.         ch.connect(clusterName);  
  28.     }  
  29.   
  30.     "hljs-keyword">private "hljs-keyword">static "hljs-keyword">void "hljs-title">doLoop(JChannel ch, CounterService counter) {  
  31.         "hljs-keyword">boolean looping = "hljs-keyword">true;  
  32.         "hljs-keyword">while (looping) {  
  33.             "hljs-keyword">int key = Util.keyPress("hljs-string">"\n[0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit\n" +  
  34.                     "hljs-string">"first-applied=" + ((RAFT) ch.getProtocolStack().findProtocol(RAFT.class)).log().firstApplied() +  
  35.                     "hljs-string">", last-applied=" + counter.lastApplied() +  
  36.                     "hljs-string">", commit-index=" + counter.commitIndex() +  
  37.                     "hljs-string">", log size=" + Util.printBytes(counter.logSize()) + "hljs-string">": ");  
  38.   
  39.             "hljs-keyword">if ((key == "hljs-string">'0' || key == "hljs-string">'1' || key == "hljs-string">'2') && !counter.isLeaderExist()) {  
  40.                 System.out.println("hljs-string">"Cannot perform cause there is no leader by now");  
  41.                 "hljs-keyword">continue;  
  42.             }  
  43.   
  44.             "hljs-keyword">long val;  
  45.             "hljs-keyword">switch (key) {  
  46.                 "hljs-keyword">case "hljs-string">'0':  
  47.                     counter.getOrCreateCounter(COUNTER_NAME, "hljs-number">1L);  
  48.                     "hljs-keyword">break;  
  49.                 "hljs-keyword">case "hljs-string">'1':  
  50.                     val = counter.incrementAndGet(COUNTER_NAME);  
  51.                     System.out.printf("hljs-string">"%s: %s\n", COUNTER_NAME, val);  
  52.                     "hljs-keyword">break;  
  53.                 "hljs-keyword">case "hljs-string">'2':  
  54.                     val = counter.decrementAndGet(COUNTER_NAME);  
  55.                     System.out.printf("hljs-string">"%s: %s\n", COUNTER_NAME, val);  
  56.                     "hljs-keyword">break;  
  57.                 "hljs-keyword">case "hljs-string">'3':  
  58.                     counter.dumpLog();  
  59.                     "hljs-keyword">break;  
  60.                 "hljs-keyword">case "hljs-string">'4':  
  61.                     counter.snapshot();  
  62.                     "hljs-keyword">break;  
  63.                 "hljs-keyword">case "hljs-string">'x':  
  64.                     looping = "hljs-keyword">false;  
  65.                     "hljs-keyword">break;  
  66.                 "hljs-keyword">case "hljs-string">'\n':  
  67.                     System.out.println(COUNTER_NAME + "hljs-string">": " + counter.get(COUNTER_NAME) + "hljs-string">"\n");  
  68.                     "hljs-keyword">break;  
  69.             }  
  70.         }  
  71.     }  
  72.   
  73. }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

3.2 CounterService

CounterService是我们的核心类,利用Raft实现了分布式的计数器操作,它的API主要由四部分组成:

  • Raft Local API:操作本地Raft的状态,像日志大小、做快照等
  • Raft API:实现Raft的监听器和状态机的方法 
    • roleChanged:本地Raft的角色发生变化
    • apply:分布式通信消息
    • readContentFrom/writeContentTo:读写快照
  • Counter API:计数器的分布式API
  • Counter Native API:计数器的本地API。直接使用的话相当于脏读
Prettyprint代码   收藏代码
  1. "language-java hljs  has-numbering">"hljs-keyword">import org.jgroups.Channel;  
  2. "hljs-keyword">import org.jgroups.protocols.raft.RAFT;  
  3. "hljs-keyword">import org.jgroups.protocols.raft.Role;  
  4. "hljs-keyword">import org.jgroups.protocols.raft.StateMachine;  
  5. "hljs-keyword">import org.jgroups.raft.RaftHandle;  
  6. "hljs-keyword">import org.jgroups.util.AsciiString;  
  7. "hljs-keyword">import org.jgroups.util.Bits;  
  8. "hljs-keyword">import org.jgroups.util.ByteArrayDataInputStream;  
  9. "hljs-keyword">import org.jgroups.util.ByteArrayDataOutputStream;  
  10. "hljs-keyword">import org.jgroups.util.Util;  
  11.   
  12. "hljs-keyword">import java.io.DataInput;  
  13. "hljs-keyword">import java.io.DataOutput;  
  14. "hljs-keyword">import java.io.IOException;  
  15. "hljs-keyword">import java.text.SimpleDateFormat;  
  16. "hljs-keyword">import java.util.Date;  
  17. "hljs-keyword">import java.util.HashMap;  
  18. "hljs-keyword">import java.util.Map;  
  19.   
  20. "hljs-javadoc">/**  
  21.  * Distribute counter service based on Raft consensus algorithm.  
  22.  */  
  23. class CounterService implements StateMachine, RAFT.RoleChange {  
  24.   
  25.     "hljs-keyword">private RaftHandle raft;  
  26.   
  27.     "hljs-keyword">private "hljs-keyword">final Map counters;  
  28.   
  29.     "hljs-keyword">private "hljs-keyword">enum Command {  
  30.         CREATE, INCREMENT_AND_GET, DECREMENT_AND_GET, GET, SET  
  31.     }  
  32.   
  33.     "hljs-keyword">public "hljs-title">CounterService(Channel ch) {  
  34.         "hljs-keyword">this.raft = "hljs-keyword">new RaftHandle(ch, "hljs-keyword">this);  
  35.         "hljs-keyword">this.counters = "hljs-keyword">new HashMap<>();  
  36.   
  37.         raft.raftId(ch.getName())  
  38.             .addRoleListener("hljs-keyword">this);  
  39.     }  
  40.   
  41.     "hljs-comment">// ===========================================  
  42.     "hljs-comment">//              Raft Status API  
  43.     "hljs-comment">// ===========================================  
  44.   
  45.     "hljs-keyword">public "hljs-keyword">int "hljs-title">lastApplied() {  
  46.         "hljs-keyword">return raft.lastApplied();  
  47.     }  
  48.   
  49.     "hljs-keyword">public "hljs-keyword">int "hljs-title">commitIndex() {  
  50.         "hljs-keyword">return raft.commitIndex();  
  51.     }  
  52.   
  53.     "hljs-keyword">public "hljs-keyword">int "hljs-title">logSize() {  
  54.         "hljs-keyword">return raft.logSize();  
  55.     }  
  56.   
  57.     "hljs-keyword">public "hljs-keyword">void "hljs-title">dumpLog() {  
  58.         System.out.println("hljs-string">"\nindex (term): command\n---------------------");  
  59.         raft.logEntries((entry, index) -> {  
  60.             StringBuilder log = "hljs-keyword">new StringBuilder()  
  61.                     .append(index)  
  62.                     .append("hljs-string">" (").append(entry.term()).append("hljs-string">"): ");  
  63.   
  64.             "hljs-keyword">if (entry.command() == "hljs-keyword">null ) {  
  65.                 System.out.println(log.append("hljs-string">""));  
  66.                 "hljs-keyword">return;  
  67.             } "hljs-keyword">else "hljs-keyword">if (entry.internal()) {  
  68.                 System.out.println(log.append("hljs-string">""));  
  69.                 "hljs-keyword">return;  
  70.             }  
  71.   
  72.             ByteArrayDataInputStream in = "hljs-keyword">new ByteArrayDataInputStream(  
  73.                     entry.command(), entry.offset(), entry.length()  
  74.             );  
  75.             "hljs-keyword">try {  
  76.                 Command cmd = Command.values()[in.readByte()];  
  77.                 String name = Bits.readAsciiString(in).toString();  
  78.                 "hljs-keyword">switch (cmd) {  
  79.                     "hljs-keyword">case CREATE:  
  80.                         log.append(cmd)  
  81.                             .append("hljs-string">"(").append(name).append("hljs-string">", ")  
  82.                             .append(Bits.readLong(in))  
  83.                             .append("hljs-string">")");  
  84.                         "hljs-keyword">break;  
  85.                     "hljs-keyword">case GET:  
  86.                     "hljs-keyword">case INCREMENT_AND_GET:  
  87.                     "hljs-keyword">case DECREMENT_AND_GET:  
  88.                         log.append(cmd)  
  89.                             .append("hljs-string">"(").append(name).append("hljs-string">")");  
  90.                         "hljs-keyword">break;  
  91.                     "hljs-keyword">default:  
  92.                         "hljs-keyword">throw "hljs-keyword">new IllegalArgumentException("hljs-string">"Command " + cmd + "hljs-string">"is unknown");  
  93.                 }  
  94.                 System.out.println(log);  
  95.             }  
  96.             "hljs-keyword">catch (IOException e) {  
  97.                 "hljs-keyword">throw "hljs-keyword">new IllegalStateException("hljs-string">"Error when dump log", e);  
  98.             }  
  99.         });  
  100.         System.out.println();  
  101.     }  
  102.   
  103.     "hljs-keyword">public "hljs-keyword">void "hljs-title">snapshot() {  
  104.         "hljs-keyword">try {  
  105.             raft.snapshot();  
  106.         } "hljs-keyword">catch (Exception e) {  
  107.             "hljs-keyword">throw "hljs-keyword">new IllegalStateException("hljs-string">"Error when snapshot", e);  
  108.         }  
  109.     }  
  110.   
  111.     "hljs-keyword">public "hljs-keyword">boolean "hljs-title">isLeaderExist() {  
  112.         "hljs-keyword">return raft.leader() != "hljs-keyword">null;  
  113.     }  
  114.   
  115.     "hljs-comment">// ===========================================  
  116.     "hljs-comment">//              Raft API  
  117.     "hljs-comment">// ===========================================  
  118.   
  119.     "hljs-annotation">@Override  
  120.     "hljs-keyword">public "hljs-keyword">void "hljs-title">roleChanged(Role role) {  
  121.         System.out.println("hljs-string">"roleChanged to: " + role);  
  122.     }  
  123.   
  124.     "hljs-annotation">@Override  
  125.     "hljs-keyword">public "hljs-keyword">byte[] "hljs-title">apply("hljs-keyword">byte[] data, "hljs-keyword">int offset, "hljs-keyword">int length) "hljs-keyword">throws Exception {  
  126.         ByteArrayDataInputStream in = "hljs-keyword">new ByteArrayDataInputStream(data, offset, length);  
  127.         Command cmd = Command.values()[in.readByte()];  
  128.         String name = Bits.readAsciiString(in).toString();  
  129.         System.out.println("hljs-string">"[" + "hljs-keyword">new SimpleDateFormat("hljs-string">"HH:mm:ss.SSS").format("hljs-keyword">new Date())  
  130.                 + "hljs-string">"] Apply: cmd=[" + cmd + "hljs-string">"]");  
  131.   
  132.         "hljs-keyword">long v1, retVal;  
  133.         "hljs-keyword">switch (cmd) {  
  134.             "hljs-keyword">case CREATE:  
  135.                 v1 = Bits.readLong(in);  
  136.                 retVal = create0(name, v1);  
  137.                 "hljs-keyword">return Util.objectToByteBuffer(retVal);  
  138.             "hljs-keyword">case GET:  
  139.                 retVal = get0(name);  
  140.                 "hljs-keyword">return Util.objectToByteBuffer(retVal);  
  141.             "hljs-keyword">case INCREMENT_AND_GET:  
  142.                 retVal = add0(name, "hljs-number">1L);  
  143.                 "hljs-keyword">return Util.objectToByteBuffer(retVal);  
  144.             "hljs-keyword">case DECREMENT_AND_GET:  
  145.                 retVal = add0(name, -"hljs-number">1L);  
  146.                 "hljs-keyword">return Util.objectToByteBuffer(retVal);  
  147.             "hljs-keyword">default:  
  148.                 "hljs-keyword">throw "hljs-keyword">new IllegalArgumentException("hljs-string">"Command " + cmd + "hljs-string">"is unknown");  
  149.         }  
  150.     }  
  151.   
  152.     "hljs-annotation">@Override  
  153.     "hljs-keyword">public "hljs-keyword">void "hljs-title">readContentFrom(DataInput in) "hljs-keyword">throws Exception {  
  154.         "hljs-keyword">int size = in.readInt();  
  155.         System.out.println("hljs-string">"ReadContentFrom: size=[" + size + "hljs-string">"]");  
  156.         "hljs-keyword">for ("hljs-keyword">int i = "hljs-number">0; i < size; i++) {  
  157.             AsciiString name = Bits.readAsciiString(in);  
  158.             Long value = Bits.readLong(in);  
  159.             counters.put(name.toString(), value);  
  160.         }  
  161.     }  
  162.   
  163.     "hljs-annotation">@Override  
  164.     "hljs-keyword">public "hljs-keyword">void "hljs-title">writeContentTo(DataOutput out) "hljs-keyword">throws Exception {  
  165.         "hljs-keyword">synchronized (counters) {  
  166.             "hljs-keyword">int size = counters.size();  
  167.             System.out.println("hljs-string">"WriteContentFrom: size=[" + size + "hljs-string">"]");  
  168.             out.writeInt(size);  
  169.             "hljs-keyword">for (Map.Entry entry : counters.entrySet()) {  
  170.                 AsciiString name = "hljs-keyword">new AsciiString(entry.getKey());  
  171.                 Long value = entry.getValue();  
  172.                 Bits.writeAsciiString(name, out);  
  173.                 Bits.writeLong(value, out);  
  174.             }  
  175.         }  
  176.     }  
  177.   
  178.     "hljs-comment">// ===========================================  
  179.     "hljs-comment">//              Counter API  
  180.     "hljs-comment">// ===========================================  
  181.   
  182.     "hljs-keyword">public "hljs-keyword">void "hljs-title">getOrCreateCounter(String name, "hljs-keyword">long initVal) {  
  183.         Object retVal = invoke(Command.CREATE, name, "hljs-keyword">false, initVal);  
  184.         counters.put(name, (Long) retVal);  
  185.     }  
  186.   
  187.     "hljs-keyword">public "hljs-keyword">long "hljs-title">incrementAndGet(String name) {  
  188.         "hljs-keyword">return ("hljs-keyword">long) invoke(Command.INCREMENT_AND_GET, name, "hljs-keyword">false);  
  189.     }  
  190.   
  191.     "hljs-keyword">public "hljs-keyword">long "hljs-title">decrementAndGet(String name) {  
  192.         "hljs-keyword">return ("hljs-keyword">long) invoke(Command.DECREMENT_AND_GET, name, "hljs-keyword">false);  
  193.     }  
  194.   
  195.     "hljs-keyword">public "hljs-keyword">long "hljs-title">get(String name) {  
  196.         "hljs-keyword">return ("hljs-keyword">long) invoke(Command.GET, name, "hljs-keyword">false);  
  197.     }  
  198.   
  199.     "hljs-keyword">private Object "hljs-title">invoke(Command cmd, String name, "hljs-keyword">boolean ignoreRetVal, "hljs-keyword">long... values) {  
  200.         ByteArrayDataOutputStream out = "hljs-keyword">new ByteArrayDataOutputStream("hljs-number">256);  
  201.         "hljs-keyword">try {  
  202.             out.writeByte(cmd.ordinal());  
  203.             Bits.writeAsciiString("hljs-keyword">new AsciiString(name), out);  
  204.             "hljs-keyword">for ("hljs-keyword">long val : values) {  
  205.                 Bits.writeLong(val, out);  
  206.             }  
  207.   
  208.             "hljs-keyword">byte[] rsp = raft.set(out.buffer(), "hljs-number">0, out.position());  
  209.             "hljs-keyword">return ignoreRetVal ? "hljs-keyword">null : Util.objectFromByteBuffer(rsp);  
  210.         }  
  211.         "hljs-keyword">catch (IOException ex) {  
  212.             "hljs-keyword">throw "hljs-keyword">new RuntimeException("hljs-string">"Serialization failure (cmd="  
  213.                     + cmd + "hljs-string">", name=" + name + "hljs-string">")", ex);  
  214.         }  
  215.         "hljs-keyword">catch (Exception ex) {  
  216.             "hljs-keyword">throw "hljs-keyword">new RuntimeException("hljs-string">"Raft set failure (cmd="  
  217.                     + cmd + "hljs-string">", name=" + name + "hljs-string">")", ex);  
  218.         }  
  219.     }  
  220.   
  221.     "hljs-comment">// ===========================================  
  222.     "hljs-comment">//              Counter Native API  
  223.     "hljs-comment">// ===========================================  
  224.   
  225.     "hljs-keyword">public "hljs-keyword">synchronized Long "hljs-title">create0(String name, "hljs-keyword">long initVal) {  
  226.         counters.putIfAbsent(name, initVal);  
  227.         "hljs-keyword">return counters.get(name);  
  228.     }  
  229.   
  230.     "hljs-keyword">public "hljs-keyword">synchronized Long "hljs-title">get0(String name) {  
  231.         "hljs-keyword">return counters.getOrDefault(name, "hljs-number">0L);  
  232.     }  
  233.   
  234.     "hljs-keyword">public "hljs-keyword">synchronized Long "hljs-title">add0(String name, "hljs-keyword">long delta) {  
  235.         Long oldVal = counters.getOrDefault(name, "hljs-number">0L);  
  236.         "hljs-keyword">return counters.put(name, oldVal + delta);  
  237.     }  
  238. }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238

3.3 运行测试

我们分别以A、B、C为参数,启动三个JGroupsRaftTest服务。这样会自动在C:\Users\cdai\AppData\Local\Temp下生成A.log、B.log、C.log三个日志文件夹。

Prettyprint代码   收藏代码
  1. "hljs livecodeserver has-numbering">cdai@vm /cygdrive/c/Users/cdai/AppData/Local/Temp  
  2. $ tree A."hljs-built_in">log/ B."hljs-built_in">log/ C."hljs-built_in">log/  
  3. A."hljs-built_in">log/  
  4. |"hljs-comment">-- 000005.sst  
  5. |"hljs-comment">-- 000006.log  
  6. |"hljs-comment">-- CURRENT  
  7. |"hljs-comment">-- LOCK  
  8. |"hljs-comment">-- LOG  
  9. |"hljs-comment">-- LOG.old  
  10. `"hljs-comment">-- MANIFEST-000004  
  11. B."hljs-built_in">log/  
  12. |"hljs-comment">-- 000003.log  
  13. |"hljs-comment">-- CURRENT  
  14. |"hljs-comment">-- LOCK  
  15. |"hljs-comment">-- LOG  
  16. `"hljs-comment">-- MANIFEST-000002  
  17. C."hljs-built_in">log/  
  18. |"hljs-comment">-- 000003.log  
  19. |"hljs-comment">-- CURRENT  
  20. |"hljs-comment">-- LOCK  
  21. |"hljs-comment">-- LOG  
  22. `"hljs-comment">-- MANIFEST-000002  
  23.   
  24. "hljs-number">0 "hljs-built_in">directories, "hljs-number">17 "hljs-built_in">files  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.3.1 分布式一致性

首先A创建计数器,B“加一”,C“减一”。可以看到尽管我们是分别在A、B、C上执行这三个操作,但三个结点都先后(leader提交日志后通知follower)通过apply()方法收到消息,并在本地的计数器Map上同步执行操作,保证了数据的一致性。最后停掉A服务,可以看到B通过roleChanged()得到消息,提升为新的Leader,并与C一同继续提供服务。

A的控制台输出:

Prettyprint代码   收藏代码
  1. "hljs asciidoc has-numbering">"hljs-code">-------------------------------------------------------------------  
  2. GMS: address=A, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:50100  
  3. -------------------------------------------------------------------  
  4.   
  5. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit  
  6. first-applied=0, last-applied=0, commit-index=0, log size=0b:   
  7. roleChanged to: Candidate  
  8. roleChanged to: Leader  
  9. 0  
  10. "hljs-attribute">[14:16:00.744] Apply: cmd=[CREATE]  
  11.   
  12. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit  
  13. first-applied=0, last-applied=1, commit-index=1, log size=1b:   
  14. "hljs-attribute">[14:16:07.002] Apply: cmd=[INCREMENT_AND_GET]  
  15. "hljs-attribute">[14:16:14.264] Apply: cmd=[DECREMENT_AND_GET]  
  16. 3  
  17.   
  18. "hljs-header">index (term): command  
  19. ---------------------  
  20. 1 (29): CREATE(counter, 1)  
  21. 2 (29): INCREMENT"hljs-emphasis">_AND_GET(counter)  
  22. 3 (29): DECREMENT"hljs-emphasis">_AND_GET(counter)  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

B的控制台输出:

Prettyprint代码   收藏代码
  1. "hljs sql has-numbering">"hljs-comment">-------------------------------------------------------------------  
  2. GMS: address=B, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:50101  
  3. "hljs-comment">-------------------------------------------------------------------  
  4.   
  5. [0"hljs-operator">"hljs-keyword">Create ["hljs-number">1] Increment ["hljs-number">2] Decrement ["hljs-number">3] Dump log ["hljs-number">4] Snapshot [x] Exit  
  6. "hljs-keyword">first-applied="hljs-number">0"hljs-keyword">last-applied="hljs-number">0"hljs-keyword">commit-index="hljs-number">0, log "hljs-keyword">size="hljs-number">0b:   
  7. ["hljs-number">14:"hljs-number">16:"hljs-number">01.300] Apply: cmd=["hljs-keyword">CREATE]  
  8. "hljs-number">1  
  9. counter: "hljs-number">2  
  10.   
  11. ["hljs-number">0"hljs-keyword">Create ["hljs-number">1] Increment ["hljs-number">2] Decrement ["hljs-number">3] Dump log ["hljs-number">4] Snapshot [x] Exit  
  12. "hljs-keyword">first-applied="hljs-number">0"hljs-keyword">last-applied="hljs-number">2"hljs-keyword">commit-index="hljs-number">1, log "hljs-keyword">size="hljs-number">2b:   
  13. ["hljs-number">14:"hljs-number">16:"hljs-number">07.299] Apply: cmd=[INCREMENT_AND_GET]  
  14. ["hljs-number">14:"hljs-number">16:"hljs-number">14.304] Apply: cmd=[DECREMENT_AND_GET]  
  15. roleChanged "hljs-keyword">to: Candidate  
  16. roleChanged "hljs-keyword">to: Leader  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

C的控制台输出:

Prettyprint代码   收藏代码
  1. "hljs sql has-numbering">"hljs-comment">-------------------------------------------------------------------  
  2. GMS: address=C, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:55800  
  3. "hljs-comment">-------------------------------------------------------------------  
  4.   
  5. [0"hljs-operator">"hljs-keyword">Create ["hljs-number">1] Increment ["hljs-number">2] Decrement ["hljs-number">3] Dump log ["hljs-number">4] Snapshot [x] Exit  
  6. "hljs-keyword">first-applied="hljs-number">0"hljs-keyword">last-applied="hljs-number">0"hljs-keyword">commit-index="hljs-number">0, log "hljs-keyword">size="hljs-number">0b:   
  7. ["hljs-number">14:"hljs-number">16:"hljs-number">01.300] Apply: cmd=["hljs-keyword">CREATE]  
  8. ["hljs-number">14:"hljs-number">16:"hljs-number">07.299] Apply: cmd=[INCREMENT_AND_GET]  
  9. "hljs-number">2  
  10. counter: "hljs-number">3  
  11.   
  12. ["hljs-number">0"hljs-keyword">Create ["hljs-number">1] Increment ["hljs-number">2] Decrement ["hljs-number">3] Dump log ["hljs-number">4] Snapshot [x] Exit  
  13. "hljs-keyword">first-applied="hljs-number">0"hljs-keyword">last-applied="hljs-number">3"hljs-keyword">commit-index="hljs-number">2, log "hljs-keyword">size="hljs-number">3b:   
  14. ["hljs-number">14:"hljs-number">16:"hljs-number">14.304] Apply: cmd=[DECREMENT_AND_GET]  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.3.2 服务恢复

在只有B和C的集群中,我们执行了一次“加一”。当我们重新启动A服务时,它会自动执行这条日志,保持与B和C的一致。从日志的index能够看出,69是一个Term,也就是A为Leader时的“任期”,而70也就是B为Leader时。

A的控制台输出:

Prettyprint代码   收藏代码
  1. "hljs asciidoc has-numbering">"hljs-code">-------------------------------------------------------------------  
  2. GMS: address=A, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:53237  
  3. -------------------------------------------------------------------  
  4.   
  5. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit  
  6. first-applied=0, last-applied=3, commit-index=3, log size=3b:   
  7. "hljs-attribute">[14:18:45.275] Apply: cmd=[INCREMENT_AND_GET]  
  8. "hljs-attribute">[14:18:45.277] Apply: cmd=[GET]  
  9. 3  
  10.   
  11. "hljs-header">index (term): command  
  12. ---------------------  
  13. 1 (69): CREATE(counter, 1)  
  14. 2 (69): INCREMENT"hljs-emphasis">_AND_GET(counter)  
  15. 3 (69): DECREMENT"hljs-emphasis">_AND_GET(counter)  
  16. 4 (70): INCREMENT"hljs-emphasis">_AND_GET(counter)  
  17. 5 (70): GET(counter)  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
 
http://blog.csdn.net/dc_726/article/details/48832405

你可能感兴趣的:(分布式一致性协议Raft原理与实例)