├── .gitignore
├── lu-raft-kv
├── src
│ ├── main
│ │ ├── java
│ │ │ ├── cn
│ │ │ │ └── think
│ │ │ │ │ └── in
│ │ │ │ │ └── java
│ │ │ │ │ ├── LifeCycle.java
│ │ │ │ │ ├── rpc
│ │ │ │ │ ├── RpcClient.java
│ │ │ │ │ ├── RpcServer.java
│ │ │ │ │ ├── RaftUserProcessor.java
│ │ │ │ │ ├── DefaultRpcClient.java
│ │ │ │ │ ├── Response.java
│ │ │ │ │ ├── Request.java
│ │ │ │ │ └── DefaultRpcServer.java
│ │ │ │ │ ├── membership
│ │ │ │ │ └── changes
│ │ │ │ │ │ ├── Server.java
│ │ │ │ │ │ ├── ClusterMembershipChanges.java
│ │ │ │ │ │ └── Result.java
│ │ │ │ │ ├── util
│ │ │ │ │ └── LongConvert.java
│ │ │ │ │ ├── exception
│ │ │ │ │ ├── RaftNotSupportException.java
│ │ │ │ │ └── RaftRemotingException.java
│ │ │ │ │ ├── LogModule.java
│ │ │ │ │ ├── entity
│ │ │ │ │ ├── BaseParam.java
│ │ │ │ │ ├── RvoteResult.java
│ │ │ │ │ ├── Command.java
│ │ │ │ │ ├── AentryResult.java
│ │ │ │ │ ├── RvoteParam.java
│ │ │ │ │ ├── ReplicationFailModel.java
│ │ │ │ │ ├── LogEntry.java
│ │ │ │ │ └── AentryParam.java
│ │ │ │ │ ├── StateMachine.java
│ │ │ │ │ ├── common
│ │ │ │ │ ├── NodeConfig.java
│ │ │ │ │ ├── NodeStatus.java
│ │ │ │ │ ├── Peer.java
│ │ │ │ │ └── PeerSet.java
│ │ │ │ │ ├── current
│ │ │ │ │ ├── RaftThread.java
│ │ │ │ │ ├── SleepHelper.java
│ │ │ │ │ ├── RaftThreadPoolExecutor.java
│ │ │ │ │ └── RaftThreadPool.java
│ │ │ │ │ ├── constant
│ │ │ │ │ └── StateMachineSaveType.java
│ │ │ │ │ ├── Consensus.java
│ │ │ │ │ ├── Node.java
│ │ │ │ │ ├── RaftNodeBootStrap.java
│ │ │ │ │ └── impl
│ │ │ │ │ ├── ClusterMembershipChangesImpl.java
│ │ │ │ │ ├── RedisStateMachine.java
│ │ │ │ │ ├── DefaultStateMachine.java
│ │ │ │ │ ├── DefaultLogModule.java
│ │ │ │ │ ├── DefaultConsensus.java
│ │ │ │ │ └── DefaultNode.java
│ │ │ └── raft
│ │ │ │ └── client
│ │ │ │ ├── ClientKVAck.java
│ │ │ │ ├── ClientKVReq.java
│ │ │ │ ├── RaftClient2.java
│ │ │ │ ├── RaftClient.java
│ │ │ │ └── RaftClient3.java
│ │ └── resources
│ │ │ └── log4j.xml
│ └── test
│ │ └── java
│ │ └── cn
│ │ └── think
│ │ └── in
│ │ └── java
│ │ └── impl
│ │ ├── DefaultStateMachineTest.java
│ │ ├── DefaultLogModuleTest.java
│ │ └── RocksDBTest.java
└── pom.xml
├── readme.md
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | /target
3 | lu-raft/.idea
4 | *.iml
5 | /rocksDB-raft
6 | .idea
7 | target/
8 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/LifeCycle.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | /**
4 | *
5 | * @author 莫那·鲁道
6 | */
7 | public interface LifeCycle {
8 |
9 | void init() throws Throwable;
10 |
11 | void destroy() throws Throwable;
12 | }
13 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/RpcClient.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | /**
4 | *
5 | * @author 莫那·鲁道
6 | */
7 | public interface RpcClient {
8 |
9 | Response send(Request request);
10 |
11 | Response send(Request request, int timeout);
12 | }
13 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/RpcServer.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | /**
4 | * @author 莫那·鲁道
5 | */
6 | public interface RpcServer {
7 |
8 | void start();
9 |
10 | void stop();
11 |
12 | Response handlerRequest(Request request);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/membership/changes/Server.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.membership.changes;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | * @author 莫那·鲁道
8 | */
9 | @Getter
10 | @Setter
11 | public class Server {
12 |
13 | String address;
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/util/LongConvert.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.util;
2 |
3 | /**
4 | *
5 | * @author 莫那·鲁道
6 | */
7 | public class LongConvert {
8 |
9 | public static long convert(Long l) {
10 | if (l == null) {
11 | return 0;
12 | }
13 | return l;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/exception/RaftNotSupportException.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.exception;
2 |
3 | /**
4 | *
5 | */
6 | public class RaftNotSupportException extends RuntimeException {
7 |
8 | public RaftNotSupportException() {
9 | }
10 |
11 | public RaftNotSupportException(String message) {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/exception/RaftRemotingException.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.exception;
2 |
3 | /**
4 | *
5 | */
6 | public class RaftRemotingException extends RuntimeException {
7 |
8 | public RaftRemotingException() {
9 | super();
10 | }
11 |
12 | public RaftRemotingException(String message) {
13 | super(message);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/LogModule.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | import cn.think.in.java.entity.LogEntry;
4 |
5 | /**
6 | *
7 | * @see cn.think.in.java.entity.LogEntry
8 | * @author 莫那·鲁道
9 | */
10 | public interface LogModule {
11 |
12 | void write(LogEntry logEntry);
13 |
14 | LogEntry read(Long index);
15 |
16 | void removeOnStartIndex(Long startIndex);
17 |
18 | LogEntry getLast();
19 |
20 | Long getLastIndex();
21 | }
22 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/BaseParam.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | *
11 | */
12 | @Getter
13 | @Setter
14 | @ToString
15 | public class BaseParam implements Serializable {
16 |
17 | /** 候选人的任期号 */
18 | public long term;
19 |
20 | /** 被请求者 ID(ip:selfPort) */
21 | public String serverId;
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/membership/changes/ClusterMembershipChanges.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.membership.changes;
2 |
3 | import cn.think.in.java.common.Peer;
4 |
5 | /**
6 | *
7 | * 集群配置变更接口.
8 | *
9 | * @author 莫那·鲁道
10 | */
11 | public interface ClusterMembershipChanges {
12 |
13 | /**
14 | * 添加节点.
15 | * @param newPeer
16 | * @return
17 | */
18 | Result addPeer(Peer newPeer);
19 |
20 | /**
21 | * 删除节点.
22 | * @param oldPeer
23 | * @return
24 | */
25 | Result removePeer(Peer oldPeer);
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/StateMachine.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | import cn.think.in.java.entity.LogEntry;
4 |
5 | /**
6 | * 状态机接口.
7 | * @author 莫那·鲁道
8 | */
9 | public interface StateMachine {
10 |
11 | /**
12 | * 将数据应用到状态机.
13 | *
14 | * 原则上,只需这一个方法(apply). 其他的方法是为了更方便的使用状态机.
15 | * @param logEntry 日志中的数据.
16 | */
17 | void apply(LogEntry logEntry);
18 |
19 | LogEntry get(String key);
20 |
21 | String getString(String key);
22 |
23 | void setString(String key, String value);
24 |
25 | void delString(String... key);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/common/NodeConfig.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.common;
2 |
3 | import java.util.List;
4 |
5 | import cn.think.in.java.constant.StateMachineSaveType;
6 | import lombok.Getter;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 |
10 | /**
11 | *
12 | * 节点配置
13 | *
14 | * @author 莫那·鲁道
15 | */
16 | @Getter
17 | @Setter
18 | @ToString
19 | public class NodeConfig {
20 |
21 | /** 自身 selfPort */
22 | public int selfPort;
23 |
24 | /** 所有节点地址. */
25 | public List peerAddrs;
26 | /**
27 | * 状态快照存储类型
28 | */
29 | public StateMachineSaveType stateMachineSaveType;
30 | }
31 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/current/RaftThread.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.current;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | *
8 | * @author 莫那·鲁道
9 | */
10 | public class RaftThread extends Thread {
11 |
12 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftThread.class);
13 | private static final UncaughtExceptionHandler uncaughtExceptionHandler = (t, e)
14 | -> LOGGER.warn("Exception occurred from thread {}", t.getName(), e);
15 |
16 | public RaftThread(String threadName, Runnable r) {
17 | super(r, threadName);
18 | setUncaughtExceptionHandler(uncaughtExceptionHandler);
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/common/NodeStatus.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.common;
2 |
3 | import lombok.Getter;
4 |
5 | /**
6 | *
7 | * @author 莫那·鲁道
8 | */
9 | public interface NodeStatus {
10 |
11 | int FOLLOWER = 0;
12 | int CANDIDATE = 1;
13 | int LEADER = 2;
14 |
15 | @Getter
16 | enum Enum {
17 | FOLLOWER(0), CANDIDATE(1), LEADER(2);
18 |
19 | Enum(int code) {
20 | this.code = code;
21 | }
22 |
23 | int code;
24 |
25 | public static Enum value(int i) {
26 | for (Enum value : Enum.values()) {
27 | if (value.code == i) {
28 | return value;
29 | }
30 | }
31 | return null;
32 | }
33 |
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/RaftUserProcessor.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | import com.alipay.remoting.AsyncContext;
4 | import com.alipay.remoting.BizContext;
5 | import com.alipay.remoting.rpc.protocol.AbstractUserProcessor;
6 |
7 | import cn.think.in.java.exception.RaftNotSupportException;
8 |
9 | /**
10 | *
11 | * @author 莫那·鲁道
12 | */
13 | public abstract class RaftUserProcessor extends AbstractUserProcessor {
14 |
15 | @Override
16 | public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, T request) {
17 | throw new RaftNotSupportException(
18 | "Raft Server not support handleRequest(BizContext bizCtx, AsyncContext asyncCtx, T request) ");
19 | }
20 |
21 |
22 | @Override
23 | public String interest() {
24 | return Request.class.getName();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/current/SleepHelper.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.current;
2 |
3 | import java.util.concurrent.TimeUnit;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | /**
9 | *
10 | * @author 莫那·鲁道
11 | */
12 | public class SleepHelper {
13 |
14 | private static final Logger LOGGER = LoggerFactory.getLogger(SleepHelper.class);
15 |
16 |
17 | public static void sleep(int ms) {
18 | try {
19 | Thread.sleep(ms);
20 | } catch (InterruptedException e) {
21 | LOGGER.warn(e.getMessage());
22 | }
23 |
24 | }
25 |
26 | public static void sleep2(int seconds) {
27 | try {
28 | TimeUnit.SECONDS.sleep(seconds);
29 | } catch (InterruptedException e) {
30 | LOGGER.warn(e.getMessage());
31 | }
32 |
33 | }
34 |
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/constant/StateMachineSaveType.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.constant;
2 |
3 | import cn.think.in.java.StateMachine;
4 | import cn.think.in.java.impl.DefaultStateMachine;
5 | import cn.think.in.java.impl.RedisStateMachine;
6 |
7 | /**
8 | *
9 | * 快照存储类型
10 | *
11 | * @author rensailong
12 | */
13 | public enum StateMachineSaveType {
14 | REDIS("redis", "redis存储", RedisStateMachine.getInstance()),
15 | ROCKS_DB("RocksDB", "RocksDB本地存储", DefaultStateMachine.getInstance())
16 | ;
17 |
18 | public StateMachine getStateMachine() {
19 | return this.stateMachine;
20 | }
21 |
22 | private String typeName;
23 |
24 | private String desc;
25 |
26 | private StateMachine stateMachine;
27 |
28 | StateMachineSaveType(String typeName, String desc, StateMachine stateMachine) {
29 | this.typeName = typeName;
30 | this.desc = desc;
31 | this.stateMachine = stateMachine;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/common/Peer.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.common;
2 |
3 | import java.util.Objects;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | * 当前节点的 同伴.
10 | *
11 | * @author 莫那·鲁道
12 | */
13 | @Getter
14 | @Setter
15 | public class Peer {
16 |
17 | /** ip:selfPort */
18 | private final String addr;
19 |
20 |
21 | public Peer(String addr) {
22 | this.addr = addr;
23 | }
24 |
25 | @Override
26 | public boolean equals(Object o) {
27 | if (this == o) {
28 | return true;
29 | }
30 | if (o == null || getClass() != o.getClass()) {
31 | return false;
32 | }
33 | Peer peer = (Peer) o;
34 | return Objects.equals(addr, peer.addr);
35 | }
36 |
37 | @Override
38 | public int hashCode() {
39 |
40 | return Objects.hash(addr);
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | return "Peer{" +
46 | "addr='" + addr + '\'' +
47 | '}';
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/Consensus.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | import cn.think.in.java.entity.AentryParam;
4 | import cn.think.in.java.entity.AentryResult;
5 | import cn.think.in.java.entity.RvoteParam;
6 | import cn.think.in.java.entity.RvoteResult;
7 |
8 | /**
9 | *
10 | * Raft 一致性模块.
11 | * @author 莫那·鲁道
12 | */
13 | public interface Consensus {
14 |
15 | /**
16 | * 请求投票 RPC
17 | *
18 | * 接收者实现:
19 | *
20 | * 如果term < currentTerm返回 false (5.2 节)
21 | * 如果 votedFor 为空或者就是 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)
22 | * @return
23 | */
24 | RvoteResult requestVote(RvoteParam param);
25 |
26 | /**
27 | * 附加日志(多个日志,为了提高效率) RPC
28 | *
29 | * 接收者实现:
30 | *
31 | * 如果 term < currentTerm 就返回 false (5.1 节)
32 | * 如果日志在 prevLogIndex 位置处的日志条目的任期号和 prevLogTerm 不匹配,则返回 false (5.3 节)
33 | * 如果已经存在的日志条目和新的产生冲突(索引值相同但是任期号不同),删除这一条和之后所有的 (5.3 节)
34 | * 附加任何在已有的日志中不存在的条目
35 | * 如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个
36 | * @return
37 | */
38 | AentryResult appendEntries(AentryParam param);
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/test/java/cn/think/in/java/impl/DefaultStateMachineTest.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.rocksdb.RocksDBException;
6 |
7 | import cn.think.in.java.entity.Command;
8 | import cn.think.in.java.entity.LogEntry;
9 |
10 | /**
11 | *
12 | * @author 莫那·鲁道
13 | */
14 | public class DefaultStateMachineTest {
15 |
16 | static {
17 | System.setProperty("serverPort", "8777");
18 | DefaultStateMachine.dbDir = "/Users/cxs/code/lu-raft-revert/rocksDB-raft/" + System.getProperty("serverPort");
19 | DefaultStateMachine.stateMachineDir = DefaultStateMachine.dbDir + "/stateMachine";
20 | }
21 | DefaultStateMachine machine;
22 |
23 | @Before
24 | public void before() {
25 | machine = DefaultStateMachine.getInstance();
26 |
27 | }
28 |
29 | @Test
30 | public void apply() {
31 | LogEntry logEntry = LogEntry.newBuilder().term(1).command(Command.newBuilder().key("hello").value("value1").build()).build();
32 | machine.apply(logEntry);
33 | }
34 |
35 |
36 | @Test
37 | public void applyRead() throws RocksDBException {
38 |
39 | System.out.println(machine.get("hello:7"));
40 | }
41 | }
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/raft/client/ClientKVAck.java:
--------------------------------------------------------------------------------
1 | package raft.client;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | *
11 | * @author 莫那·鲁道
12 | */
13 | @Getter
14 | @Setter
15 | @ToString
16 | public class ClientKVAck implements Serializable {
17 |
18 | Object result;
19 |
20 | public ClientKVAck(Object result) {
21 | this.result = result;
22 | }
23 |
24 | private ClientKVAck(Builder builder) {
25 | setResult(builder.result);
26 | }
27 |
28 | public static ClientKVAck ok() {
29 | return new ClientKVAck("ok");
30 | }
31 |
32 | public static ClientKVAck fail() {
33 | return new ClientKVAck("fail");
34 | }
35 |
36 | public static Builder newBuilder() {
37 | return new Builder();
38 | }
39 |
40 |
41 | public static final class Builder {
42 |
43 | private Object result;
44 |
45 | private Builder() {
46 | }
47 |
48 | public Builder result(Object val) {
49 | result = val;
50 | return this;
51 | }
52 |
53 | public ClientKVAck build() {
54 | return new ClientKVAck(this);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/Node.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | import cn.think.in.java.common.NodeConfig;
4 | import cn.think.in.java.entity.AentryParam;
5 | import cn.think.in.java.entity.AentryResult;
6 | import cn.think.in.java.entity.RvoteParam;
7 | import cn.think.in.java.entity.RvoteResult;
8 | import raft.client.ClientKVAck;
9 | import raft.client.ClientKVReq;
10 |
11 | /**
12 | *
13 | * @author 莫那·鲁道
14 | */
15 | public interface Node extends LifeCycle{
16 |
17 | /**
18 | * 设置配置文件.
19 | *
20 | * @param config
21 | */
22 | void setConfig(NodeConfig config);
23 |
24 | /**
25 | * 处理请求投票 RPC.
26 | *
27 | * @param param
28 | * @return
29 | */
30 | RvoteResult handlerRequestVote(RvoteParam param);
31 |
32 | /**
33 | * 处理附加日志请求.
34 | *
35 | * @param param
36 | * @return
37 | */
38 | AentryResult handlerAppendEntries(AentryParam param);
39 |
40 | /**
41 | * 处理客户端请求.
42 | *
43 | * @param request
44 | * @return
45 | */
46 | ClientKVAck handlerClientRequest(ClientKVReq request);
47 |
48 | /**
49 | * 转发给 leader 节点.
50 | * @param request
51 | * @return
52 | */
53 | ClientKVAck redirect(ClientKVReq request);
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/DefaultRpcClient.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | import com.alipay.remoting.exception.RemotingException;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import cn.think.in.java.exception.RaftRemotingException;
9 |
10 | /**
11 | *
12 | * @author 莫那·鲁道
13 | */
14 | public class DefaultRpcClient implements RpcClient {
15 |
16 | public static Logger logger = LoggerFactory.getLogger(DefaultRpcClient.class.getName());
17 |
18 | private final static com.alipay.remoting.rpc.RpcClient CLIENT = new com.alipay.remoting.rpc.RpcClient();
19 | static {
20 | CLIENT.init();
21 | }
22 |
23 |
24 | @Override
25 | public Response send(Request request) {
26 | return send(request, 200000);
27 | }
28 |
29 | @Override
30 | public Response send(Request request, int timeout) {
31 | Response result = null;
32 | try {
33 | result = (Response) CLIENT.invokeSync(request.getUrl(), request, timeout);
34 | } catch (RemotingException e) {
35 | e.printStackTrace();
36 | logger.info("rpc RaftRemotingException ");
37 | throw new RaftRemotingException();
38 | } catch (InterruptedException e) {
39 | e.printStackTrace();
40 | }
41 | return (result);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/Response.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | *
10 | * @author 莫那·鲁道
11 | */
12 | @Getter
13 | @Setter
14 | public class Response implements Serializable {
15 |
16 |
17 | private T result;
18 |
19 | public Response(T result) {
20 | this.result = result;
21 | }
22 |
23 | private Response(Builder builder) {
24 | setResult((T) builder.result);
25 | }
26 |
27 | public static Response ok() {
28 | return new Response<>("ok");
29 | }
30 |
31 | public static Response fail() {
32 | return new Response<>("fail");
33 | }
34 |
35 | public static Builder newBuilder() {
36 | return new Builder();
37 | }
38 |
39 |
40 | @Override
41 | public String toString() {
42 | return "Response{" +
43 | "result=" + result +
44 | '}';
45 | }
46 |
47 | public static final class Builder {
48 |
49 | private Object result;
50 |
51 | private Builder() {
52 | }
53 |
54 | public Builder result(Object val) {
55 | result = val;
56 | return this;
57 | }
58 |
59 | public Response build() {
60 | return new Response(this);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/RaftNodeBootStrap.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java;
2 |
3 | import java.util.Arrays;
4 |
5 | import com.google.common.collect.Lists;
6 |
7 | import cn.think.in.java.common.NodeConfig;
8 | import cn.think.in.java.constant.StateMachineSaveType;
9 | import cn.think.in.java.impl.DefaultNode;
10 |
11 | /**
12 | * -DserverPort=8775
13 | * -DserverPort=8776
14 | * -DserverPort=8777
15 | * -DserverPort=8778
16 | * -DserverPort=8779
17 | */
18 | public class RaftNodeBootStrap {
19 |
20 | public static void main(String[] args) throws Throwable {
21 | main0();
22 | }
23 |
24 | public static void main0() throws Throwable {
25 | String[] peerAddr = {"localhost:8775","localhost:8776","localhost:8777", "localhost:8778", "localhost:8779"};
26 |
27 | NodeConfig config = new NodeConfig();
28 |
29 | // 自身节点
30 | config.setSelfPort(Integer.valueOf(System.getProperty("serverPort")));
31 |
32 | // 其他节点地址
33 | config.setPeerAddrs(Arrays.asList(peerAddr));
34 | config.setStateMachineSaveType(StateMachineSaveType.ROCKS_DB);
35 | Node node = DefaultNode.getInstance();
36 | node.setConfig(config);
37 |
38 | node.init();
39 |
40 | Runtime.getRuntime().addShutdownHook(new Thread(() -> {
41 | try {
42 | node.destroy();
43 | } catch (Throwable throwable) {
44 | throwable.printStackTrace();
45 | }
46 | }));
47 |
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/RvoteResult.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 |
8 | /**
9 | *
10 | * 请求投票 RPC 返回值.
11 | *
12 | */
13 | @Getter
14 | @Setter
15 | public class RvoteResult implements Serializable {
16 |
17 | /** 当前任期号,以便于候选人去更新自己的任期 */
18 | long term;
19 |
20 | /** 候选人赢得了此张选票时为真 */
21 | boolean voteGranted;
22 |
23 | public RvoteResult(boolean voteGranted) {
24 | this.voteGranted = voteGranted;
25 | }
26 |
27 | private RvoteResult(Builder builder) {
28 | setTerm(builder.term);
29 | setVoteGranted(builder.voteGranted);
30 | }
31 |
32 | public static RvoteResult fail() {
33 | return new RvoteResult(false);
34 | }
35 |
36 | public static RvoteResult ok() {
37 | return new RvoteResult(true);
38 | }
39 |
40 | public static Builder newBuilder() {
41 | return new Builder();
42 | }
43 |
44 |
45 | public static final class Builder {
46 |
47 | private long term;
48 | private boolean voteGranted;
49 |
50 | private Builder() {
51 | }
52 |
53 | public Builder term(long term) {
54 | this.term = term;
55 | return this;
56 | }
57 |
58 | public Builder voteGranted(boolean voteGranted) {
59 | this.voteGranted = voteGranted;
60 | return this;
61 | }
62 |
63 | public RvoteResult build() {
64 | return new RvoteResult(this);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lu-raft-kv/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | cn.think.in.java
9 | lu-raft-parent
10 | 1.0-RELEASE
11 |
12 |
13 | lu-raft-kv
14 |
15 |
16 | 2.7.1
17 |
18 |
19 |
20 |
21 | junit
22 | junit
23 | 4.12
24 | test
25 |
26 |
27 |
28 | redis.clients
29 | jedis
30 | ${jedis-jar.version}
31 |
32 |
33 |
34 | com.google.guava
35 | guava
36 | 23.0
37 |
38 |
41 |
42 | org.rocksdb
43 | rocksdbjni
44 | 5.14.3
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/current/RaftThreadPoolExecutor.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.current;
2 |
3 | import java.util.concurrent.BlockingQueue;
4 | import java.util.concurrent.ThreadPoolExecutor;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | /**
11 | *
12 | * @author 莫那·鲁道
13 | */
14 | public class RaftThreadPoolExecutor extends ThreadPoolExecutor {
15 |
16 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftThreadPoolExecutor.class);
17 |
18 | private static final ThreadLocal COST_TIME_WATCH = ThreadLocal.withInitial(System::currentTimeMillis);
19 |
20 | public RaftThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
21 | BlockingQueue workQueue, RaftThreadPool.NameThreadFactory nameThreadFactory) {
22 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, nameThreadFactory);
23 | }
24 |
25 | @Override
26 | protected void beforeExecute(Thread t, Runnable r) {
27 | COST_TIME_WATCH.get();
28 | LOGGER.debug("raft thread pool before Execute");
29 | }
30 |
31 | @Override
32 | protected void afterExecute(Runnable r, Throwable t) {
33 | LOGGER.debug("raft thread pool after Execute, cost time : {}", System.currentTimeMillis() - COST_TIME_WATCH.get());
34 | COST_TIME_WATCH.remove();
35 | }
36 |
37 | @Override
38 | protected void terminated() {
39 | LOGGER.info("active count : {}, queueSize : {}, poolSize : {}", getActiveCount(), getQueue().size(), getPoolSize());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/common/PeerSet.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.common;
2 |
3 | import java.io.Serializable;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | /**
8 | *
9 | * 节点集合. 去重.
10 | *
11 | * @author 莫那·鲁道
12 | */
13 | public class PeerSet implements Serializable {
14 |
15 | private List list = new ArrayList<>();
16 |
17 | private volatile Peer leader;
18 |
19 | /** final */
20 | private volatile Peer self;
21 |
22 | private PeerSet() {
23 | }
24 |
25 | public static PeerSet getInstance() {
26 | return PeerSetLazyHolder.INSTANCE;
27 | }
28 |
29 | private static class PeerSetLazyHolder {
30 |
31 | private static final PeerSet INSTANCE = new PeerSet();
32 | }
33 |
34 | public void setSelf(Peer peer) {
35 | self = peer;
36 | }
37 |
38 | public Peer getSelf() {
39 | return self;
40 | }
41 |
42 | public void addPeer(Peer peer) {
43 | list.add(peer);
44 | }
45 |
46 | public void removePeer(Peer peer) {
47 | list.remove(peer);
48 | }
49 |
50 | public List getPeers() {
51 | return list;
52 | }
53 |
54 | public List getPeersWithOutSelf() {
55 | List list2 = new ArrayList<>(list);
56 | list2.remove(self);
57 | return list2;
58 | }
59 |
60 |
61 | public Peer getLeader() {
62 | return leader;
63 | }
64 |
65 | public void setLeader(Peer peer) {
66 | leader = peer;
67 | }
68 |
69 | @Override
70 | public String toString() {
71 | return "PeerSet{" +
72 | "list=" + list +
73 | ", leader=" + leader +
74 | ", self=" + self +
75 | '}';
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/Command.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.io.Serializable;
4 | import java.util.Objects;
5 |
6 | import lombok.Getter;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 |
10 | /**
11 | *
12 | * @author 莫那·鲁道
13 | */
14 | @Getter
15 | @Setter
16 | @ToString
17 | public class Command implements Serializable {
18 |
19 | String key;
20 |
21 | String value;
22 |
23 | public Command(String key, String value) {
24 | this.key = key;
25 | this.value = value;
26 | }
27 |
28 | private Command(Builder builder) {
29 | setKey(builder.key);
30 | setValue(builder.value);
31 | }
32 |
33 | public static Builder newBuilder() {
34 | return new Builder();
35 | }
36 |
37 |
38 | @Override
39 | public boolean equals(Object o) {
40 | if (this == o) {
41 | return true;
42 | }
43 | if (o == null || getClass() != o.getClass()) {
44 | return false;
45 | }
46 | Command command = (Command) o;
47 | return Objects.equals(key, command.key) &&
48 | Objects.equals(value, command.value);
49 | }
50 |
51 | @Override
52 | public int hashCode() {
53 | return Objects.hash(key, value);
54 | }
55 |
56 | public static final class Builder {
57 |
58 | private String key;
59 | private String value;
60 |
61 | private Builder() {
62 | }
63 |
64 | public Builder key(String val) {
65 | key = val;
66 | return this;
67 | }
68 |
69 | public Builder value(String val) {
70 | value = val;
71 | return this;
72 | }
73 |
74 | public Command build() {
75 | return new Command(this);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/AentryResult.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | *
11 | * 附加 RPC 日志返回值.
12 | *
13 | * @author 莫那·鲁道
14 | */
15 | @Setter
16 | @Getter
17 | @ToString
18 | public class AentryResult implements Serializable {
19 |
20 | /** 当前的任期号,用于领导人去更新自己 */
21 | long term;
22 |
23 | /** 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真 */
24 | boolean success;
25 |
26 | public AentryResult(long term) {
27 | this.term = term;
28 | }
29 |
30 | public AentryResult(boolean success) {
31 | this.success = success;
32 | }
33 |
34 | public AentryResult(long term, boolean success) {
35 | this.term = term;
36 | this.success = success;
37 | }
38 |
39 | private AentryResult(Builder builder) {
40 | setTerm(builder.term);
41 | setSuccess(builder.success);
42 | }
43 |
44 | public static AentryResult fail() {
45 | return new AentryResult(false);
46 | }
47 |
48 | public static AentryResult ok() {
49 | return new AentryResult(true);
50 | }
51 |
52 | public static Builder newBuilder() {
53 | return new Builder();
54 | }
55 |
56 |
57 | public static final class Builder {
58 |
59 | private long term;
60 | private boolean success;
61 |
62 | private Builder() {
63 | }
64 |
65 | public Builder term(long val) {
66 | term = val;
67 | return this;
68 | }
69 |
70 | public Builder success(boolean val) {
71 | success = val;
72 | return this;
73 | }
74 |
75 | public AentryResult build() {
76 | return new AentryResult(this);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/raft/client/ClientKVReq.java:
--------------------------------------------------------------------------------
1 | package raft.client;
2 |
3 | import java.io.Serializable;
4 |
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import lombok.ToString;
8 |
9 | /**
10 | *
11 | * @author 莫那·鲁道
12 | */
13 | @Getter
14 | @Setter
15 | @ToString
16 | public class ClientKVReq implements Serializable {
17 |
18 | public static int PUT = 0;
19 | public static int GET = 1;
20 |
21 | int type;
22 |
23 | String key;
24 |
25 | String value;
26 |
27 | private ClientKVReq(Builder builder) {
28 | setType(builder.type);
29 | setKey(builder.key);
30 | setValue(builder.value);
31 | }
32 |
33 | public static Builder newBuilder() {
34 | return new Builder();
35 | }
36 |
37 | public enum Type {
38 | PUT(0), GET(1);
39 | int code;
40 |
41 | Type(int code) {
42 | this.code = code;
43 | }
44 |
45 | public static Type value(int code ) {
46 | for (Type type : values()) {
47 | if (type.code == code) {
48 | return type;
49 | }
50 | }
51 | return null;
52 | }
53 | }
54 |
55 |
56 | public static final class Builder {
57 |
58 | private int type;
59 | private String key;
60 | private String value;
61 |
62 | private Builder() {
63 | }
64 |
65 |
66 | public Builder type(int val) {
67 | type = val;
68 | return this;
69 | }
70 |
71 | public Builder key(String val) {
72 | key = val;
73 | return this;
74 | }
75 |
76 | public Builder value(String val) {
77 | value = val;
78 | return this;
79 | }
80 |
81 | public ClientKVReq build() {
82 | return new ClientKVReq(this);
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/membership/changes/Result.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.membership.changes;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | *
8 | * @author 莫那·鲁道
9 | */
10 | @Getter
11 | @Setter
12 | public class Result {
13 |
14 | public static final int FAIL = 0;
15 | public static final int SUCCESS = 1;
16 |
17 | int status;
18 |
19 | String leaderHint;
20 |
21 | public Result() {
22 | }
23 |
24 | public Result(Builder builder) {
25 | setStatus(builder.status);
26 | setLeaderHint(builder.leaderHint);
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return "Result{" +
32 | "status=" + status +
33 | ", leaderHint='" + leaderHint + '\'' +
34 | '}';
35 | }
36 |
37 | public static Builder newBuilder() {
38 | return new Builder();
39 | }
40 |
41 | @Getter
42 | public enum Status {
43 | FAIL(0), SUCCESS(1);
44 |
45 | int code;
46 |
47 | Status(int code) {
48 | this.code = code;
49 | }
50 |
51 | public static Status value(int v) {
52 | for (Status i : values()) {
53 | if (i.code == v) {
54 | return i;
55 | }
56 | }
57 | return null;
58 | }
59 | }
60 |
61 | public static final class Builder {
62 |
63 | private int status;
64 | private String leaderHint;
65 |
66 | private Builder() {
67 | }
68 |
69 | public Builder status(int val) {
70 | status = val;
71 | return this;
72 | }
73 |
74 | public Builder leaderHint(String val) {
75 | leaderHint = val;
76 | return this;
77 | }
78 |
79 | public Result build() {
80 | return new Result(this);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/test/java/cn/think/in/java/impl/DefaultLogModuleTest.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import org.junit.After;
4 | import org.junit.Assert;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import cn.think.in.java.entity.Command;
9 | import cn.think.in.java.entity.LogEntry;
10 |
11 |
12 | /**
13 | *
14 | * @author 莫那·鲁道
15 | */
16 | public class DefaultLogModuleTest {
17 |
18 | static {
19 | System.setProperty("serverPort", "8779");
20 | DefaultLogModule.dbDir = "/Users/cxs/code/lu-raft-revert/rocksDB-raft/" + System.getProperty("serverPort");
21 | DefaultLogModule.logsDir = DefaultLogModule.dbDir + "/logModule";
22 | }
23 |
24 | DefaultLogModule defaultLogs;
25 |
26 | @Before
27 | public void setUp() throws Exception {
28 | System.setProperty("serverPort", "8777");
29 |
30 | defaultLogs = DefaultLogModule.getInstance();
31 | }
32 |
33 | @After
34 | public void tearDown() throws Exception {
35 |
36 | }
37 |
38 | @Test
39 | public void write() {
40 | LogEntry entry = LogEntry.newBuilder().
41 | term(1).
42 | command(Command.newBuilder().key("hello").value("world").build()).
43 | build();
44 | defaultLogs.write(entry);
45 |
46 | Assert.assertEquals(entry, defaultLogs.read(entry.getIndex()));
47 | }
48 |
49 | @Test
50 | public void read() {
51 | System.out.println(defaultLogs.getLastIndex());
52 | }
53 |
54 | @Test
55 | public void remove() {
56 | defaultLogs.removeOnStartIndex(3L);
57 | }
58 |
59 | @Test
60 | public void getLast() {
61 |
62 | }
63 |
64 | @Test
65 | public void getLastIndex() {
66 | }
67 |
68 | @Test
69 | public void getDbDir() {
70 | }
71 |
72 | @Test
73 | public void getLogsDir() {
74 | }
75 |
76 | @Test
77 | public void setDbDir() {
78 | }
79 | }
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/raft/client/RaftClient2.java:
--------------------------------------------------------------------------------
1 | package raft.client;
2 |
3 | import java.util.List;
4 |
5 | import com.google.common.collect.Lists;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 |
10 | import cn.think.in.java.entity.LogEntry;
11 | import cn.think.in.java.rpc.DefaultRpcClient;
12 | import cn.think.in.java.rpc.Request;
13 | import cn.think.in.java.rpc.Response;
14 | import cn.think.in.java.rpc.RpcClient;
15 |
16 | /**
17 | *
18 | * @author 莫那·鲁道
19 | */
20 | public class RaftClient2 {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftClient.class);
23 |
24 |
25 | private final static RpcClient client = new DefaultRpcClient();
26 |
27 | static String addr = "localhost:8778";
28 | static List list3 = Lists.newArrayList("localhost:8777", "localhost:8778", "localhost:8779");
29 | static List list2 = Lists.newArrayList( "localhost:8777", "localhost:8779");
30 | static List list1 = Lists.newArrayList( "localhost:8779");
31 |
32 | public static void main(String[] args) throws InterruptedException {
33 | for (int i = 3; ; i++) {
34 |
35 | try {
36 | Request r = new Request<>();
37 |
38 | int size = list2.size();
39 |
40 | ClientKVReq obj = ClientKVReq.newBuilder().key("hello:" + i).type(ClientKVReq.GET).build();
41 | int index = (i) % size;
42 | addr = list2.get(index);
43 | r.setUrl(addr);
44 | r.setObj(obj);
45 | r.setCmd(Request.CLIENT_REQ);
46 |
47 | Response response2 = client.send(r);
48 |
49 | LOGGER.info("request content : {}, url : {}, get response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response2.getResult());
50 | } catch (Exception e) {
51 | e.printStackTrace();
52 | } finally {
53 | Thread.sleep(1000);
54 |
55 | }
56 |
57 | }
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Lu-Raft-KV-Storage
2 |
3 | 这是一个 Java 版本的 Raft(CP) KV 分布式存储实现. 可用于 Raft 初学者深入学习 Raft 协议.
4 |
5 | 相关文章 http://thinkinjava.cn/2019/01/12/2019/2019-01-12-lu-raft-kv/
6 |
7 | 为了尽可能的保证数据一致性,该实现的"性能"没有基于 AP 的实现好。
8 |
9 | 目前实现了 Raft 4 大核心功能的其中 2 个功能.
10 |
11 | 1. leader 选举
12 | 2. 日志复制
13 | 3. 成员变更(未测试)
14 | 4. 快照压缩(未实现)
15 |
16 | ## Design
17 |
18 | 完全是参照 RAFT 论文来写的. 没有任何妥协.
19 |
20 | 
21 |
22 |
23 |
24 | ## quick start
25 | #### 验证 "leader 选举"
26 |
27 | 1. 在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779
28 | 系统配置, 表示分布式环境下的 5 个机器节点.
29 | 2. 依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.
30 | 3. 观察控制台, 约 6 秒后, 会发生选举事件,此时,会产生一个 leader. 而 leader 会立刻发送心跳维持自己的地位.
31 | 4. 如果leader 的端口是 8775, 使用 idea 关闭 8775 端口,模拟节点挂掉, 大约 15 秒后, 会重新开始选举, 并且会在剩余的 4 个节点中,产生一个新的 leader. 并开始发送心跳日志。
32 |
33 | #### 验证"日志复制"
34 |
35 | ##### 正常状态下
36 |
37 | 1. 在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779
38 | 2. 依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.
39 | 3. 使用客户端写入 kv 数据.
40 | 4. 杀掉所有节点, 使用 junit test 读取每个 rocksDB 的值, 验证每个节点的数据是否一致.
41 |
42 | ##### 非正常状态下
43 |
44 | 1. 在 idea 中配置 5 个 application 启动项,配置 main 类为 RaftNodeBootStrap 类, 加入 -DserverPort=8775 -DserverPort=8776 -DserverPort=8777 -DserverPort=8778 -DserverPort=8779
45 | 2. 依次启动 5 个 RaftNodeBootStrap 节点, 端口分别是 8775,8776, 8777, 8778, 8779.
46 | 3. 使用客户端写入 kv 数据.
47 | 4. 杀掉 leader (假设是 8775).
48 | 5. 再次写入数据.
49 | 6. 重启 8775.
50 | 7. 关闭所有节点, 读取 RocksDB 验证数据一致性.
51 |
52 |
53 | ## And
54 |
55 | 欢迎提交 RP, issue. 加微信一起探讨 Raft。
56 |
57 | 本人微信:
58 |
59 | 
60 |
61 | ## Acknowledgments
62 |
63 | 感谢 SOFA-Bolt 提供 RPC 网络框架 https://github.com/alipay/sofa-bolt
64 |
65 | 感谢 rocksDB 提供 KV 存储 https://github.com/facebook/rocksdb
66 |
67 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/Request.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | import java.io.Serializable;
4 |
5 | import cn.think.in.java.entity.AentryParam;
6 | import cn.think.in.java.entity.RvoteParam;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 | import lombok.ToString;
10 | import raft.client.ClientKVReq;
11 |
12 | /**
13 | * @author 莫那·鲁道
14 | */
15 | @Getter
16 | @Setter
17 | @ToString
18 | public class Request implements Serializable {
19 |
20 | /** 请求投票 */
21 | public static final int R_VOTE = 0;
22 | /** 附加日志 */
23 | public static final int A_ENTRIES = 1;
24 | /** 客户端 */
25 | public static final int CLIENT_REQ = 2;
26 | /** 配置变更. add*/
27 | public static final int CHANGE_CONFIG_ADD = 3;
28 | /** 配置变更. remove*/
29 | public static final int CHANGE_CONFIG_REMOVE = 4;
30 | /** 请求类型 */
31 | private int cmd = -1;
32 |
33 | /** param
34 | * @see AentryParam
35 | * @see RvoteParam
36 | * @see ClientKVReq
37 | * */
38 | private T obj;
39 |
40 | String url;
41 |
42 | public Request() {
43 | }
44 |
45 | public Request(T obj) {
46 | this.obj = obj;
47 | }
48 |
49 | public Request(int cmd, T obj, String url) {
50 | this.cmd = cmd;
51 | this.obj = obj;
52 | this.url = url;
53 | }
54 |
55 | private Request(Builder builder) {
56 | setCmd(builder.cmd);
57 | setObj((T) builder.obj);
58 | setUrl(builder.url);
59 | }
60 |
61 | public static Builder newBuilder() {
62 | return new Builder<>();
63 | }
64 |
65 |
66 | public final static class Builder {
67 |
68 | private int cmd;
69 | private Object obj;
70 | private String url;
71 |
72 | private Builder() {
73 | }
74 |
75 | public Builder cmd(int val) {
76 | cmd = val;
77 | return this;
78 | }
79 |
80 | public Builder obj(Object val) {
81 | obj = val;
82 | return this;
83 | }
84 |
85 | public Builder url(String val) {
86 | url = val;
87 | return this;
88 | }
89 |
90 | public Request build() {
91 | return new Request(this);
92 | }
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/RvoteParam.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import cn.think.in.java.Consensus;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | /**
8 | * 请求投票 RPC 参数.
9 | *
10 | * @author 莫那·鲁道
11 | * @see Consensus#requestVote(RvoteParam)
12 | */
13 | @Getter
14 | @Setter
15 | public class RvoteParam extends BaseParam {
16 |
17 | /** 请求选票的候选人的 Id(ip:selfPort) */
18 | String candidateId;
19 |
20 | /** 候选人的最后日志条目的索引值 */
21 | long lastLogIndex;
22 |
23 | /** 候选人最后日志条目的任期号 */
24 | long lastLogTerm;
25 |
26 | private RvoteParam(Builder builder) {
27 | setTerm(builder.term);
28 | setServerId(builder.serverId);
29 | setCandidateId(builder.candidateId);
30 | setLastLogIndex(builder.lastLogIndex);
31 | setLastLogTerm(builder.lastLogTerm);
32 | }
33 |
34 | @Override
35 | public String toString() {
36 | return "RvoteParam{" +
37 | "candidateId='" + candidateId + '\'' +
38 | ", lastLogIndex=" + lastLogIndex +
39 | ", lastLogTerm=" + lastLogTerm +
40 | ", term=" + term +
41 | ", serverId='" + serverId + '\'' +
42 | '}';
43 | }
44 |
45 | public static Builder newBuilder() {
46 | return new Builder();
47 | }
48 |
49 |
50 | public static final class Builder {
51 |
52 | private long term;
53 | private String serverId;
54 | private String candidateId;
55 | private long lastLogIndex;
56 | private long lastLogTerm;
57 |
58 | private Builder() {
59 | }
60 |
61 | public Builder term(long val) {
62 | term = val;
63 | return this;
64 | }
65 |
66 | public Builder serverId(String val) {
67 | serverId = val;
68 | return this;
69 | }
70 |
71 | public Builder candidateId(String val) {
72 | candidateId = val;
73 | return this;
74 | }
75 |
76 | public Builder lastLogIndex(long val) {
77 | lastLogIndex = val;
78 | return this;
79 | }
80 |
81 | public Builder lastLogTerm(long val) {
82 | lastLogTerm = val;
83 | return this;
84 | }
85 |
86 | public RvoteParam build() {
87 | return new RvoteParam(this);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/ReplicationFailModel.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.util.concurrent.Callable;
4 |
5 | import cn.think.in.java.common.Peer;
6 |
7 | /**
8 | *
9 | * @author 莫那·鲁道
10 | */
11 | public class ReplicationFailModel {
12 | static String count = "_count";
13 | static String success = "_success";
14 |
15 | public String countKey;
16 | public String successKey;
17 | public Callable callable;
18 | public LogEntry logEntry;
19 | public Peer peer;
20 | public Long offerTime;
21 |
22 | public ReplicationFailModel(Callable callable, LogEntry logEntry, Peer peer, Long offerTime) {
23 | this.callable = callable;
24 | this.logEntry = logEntry;
25 | this.peer = peer;
26 | this.offerTime = offerTime;
27 | countKey = logEntry.getCommand().getKey() + count;
28 | successKey = logEntry.getCommand().getKey() + success;
29 | }
30 |
31 | private ReplicationFailModel(Builder builder) {
32 | countKey = builder.countKey;
33 | successKey = builder.successKey;
34 | callable = builder.callable;
35 | logEntry = builder.logEntry;
36 | peer = builder.peer;
37 | offerTime = builder.offerTime;
38 | }
39 |
40 | public static Builder newBuilder() {
41 | return new Builder();
42 | }
43 |
44 |
45 | public static final class Builder {
46 |
47 | private String countKey;
48 | private String successKey;
49 | private Callable callable;
50 | private LogEntry logEntry;
51 | private Peer peer;
52 | private Long offerTime;
53 |
54 | private Builder() {
55 | }
56 |
57 | public Builder countKey(String val) {
58 | countKey = val;
59 | return this;
60 | }
61 |
62 | public Builder successKey(String val) {
63 | successKey = val;
64 | return this;
65 | }
66 |
67 | public Builder callable(Callable val) {
68 | callable = val;
69 | return this;
70 | }
71 |
72 | public Builder logEntry(LogEntry val) {
73 | logEntry = val;
74 | return this;
75 | }
76 |
77 | public Builder peer(Peer val) {
78 | peer = val;
79 | return this;
80 | }
81 |
82 | public Builder offerTime(Long val) {
83 | offerTime = val;
84 | return this;
85 | }
86 |
87 | public ReplicationFailModel build() {
88 | return new ReplicationFailModel(this);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/current/RaftThreadPool.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.current;
2 |
3 | import java.util.concurrent.Callable;
4 | import java.util.concurrent.Future;
5 | import java.util.concurrent.LinkedBlockingQueue;
6 | import java.util.concurrent.ScheduledExecutorService;
7 | import java.util.concurrent.ScheduledThreadPoolExecutor;
8 | import java.util.concurrent.ThreadFactory;
9 | import java.util.concurrent.ThreadPoolExecutor;
10 | import java.util.concurrent.TimeUnit;
11 |
12 | /**
13 | *
14 | * @author 莫那·鲁道
15 | */
16 | public class RaftThreadPool {
17 |
18 | private static int cup = Runtime.getRuntime().availableProcessors();
19 | private static int maxPoolSize = cup * 2;
20 | private static final int queueSize = 1024;
21 | private static final long keepTime = 1000 * 60;
22 | private static TimeUnit keepTimeUnit = TimeUnit.MILLISECONDS;
23 |
24 | private static ScheduledExecutorService ss = getScheduled();
25 | private static ThreadPoolExecutor te = getThreadPool();
26 |
27 | private static ThreadPoolExecutor getThreadPool() {
28 | return new RaftThreadPoolExecutor(
29 | cup,
30 | maxPoolSize,
31 | keepTime,
32 | keepTimeUnit,
33 | new LinkedBlockingQueue<>(queueSize),
34 | new NameThreadFactory());
35 | }
36 |
37 | private static ScheduledExecutorService getScheduled() {
38 | return new ScheduledThreadPoolExecutor(cup, new NameThreadFactory());
39 | }
40 |
41 |
42 | public static void scheduleAtFixedRate(Runnable r, long initDelay, long delay) {
43 | ss.scheduleAtFixedRate(r, initDelay, delay, TimeUnit.MILLISECONDS);
44 | }
45 |
46 |
47 | public static void scheduleWithFixedDelay(Runnable r, long delay) {
48 | ss.scheduleWithFixedDelay(r, 0, delay, TimeUnit.MILLISECONDS);
49 | }
50 |
51 | @SuppressWarnings("unchecked")
52 | public static Future submit(Callable r) {
53 | return te.submit(r);
54 | }
55 |
56 | public static void execute(Runnable r) {
57 | te.execute(r);
58 | }
59 |
60 | public static void execute(Runnable r, boolean sync) {
61 | if (sync) {
62 | r.run();
63 | } else {
64 | te.execute(r);
65 | }
66 | }
67 |
68 | static class NameThreadFactory implements ThreadFactory {
69 |
70 | @Override
71 | public Thread newThread(Runnable r) {
72 | Thread t = new RaftThread("Raft thread", r);
73 | t.setDaemon(true);
74 | t.setPriority(5);
75 | return t;
76 | }
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/rpc/DefaultRpcServer.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.rpc;
2 |
3 | import com.alipay.remoting.BizContext;
4 |
5 | import cn.think.in.java.common.Peer;
6 | import cn.think.in.java.entity.AentryParam;
7 | import cn.think.in.java.entity.RvoteParam;
8 | import cn.think.in.java.impl.DefaultNode;
9 | import cn.think.in.java.membership.changes.ClusterMembershipChanges;
10 | import raft.client.ClientKVReq;
11 |
12 | /**
13 | *
14 | * Raft Server
15 | *
16 | * @author 莫那·鲁道
17 | *
18 | */
19 | @SuppressWarnings("unchecked")
20 | public class DefaultRpcServer implements RpcServer {
21 |
22 | private volatile boolean flag;
23 |
24 | private DefaultNode node;
25 |
26 | private com.alipay.remoting.rpc.RpcServer rpcServer;
27 |
28 | public DefaultRpcServer(int port, DefaultNode node) {
29 |
30 | if (flag) {
31 | return;
32 | }
33 | synchronized (this) {
34 | if (flag) {
35 | return;
36 | }
37 |
38 | rpcServer = new com.alipay.remoting.rpc.RpcServer(port, false, false);
39 |
40 | rpcServer.registerUserProcessor(new RaftUserProcessor() {
41 |
42 | @Override
43 | public Object handleRequest(BizContext bizCtx, Request request) throws Exception {
44 | return handlerRequest(request);
45 | }
46 | });
47 |
48 | this.node = node;
49 | flag = true;
50 | }
51 |
52 | }
53 |
54 | @Override
55 | public void start() {
56 | rpcServer.start();
57 | }
58 |
59 | @Override
60 | public void stop() {
61 | rpcServer.stop();
62 | }
63 |
64 | @Override
65 | public Response handlerRequest(Request request) {
66 | if (request.getCmd() == Request.R_VOTE) {
67 | return new Response(node.handlerRequestVote((RvoteParam) request.getObj()));
68 | } else if (request.getCmd() == Request.A_ENTRIES) {
69 | return new Response(node.handlerAppendEntries((AentryParam) request.getObj()));
70 | } else if (request.getCmd() == Request.CLIENT_REQ) {
71 | return new Response(node.handlerClientRequest((ClientKVReq) request.getObj()));
72 | } else if (request.getCmd() == Request.CHANGE_CONFIG_REMOVE) {
73 | return new Response(((ClusterMembershipChanges) node).removePeer((Peer) request.getObj()));
74 | } else if (request.getCmd() == Request.CHANGE_CONFIG_ADD) {
75 | return new Response(((ClusterMembershipChanges) node).addPeer((Peer) request.getObj()));
76 | }
77 | return null;
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.think.in.java
8 | lu-raft-parent
9 | pom
10 | 1.0-RELEASE
11 |
12 |
13 | lu-raft-kv
14 |
15 |
16 |
17 |
18 |
19 |
20 | org.slf4j
21 | slf4j-api
22 | 1.7.25
23 |
24 |
25 |
26 | org.slf4j
27 | slf4j-log4j12
28 | 1.7.21
29 |
30 |
31 |
32 |
33 |
34 | com.alipay.sofa
35 | bolt
36 | 1.4.1
37 |
38 |
39 | slf4j-api
40 | org.slf4j
41 |
42 |
43 |
44 |
45 |
46 |
47 | redis.clients
48 | jedis
49 | 2.9.0
50 |
51 |
52 |
53 |
54 | com.alibaba
55 | fastjson
56 | 1.2.49
57 |
58 |
59 |
60 |
61 | org.projectlombok
62 | lombok
63 | 1.18.2
64 |
65 |
66 |
67 |
68 | com.google.guava
69 | guava
70 | 23.0
71 |
72 |
73 |
74 | com.alipay.sofa
75 | hessian
76 | 3.3.2
77 |
78 |
79 |
80 |
81 | lu-raft
82 |
83 |
84 | org.apache.maven.plugins
85 | maven-compiler-plugin
86 |
87 | 8
88 | 8
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/ClusterMembershipChangesImpl.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import cn.think.in.java.common.NodeStatus;
7 | import cn.think.in.java.common.Peer;
8 | import cn.think.in.java.entity.LogEntry;
9 | import cn.think.in.java.membership.changes.ClusterMembershipChanges;
10 | import cn.think.in.java.membership.changes.Result;
11 | import cn.think.in.java.rpc.Request;
12 | import cn.think.in.java.rpc.Response;
13 |
14 | /**
15 | *
16 | * 集群配置变更接口默认实现.
17 | *
18 | * @author 莫那·鲁道
19 | */
20 | public class ClusterMembershipChangesImpl implements ClusterMembershipChanges {
21 |
22 | private static final Logger LOGGER = LoggerFactory.getLogger(ClusterMembershipChangesImpl.class);
23 |
24 |
25 | private final DefaultNode node;
26 |
27 | public ClusterMembershipChangesImpl(DefaultNode node) {
28 | this.node = node;
29 | }
30 |
31 | /** 必须是同步的,一次只能添加一个节点
32 | * @param newPeer*/
33 | @Override
34 | public synchronized Result addPeer(Peer newPeer) {
35 | // 已经存在
36 | if (node.peerSet.getPeersWithOutSelf().contains(newPeer)) {
37 | return new Result();
38 | }
39 |
40 | node.peerSet.getPeersWithOutSelf().add(newPeer);
41 |
42 | if (node.status == NodeStatus.LEADER) {
43 | node.nextIndexs.put(newPeer, 0L);
44 | node.matchIndexs.put(newPeer, 0L);
45 |
46 | for (long i = 0; i < node.logModule.getLastIndex(); i++) {
47 | LogEntry e = node.logModule.read(i);
48 | if (e != null) {
49 | node.replication(newPeer, e);
50 | }
51 | }
52 |
53 | for (Peer item : node.peerSet.getPeersWithOutSelf()) {
54 | // TODO 同步到其他节点.
55 | Request request = Request.newBuilder()
56 | .cmd(Request.CHANGE_CONFIG_ADD)
57 | .url(newPeer.getAddr())
58 | .obj(newPeer)
59 | .build();
60 |
61 | Response response = node.rpcClient.send(request);
62 | Result result = (Result) response.getResult();
63 | if (result != null && result.getStatus() == Result.Status.SUCCESS.getCode()) {
64 | LOGGER.info("replication config success, peer : {}, newServer : {}", newPeer, newPeer);
65 | } else {
66 | LOGGER.warn("replication config fail, peer : {}, newServer : {}", newPeer, newPeer);
67 | }
68 | }
69 |
70 | }
71 |
72 | return new Result();
73 | }
74 |
75 |
76 | /** 必须是同步的,一次只能删除一个节点
77 | * @param oldPeer*/
78 | @Override
79 | public synchronized Result removePeer(Peer oldPeer) {
80 | node.peerSet.getPeersWithOutSelf().remove(oldPeer);
81 | node.nextIndexs.remove(oldPeer);
82 | node.matchIndexs.remove(oldPeer);
83 |
84 | return new Result();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/LogEntry.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.io.Serializable;
4 | import java.util.Objects;
5 |
6 | import cn.think.in.java.LogModule;
7 | import lombok.Getter;
8 | import lombok.Setter;
9 |
10 | /**
11 | * 日志条目
12 | *
13 | * @author 莫那·鲁道
14 | * @see LogModule
15 | */
16 | @Getter
17 | @Setter
18 | public class LogEntry implements Serializable, Comparable {
19 |
20 | private Long index;
21 |
22 | private long term;
23 |
24 | private Command command;
25 |
26 | public LogEntry() {
27 | }
28 |
29 | public LogEntry(long term, Command command) {
30 | this.term = term;
31 | this.command = command;
32 | }
33 |
34 | public LogEntry(Long index, long term, Command command) {
35 | this.index = index;
36 | this.term = term;
37 | this.command = command;
38 | }
39 |
40 | private LogEntry(Builder builder) {
41 | setIndex(builder.index);
42 | setTerm(builder.term);
43 | setCommand(builder.command);
44 | }
45 |
46 | public static Builder newBuilder() {
47 | return new Builder();
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "{" +
53 | "index=" + index +
54 | ", term=" + term +
55 | ", command=" + command +
56 | '}';
57 | }
58 |
59 | @Override
60 | public int compareTo(Object o) {
61 | if (o == null) {
62 | return -1;
63 | }
64 | if (this.getIndex() > ((LogEntry) o).getIndex()) {
65 | return 1;
66 | }
67 | return -1;
68 | }
69 |
70 | @Override
71 | public boolean equals(Object o) {
72 | if (this == o) {
73 | return true;
74 | }
75 | if (o == null || getClass() != o.getClass()) {
76 | return false;
77 | }
78 | LogEntry logEntry = (LogEntry) o;
79 | return term == logEntry.term &&
80 | Objects.equals(index, logEntry.index) &&
81 | Objects.equals(command, logEntry.command);
82 | }
83 |
84 | @Override
85 | public int hashCode() {
86 | return Objects.hash(index, term, command);
87 | }
88 |
89 | public static final class Builder {
90 |
91 | private Long index;
92 | private long term;
93 | private Command command;
94 |
95 | private Builder() {
96 | }
97 |
98 | public Builder index(Long val) {
99 | index = val;
100 | return this;
101 | }
102 |
103 | public Builder term(long val) {
104 | term = val;
105 | return this;
106 | }
107 |
108 | public Builder command(Command val) {
109 | command = val;
110 | return this;
111 | }
112 |
113 | public LogEntry build() {
114 | return new LogEntry(this);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/raft/client/RaftClient.java:
--------------------------------------------------------------------------------
1 | package raft.client;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.atomic.AtomicLong;
5 |
6 | import com.alipay.remoting.exception.RemotingException;
7 | import com.google.common.collect.Lists;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import cn.think.in.java.current.SleepHelper;
13 | import cn.think.in.java.entity.LogEntry;
14 | import cn.think.in.java.rpc.DefaultRpcClient;
15 | import cn.think.in.java.rpc.Request;
16 | import cn.think.in.java.rpc.Response;
17 | import cn.think.in.java.rpc.RpcClient;
18 |
19 | /**
20 | *
21 | * @author 莫那·鲁道
22 | */
23 | public class RaftClient {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftClient.class);
26 |
27 |
28 | private final static RpcClient client = new DefaultRpcClient();
29 |
30 | static String addr = "localhost:8778";
31 | static List list = Lists.newArrayList("localhost:8777", "localhost:8778", "localhost:8779");
32 |
33 | public static void main(String[] args) throws RemotingException, InterruptedException {
34 |
35 | AtomicLong count = new AtomicLong(3);
36 |
37 | for (int i = 3; ; i++) {
38 | try {
39 | int index = (int) (count.incrementAndGet() % list.size());
40 | addr = list.get(index);
41 |
42 | ClientKVReq obj = ClientKVReq.newBuilder().key("hello:" + i).value("world:" + i).type(ClientKVReq.PUT).build();
43 |
44 | Request r = new Request<>();
45 | r.setObj(obj);
46 | r.setUrl(addr);
47 | r.setCmd(Request.CLIENT_REQ);
48 | Response response;
49 | try {
50 | response = client.send(r);
51 | } catch (Exception e) {
52 | r.setUrl(list.get((int) ((count.incrementAndGet()) % list.size())));
53 | response = client.send(r);
54 | }
55 |
56 | LOGGER.info("request content : {}, url : {}, put response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response.getResult());
57 |
58 | SleepHelper.sleep(1000);
59 |
60 | obj = ClientKVReq.newBuilder().key("hello:" + i).type(ClientKVReq.GET).build();
61 |
62 | addr = list.get(index);
63 | addr = list.get(index);
64 | r.setUrl(addr);
65 | r.setObj(obj);
66 |
67 | Response response2;
68 | try {
69 | response2 = client.send(r);
70 | } catch (Exception e) {
71 | r.setUrl(list.get((int) ((count.incrementAndGet()) % list.size())));
72 | response2 = client.send(r);
73 | }
74 |
75 | LOGGER.info("request content : {}, url : {}, get response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response2.getResult());
76 | } catch (Exception e) {
77 | e.printStackTrace();
78 | i = i - 1;
79 | }
80 |
81 | SleepHelper.sleep(5000);
82 | }
83 |
84 |
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/raft/client/RaftClient3.java:
--------------------------------------------------------------------------------
1 | package raft.client;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.atomic.AtomicLong;
5 |
6 | import com.alipay.remoting.exception.RemotingException;
7 | import com.google.common.collect.Lists;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import cn.think.in.java.current.SleepHelper;
13 | import cn.think.in.java.entity.LogEntry;
14 | import cn.think.in.java.rpc.DefaultRpcClient;
15 | import cn.think.in.java.rpc.Request;
16 | import cn.think.in.java.rpc.Response;
17 | import cn.think.in.java.rpc.RpcClient;
18 |
19 | /**
20 | *
21 | * @author 莫那·鲁道
22 | */
23 | public class RaftClient3 {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(RaftClient3.class);
26 |
27 |
28 | private final static RpcClient client = new DefaultRpcClient();
29 |
30 | static String addr = "localhost:8777";
31 | static List list = Lists.newArrayList("localhost:8777", "localhost:8778", "localhost:8779");
32 |
33 | public static void main(String[] args) throws RemotingException, InterruptedException {
34 |
35 | AtomicLong count = new AtomicLong(3);
36 |
37 | int keyNum = 4;
38 | try {
39 | int index = (int) (count.incrementAndGet() % list.size());
40 | index = 1;
41 | addr = list.get(index);
42 |
43 | ClientKVReq obj = ClientKVReq.newBuilder().key("hello:" + keyNum).value("world:" + keyNum).type(ClientKVReq.PUT).build();
44 |
45 | Request r = new Request<>();
46 | r.setObj(obj);
47 | r.setUrl(addr);
48 | r.setCmd(Request.CLIENT_REQ);
49 | Response response = null;
50 | try {
51 | response = client.send(r);
52 | } catch (Exception e) {
53 | }
54 |
55 | LOGGER.info("request content : {}, url : {}, put response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response.getResult());
56 |
57 | SleepHelper.sleep(1000);
58 |
59 | obj = ClientKVReq.newBuilder().key("hello:" + keyNum).type(ClientKVReq.GET).build();
60 |
61 | addr = list.get(index);
62 | addr = list.get(index);
63 | r.setUrl(addr);
64 | r.setObj(obj);
65 |
66 | Response response2;
67 | try {
68 | response2 = client.send(r);
69 | } catch (Exception e) {
70 | r.setUrl(list.get((int) ((count.incrementAndGet()) % list.size())));
71 | response2 = client.send(r);
72 | }
73 |
74 | if (response.getResult() == null) {
75 | LOGGER.error("request content : {}, url : {}, get response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response2.getResult());
76 | System.exit(1);
77 | return;
78 | }
79 | LOGGER.info("request content : {}, url : {}, get response : {}", obj.key + "=" + obj.getValue(), r.getUrl(), response2.getResult());
80 | } catch (Exception e) {
81 | e.printStackTrace();
82 | }
83 |
84 | System.exit(1);
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/entity/AentryParam.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.entity;
2 |
3 | import java.util.Arrays;
4 |
5 | import cn.think.in.java.Consensus;
6 | import lombok.Getter;
7 | import lombok.Setter;
8 | import lombok.ToString;
9 |
10 | /**
11 | *
12 | * 附加日志 RPC 参数. handlerAppendEntries
13 | *
14 | * @author 莫那·鲁道
15 | * @see Consensus#appendEntries(AentryParam)
16 | */
17 | @Getter
18 | @Setter
19 | @ToString
20 | public class AentryParam extends BaseParam {
21 |
22 | /** 领导人的 Id,以便于跟随者重定向请求 */
23 | String leaderId;
24 |
25 | /**新的日志条目紧随之前的索引值 */
26 | long prevLogIndex;
27 |
28 | /** prevLogIndex 条目的任期号 */
29 | long preLogTerm;
30 |
31 | /** 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提高效率) */
32 | LogEntry[] entries;
33 |
34 | /** 领导人已经提交的日志的索引值 */
35 | long leaderCommit;
36 |
37 | public AentryParam() {
38 | }
39 |
40 | private AentryParam(Builder builder) {
41 | setTerm(builder.term);
42 | setServerId(builder.serverId);
43 | setLeaderId(builder.leaderId);
44 | setPrevLogIndex(builder.prevLogIndex);
45 | setPreLogTerm(builder.preLogTerm);
46 | setEntries(builder.entries);
47 | setLeaderCommit(builder.leaderCommit);
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "AentryParam{" +
53 | "leaderId='" + leaderId + '\'' +
54 | ", prevLogIndex=" + prevLogIndex +
55 | ", preLogTerm=" + preLogTerm +
56 | ", entries=" + Arrays.toString(entries) +
57 | ", leaderCommit=" + leaderCommit +
58 | ", term=" + term +
59 | ", serverId='" + serverId + '\'' +
60 | '}';
61 | }
62 |
63 | public static Builder newBuilder() {
64 | return new Builder();
65 | }
66 |
67 |
68 | public static final class Builder {
69 |
70 | private long term;
71 | private String serverId;
72 | private String leaderId;
73 | private long prevLogIndex;
74 | private long preLogTerm;
75 | private LogEntry[] entries;
76 | private long leaderCommit;
77 |
78 | private Builder() {
79 | }
80 |
81 | public Builder term(long val) {
82 | term = val;
83 | return this;
84 | }
85 |
86 | public Builder serverId(String val) {
87 | serverId = val;
88 | return this;
89 | }
90 |
91 | public Builder leaderId(String val) {
92 | leaderId = val;
93 | return this;
94 | }
95 |
96 | public Builder prevLogIndex(long val) {
97 | prevLogIndex = val;
98 | return this;
99 | }
100 |
101 | public Builder preLogTerm(long val) {
102 | preLogTerm = val;
103 | return this;
104 | }
105 |
106 | public Builder entries(LogEntry[] val) {
107 | entries = val;
108 | return this;
109 | }
110 |
111 | public Builder leaderCommit(long val) {
112 | leaderCommit = val;
113 | return this;
114 | }
115 |
116 | public AentryParam build() {
117 | return new AentryParam(this);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/RedisStateMachine.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import com.alibaba.fastjson.JSON;
8 |
9 | import cn.think.in.java.StateMachine;
10 | import cn.think.in.java.entity.Command;
11 | import cn.think.in.java.entity.LogEntry;
12 | import redis.clients.jedis.Jedis;
13 | import redis.clients.jedis.JedisPool;
14 |
15 | /**
16 | * redis实现状态机存储
17 | *
18 | * @author rensailong
19 | */
20 | public class RedisStateMachine implements StateMachine {
21 | private static final Logger LOGGER = LoggerFactory.getLogger(RedisStateMachine.class);
22 |
23 | private static JedisPool jedisPool;
24 |
25 | static {
26 | GenericObjectPoolConfig redisConfig = new GenericObjectPoolConfig();
27 | redisConfig.setMaxTotal(100);
28 | redisConfig.setMaxWaitMillis(10 * 1000);
29 | redisConfig.setMaxIdle(1000);
30 | redisConfig.setTestOnBorrow(true);
31 | jedisPool = new JedisPool(redisConfig, "127.0.0.1", 6379);
32 | }
33 |
34 | public static RedisStateMachine getInstance() {
35 | return RedisStateMachine.RedisStateMachineLazyHolder.INSTANCE;
36 | }
37 |
38 | private static class RedisStateMachineLazyHolder {
39 |
40 | private static final RedisStateMachine INSTANCE = new RedisStateMachine();
41 | }
42 |
43 | @Override
44 | public void apply(LogEntry logEntry) {
45 | Jedis jedis = null;
46 | try {
47 | jedis = jedisPool.getResource();
48 | Command command = logEntry.getCommand();
49 | if (command == null) {
50 | throw new IllegalArgumentException("command can not be null, logEntry : " + logEntry.toString());
51 | }
52 | String key = command.getKey();
53 | jedis.set(key.getBytes(), JSON.toJSONBytes(logEntry));
54 | } catch (Exception e) {
55 | LOGGER.info(e.getMessage());
56 | } finally {
57 | if (jedis != null) {
58 | jedis.close();
59 | }
60 | }
61 | }
62 |
63 | @Override
64 | public LogEntry get(String key) {
65 | LogEntry result = null;
66 | Jedis jedis = null;
67 | try {
68 | jedis = jedisPool.getResource();
69 | result = JSON.parseObject(jedis.get(key), LogEntry.class);
70 | } catch (Exception e) {
71 | LOGGER.error("redis error ", e);
72 | } finally {
73 | if (jedis != null) {
74 | jedis.close();
75 | }
76 | }
77 | return result;
78 | }
79 |
80 | @Override
81 | public String getString(String key) {
82 | String result = null;
83 | Jedis jedis = null;
84 | try {
85 | jedis = jedisPool.getResource();
86 | result = jedis.get(key);
87 | } catch (Exception e) {
88 | LOGGER.error("redis error ", e);
89 | } finally {
90 | if (jedis != null) {
91 | jedis.close();
92 | }
93 | }
94 | return result;
95 | }
96 |
97 | @Override
98 | public void setString(String key, String value) {
99 | Jedis jedis = null;
100 | try {
101 | jedis = jedisPool.getResource();
102 | jedis.set(key, value);
103 | } catch (Exception e) {
104 | LOGGER.error("redis error ", e);
105 | } finally {
106 | if (jedis != null) {
107 | jedis.close();
108 | }
109 | }
110 | }
111 |
112 | @Override
113 | public void delString(String... keys) {
114 | Jedis jedis = null;
115 | try {
116 | jedis.del(keys);
117 | } catch (Exception e) {
118 | LOGGER.error("redis error ", e);
119 | } finally {
120 | if (jedis != null) {
121 | jedis.close();
122 | }
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/DefaultStateMachine.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import java.io.File;
4 |
5 | import com.alibaba.fastjson.JSON;
6 |
7 | import org.rocksdb.Options;
8 | import org.rocksdb.RocksDB;
9 | import org.rocksdb.RocksDBException;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | import cn.think.in.java.StateMachine;
14 | import cn.think.in.java.entity.Command;
15 | import cn.think.in.java.entity.LogEntry;
16 |
17 | /**
18 | *
19 | * 默认的状态机实现.
20 | *
21 | * @author 莫那·鲁道
22 | */
23 | public class DefaultStateMachine implements StateMachine {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStateMachine.class);
26 |
27 | /** public just for test */
28 | public static String dbDir;
29 | public static String stateMachineDir;
30 |
31 | public static RocksDB machineDb;
32 |
33 | static {
34 | if (dbDir == null) {
35 | dbDir = "./rocksDB-raft/" + System.getProperty("serverPort");
36 | }
37 | if (stateMachineDir == null) {
38 | stateMachineDir =dbDir + "/stateMachine";
39 | }
40 | RocksDB.loadLibrary();
41 | }
42 |
43 |
44 | private DefaultStateMachine() {
45 | try {
46 | File file = new File(stateMachineDir);
47 | boolean success = false;
48 | if (!file.exists()) {
49 | success = file.mkdirs();
50 | }
51 | if (success) {
52 | LOGGER.warn("make a new dir : " + stateMachineDir);
53 | }
54 | Options options = new Options();
55 | options.setCreateIfMissing(true);
56 | machineDb = RocksDB.open(options, stateMachineDir);
57 |
58 | } catch (RocksDBException e) {
59 | LOGGER.info(e.getMessage());
60 | }
61 | }
62 |
63 | public static DefaultStateMachine getInstance() {
64 | return DefaultStateMachineLazyHolder.INSTANCE;
65 | }
66 |
67 | private static class DefaultStateMachineLazyHolder {
68 |
69 | private static final DefaultStateMachine INSTANCE = new DefaultStateMachine();
70 | }
71 |
72 | @Override
73 | public LogEntry get(String key) {
74 | try {
75 | byte[] result = machineDb.get(key.getBytes());
76 | if (result == null) {
77 | return null;
78 | }
79 | return JSON.parseObject(result, LogEntry.class);
80 | } catch (RocksDBException e) {
81 | LOGGER.info(e.getMessage());
82 | }
83 | return null;
84 | }
85 |
86 | @Override
87 | public String getString(String key) {
88 | try {
89 | byte[] bytes = machineDb.get(key.getBytes());
90 | if (bytes != null) {
91 | return new String(bytes);
92 | }
93 | } catch (RocksDBException e) {
94 | LOGGER.info(e.getMessage());
95 | }
96 | return "";
97 | }
98 |
99 | @Override
100 | public void setString(String key, String value) {
101 | try {
102 | machineDb.put(key.getBytes(), value.getBytes());
103 | } catch (RocksDBException e) {
104 | LOGGER.info(e.getMessage());
105 | }
106 | }
107 |
108 | @Override
109 | public void delString(String... key) {
110 | try {
111 | for (String s : key) {
112 | machineDb.delete(s.getBytes());
113 | }
114 | } catch (RocksDBException e) {
115 | LOGGER.info(e.getMessage());
116 | }
117 | }
118 |
119 | @Override
120 | public synchronized void apply(LogEntry logEntry) {
121 |
122 | try {
123 | Command command = logEntry.getCommand();
124 |
125 | if (command == null) {
126 | throw new IllegalArgumentException("command can not be null, logEntry : " + logEntry.toString());
127 | }
128 | String key = command.getKey();
129 | machineDb.put(key.getBytes(), JSON.toJSONBytes(logEntry));
130 | } catch (RocksDBException e) {
131 | LOGGER.info(e.getMessage());
132 | }
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/test/java/cn/think/in/java/impl/RocksDBTest.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import java.io.File;
4 |
5 | import com.alibaba.fastjson.JSON;
6 |
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.rocksdb.Options;
10 | import org.rocksdb.RocksDB;
11 | import org.rocksdb.RocksDBException;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 |
15 | import lombok.Getter;
16 | import lombok.Setter;
17 | import lombok.ToString;
18 |
19 | /**
20 | *
21 | * @author 莫那·鲁道
22 | */
23 | public class RocksDBTest {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStateMachine.class);
26 |
27 | private String dbDir = "./rocksDB-raft/" + System.getProperty("serverPort");
28 | private String stateMachineDir = dbDir + "/test";
29 |
30 | public RocksDB machineDb;
31 |
32 | static {
33 | RocksDB.loadLibrary();
34 | }
35 |
36 | public byte[] lastIndexKey = "LAST_INDEX_KEY".getBytes();
37 |
38 | public RocksDBTest() {
39 | try {
40 | System.setProperty("serverPort", "8078");
41 | File file = new File(stateMachineDir);
42 | if (!file.exists()) {
43 | file.mkdirs();
44 | }
45 | Options options = new Options();
46 | options.setCreateIfMissing(true);
47 | machineDb = RocksDB.open(options, stateMachineDir);
48 |
49 | } catch (RocksDBException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 |
55 | public static RocksDBTest getInstance() {
56 | return RocksDBTestLazyHolder.INSTANCE;
57 | }
58 |
59 | private static class RocksDBTestLazyHolder {
60 |
61 | private static final RocksDBTest INSTANCE = new RocksDBTest();
62 | }
63 |
64 | RocksDBTest instance;
65 |
66 | @Before
67 | public void before() {
68 | instance = getInstance();
69 | }
70 |
71 | @Test
72 | public void test() throws RocksDBException {
73 | System.out.println(getLastIndex());
74 | System.out.println(get(getLastIndex()));
75 |
76 | write(new Cmd("hello", "value"));
77 |
78 | System.out.println(getLastIndex());
79 |
80 | System.out.println(get(getLastIndex()));
81 |
82 | deleteOnStartIndex(getLastIndex());
83 |
84 | write(new Cmd("hello", "value"));
85 |
86 | deleteOnStartIndex(1L);
87 |
88 | System.out.println(getLastIndex());
89 |
90 | System.out.println(get(getLastIndex()));
91 |
92 |
93 | }
94 |
95 | public synchronized void write(Cmd cmd) {
96 | try {
97 | cmd.setIndex(getLastIndex() + 1);
98 | machineDb.put(cmd.getIndex().toString().getBytes(), JSON.toJSONBytes(cmd));
99 | } catch (RocksDBException e) {
100 | e.printStackTrace();
101 | } finally {
102 | updateLastIndex(cmd.getIndex());
103 | }
104 | }
105 |
106 | public synchronized void deleteOnStartIndex(Long index) {
107 | try {
108 | for (long i = index; i <= getLastIndex(); i++) {
109 | try {
110 | machineDb.delete((i + "").getBytes());
111 | } catch (RocksDBException e) {
112 | e.printStackTrace();
113 | }
114 | }
115 |
116 | } finally {
117 | updateLastIndex(index - 1);
118 | }
119 | }
120 |
121 | public Cmd get(Long index) {
122 | try {
123 | if (index == null) {
124 | throw new IllegalArgumentException();
125 | }
126 | byte[] cmd = machineDb.get(index.toString().getBytes());
127 | if (cmd != null) {
128 | return JSON.parseObject(machineDb.get(index.toString().getBytes()), Cmd.class);
129 | }
130 | } catch (RocksDBException e) {
131 | e.printStackTrace();
132 | }
133 | return null;
134 | }
135 |
136 |
137 | public void updateLastIndex(Long index) {
138 | try {
139 | // overWrite
140 | machineDb.put(this.lastIndexKey, index.toString().getBytes());
141 | } catch (RocksDBException e) {
142 | e.printStackTrace();
143 | }
144 | }
145 |
146 | public Long getLastIndex() {
147 | byte[] lastIndex = new byte[0];
148 | try {
149 | lastIndex = machineDb.get(this.lastIndexKey);
150 | if (lastIndex == null) {
151 | lastIndex = "0".getBytes();
152 | }
153 | } catch (RocksDBException e) {
154 | e.printStackTrace();
155 | }
156 | return Long.valueOf(new String(lastIndex));
157 | }
158 |
159 | @Setter
160 | @Getter
161 | @ToString
162 | static class Cmd {
163 |
164 | Long index;
165 | String key;
166 | String value;
167 |
168 | public Cmd() {
169 | }
170 |
171 | public Cmd(String key, String value) {
172 | this.key = key;
173 | this.value = value;
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/DefaultLogModule.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import java.io.File;
4 | import java.util.concurrent.locks.ReentrantLock;
5 |
6 | import com.alibaba.fastjson.JSON;
7 |
8 | import org.rocksdb.Options;
9 | import org.rocksdb.RocksDB;
10 | import org.rocksdb.RocksDBException;
11 | import org.slf4j.Logger;
12 | import org.slf4j.LoggerFactory;
13 |
14 | import cn.think.in.java.LogModule;
15 | import cn.think.in.java.entity.LogEntry;
16 | import lombok.Getter;
17 | import lombok.Setter;
18 |
19 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
20 |
21 | /**
22 | *
23 | * 默认的日志实现. 日志模块不关心 key, 只关心 index.
24 | *
25 | * @author 莫那·鲁道
26 | * @see cn.think.in.java.entity.LogEntry
27 | */
28 | @Setter
29 | @Getter
30 | public class DefaultLogModule implements LogModule {
31 |
32 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLogModule.class);
33 |
34 |
35 | /** public just for test */
36 | public static String dbDir;
37 | public static String logsDir;
38 |
39 | private static RocksDB logDb;
40 |
41 | public final static byte[] LAST_INDEX_KEY = "LAST_INDEX_KEY".getBytes();
42 |
43 | ReentrantLock lock = new ReentrantLock();
44 |
45 | static {
46 | if (dbDir == null) {
47 | dbDir = "./rocksDB-raft/" + System.getProperty("serverPort");
48 | }
49 | if (logsDir == null) {
50 | logsDir = dbDir + "/logModule";
51 | }
52 | RocksDB.loadLibrary();
53 | }
54 |
55 | private DefaultLogModule() {
56 | Options options = new Options();
57 | options.setCreateIfMissing(true);
58 |
59 | File file = new File(logsDir);
60 | boolean success = false;
61 | if (!file.exists()) {
62 | success = file.mkdirs();
63 | }
64 | if (success) {
65 | LOGGER.warn("make a new dir : " + logsDir);
66 | }
67 | try {
68 | logDb = RocksDB.open(options, logsDir);
69 | } catch (RocksDBException e) {
70 | LOGGER.warn(e.getMessage());
71 | }
72 | }
73 |
74 | public static DefaultLogModule getInstance() {
75 | return DefaultLogsLazyHolder.INSTANCE;
76 | }
77 |
78 | private static class DefaultLogsLazyHolder {
79 |
80 | private static final DefaultLogModule INSTANCE = new DefaultLogModule();
81 | }
82 |
83 | /**
84 | * logEntry 的 index 就是 key. 严格保证递增.
85 | *
86 | * @param logEntry
87 | */
88 | @Override
89 | public void write(LogEntry logEntry) {
90 |
91 | boolean success = false;
92 | try {
93 | lock.tryLock(3000, MILLISECONDS);
94 | logEntry.setIndex(getLastIndex() + 1);
95 | logDb.put(logEntry.getIndex().toString().getBytes(), JSON.toJSONBytes(logEntry));
96 | success = true;
97 | LOGGER.info("DefaultLogModule write rocksDB success, logEntry info : [{}]", logEntry);
98 | } catch (RocksDBException | InterruptedException e) {
99 | LOGGER.warn(e.getMessage());
100 | } finally {
101 | if (success) {
102 | updateLastIndex(logEntry.getIndex());
103 | }
104 | lock.unlock();
105 | }
106 | }
107 |
108 |
109 | @Override
110 | public LogEntry read(Long index) {
111 | try {
112 | byte[] result = logDb.get(convert(index));
113 | if (result == null) {
114 | return null;
115 | }
116 | return JSON.parseObject(result, LogEntry.class);
117 | } catch (RocksDBException e) {
118 | LOGGER.warn(e.getMessage(), e);
119 | }
120 | return null;
121 | }
122 |
123 | @Override
124 | public void removeOnStartIndex(Long startIndex) {
125 | boolean success = false;
126 | int count = 0;
127 | try {
128 | lock.tryLock(3000, MILLISECONDS);
129 | for (long i = startIndex; i <= getLastIndex(); i++) {
130 | logDb.delete(String.valueOf(i).getBytes());
131 | ++count;
132 | }
133 | success = true;
134 | LOGGER.warn("rocksDB removeOnStartIndex success, count={} startIndex={}, lastIndex={}", count, startIndex, getLastIndex());
135 | } catch (InterruptedException | RocksDBException e) {
136 | LOGGER.warn(e.getMessage());
137 | } finally {
138 | if (success) {
139 | updateLastIndex(getLastIndex() - count);
140 | }
141 | lock.unlock();
142 | }
143 | }
144 |
145 |
146 | @Override
147 | public LogEntry getLast() {
148 | try {
149 | byte[] result = logDb.get(convert(getLastIndex()));
150 | if (result == null) {
151 | return null;
152 | }
153 | return JSON.parseObject(result, LogEntry.class);
154 | } catch (RocksDBException e) {
155 | e.printStackTrace();
156 | }
157 | return null;
158 | }
159 |
160 | @Override
161 | public Long getLastIndex() {
162 | byte[] lastIndex = "-1".getBytes();
163 | try {
164 | lastIndex = logDb.get(LAST_INDEX_KEY);
165 | if (lastIndex == null) {
166 | lastIndex = "-1".getBytes();
167 | }
168 | } catch (RocksDBException e) {
169 | e.printStackTrace();
170 | }
171 | return Long.valueOf(new String(lastIndex));
172 | }
173 |
174 | private byte[] convert(Long key) {
175 | return key.toString().getBytes();
176 | }
177 |
178 | // on lock
179 | private void updateLastIndex(Long index) {
180 | try {
181 | // overWrite
182 | logDb.put(LAST_INDEX_KEY, index.toString().getBytes());
183 | } catch (RocksDBException e) {
184 | e.printStackTrace();
185 | }
186 | }
187 |
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/DefaultConsensus.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import java.util.concurrent.locks.ReentrantLock;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import cn.think.in.java.Consensus;
9 | import cn.think.in.java.common.NodeStatus;
10 | import cn.think.in.java.common.Peer;
11 | import cn.think.in.java.entity.AentryParam;
12 | import cn.think.in.java.entity.AentryResult;
13 | import cn.think.in.java.entity.LogEntry;
14 | import cn.think.in.java.entity.RvoteParam;
15 | import cn.think.in.java.entity.RvoteResult;
16 | import io.netty.util.internal.StringUtil;
17 | import lombok.Getter;
18 | import lombok.Setter;
19 |
20 | /**
21 | *
22 | * 默认的一致性模块实现.
23 | *
24 | * @author 莫那·鲁道
25 | */
26 | @Setter
27 | @Getter
28 | public class DefaultConsensus implements Consensus {
29 |
30 |
31 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConsensus.class);
32 |
33 |
34 | public final DefaultNode node;
35 |
36 | public final ReentrantLock voteLock = new ReentrantLock();
37 | public final ReentrantLock appendLock = new ReentrantLock();
38 |
39 | public DefaultConsensus(DefaultNode node) {
40 | this.node = node;
41 | }
42 |
43 | /**
44 | * 请求投票 RPC
45 | *
46 | * 接收者实现:
47 | * 如果term < currentTerm返回 false (5.2 节)
48 | * 如果 votedFor 为空或者就是 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)
49 | */
50 | @Override
51 | public RvoteResult requestVote(RvoteParam param) {
52 | try {
53 | RvoteResult.Builder builder = RvoteResult.newBuilder();
54 | if (!voteLock.tryLock()) {
55 | return builder.term(node.getCurrentTerm()).voteGranted(false).build();
56 | }
57 |
58 | // 对方任期没有自己新
59 | if (param.getTerm() < node.getCurrentTerm()) {
60 | return builder.term(node.getCurrentTerm()).voteGranted(false).build();
61 | }
62 |
63 | // (当前节点并没有投票 或者 已经投票过了且是对方节点) && 对方日志和自己一样新
64 | LOGGER.info("node {} current vote for [{}], param candidateId : {}", node.peerSet.getSelf(), node.getVotedFor(), param.getCandidateId());
65 | LOGGER.info("node {} current term {}, peer term : {}", node.peerSet.getSelf(), node.getCurrentTerm(), param.getTerm());
66 |
67 | if ((StringUtil.isNullOrEmpty(node.getVotedFor()) || node.getVotedFor().equals(param.getCandidateId()))) {
68 |
69 | if (node.getLogModule().getLast() != null) {
70 | // 对方没有自己新
71 | if (node.getLogModule().getLast().getTerm() > param.getLastLogTerm()) {
72 | return RvoteResult.fail();
73 | }
74 | // 对方没有自己新
75 | if (node.getLogModule().getLastIndex() > param.getLastLogIndex()) {
76 | return RvoteResult.fail();
77 | }
78 | }
79 |
80 | // 切换状态
81 | node.status = NodeStatus.FOLLOWER;
82 | // 更新
83 | node.peerSet.setLeader(new Peer(param.getCandidateId()));
84 | node.setCurrentTerm(param.getTerm());
85 | node.setVotedFor(param.serverId);
86 | // 返回成功
87 | return builder.term(node.currentTerm).voteGranted(true).build();
88 | }
89 |
90 | return builder.term(node.currentTerm).voteGranted(false).build();
91 |
92 | } finally {
93 | voteLock.unlock();
94 | }
95 | }
96 |
97 |
98 | /**
99 | * 附加日志(多个日志,为了提高效率) RPC
100 | *
101 | * 接收者实现:
102 | * 如果 term < currentTerm 就返回 false (5.1 节)
103 | * 如果日志在 prevLogIndex 位置处的日志条目的任期号和 prevLogTerm 不匹配,则返回 false (5.3 节)
104 | * 如果已经存在的日志条目和新的产生冲突(索引值相同但是任期号不同),删除这一条和之后所有的 (5.3 节)
105 | * 附加任何在已有的日志中不存在的条目
106 | * 如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个
107 | */
108 | @Override
109 | public AentryResult appendEntries(AentryParam param) {
110 | AentryResult result = AentryResult.fail();
111 | try {
112 | if (!appendLock.tryLock()) {
113 | return result;
114 | }
115 |
116 | result.setTerm(node.getCurrentTerm());
117 | // 不够格
118 | if (param.getTerm() < node.getCurrentTerm()) {
119 | return result;
120 | }
121 |
122 | node.preHeartBeatTime = System.currentTimeMillis();
123 | node.preElectionTime = System.currentTimeMillis();
124 | node.peerSet.setLeader(new Peer(param.getLeaderId()));
125 |
126 | // 够格
127 | if (param.getTerm() >= node.getCurrentTerm()) {
128 | LOGGER.debug("node {} become FOLLOWER, currentTerm : {}, param Term : {}, param serverId",
129 | node.peerSet.getSelf(), node.currentTerm, param.getTerm(), param.getServerId());
130 | // 认怂
131 | node.status = NodeStatus.FOLLOWER;
132 | }
133 | // 使用对方的 term.
134 | node.setCurrentTerm(param.getTerm());
135 |
136 | //心跳
137 | if (param.getEntries() == null || param.getEntries().length == 0) {
138 | LOGGER.info("node {} append heartbeat success , he's term : {}, my term : {}",
139 | param.getLeaderId(), param.getTerm(), node.getCurrentTerm());
140 | return AentryResult.newBuilder().term(node.getCurrentTerm()).success(true).build();
141 | }
142 |
143 | // 真实日志
144 | // 第一次
145 | if (node.getLogModule().getLastIndex() != 0 && param.getPrevLogIndex() != 0) {
146 | LogEntry logEntry;
147 | if ((logEntry = node.getLogModule().read(param.getPrevLogIndex())) != null) {
148 | // 如果日志在 prevLogIndex 位置处的日志条目的任期号和 prevLogTerm 不匹配,则返回 false
149 | // 需要减小 nextIndex 重试.
150 | if (logEntry.getTerm() != param.getPreLogTerm()) {
151 | return result;
152 | }
153 | } else {
154 | // index 不对, 需要递减 nextIndex 重试.
155 | return result;
156 | }
157 |
158 | }
159 |
160 | // 如果已经存在的日志条目和新的产生冲突(索引值相同但是任期号不同),删除这一条和之后所有的
161 | LogEntry existLog = node.getLogModule().read(((param.getPrevLogIndex() + 1)));
162 | if (existLog != null && existLog.getTerm() != param.getEntries()[0].getTerm()) {
163 | // 删除这一条和之后所有的, 然后写入日志和状态机.
164 | node.getLogModule().removeOnStartIndex(param.getPrevLogIndex() + 1);
165 | } else if (existLog != null) {
166 | // 已经有日志了, 不能重复写入.
167 | result.setSuccess(true);
168 | return result;
169 | }
170 |
171 | // 写进日志并且应用到状态机
172 | for (LogEntry entry : param.getEntries()) {
173 | node.getLogModule().write(entry);
174 | node.stateMachine.apply(entry);
175 | result.setSuccess(true);
176 | }
177 |
178 | //如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个
179 | if (param.getLeaderCommit() > node.getCommitIndex()) {
180 | int commitIndex = (int) Math.min(param.getLeaderCommit(), node.getLogModule().getLastIndex());
181 | node.setCommitIndex(commitIndex);
182 | node.setLastApplied(commitIndex);
183 | }
184 |
185 | result.setTerm(node.getCurrentTerm());
186 |
187 | node.status = NodeStatus.FOLLOWER;
188 | // TODO, 是否应当在成功回复之后, 才正式提交? 防止 leader "等待回复"过程中 挂掉.
189 | return result;
190 | } finally {
191 | appendLock.unlock();
192 | }
193 | }
194 |
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/lu-raft-kv/src/main/java/cn/think/in/java/impl/DefaultNode.java:
--------------------------------------------------------------------------------
1 | package cn.think.in.java.impl;
2 |
3 | import static cn.think.in.java.common.NodeStatus.LEADER;
4 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.LinkedList;
9 | import java.util.List;
10 | import java.util.Map;
11 | import java.util.concurrent.Callable;
12 | import java.util.concurrent.CancellationException;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.concurrent.CopyOnWriteArrayList;
15 | import java.util.concurrent.CountDownLatch;
16 | import java.util.concurrent.ExecutionException;
17 | import java.util.concurrent.Future;
18 | import java.util.concurrent.LinkedBlockingQueue;
19 | import java.util.concurrent.ThreadLocalRandom;
20 | import java.util.concurrent.TimeoutException;
21 | import java.util.concurrent.atomic.AtomicInteger;
22 |
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 |
26 | import cn.think.in.java.Consensus;
27 | import cn.think.in.java.LifeCycle;
28 | import cn.think.in.java.LogModule;
29 | import cn.think.in.java.Node;
30 | import cn.think.in.java.StateMachine;
31 | import cn.think.in.java.common.NodeConfig;
32 | import cn.think.in.java.common.NodeStatus;
33 | import cn.think.in.java.common.Peer;
34 | import cn.think.in.java.common.PeerSet;
35 | import cn.think.in.java.current.RaftThreadPool;
36 | import cn.think.in.java.entity.AentryParam;
37 | import cn.think.in.java.entity.AentryResult;
38 | import cn.think.in.java.entity.Command;
39 | import cn.think.in.java.entity.LogEntry;
40 | import cn.think.in.java.entity.ReplicationFailModel;
41 | import cn.think.in.java.entity.RvoteParam;
42 | import cn.think.in.java.entity.RvoteResult;
43 | import cn.think.in.java.exception.RaftRemotingException;
44 | import cn.think.in.java.membership.changes.ClusterMembershipChanges;
45 | import cn.think.in.java.membership.changes.Result;
46 | import cn.think.in.java.rpc.DefaultRpcClient;
47 | import cn.think.in.java.rpc.DefaultRpcServer;
48 | import cn.think.in.java.rpc.Request;
49 | import cn.think.in.java.rpc.Response;
50 | import cn.think.in.java.rpc.RpcClient;
51 | import cn.think.in.java.rpc.RpcServer;
52 | import cn.think.in.java.util.LongConvert;
53 | import lombok.Getter;
54 | import lombok.Setter;
55 | import raft.client.ClientKVAck;
56 | import raft.client.ClientKVReq;
57 |
58 | /**
59 | * 抽象机器节点, 初始为 follower, 角色随时变化.
60 | * @author 莫那·鲁道
61 | */
62 | @Getter
63 | @Setter
64 | public class DefaultNode implements Node, LifeCycle, ClusterMembershipChanges {
65 |
66 | private static final Logger LOGGER = LoggerFactory.getLogger(DefaultNode.class);
67 |
68 | /** 选举时间间隔基数 */
69 | public volatile long electionTime = 15 * 1000;
70 | /** 上一次选举时间 */
71 | public volatile long preElectionTime = 0;
72 |
73 | /** 上次一心跳时间戳 */
74 | public volatile long preHeartBeatTime = 0;
75 | /** 心跳间隔基数 */
76 | public final long heartBeatTick = 5 * 1000;
77 |
78 |
79 | private HeartBeatTask heartBeatTask = new HeartBeatTask();
80 | private ElectionTask electionTask = new ElectionTask();
81 | private ReplicationFailQueueConsumer replicationFailQueueConsumer = new ReplicationFailQueueConsumer();
82 |
83 | private LinkedBlockingQueue replicationFailQueue = new LinkedBlockingQueue<>(2048);
84 |
85 |
86 | /**
87 | * 节点当前状态
88 | * @see NodeStatus
89 | */
90 | public volatile int status = NodeStatus.FOLLOWER;
91 |
92 | public PeerSet peerSet;
93 |
94 |
95 |
96 | /* ============ 所有服务器上持久存在的 ============= */
97 |
98 | /** 服务器最后一次知道的任期号(初始化为 0,持续递增) */
99 | volatile long currentTerm = 0;
100 | /** 在当前获得选票的候选人的 Id */
101 | volatile String votedFor;
102 | /** 日志条目集;每一个条目包含一个用户状态机执行的指令,和收到时的任期号 */
103 | LogModule logModule;
104 |
105 |
106 |
107 | /* ============ 所有服务器上经常变的 ============= */
108 |
109 | /** 已知的最大的已经被提交的日志条目的索引值 */
110 | volatile long commitIndex;
111 |
112 | /** 最后被应用到状态机的日志条目索引值(初始化为 0,持续递增) */
113 | volatile long lastApplied = 0;
114 |
115 | /* ========== 在领导人里经常改变的(选举后重新初始化) ================== */
116 |
117 | /** 对于每一个服务器,需要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加一) */
118 | Map nextIndexs;
119 |
120 | /** 对于每一个服务器,已经复制给他的日志的最高索引值 */
121 | Map matchIndexs;
122 |
123 |
124 |
125 | /* ============================== */
126 |
127 | public volatile boolean started;
128 |
129 | public NodeConfig config;
130 |
131 | public static RpcServer RPC_SERVER;
132 |
133 | public RpcClient rpcClient = new DefaultRpcClient();
134 |
135 | public StateMachine stateMachine;
136 |
137 | /* ============================== */
138 |
139 | /** 一致性模块实现 */
140 | Consensus consensus;
141 |
142 | ClusterMembershipChanges delegate;
143 |
144 |
145 | /* ============================== */
146 |
147 | private DefaultNode() {
148 | }
149 |
150 | public static DefaultNode getInstance() {
151 | return DefaultNodeLazyHolder.INSTANCE;
152 | }
153 |
154 |
155 | private static class DefaultNodeLazyHolder {
156 |
157 | private static final DefaultNode INSTANCE = new DefaultNode();
158 | }
159 |
160 | @Override
161 | public void init() throws Throwable {
162 | if (started) {
163 | return;
164 | }
165 | synchronized (this) {
166 | if (started) {
167 | return;
168 | }
169 | RPC_SERVER.start();
170 |
171 | consensus = new DefaultConsensus(this);
172 | delegate = new ClusterMembershipChangesImpl(this);
173 |
174 | RaftThreadPool.scheduleWithFixedDelay(heartBeatTask, 500);
175 | RaftThreadPool.scheduleAtFixedRate(electionTask, 6000, 500);
176 | RaftThreadPool.execute(replicationFailQueueConsumer);
177 |
178 | LogEntry logEntry = logModule.getLast();
179 | if (logEntry != null) {
180 | currentTerm = logEntry.getTerm();
181 | }
182 |
183 | started = true;
184 |
185 | LOGGER.info("start success, selfId : {} ", peerSet.getSelf());
186 | }
187 | }
188 |
189 | @Override
190 | public void setConfig(NodeConfig config) {
191 | this.config = config;
192 | stateMachine = config.getStateMachineSaveType().getStateMachine();
193 | logModule = DefaultLogModule.getInstance();
194 |
195 | peerSet = PeerSet.getInstance();
196 | for (String s : config.getPeerAddrs()) {
197 | Peer peer = new Peer(s);
198 | peerSet.addPeer(peer);
199 | if (s.equals("localhost:" + config.getSelfPort())) {
200 | peerSet.setSelf(peer);
201 | }
202 | }
203 |
204 | RPC_SERVER = new DefaultRpcServer(config.selfPort, this);
205 | }
206 |
207 |
208 | @Override
209 | public RvoteResult handlerRequestVote(RvoteParam param) {
210 | LOGGER.warn("handlerRequestVote will be invoke, param info : {}", param);
211 | return consensus.requestVote(param);
212 | }
213 |
214 | @Override
215 | public AentryResult handlerAppendEntries(AentryParam param) {
216 | if (param.getEntries() != null) {
217 | LOGGER.warn("node receive node {} append entry, entry content = {}", param.getLeaderId(), param.getEntries());
218 | }
219 |
220 | return consensus.appendEntries(param);
221 |
222 | }
223 |
224 |
225 | @SuppressWarnings("unchecked")
226 | @Override
227 | public ClientKVAck redirect(ClientKVReq request) {
228 | Request r = Request.newBuilder().
229 | obj(request).url(peerSet.getLeader().getAddr()).cmd(Request.CLIENT_REQ).build();
230 | Response response = rpcClient.send(r);
231 | return (ClientKVAck) response.getResult();
232 | }
233 |
234 | /**
235 | * 客户端的每一个请求都包含一条被复制状态机执行的指令。
236 | * 领导人把这条指令作为一条新的日志条目附加到日志中去,然后并行的发起附加条目 RPCs 给其他的服务器,让他们复制这条日志条目。
237 | * 当这条日志条目被安全的复制(下面会介绍),领导人会应用这条日志条目到它的状态机中然后把执行的结果返回给客户端。
238 | * 如果跟随者崩溃或者运行缓慢,再或者网络丢包,
239 | * 领导人会不断的重复尝试附加日志条目 RPCs (尽管已经回复了客户端)直到所有的跟随者都最终存储了所有的日志条目。
240 | * @param request
241 | * @return
242 | */
243 | @Override
244 | public synchronized ClientKVAck handlerClientRequest(ClientKVReq request) {
245 |
246 | LOGGER.warn("handlerClientRequest handler {} operation, and key : [{}], value : [{}]",
247 | ClientKVReq.Type.value(request.getType()), request.getKey(), request.getValue());
248 |
249 | if (status != LEADER) {
250 | LOGGER.warn("I not am leader , only invoke redirect method, leader addr : {}, my addr : {}",
251 | peerSet.getLeader(), peerSet.getSelf().getAddr());
252 | return redirect(request);
253 | }
254 |
255 | if (request.getType() == ClientKVReq.GET) {
256 | LogEntry logEntry = stateMachine.get(request.getKey());
257 | if (logEntry != null) {
258 | return new ClientKVAck(logEntry.getCommand());
259 | }
260 | return new ClientKVAck(null);
261 | }
262 |
263 | LogEntry logEntry = LogEntry.newBuilder()
264 | .command(Command.newBuilder().
265 | key(request.getKey()).
266 | value(request.getValue()).
267 | build())
268 | .term(currentTerm)
269 | .build();
270 |
271 | // 预提交到本地日志, TODO 预提交
272 | logModule.write(logEntry);
273 | LOGGER.info("write logModule success, logEntry info : {}, log index : {}", logEntry, logEntry.getIndex());
274 |
275 | final AtomicInteger success = new AtomicInteger(0);
276 |
277 | List> futureList = new CopyOnWriteArrayList<>();
278 |
279 | int count = 0;
280 | // 复制到其他机器
281 | for (Peer peer : peerSet.getPeersWithOutSelf()) {
282 | // TODO check self and RaftThreadPool
283 | count++;
284 | // 并行发起 RPC 复制.
285 | futureList.add(replication(peer, logEntry));
286 | }
287 |
288 | CountDownLatch latch = new CountDownLatch(futureList.size());
289 | List resultList = new CopyOnWriteArrayList<>();
290 |
291 | getRPCAppendResult(futureList, latch, resultList);
292 |
293 | try {
294 | latch.await(4000, MILLISECONDS);
295 | } catch (InterruptedException e) {
296 | e.printStackTrace();
297 | }
298 |
299 | for (Boolean aBoolean : resultList) {
300 | if (aBoolean) {
301 | success.incrementAndGet();
302 | }
303 | }
304 |
305 | // 如果存在一个满足N > commitIndex的 N,并且大多数的matchIndex[i] ≥ N成立,
306 | // 并且log[N].term == currentTerm成立,那么令 commitIndex 等于这个 N (5.3 和 5.4 节)
307 | List matchIndexList = new ArrayList<>(matchIndexs.values());
308 | // 小于 2, 没有意义
309 | int median = 0;
310 | if (matchIndexList.size() >= 2) {
311 | Collections.sort(matchIndexList);
312 | median = matchIndexList.size() / 2;
313 | }
314 | Long N = matchIndexList.get(median);
315 | if (N > commitIndex) {
316 | LogEntry entry = logModule.read(N);
317 | if (entry != null && entry.getTerm() == currentTerm) {
318 | commitIndex = N;
319 | }
320 | }
321 |
322 | // 响应客户端(成功一半)
323 | if (success.get() >= (count / 2)) {
324 | // 更新
325 | commitIndex = logEntry.getIndex();
326 | // 应用到状态机
327 | getStateMachine().apply(logEntry);
328 | lastApplied = commitIndex;
329 |
330 | LOGGER.info("success apply local state machine, logEntry info : {}", logEntry);
331 | // 返回成功.
332 | return ClientKVAck.ok();
333 | } else {
334 | // 回滚已经提交的日志.
335 | logModule.removeOnStartIndex(logEntry.getIndex());
336 | LOGGER.warn("fail apply local state machine, logEntry info : {}", logEntry);
337 | // TODO 不应用到状态机,但已经记录到日志中.由定时任务从重试队列取出,然后重复尝试,当达到条件时,应用到状态机.
338 | // 这里应该返回错误, 因为没有成功复制过半机器.
339 | return ClientKVAck.fail();
340 | }
341 | }
342 |
343 | private void getRPCAppendResult(List> futureList, CountDownLatch latch, List resultList) {
344 | for (Future future : futureList) {
345 | RaftThreadPool.execute(new Runnable() {
346 | @Override
347 | public void run() {
348 | try {
349 | resultList.add(future.get(3000, MILLISECONDS));
350 | } catch (CancellationException | TimeoutException | ExecutionException | InterruptedException e) {
351 | e.printStackTrace();
352 | resultList.add(false);
353 | } finally {
354 | latch.countDown();
355 | }
356 | }
357 | });
358 | }
359 | }
360 |
361 |
362 | /** 复制到其他机器 */
363 | public Future replication(Peer peer, LogEntry entry) {
364 |
365 | return RaftThreadPool.submit(new Callable() {
366 | @Override
367 | public Boolean call() throws Exception {
368 |
369 | long start = System.currentTimeMillis(), end = start;
370 |
371 | // 20 秒重试时间
372 | while (end - start < 20 * 1000L) {
373 |
374 | AentryParam aentryParam = new AentryParam();
375 | aentryParam.setTerm(currentTerm);
376 | aentryParam.setServerId(peer.getAddr());
377 | aentryParam.setLeaderId(peerSet.getSelf().getAddr());
378 |
379 | aentryParam.setLeaderCommit(commitIndex);
380 |
381 | // 以我这边为准, 这个行为通常是成为 leader 后,首次进行 RPC 才有意义.
382 | Long nextIndex = nextIndexs.get(peer);
383 | LinkedList logEntries = new LinkedList<>();
384 | if (entry.getIndex() >= nextIndex) {
385 | for (long i = nextIndex; i <= entry.getIndex(); i++) {
386 | LogEntry l = logModule.read(i);
387 | if (l != null) {
388 | logEntries.add(l);
389 | }
390 | }
391 | } else {
392 | logEntries.add(entry);
393 | }
394 | // 最小的那个日志.
395 | LogEntry preLog = getPreLog(logEntries.getFirst());
396 | aentryParam.setPreLogTerm(preLog.getTerm());
397 | aentryParam.setPrevLogIndex(preLog.getIndex());
398 |
399 | aentryParam.setEntries(logEntries.toArray(new LogEntry[0]));
400 |
401 | Request request = Request.newBuilder()
402 | .cmd(Request.A_ENTRIES)
403 | .obj(aentryParam)
404 | .url(peer.getAddr())
405 | .build();
406 |
407 | try {
408 | Response response = getRpcClient().send(request);
409 | if (response == null) {
410 | return false;
411 | }
412 | AentryResult result = (AentryResult) response.getResult();
413 | if (result != null && result.isSuccess()) {
414 | LOGGER.info("append follower entry success , follower=[{}], entry=[{}]", peer, aentryParam.getEntries());
415 | // update 这两个追踪值
416 | nextIndexs.put(peer, entry.getIndex() + 1);
417 | matchIndexs.put(peer, entry.getIndex());
418 | return true;
419 | } else if (result != null) {
420 | // 对方比我大
421 | if (result.getTerm() > currentTerm) {
422 | LOGGER.warn("follower [{}] term [{}] than more self, and my term = [{}], so, I will become follower",
423 | peer, result.getTerm(), currentTerm);
424 | currentTerm = result.getTerm();
425 | // 认怂, 变成跟随者
426 | status = NodeStatus.FOLLOWER;
427 | return false;
428 | } // 没我大, 却失败了,说明 index 不对.或者 term 不对.
429 | else {
430 | // 递减
431 | if (nextIndex == 0) {
432 | nextIndex = 1L;
433 | }
434 | nextIndexs.put(peer, nextIndex - 1);
435 | LOGGER.warn("follower {} nextIndex not match, will reduce nextIndex and retry RPC append, nextIndex : [{}]", peer.getAddr(),
436 | nextIndex);
437 | // 重来, 直到成功.
438 | }
439 | }
440 |
441 | end = System.currentTimeMillis();
442 |
443 | } catch (Exception e) {
444 | LOGGER.warn(e.getMessage(), e);
445 | // TODO 到底要不要放队列重试?
446 | // ReplicationFailModel model = ReplicationFailModel.newBuilder()
447 | // .callable(this)
448 | // .logEntry(entry)
449 | // .peer(peer)
450 | // .offerTime(System.currentTimeMillis())
451 | // .build();
452 | // replicationFailQueue.offer(model);
453 | return false;
454 | }
455 | }
456 | // 超时了,没办法了
457 | return false;
458 | }
459 | });
460 |
461 | }
462 |
463 | private LogEntry getPreLog(LogEntry logEntry) {
464 | LogEntry entry = logModule.read(logEntry.getIndex() - 1);
465 |
466 | if (entry == null) {
467 | LOGGER.warn("get perLog is null , parameter logEntry : {}", logEntry);
468 | entry = LogEntry.newBuilder().index(0L).term(0).command(null).build();
469 | }
470 | return entry;
471 | }
472 |
473 |
474 | class ReplicationFailQueueConsumer implements Runnable {
475 |
476 | /** 一分钟 */
477 | long intervalTime = 1000 * 60;
478 |
479 | @Override
480 | public void run() {
481 | for (; ; ) {
482 |
483 | try {
484 | ReplicationFailModel model = replicationFailQueue.take();
485 | if (status != LEADER) {
486 | // 应该清空?
487 | replicationFailQueue.clear();
488 | continue;
489 | }
490 | LOGGER.warn("replication Fail Queue Consumer take a task, will be retry replication, content detail : [{}]", model.logEntry);
491 | long offerTime = model.offerTime;
492 | if (System.currentTimeMillis() - offerTime > intervalTime) {
493 | LOGGER.warn("replication Fail event Queue maybe full or handler slow");
494 | }
495 |
496 | Callable callable = model.callable;
497 | Future future = RaftThreadPool.submit(callable);
498 | Boolean r = future.get(3000, MILLISECONDS);
499 | // 重试成功.
500 | if (r) {
501 | // 可能有资格应用到状态机.
502 | tryApplyStateMachine(model);
503 | }
504 |
505 | } catch (InterruptedException e) {
506 | // ignore
507 | } catch (ExecutionException | TimeoutException e) {
508 | LOGGER.warn(e.getMessage());
509 | }
510 | }
511 | }
512 | }
513 |
514 | private void tryApplyStateMachine(ReplicationFailModel model) {
515 |
516 | String success = stateMachine.getString(model.successKey);
517 | stateMachine.setString(model.successKey, String.valueOf(Integer.valueOf(success) + 1));
518 |
519 | String count = stateMachine.getString(model.countKey);
520 |
521 | if (Integer.valueOf(success) >= Integer.valueOf(count) / 2) {
522 | stateMachine.apply(model.logEntry);
523 | stateMachine.delString(model.countKey, model.successKey);
524 | }
525 | }
526 |
527 |
528 | @Override
529 | public void destroy() throws Throwable {
530 | RPC_SERVER.stop();
531 | }
532 |
533 |
534 | /**
535 | * 1. 在转变成候选人后就立即开始选举过程
536 | * 自增当前的任期号(currentTerm)
537 | * 给自己投票
538 | * 重置选举超时计时器
539 | * 发送请求投票的 RPC 给其他所有服务器
540 | * 2. 如果接收到大多数服务器的选票,那么就变成领导人
541 | * 3. 如果接收到来自新的领导人的附加日志 RPC,转变成跟随者
542 | * 4. 如果选举过程超时,再次发起一轮选举
543 | */
544 | class ElectionTask implements Runnable {
545 |
546 | @Override
547 | public void run() {
548 |
549 | if (status == LEADER) {
550 | return;
551 | }
552 |
553 | long current = System.currentTimeMillis();
554 | // 基于 RAFT 的随机时间,解决冲突.
555 | electionTime = electionTime + ThreadLocalRandom.current().nextInt(50);
556 | if (current - preElectionTime < electionTime) {
557 | return;
558 | }
559 | status = NodeStatus.CANDIDATE;
560 | LOGGER.error("node {} will become CANDIDATE and start election leader, current term : [{}], LastEntry : [{}]",
561 | peerSet.getSelf(), currentTerm, logModule.getLast());
562 |
563 | preElectionTime = System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(200) + 150;
564 |
565 | currentTerm = currentTerm + 1;
566 | // 推荐自己.
567 | votedFor = peerSet.getSelf().getAddr();
568 |
569 | List peers = peerSet.getPeersWithOutSelf();
570 |
571 | ArrayList futureArrayList = new ArrayList<>();
572 |
573 | LOGGER.info("peerList size : {}, peer list content : {}", peers.size(), peers);
574 |
575 | // 发送请求
576 | for (Peer peer : peers) {
577 |
578 | futureArrayList.add(RaftThreadPool.submit(new Callable() {
579 | @Override
580 | public Object call() throws Exception {
581 | long lastTerm = 0L;
582 | LogEntry last = logModule.getLast();
583 | if (last != null) {
584 | lastTerm = last.getTerm();
585 | }
586 |
587 | RvoteParam param = RvoteParam.newBuilder().
588 | term(currentTerm).
589 | candidateId(peerSet.getSelf().getAddr()).
590 | lastLogIndex(LongConvert.convert(logModule.getLastIndex())).
591 | lastLogTerm(lastTerm).
592 | build();
593 |
594 | Request request = Request.newBuilder()
595 | .cmd(Request.R_VOTE)
596 | .obj(param)
597 | .url(peer.getAddr())
598 | .build();
599 |
600 | try {
601 | @SuppressWarnings("unchecked")
602 | Response response = getRpcClient().send(request);
603 | return response;
604 |
605 | } catch (RaftRemotingException e) {
606 | LOGGER.error("ElectionTask RPC Fail , URL : " + request.getUrl());
607 | return null;
608 | }
609 | }
610 | }));
611 | }
612 |
613 | AtomicInteger success2 = new AtomicInteger(0);
614 | CountDownLatch latch = new CountDownLatch(futureArrayList.size());
615 |
616 | LOGGER.info("futureArrayList.size() : {}", futureArrayList.size());
617 | // 等待结果.
618 | for (Future future : futureArrayList) {
619 | RaftThreadPool.submit(new Callable() {
620 | @Override
621 | public Object call() throws Exception {
622 | try {
623 |
624 | @SuppressWarnings("unchecked")
625 | Response response = (Response) future.get(3000, MILLISECONDS);
626 | if (response == null) {
627 | return -1;
628 | }
629 | boolean isVoteGranted = response.getResult().isVoteGranted();
630 |
631 | if (isVoteGranted) {
632 | success2.incrementAndGet();
633 | } else {
634 | // 更新自己的任期.
635 | long resTerm = response.getResult().getTerm();
636 | if (resTerm >= currentTerm) {
637 | currentTerm = resTerm;
638 | }
639 | }
640 | return 0;
641 | } catch (Exception e) {
642 | LOGGER.error("future.get exception , e : ", e);
643 | return -1;
644 | } finally {
645 | latch.countDown();
646 | }
647 | }
648 | });
649 | }
650 |
651 | try {
652 | // 稍等片刻
653 | latch.await(3500, MILLISECONDS);
654 | } catch (InterruptedException e) {
655 | LOGGER.warn("InterruptedException By Master election Task");
656 | }
657 |
658 | int success = success2.get();
659 | LOGGER.info("node {} maybe become leader , success count = {} , status : {}", peerSet.getSelf(), success, NodeStatus.Enum.value(status));
660 | // 如果投票期间,有其他服务器发送 appendEntry , 就可能变成 follower ,这时,应该停止.
661 | if (status == NodeStatus.FOLLOWER) {
662 | return;
663 | }
664 | // 加上自身.
665 | if (success >= peers.size() / 2) {
666 | LOGGER.warn("node {} become leader ", peerSet.getSelf());
667 | status = LEADER;
668 | peerSet.setLeader(peerSet.getSelf());
669 | votedFor = "";
670 | becomeLeaderToDoThing();
671 | } else {
672 | // else 重新选举
673 | votedFor = "";
674 | }
675 |
676 | }
677 | }
678 |
679 | /**
680 | * 初始化所有的 nextIndex 值为自己的最后一条日志的 index + 1. 如果下次 RPC 时, 跟随者和leader 不一致,就会失败.
681 | * 那么 leader 尝试递减 nextIndex 并进行重试.最终将达成一致.
682 | */
683 | private void becomeLeaderToDoThing() {
684 | nextIndexs = new ConcurrentHashMap<>();
685 | matchIndexs = new ConcurrentHashMap<>();
686 | for (Peer peer : peerSet.getPeersWithOutSelf()) {
687 | nextIndexs.put(peer, logModule.getLastIndex() + 1);
688 | matchIndexs.put(peer, 0L);
689 | }
690 | }
691 |
692 |
693 | class HeartBeatTask implements Runnable {
694 |
695 | @Override
696 | public void run() {
697 |
698 | if (status != LEADER) {
699 | return;
700 | }
701 |
702 | long current = System.currentTimeMillis();
703 | if (current - preHeartBeatTime < heartBeatTick) {
704 | return;
705 | }
706 | LOGGER.info("=========== NextIndex =============");
707 | for (Peer peer : peerSet.getPeersWithOutSelf()) {
708 | LOGGER.info("Peer {} nextIndex={}", peer.getAddr(), nextIndexs.get(peer));
709 | }
710 |
711 | preHeartBeatTime = System.currentTimeMillis();
712 |
713 | // 心跳只关心 term 和 leaderID
714 | for (Peer peer : peerSet.getPeersWithOutSelf()) {
715 |
716 | AentryParam param = AentryParam.newBuilder()
717 | .entries(null)// 心跳,空日志.
718 | .leaderId(peerSet.getSelf().getAddr())
719 | .serverId(peer.getAddr())
720 | .term(currentTerm)
721 | .build();
722 |
723 | Request request = new Request<>(
724 | Request.A_ENTRIES,
725 | param,
726 | peer.getAddr());
727 |
728 | RaftThreadPool.execute(new Runnable() {
729 | @Override
730 | public void run() {
731 | try {
732 | Response response = getRpcClient().send(request);
733 | AentryResult aentryResult = (AentryResult) response.getResult();
734 | long term = aentryResult.getTerm();
735 |
736 | if (term > currentTerm) {
737 | LOGGER.error("self will become follower, he's term : {}, my term : {}", term, currentTerm);
738 | currentTerm = term;
739 | votedFor = "";
740 | status = NodeStatus.FOLLOWER;
741 | }
742 | } catch (Exception e) {
743 | LOGGER.error("HeartBeatTask RPC Fail, request URL : {} ", request.getUrl());
744 | }
745 | }
746 | }, false);
747 | }
748 | }
749 | }
750 |
751 | @Override
752 | public Result addPeer(Peer newPeer) {
753 | return delegate.addPeer(newPeer);
754 | }
755 |
756 | @Override
757 | public Result removePeer(Peer oldPeer) {
758 | return delegate.removePeer(oldPeer);
759 | }
760 |
761 | }
762 |
--------------------------------------------------------------------------------