来源: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. <code class="language-xml hljs  has-numbering">    <span class="hljs-tag"><<span class="hljs-title">dependency</span>></span>
  2. <span class="hljs-tag"><<span class="hljs-title">groupId</span>></span>org.jgroups<span class="hljs-tag"></<span class="hljs-title">groupId</span>></span>
  3. <span class="hljs-tag"><<span class="hljs-title">artifactId</span>></span>jgroups-raft<span class="hljs-tag"></<span class="hljs-title">artifactId</span>></span>
  4. <span class="hljs-tag"><<span class="hljs-title">version</span>></span>0.2<span class="hljs-tag"></<span class="hljs-title">version</span>></span>
  5. <span class="hljs-tag"></<span class="hljs-title">dependency</span>></span></code>
  • 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. <code class="language-java hljs  has-numbering">JChannel ch = <span class="hljs-keyword">null</span>;
  2. RaftHandle handle = <span class="hljs-keyword">new</span> RaftHandle(ch, <span class="hljs-keyword">this</span>);
  3. handle.addRoleListener(role -> {
  4. <span class="hljs-keyword">if</span>(role == Role.Leader)
  5. <span class="hljs-comment">// start singleton services</span>
  6. <span class="hljs-keyword">else</span>
  7. <span class="hljs-comment">// stop singleton services</span>
  8. });</code>
  • 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. <code class="language-xml hljs  has-numbering"><span class="hljs-comment"><!--
  2. Default stack using IP multicasting. It is similar to the "udp"
  3. stack in stacks.xml, but doesn't use streaming state transfer and flushing
  4. author: Bela Ban
  5. --></span>
  6. <span class="hljs-tag"><<span class="hljs-title">config</span> <span class="hljs-attribute">xmlns</span>=<span class="hljs-value">"urn:org:jgroups"</span>
  7. <span class="hljs-attribute">xmlns:xsi</span>=<span class="hljs-value">"http://www.w3.org/2001/XMLSchema-instance"</span>
  8. <span class="hljs-attribute">xsi:schemaLocation</span>=<span class="hljs-value">"urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd"</span>></span>
  9. <span class="hljs-tag"><<span class="hljs-title">UDP
  10. </span>         <span class="hljs-attribute">mcast_addr</span>=<span class="hljs-value">"228.5.5.5"</span>
  11. <span class="hljs-attribute">mcast_port</span>=<span class="hljs-value">"${jgroups.udp.mcast_port:45588}"</span>
  12. <span class="hljs-attribute">...</span> /></span>
  13. ...
  14. <span class="hljs-tag"><<span class="hljs-title">raft.NO_DUPES</span>/></span>
  15. <span class="hljs-tag"><<span class="hljs-title">raft.ELECTION</span> <span class="hljs-attribute">election_min_interval</span>=<span class="hljs-value">"100"</span> <span class="hljs-attribute">election_max_interval</span>=<span class="hljs-value">"500"</span>/></span>
  16. <span class="hljs-tag"><<span class="hljs-title">raft.RAFT</span> <span class="hljs-attribute">members</span>=<span class="hljs-value">"A,B,C"</span> <span class="hljs-attribute">raft_id</span>=<span class="hljs-value">"${raft_id:undefined}"</span>/></span>
  17. <span class="hljs-tag"><<span class="hljs-title">raft.REDIRECT</span>/></span>
  18. <span class="hljs-tag"><<span class="hljs-title">raft.CLIENT</span> <span class="hljs-attribute">bind_addr</span>=<span class="hljs-value">"0.0.0.0"</span> /></span>
  19. <span class="hljs-tag"></<span class="hljs-title">config</span>></span></code>
  • 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. <code class="language-java hljs  has-numbering"><span class="hljs-keyword">import</span> org.jgroups.JChannel;
  2. <span class="hljs-keyword">import</span> org.jgroups.protocols.raft.RAFT;
  3. <span class="hljs-keyword">import</span> org.jgroups.util.Util;
  4. <span class="hljs-javadoc">/**
  5. * Test jgroups raft algorithm implementation.
  6. */</span>
  7. <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JGroupsRaftTest</span> {</span>
  8. <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String CLUSTER_NAME = <span class="hljs-string">"ctr-cluster"</span>;
  9. <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String COUNTER_NAME = <span class="hljs-string">"counter"</span>;
  10. <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String RAFT_XML = <span class="hljs-string">"raft.xml"</span>;
  11. <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) <span class="hljs-keyword">throws</span> Exception {
  12. JChannel ch = <span class="hljs-keyword">new</span> JChannel(RAFT_XML).name(args[<span class="hljs-number">0</span>]);
  13. CounterService counter = <span class="hljs-keyword">new</span> CounterService(ch);
  14. <span class="hljs-keyword">try</span> {
  15. doConnect(ch, CLUSTER_NAME);
  16. doLoop(ch, counter);
  17. } <span class="hljs-keyword">finally</span> {
  18. Util.close(ch);
  19. }
  20. }
  21. <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doConnect</span>(JChannel ch, String clusterName) <span class="hljs-keyword">throws</span> Exception {
  22. ch.connect(clusterName);
  23. }
  24. <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doLoop</span>(JChannel ch, CounterService counter) {
  25. <span class="hljs-keyword">boolean</span> looping = <span class="hljs-keyword">true</span>;
  26. <span class="hljs-keyword">while</span> (looping) {
  27. <span class="hljs-keyword">int</span> key = Util.keyPress(<span class="hljs-string">"\n[0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit\n"</span> +
  28. <span class="hljs-string">"first-applied="</span> + ((RAFT) ch.getProtocolStack().findProtocol(RAFT.class)).log().firstApplied() +
  29. <span class="hljs-string">", last-applied="</span> + counter.lastApplied() +
  30. <span class="hljs-string">", commit-index="</span> + counter.commitIndex() +
  31. <span class="hljs-string">", log size="</span> + Util.printBytes(counter.logSize()) + <span class="hljs-string">": "</span>);
  32. <span class="hljs-keyword">if</span> ((key == <span class="hljs-string">'0'</span> || key == <span class="hljs-string">'1'</span> || key == <span class="hljs-string">'2'</span>) && !counter.isLeaderExist()) {
  33. System.out.println(<span class="hljs-string">"Cannot perform cause there is no leader by now"</span>);
  34. <span class="hljs-keyword">continue</span>;
  35. }
  36. <span class="hljs-keyword">long</span> val;
  37. <span class="hljs-keyword">switch</span> (key) {
  38. <span class="hljs-keyword">case</span> <span class="hljs-string">'0'</span>:
  39. counter.getOrCreateCounter(COUNTER_NAME, <span class="hljs-number">1</span>L);
  40. <span class="hljs-keyword">break</span>;
  41. <span class="hljs-keyword">case</span> <span class="hljs-string">'1'</span>:
  42. val = counter.incrementAndGet(COUNTER_NAME);
  43. System.out.printf(<span class="hljs-string">"%s: %s\n"</span>, COUNTER_NAME, val);
  44. <span class="hljs-keyword">break</span>;
  45. <span class="hljs-keyword">case</span> <span class="hljs-string">'2'</span>:
  46. val = counter.decrementAndGet(COUNTER_NAME);
  47. System.out.printf(<span class="hljs-string">"%s: %s\n"</span>, COUNTER_NAME, val);
  48. <span class="hljs-keyword">break</span>;
  49. <span class="hljs-keyword">case</span> <span class="hljs-string">'3'</span>:
  50. counter.dumpLog();
  51. <span class="hljs-keyword">break</span>;
  52. <span class="hljs-keyword">case</span> <span class="hljs-string">'4'</span>:
  53. counter.snapshot();
  54. <span class="hljs-keyword">break</span>;
  55. <span class="hljs-keyword">case</span> <span class="hljs-string">'x'</span>:
  56. looping = <span class="hljs-keyword">false</span>;
  57. <span class="hljs-keyword">break</span>;
  58. <span class="hljs-keyword">case</span> <span class="hljs-string">'\n'</span>:
  59. System.out.println(COUNTER_NAME + <span class="hljs-string">": "</span> + counter.get(COUNTER_NAME) + <span class="hljs-string">"\n"</span>);
  60. <span class="hljs-keyword">break</span>;
  61. }
  62. }
  63. }
  64. }</code>
  • 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. <code class="language-java hljs  has-numbering"><span class="hljs-keyword">import</span> org.jgroups.Channel;
  2. <span class="hljs-keyword">import</span> org.jgroups.protocols.raft.RAFT;
  3. <span class="hljs-keyword">import</span> org.jgroups.protocols.raft.Role;
  4. <span class="hljs-keyword">import</span> org.jgroups.protocols.raft.StateMachine;
  5. <span class="hljs-keyword">import</span> org.jgroups.raft.RaftHandle;
  6. <span class="hljs-keyword">import</span> org.jgroups.util.AsciiString;
  7. <span class="hljs-keyword">import</span> org.jgroups.util.Bits;
  8. <span class="hljs-keyword">import</span> org.jgroups.util.ByteArrayDataInputStream;
  9. <span class="hljs-keyword">import</span> org.jgroups.util.ByteArrayDataOutputStream;
  10. <span class="hljs-keyword">import</span> org.jgroups.util.Util;
  11. <span class="hljs-keyword">import</span> java.io.DataInput;
  12. <span class="hljs-keyword">import</span> java.io.DataOutput;
  13. <span class="hljs-keyword">import</span> java.io.IOException;
  14. <span class="hljs-keyword">import</span> java.text.SimpleDateFormat;
  15. <span class="hljs-keyword">import</span> java.util.Date;
  16. <span class="hljs-keyword">import</span> java.util.HashMap;
  17. <span class="hljs-keyword">import</span> java.util.Map;
  18. <span class="hljs-javadoc">/**
  19. * Distribute counter service based on Raft consensus algorithm.
  20. */</span>
  21. class CounterService implements StateMachine, RAFT.RoleChange {
  22. <span class="hljs-keyword">private</span> RaftHandle raft;
  23. <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, Long> counters;
  24. <span class="hljs-keyword">private</span> <span class="hljs-keyword">enum</span> Command {
  25. CREATE, INCREMENT_AND_GET, DECREMENT_AND_GET, GET, SET
  26. }
  27. <span class="hljs-keyword">public</span> <span class="hljs-title">CounterService</span>(Channel ch) {
  28. <span class="hljs-keyword">this</span>.raft = <span class="hljs-keyword">new</span> RaftHandle(ch, <span class="hljs-keyword">this</span>);
  29. <span class="hljs-keyword">this</span>.counters = <span class="hljs-keyword">new</span> HashMap<>();
  30. raft.raftId(ch.getName())
  31. .addRoleListener(<span class="hljs-keyword">this</span>);
  32. }
  33. <span class="hljs-comment">// ===========================================</span>
  34. <span class="hljs-comment">//              Raft Status API</span>
  35. <span class="hljs-comment">// ===========================================</span>
  36. <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">lastApplied</span>() {
  37. <span class="hljs-keyword">return</span> raft.lastApplied();
  38. }
  39. <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">commitIndex</span>() {
  40. <span class="hljs-keyword">return</span> raft.commitIndex();
  41. }
  42. <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">logSize</span>() {
  43. <span class="hljs-keyword">return</span> raft.logSize();
  44. }
  45. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">dumpLog</span>() {
  46. System.out.println(<span class="hljs-string">"\nindex (term): command\n---------------------"</span>);
  47. raft.logEntries((entry, index) -> {
  48. StringBuilder log = <span class="hljs-keyword">new</span> StringBuilder()
  49. .append(index)
  50. .append(<span class="hljs-string">" ("</span>).append(entry.term()).append(<span class="hljs-string">"): "</span>);
  51. <span class="hljs-keyword">if</span> (entry.command() == <span class="hljs-keyword">null</span> ) {
  52. System.out.println(log.append(<span class="hljs-string">"<marker record>"</span>));
  53. <span class="hljs-keyword">return</span>;
  54. } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (entry.internal()) {
  55. System.out.println(log.append(<span class="hljs-string">"<internal command>"</span>));
  56. <span class="hljs-keyword">return</span>;
  57. }
  58. ByteArrayDataInputStream in = <span class="hljs-keyword">new</span> ByteArrayDataInputStream(
  59. entry.command(), entry.offset(), entry.length()
  60. );
  61. <span class="hljs-keyword">try</span> {
  62. Command cmd = Command.values()[in.readByte()];
  63. String name = Bits.readAsciiString(in).toString();
  64. <span class="hljs-keyword">switch</span> (cmd) {
  65. <span class="hljs-keyword">case</span> CREATE:
  66. log.append(cmd)
  67. .append(<span class="hljs-string">"("</span>).append(name).append(<span class="hljs-string">", "</span>)
  68. .append(Bits.readLong(in))
  69. .append(<span class="hljs-string">")"</span>);
  70. <span class="hljs-keyword">break</span>;
  71. <span class="hljs-keyword">case</span> GET:
  72. <span class="hljs-keyword">case</span> INCREMENT_AND_GET:
  73. <span class="hljs-keyword">case</span> DECREMENT_AND_GET:
  74. log.append(cmd)
  75. .append(<span class="hljs-string">"("</span>).append(name).append(<span class="hljs-string">")"</span>);
  76. <span class="hljs-keyword">break</span>;
  77. <span class="hljs-keyword">default</span>:
  78. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Command "</span> + cmd + <span class="hljs-string">"is unknown"</span>);
  79. }
  80. System.out.println(log);
  81. }
  82. <span class="hljs-keyword">catch</span> (IOException e) {
  83. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Error when dump log"</span>, e);
  84. }
  85. });
  86. System.out.println();
  87. }
  88. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">snapshot</span>() {
  89. <span class="hljs-keyword">try</span> {
  90. raft.snapshot();
  91. } <span class="hljs-keyword">catch</span> (Exception e) {
  92. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"Error when snapshot"</span>, e);
  93. }
  94. }
  95. <span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isLeaderExist</span>() {
  96. <span class="hljs-keyword">return</span> raft.leader() != <span class="hljs-keyword">null</span>;
  97. }
  98. <span class="hljs-comment">// ===========================================</span>
  99. <span class="hljs-comment">//              Raft API</span>
  100. <span class="hljs-comment">// ===========================================</span>
  101. <span class="hljs-annotation">@Override</span>
  102. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">roleChanged</span>(Role role) {
  103. System.out.println(<span class="hljs-string">"roleChanged to: "</span> + role);
  104. }
  105. <span class="hljs-annotation">@Override</span>
  106. <span class="hljs-keyword">public</span> <span class="hljs-keyword">byte</span>[] <span class="hljs-title">apply</span>(<span class="hljs-keyword">byte</span>[] data, <span class="hljs-keyword">int</span> offset, <span class="hljs-keyword">int</span> length) <span class="hljs-keyword">throws</span> Exception {
  107. ByteArrayDataInputStream in = <span class="hljs-keyword">new</span> ByteArrayDataInputStream(data, offset, length);
  108. Command cmd = Command.values()[in.readByte()];
  109. String name = Bits.readAsciiString(in).toString();
  110. System.out.println(<span class="hljs-string">"["</span> + <span class="hljs-keyword">new</span> SimpleDateFormat(<span class="hljs-string">"HH:mm:ss.SSS"</span>).format(<span class="hljs-keyword">new</span> Date())
  111. + <span class="hljs-string">"] Apply: cmd=["</span> + cmd + <span class="hljs-string">"]"</span>);
  112. <span class="hljs-keyword">long</span> v1, retVal;
  113. <span class="hljs-keyword">switch</span> (cmd) {
  114. <span class="hljs-keyword">case</span> CREATE:
  115. v1 = Bits.readLong(in);
  116. retVal = create0(name, v1);
  117. <span class="hljs-keyword">return</span> Util.objectToByteBuffer(retVal);
  118. <span class="hljs-keyword">case</span> GET:
  119. retVal = get0(name);
  120. <span class="hljs-keyword">return</span> Util.objectToByteBuffer(retVal);
  121. <span class="hljs-keyword">case</span> INCREMENT_AND_GET:
  122. retVal = add0(name, <span class="hljs-number">1</span>L);
  123. <span class="hljs-keyword">return</span> Util.objectToByteBuffer(retVal);
  124. <span class="hljs-keyword">case</span> DECREMENT_AND_GET:
  125. retVal = add0(name, -<span class="hljs-number">1</span>L);
  126. <span class="hljs-keyword">return</span> Util.objectToByteBuffer(retVal);
  127. <span class="hljs-keyword">default</span>:
  128. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Command "</span> + cmd + <span class="hljs-string">"is unknown"</span>);
  129. }
  130. }
  131. <span class="hljs-annotation">@Override</span>
  132. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">readContentFrom</span>(DataInput in) <span class="hljs-keyword">throws</span> Exception {
  133. <span class="hljs-keyword">int</span> size = in.readInt();
  134. System.out.println(<span class="hljs-string">"ReadContentFrom: size=["</span> + size + <span class="hljs-string">"]"</span>);
  135. <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < size; i++) {
  136. AsciiString name = Bits.readAsciiString(in);
  137. Long value = Bits.readLong(in);
  138. counters.put(name.toString(), value);
  139. }
  140. }
  141. <span class="hljs-annotation">@Override</span>
  142. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">writeContentTo</span>(DataOutput out) <span class="hljs-keyword">throws</span> Exception {
  143. <span class="hljs-keyword">synchronized</span> (counters) {
  144. <span class="hljs-keyword">int</span> size = counters.size();
  145. System.out.println(<span class="hljs-string">"WriteContentFrom: size=["</span> + size + <span class="hljs-string">"]"</span>);
  146. out.writeInt(size);
  147. <span class="hljs-keyword">for</span> (Map.Entry<String, Long> entry : counters.entrySet()) {
  148. AsciiString name = <span class="hljs-keyword">new</span> AsciiString(entry.getKey());
  149. Long value = entry.getValue();
  150. Bits.writeAsciiString(name, out);
  151. Bits.writeLong(value, out);
  152. }
  153. }
  154. }
  155. <span class="hljs-comment">// ===========================================</span>
  156. <span class="hljs-comment">//              Counter API</span>
  157. <span class="hljs-comment">// ===========================================</span>
  158. <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getOrCreateCounter</span>(String name, <span class="hljs-keyword">long</span> initVal) {
  159. Object retVal = invoke(Command.CREATE, name, <span class="hljs-keyword">false</span>, initVal);
  160. counters.put(name, (Long) retVal);
  161. }
  162. <span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">incrementAndGet</span>(String name) {
  163. <span class="hljs-keyword">return</span> (<span class="hljs-keyword">long</span>) invoke(Command.INCREMENT_AND_GET, name, <span class="hljs-keyword">false</span>);
  164. }
  165. <span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">decrementAndGet</span>(String name) {
  166. <span class="hljs-keyword">return</span> (<span class="hljs-keyword">long</span>) invoke(Command.DECREMENT_AND_GET, name, <span class="hljs-keyword">false</span>);
  167. }
  168. <span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">get</span>(String name) {
  169. <span class="hljs-keyword">return</span> (<span class="hljs-keyword">long</span>) invoke(Command.GET, name, <span class="hljs-keyword">false</span>);
  170. }
  171. <span class="hljs-keyword">private</span> Object <span class="hljs-title">invoke</span>(Command cmd, String name, <span class="hljs-keyword">boolean</span> ignoreRetVal, <span class="hljs-keyword">long</span>... values) {
  172. ByteArrayDataOutputStream out = <span class="hljs-keyword">new</span> ByteArrayDataOutputStream(<span class="hljs-number">256</span>);
  173. <span class="hljs-keyword">try</span> {
  174. out.writeByte(cmd.ordinal());
  175. Bits.writeAsciiString(<span class="hljs-keyword">new</span> AsciiString(name), out);
  176. <span class="hljs-keyword">for</span> (<span class="hljs-keyword">long</span> val : values) {
  177. Bits.writeLong(val, out);
  178. }
  179. <span class="hljs-keyword">byte</span>[] rsp = raft.set(out.buffer(), <span class="hljs-number">0</span>, out.position());
  180. <span class="hljs-keyword">return</span> ignoreRetVal ? <span class="hljs-keyword">null</span> : Util.objectFromByteBuffer(rsp);
  181. }
  182. <span class="hljs-keyword">catch</span> (IOException ex) {
  183. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Serialization failure (cmd="</span>
  184. + cmd + <span class="hljs-string">", name="</span> + name + <span class="hljs-string">")"</span>, ex);
  185. }
  186. <span class="hljs-keyword">catch</span> (Exception ex) {
  187. <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Raft set failure (cmd="</span>
  188. + cmd + <span class="hljs-string">", name="</span> + name + <span class="hljs-string">")"</span>, ex);
  189. }
  190. }
  191. <span class="hljs-comment">// ===========================================</span>
  192. <span class="hljs-comment">//              Counter Native API</span>
  193. <span class="hljs-comment">// ===========================================</span>
  194. <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> Long <span class="hljs-title">create0</span>(String name, <span class="hljs-keyword">long</span> initVal) {
  195. counters.putIfAbsent(name, initVal);
  196. <span class="hljs-keyword">return</span> counters.get(name);
  197. }
  198. <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> Long <span class="hljs-title">get0</span>(String name) {
  199. <span class="hljs-keyword">return</span> counters.getOrDefault(name, <span class="hljs-number">0</span>L);
  200. }
  201. <span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> Long <span class="hljs-title">add0</span>(String name, <span class="hljs-keyword">long</span> delta) {
  202. Long oldVal = counters.getOrDefault(name, <span class="hljs-number">0</span>L);
  203. <span class="hljs-keyword">return</span> counters.put(name, oldVal + delta);
  204. }
  205. }</code>
  • 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. <code class="hljs livecodeserver has-numbering">cdai@vm /cygdrive/c/Users/cdai/AppData/Local/Temp
  2. $ tree A.<span class="hljs-built_in">log</span>/ B.<span class="hljs-built_in">log</span>/ C.<span class="hljs-built_in">log</span>/
  3. A.<span class="hljs-built_in">log</span>/
  4. |<span class="hljs-comment">-- 000005.sst</span>
  5. |<span class="hljs-comment">-- 000006.log</span>
  6. |<span class="hljs-comment">-- CURRENT</span>
  7. |<span class="hljs-comment">-- LOCK</span>
  8. |<span class="hljs-comment">-- LOG</span>
  9. |<span class="hljs-comment">-- LOG.old</span>
  10. `<span class="hljs-comment">-- MANIFEST-000004</span>
  11. B.<span class="hljs-built_in">log</span>/
  12. |<span class="hljs-comment">-- 000003.log</span>
  13. |<span class="hljs-comment">-- CURRENT</span>
  14. |<span class="hljs-comment">-- LOCK</span>
  15. |<span class="hljs-comment">-- LOG</span>
  16. `<span class="hljs-comment">-- MANIFEST-000002</span>
  17. C.<span class="hljs-built_in">log</span>/
  18. |<span class="hljs-comment">-- 000003.log</span>
  19. |<span class="hljs-comment">-- CURRENT</span>
  20. |<span class="hljs-comment">-- LOCK</span>
  21. |<span class="hljs-comment">-- LOG</span>
  22. `<span class="hljs-comment">-- MANIFEST-000002</span>
  23. <span class="hljs-number">0</span> <span class="hljs-built_in">directories</span>, <span class="hljs-number">17</span> <span class="hljs-built_in">files</span></code>
  • 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. <code class="hljs asciidoc has-numbering"><span class="hljs-code">-------------------------------------------------------------------
  2. GMS: address=A, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:50100
  3. -------------------------------------------------------------------</span>
  4. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit
  5. first-applied=0, last-applied=0, commit-index=0, log size=0b:
  6. roleChanged to: Candidate
  7. roleChanged to: Leader
  8. 0
  9. <span class="hljs-attribute">[14:16:00.744] Apply: cmd=[CREATE]</span>
  10. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit
  11. first-applied=0, last-applied=1, commit-index=1, log size=1b:
  12. <span class="hljs-attribute">[14:16:07.002] Apply: cmd=[INCREMENT_AND_GET]</span>
  13. <span class="hljs-attribute">[14:16:14.264] Apply: cmd=[DECREMENT_AND_GET]</span>
  14. 3
  15. <span class="hljs-header">index (term): command
  16. ---------------------</span>
  17. 1 (29): CREATE(counter, 1)
  18. 2 (29): INCREMENT<span class="hljs-emphasis">_AND_</span>GET(counter)
  19. 3 (29): DECREMENT<span class="hljs-emphasis">_AND_</span>GET(counter)</code>
  • 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. <code class="hljs sql has-numbering"><span class="hljs-comment">-------------------------------------------------------------------</span>
  2. GMS: address=B, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:50101
  3. <span class="hljs-comment">-------------------------------------------------------------------</span>
  4. [0] <span class="hljs-operator"><span class="hljs-keyword">Create</span> [<span class="hljs-number">1</span>] Increment [<span class="hljs-number">2</span>] Decrement [<span class="hljs-number">3</span>] Dump log [<span class="hljs-number">4</span>] Snapshot [x] Exit
  5. <span class="hljs-keyword">first</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">last</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">commit</span>-index=<span class="hljs-number">0</span>, log <span class="hljs-keyword">size</span>=<span class="hljs-number">0</span>b:
  6. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">01.300</span>] Apply: cmd=[<span class="hljs-keyword">CREATE</span>]
  7. <span class="hljs-number">1</span>
  8. counter: <span class="hljs-number">2</span>
  9. [<span class="hljs-number">0</span>] <span class="hljs-keyword">Create</span> [<span class="hljs-number">1</span>] Increment [<span class="hljs-number">2</span>] Decrement [<span class="hljs-number">3</span>] Dump log [<span class="hljs-number">4</span>] Snapshot [x] Exit
  10. <span class="hljs-keyword">first</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">last</span>-applied=<span class="hljs-number">2</span>, <span class="hljs-keyword">commit</span>-index=<span class="hljs-number">1</span>, log <span class="hljs-keyword">size</span>=<span class="hljs-number">2</span>b:
  11. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">07.299</span>] Apply: cmd=[INCREMENT_AND_GET]
  12. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">14.304</span>] Apply: cmd=[DECREMENT_AND_GET]
  13. roleChanged <span class="hljs-keyword">to</span>: Candidate
  14. roleChanged <span class="hljs-keyword">to</span>: Leader</span></code>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

C的控制台输出:

Prettyprint代码  
  1. <code class="hljs sql has-numbering"><span class="hljs-comment">-------------------------------------------------------------------</span>
  2. GMS: address=C, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:55800
  3. <span class="hljs-comment">-------------------------------------------------------------------</span>
  4. [0] <span class="hljs-operator"><span class="hljs-keyword">Create</span> [<span class="hljs-number">1</span>] Increment [<span class="hljs-number">2</span>] Decrement [<span class="hljs-number">3</span>] Dump log [<span class="hljs-number">4</span>] Snapshot [x] Exit
  5. <span class="hljs-keyword">first</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">last</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">commit</span>-index=<span class="hljs-number">0</span>, log <span class="hljs-keyword">size</span>=<span class="hljs-number">0</span>b:
  6. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">01.300</span>] Apply: cmd=[<span class="hljs-keyword">CREATE</span>]
  7. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">07.299</span>] Apply: cmd=[INCREMENT_AND_GET]
  8. <span class="hljs-number">2</span>
  9. counter: <span class="hljs-number">3</span>
  10. [<span class="hljs-number">0</span>] <span class="hljs-keyword">Create</span> [<span class="hljs-number">1</span>] Increment [<span class="hljs-number">2</span>] Decrement [<span class="hljs-number">3</span>] Dump log [<span class="hljs-number">4</span>] Snapshot [x] Exit
  11. <span class="hljs-keyword">first</span>-applied=<span class="hljs-number">0</span>, <span class="hljs-keyword">last</span>-applied=<span class="hljs-number">3</span>, <span class="hljs-keyword">commit</span>-index=<span class="hljs-number">2</span>, log <span class="hljs-keyword">size</span>=<span class="hljs-number">3</span>b:
  12. [<span class="hljs-number">14</span>:<span class="hljs-number">16</span>:<span class="hljs-number">14.304</span>] Apply: cmd=[DECREMENT_AND_GET]</span></code>
  • 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. <code class="hljs asciidoc has-numbering"><span class="hljs-code">-------------------------------------------------------------------
  2. GMS: address=A, cluster=ctr-cluster, physical address=2001:0:9d38:6abd:cbb:1f78:3f57:50f6:53237
  3. -------------------------------------------------------------------</span>
  4. [0] Create [1] Increment [2] Decrement [3] Dump log [4] Snapshot [x] Exit
  5. first-applied=0, last-applied=3, commit-index=3, log size=3b:
  6. <span class="hljs-attribute">[14:18:45.275] Apply: cmd=[INCREMENT_AND_GET]</span>
  7. <span class="hljs-attribute">[14:18:45.277] Apply: cmd=[GET]</span>
  8. 3
  9. <span class="hljs-header">index (term): command
  10. ---------------------</span>
  11. 1 (69): CREATE(counter, 1)
  12. 2 (69): INCREMENT<span class="hljs-emphasis">_AND_</span>GET(counter)
  13. 3 (69): DECREMENT<span class="hljs-emphasis">_AND_</span>GET(counter)
  14. 4 (70): INCREMENT<span class="hljs-emphasis">_AND_</span>GET(counter)
  15. 5 (70): GET(counter)</code>
  • 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原理与实例相关推荐

  1. [分布式一致性协议] ------ raft协议的解释与理解

    前言 在分布式系统中,为了保证容错性,一般会维护多个副本集群,提高系统的高可用,但与之带来的问题就是多个副本的一致性(consensus)问题. 我们认为,对于一个具有一致性的的集群中,同一时刻所有节 ...

  2. 分布式一致性协议Raft,以及难搞的Paxos

    Raft这玩意,网上已经有好多解读文章了,大概比Paxos还要多一些,所以,这篇,不求细节,但求核心思想方面,追一下本源,然后,给自己做个笔记. Raft是什么,它想解决什么问题? 所以Raft是什么 ...

  3. 分布式一致性协议Raft(一)

    铺垫 一个设计良好的分布式系统,应具备四大特点: 并行性能(parallel performance):任务能均衡高效地在多台机器上执行,无需过高的通讯和锁消耗. 容错性(fault-toleranc ...

  4. 分布式一致性协议 raft协议 动画版

    https://raft.github.io/raft.pdf 动画: http://thesecretlivesofdata.com/raft/ 感觉还要梳理一下.

  5. JUST技术:分布式一致性协议概念及Raft协议简介

    分布式系统通常由异步网络连接的多个节点构成,每个节点的计算和存储相互独立.分布式一致性指多个节点对某一变量的取值达成一致,一旦达成一致,则变量的本次取值被确定.本文将简单介绍一致性的一些基本概念,以及 ...

  6. Raft分布式一致性协议基本过程

    前言 raft协议是分布式一致性协议的一种实现方案,那么什么是分布式一致性,这就还得需要了解其他的网络知识了 为了向用户提供服务,就需要有对应的服务器提供,在早些年的时候,很多网络应用服务器节点都只有 ...

  7. 【人工智能 Open AI】解释一下 Raft 分布式一致性协议算法,并用伪代码实例说明。

    解释一下 Raft 分布式一致性协议算法,并用伪代码实例说明. 文章目录 Raft 简介 Raft Protocol Description Raft 协议讲解 Raft vs. Paxos Raft ...

  8. 分布式一致性协议三部曲-从paxos幽灵复现看Raft实现原理

    幽灵复现 Mutlti-Paxos下存在Leader切换情况,因而可能出现下面的场景 第一轮中A被选为 Leader,写下了 1-10 号日志,其中 1-5 号日志形成了多数派,并且已给客户端应答,而 ...

  9. raft算法_学习分布式一致性协议:自己实现一个Raft算法

    前言 MIT6.824是麻省理工学院开设的一个很棒的分布式系统公开课程,课程的Schedule在这里 ,这门课程的学习方式主要是通过教授的 lecture 讲解.Paper阅读.FAQ答疑,以及实践l ...

最新文章

  1. OpenCV 【四】————Watershed Algorithm(图像分割)——分水岭算法的原理及实现
  2. HDU 2896 病毒侵袭 AC自己主动机题解
  3. 偶然发现静态函数与性能一例
  4. python迭代列表_迭代建立列表的最python方法?
  5. Java:ChronicleMap第3部分,快速微服务
  6. DotNet软件开发框架
  7. Quality of Service 0, 1 2
  8. 关于vue2.0+hbuilder打包移动端app之后空白页面的解决方案
  9. CCNA配置试验之八 帧中继——点到点子接口(point-to-point)的配置
  10. 牛客小白月赛5求阶乘末尾有多少个0
  11. Java中的【锁】事 - 极客大学架构师训练营 架构师 Albert 分享
  12. [网络安全自学篇] 八十七.恶意代码检测技术详解及总结
  13. python基础分析_数据分析之Python基础
  14. 小米路由器Mesh,信号有多牛?
  15. 华为g9青春版连接计算机,一键式访问华为G9和USB驱动程序
  16. PHP获取微信支付v2预支付参数prepay_id后在小程序端完成支付
  17. 信创云“华山论剑” 五强鼎力谁与争锋?
  18. 4.电子计算机的分类,国际上对计算机进行分类的依据是什么
  19. html批量处理图片大小,美图看看批量处理百张图片(大小、格式以及名称的批量编辑)...
  20. python中msg函数_Python之函数

热门文章

  1. mysql基于.frm和.ibd进行mysql数据恢复
  2. Spark rdd 介绍,和案例介绍
  3. 5.Maven和Eclipse整合(两种方式进行插件的安装),Maven相关设置,Eclipse下创建Maven项目
  4. 2021年计算机网络期末考试题,2021年计算机网络期末考试试题及答案-20210515145802.doc-原创力文档...
  5. python图像处理专业博客
  6. 高效运维最佳实践:如何做好On-call和事故响应?
  7. javascript闭包,你大爷永远是你大爷
  8. 3月16日学习内容整理:metaclass
  9. Python爬虫 搜索并下载图片
  10. Sql Server 常用日期格式