├── .gitignore
├── raft-web
├── src
│ └── main
│ │ ├── webapp
│ │ ├── index.jsp
│ │ └── WEB-INF
│ │ │ └── web.xml
│ │ ├── java
│ │ └── top
│ │ │ └── datadriven
│ │ │ └── raft
│ │ │ └── web
│ │ │ ├── AppMain.java
│ │ │ └── OptionFacadeTest.java
│ │ └── resources
│ │ ├── spring
│ │ ├── spring-content.xml
│ │ ├── spring-mvc.xml
│ │ └── logback.xml
│ │ ├── props
│ │ └── raft.properties
│ │ └── log4j.properties
└── pom.xml
├── raft-config-loader
├── src
│ └── main
│ │ ├── resources
│ │ └── server-rpc-config.yml
│ │ └── java
│ │ └── top
│ │ └── datadriven
│ │ └── raft
│ │ └── config
│ │ └── loader
│ │ └── ConfigLoader.java
└── pom.xml
├── raft-core-service
├── src
│ └── main
│ │ ├── java
│ │ └── top
│ │ │ └── datadriven
│ │ │ └── raft
│ │ │ └── core
│ │ │ └── service
│ │ │ ├── handler
│ │ │ ├── StateMachineHandler.java
│ │ │ └── impl
│ │ │ │ └── StateMachineHandlerImpl.java
│ │ │ ├── transformer
│ │ │ ├── ServerStateTransformerStarter.java
│ │ │ ├── ServerStateFactory.java
│ │ │ ├── impl
│ │ │ │ ├── ServerStateFactoryImpl.java
│ │ │ │ ├── ServerStateTransformerStarterImpl.java
│ │ │ │ ├── AbstractServerStateTransformer.java
│ │ │ │ ├── FollowerStateImpl.java
│ │ │ │ ├── CandidateStateImpl.java
│ │ │ │ └── LeaderStateImpl.java
│ │ │ ├── convertor
│ │ │ │ └── FollowerConvertor.java
│ │ │ └── ServerStateTransformer.java
│ │ │ ├── service
│ │ │ ├── VoteService.java
│ │ │ ├── AppendEntriesService.java
│ │ │ └── impl
│ │ │ │ ├── VoteServiceImpl.java
│ │ │ │ └── AppendEntriesServiceImpl.java
│ │ │ ├── component
│ │ │ ├── VoteComponent.java
│ │ │ ├── AppendEntriesComponent.java
│ │ │ └── impl
│ │ │ │ ├── VoteComponentImpl.java
│ │ │ │ └── AppendEntriesComponentImpl.java
│ │ │ └── pool
│ │ │ └── RaftThreadPool.java
│ │ └── resources
│ │ └── spring
│ │ └── spring-beans.xml
└── pom.xml
├── raft-biz-service-impl
├── src
│ └── main
│ │ ├── java
│ │ └── top
│ │ │ └── datadriven
│ │ │ └── raft
│ │ │ └── biz
│ │ │ └── service
│ │ │ └── impl
│ │ │ ├── provider
│ │ │ ├── DubboServiceRegister.java
│ │ │ └── impl
│ │ │ │ └── DubboServiceRegisterImpl.java
│ │ │ ├── api
│ │ │ └── impl
│ │ │ │ ├── RaftFacadeImpl.java
│ │ │ │ └── OperationFacadeImpl.java
│ │ │ └── component
│ │ │ └── RaftStarter.java
│ │ └── resources
│ │ └── spring
│ │ └── spring-beans.xml
└── pom.xml
├── raft-state-machine
├── src
│ └── main
│ │ └── java
│ │ └── top
│ │ └── datadriven
│ │ └── raft
│ │ └── state
│ │ └── machine
│ │ ├── StateMachine.java
│ │ └── impl
│ │ └── KvStateMachineImpl.java
└── pom.xml
├── raft-facade
├── src
│ └── main
│ │ └── java
│ │ └── top
│ │ └── datadriven
│ │ └── raft
│ │ └── facade
│ │ ├── api
│ │ ├── OperationFacade.java
│ │ └── RaftFacade.java
│ │ ├── base
│ │ └── BaseToString.java
│ │ └── model
│ │ ├── LogEntryModel.java
│ │ ├── VoteResponse.java
│ │ ├── SubmitResponse.java
│ │ ├── AppendEntriesResponse.java
│ │ ├── VoteRequest.java
│ │ └── AppendEntriesRequest.java
└── pom.xml
├── raft-integration
├── src
│ └── main
│ │ └── java
│ │ └── top
│ │ └── datadriven
│ │ └── raft
│ │ └── integration
│ │ ├── consumer
│ │ ├── DubboServiceConsumer.java
│ │ └── impl
│ │ │ └── DubboServiceConsumerImpl.java
│ │ ├── RaftClient.java
│ │ └── impl
│ │ └── RaftClientImpl.java
└── pom.xml
├── raft-core-model
├── src
│ └── main
│ │ └── java
│ │ └── top
│ │ └── datadriven
│ │ └── raft
│ │ └── core
│ │ └── model
│ │ ├── model
│ │ ├── ServerStateModel.java
│ │ ├── LeaderStateModel.java
│ │ ├── PersistentStateModel.java
│ │ └── RaftCoreModel.java
│ │ ├── config
│ │ ├── RaftNodeModel.java
│ │ └── ConfigModel.java
│ │ ├── util
│ │ ├── EntryUtil.java
│ │ ├── CommonUtil.java
│ │ ├── SpringContextUtil.java
│ │ └── AssertUtil.java
│ │ ├── constant
│ │ └── CommonConstant.java
│ │ ├── enums
│ │ ├── OptionEnum.java
│ │ └── ServerStateEnum.java
│ │ └── exception
│ │ ├── ErrorCodeEnum.java
│ │ └── RaftException.java
└── pom.xml
├── raft-test
└── pom.xml
├── raft-common-util
└── pom.xml
├── README.md
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 | */target
4 | */target/
5 | */target/*
6 |
--------------------------------------------------------------------------------
/raft-web/src/main/webapp/index.jsp:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello World!
4 |
5 |
6 |
--------------------------------------------------------------------------------
/raft-config-loader/src/main/resources/server-rpc-config.yml:
--------------------------------------------------------------------------------
1 | !!top.datadriven.raft.core.model.config.ConfigModel
2 | localNode: {ip: localhost, port: 20881, serverId: 1}
3 | allNodes:
4 | - {ip: localhost, port: 20881, serverId: 1}
5 | - {ip: localhost, port: 20882, serverId: 2}
6 | - {ip: localhost, port: 20883, serverId: 3}
7 | # - {ip: localhost, port: 20884, serverId: 4}
8 | # - {ip: localhost, port: 20885, serverId: 5}
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/handler/StateMachineHandler.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.handler;
2 |
3 | /**
4 | * @description: 状态机控制器
5 | * @author: jiayancheng
6 | * @email: jiayancheng@foxmail.com
7 | * @datetime: 2020/4/14 11:49 下午
8 | * @version: 1.0.0
9 | */
10 | public interface StateMachineHandler {
11 | /**
12 | * commit到apply状态
13 | */
14 | void commit2Apply();
15 | }
16 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/java/top/datadriven/raft/biz/service/impl/provider/DubboServiceRegister.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.biz.service.impl.provider;
2 |
3 | /**
4 | * @description: 注册当前server的dubbo服务
5 | * @author: jiayancheng
6 | * @email: jiayancheng@foxmail.com
7 | * @datetime: 2020/4/26 10:16 下午
8 | * @version: 1.0.0
9 | */
10 | public interface DubboServiceRegister {
11 | /**
12 | * 注册
13 | */
14 | void registry();
15 | }
16 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/ServerStateTransformerStarter.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer;
2 |
3 | /**
4 | * @description: 节点状态转换器启动(即角色转换状态机)
5 | * @author: jiayancheng
6 | * @email: jiayancheng@foxmail.com
7 | * @datetime: 2020/4/14 11:18 下午
8 | * @version: 1.0.0
9 | */
10 | public interface ServerStateTransformerStarter {
11 | /**
12 | * 开始执行
13 | * 启动后,在满足相应条件后,会自动在各个状态之间进行转换
14 | */
15 | void start();
16 | }
17 |
--------------------------------------------------------------------------------
/raft-state-machine/src/main/java/top/datadriven/raft/state/machine/StateMachine.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.state.machine;
2 |
3 | import top.datadriven.raft.facade.model.LogEntryModel;
4 |
5 | /**
6 | * @description: 状态机
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/12 11:57 下午
10 | * @version: 1.0.0
11 | */
12 | public interface StateMachine {
13 | /**
14 | * 状态机执行
15 | *
16 | * @param logEntryModel log条目
17 | */
18 | void execute(LogEntryModel logEntryModel);
19 | }
20 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/api/OperationFacade.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.api;
2 |
3 | import top.datadriven.raft.facade.model.SubmitResponse;
4 |
5 | /**
6 | * @description: 客户端的操作接口
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/15 11:10 下午
10 | * @version: 1.0.0
11 | */
12 | public interface OperationFacade {
13 | /**
14 | * 客户端提交数据请求
15 | *
16 | * @param option 操作类型
17 | * @param data 数据
18 | * @return 提交结果
19 | */
20 | SubmitResponse submitData(String option, String data);
21 | }
22 |
--------------------------------------------------------------------------------
/raft-integration/src/main/java/top/datadriven/raft/integration/consumer/DubboServiceConsumer.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.integration.consumer;
2 |
3 | import top.datadriven.raft.facade.api.RaftFacade;
4 |
5 | /**
6 | * @description: dubbo 服务消费
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/26 8:23 下午
10 | * @version: 1.0.0
11 | */
12 | public interface DubboServiceConsumer {
13 |
14 | /**
15 | * 通过server id获取对应facade
16 | *
17 | * @param serverId id
18 | * @return facade
19 | */
20 | RaftFacade getFacade(Long serverId);
21 | }
22 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/ServerStateFactory.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer;
2 |
3 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
4 |
5 | /**
6 | * @description: 状态执行器工厂
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/28 8:47 下午
10 | * @version: 1.0.0
11 | */
12 | public interface ServerStateFactory {
13 | /**
14 | * 根据状态获取执行器
15 | *
16 | * @param currentState 状态
17 | * @return 执行器
18 | */
19 | ServerStateTransformer getByType(ServerStateEnum currentState);
20 | }
21 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/service/VoteService.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.service;
2 |
3 | import top.datadriven.raft.facade.model.VoteRequest;
4 | import top.datadriven.raft.facade.model.VoteResponse;
5 |
6 | /**
7 | * @description: 接受投票服务
8 | * @author: jiayancheng
9 | * @email: jiayancheng@foxmail.com
10 | * @datetime: 2020/4/14 11:23 下午
11 | * @version: 1.0.0
12 | */
13 | public interface VoteService {
14 |
15 | /**
16 | * 发起投票
17 | *
18 | * @param voteRequest 请求
19 | * @return 投票结果
20 | */
21 | VoteResponse receiveVote(VoteRequest voteRequest);
22 | }
23 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/model/ServerStateModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: server状态数据
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 8:48 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class ServerStateModel extends BaseToString {
17 | private static final long serialVersionUID = -2537321438890981740L;
18 |
19 | private Long commitIndex;
20 | private Long lastApplied;
21 | }
22 |
--------------------------------------------------------------------------------
/raft-web/src/main/java/top/datadriven/raft/web/AppMain.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.web;
2 |
3 | import org.springframework.context.support.ClassPathXmlApplicationContext;
4 |
5 | /**
6 | * @description: 启动执行
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/28 10:05 下午
10 | * @version: 1.0.0
11 | */
12 | public class AppMain {
13 | public static void main(String[] args) {
14 | ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
15 | new String[]{"classpath*:spring/spring-content.xml"}
16 | );
17 | context.start();
18 |
19 | System.out.println("开始启动 ...");
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/service/AppendEntriesService.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.service;
2 |
3 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
4 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
5 |
6 | /**
7 | * @description: 附件日志条目服务
8 | * @author: jiayancheng
9 | * @email: jiayancheng@foxmail.com
10 | * @datetime: 2020/4/14 11:23 下午
11 | * @version: 1.0.0
12 | */
13 | public interface AppendEntriesService {
14 |
15 | /**
16 | * 发起请求:附加日志
17 | *
18 | * @param appendEntriesRequest 请求
19 | * @return 结果
20 | */
21 | AppendEntriesResponse receiveAppendEntries(AppendEntriesRequest appendEntriesRequest);
22 | }
23 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/base/BaseToString.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.base;
2 |
3 |
4 | import org.apache.commons.lang3.builder.ToStringBuilder;
5 | import org.apache.commons.lang3.builder.ToStringStyle;
6 |
7 | import java.io.Serializable;
8 |
9 | /**
10 | * @description: 基础类
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/04/14 下午9:07
14 | * @version: 1.0.0
15 | */
16 | public class BaseToString implements Serializable {
17 |
18 | private static final long serialVersionUID = -9222282455453228433L;
19 |
20 | @Override
21 | public String toString() {
22 | return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/LogEntryModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: 日志条目
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 8:47 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class LogEntryModel extends BaseToString {
17 | private static final long serialVersionUID = 7076763685587042394L;
18 |
19 | private Long index;
20 | private Long term;
21 |
22 | /**
23 | * 操作类型
24 | */
25 | private String option;
26 |
27 | /**
28 | * 操作数据
29 | */
30 | private String data;
31 | }
32 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/VoteResponse.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: 投票返回对象
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 9:21 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class VoteResponse extends BaseToString {
17 | private static final long serialVersionUID = 2860045664738196559L;
18 |
19 | public VoteResponse(Long term, Boolean voteGranted) {
20 | this.term = term;
21 | this.voteGranted = voteGranted;
22 | }
23 |
24 | private Long term;
25 | private Boolean voteGranted;
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/component/VoteComponent.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.component;
2 |
3 | import top.datadriven.raft.facade.model.VoteRequest;
4 |
5 | /**
6 | * @description: 发起投票服务
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/14 11:23 下午
10 | * @version: 1.0.0
11 | */
12 | public interface VoteComponent {
13 | /**
14 | * 广播投票
15 | * 备注:同步获取投票结果
16 | *
17 | * @return 投票结果
18 | */
19 | Boolean broadcastVote();
20 |
21 | /**
22 | * 发起投票
23 | *
24 | * @param remoteServerId 远程服务的server id
25 | * @param voteRequest 请求
26 | * @return 投票结果
27 | */
28 | Boolean requestVote(Long remoteServerId, VoteRequest voteRequest);
29 | }
30 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/SubmitResponse.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import top.datadriven.raft.facade.base.BaseToString;
7 |
8 | /**
9 | * @description: 提交数据的相应
10 | * @author: jiayancheng
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2020/5/3 3:41 下午
13 | * @version: 1.0.0
14 | */
15 | @Setter
16 | @Getter
17 | @AllArgsConstructor
18 | public class SubmitResponse extends BaseToString {
19 | private static final long serialVersionUID = -1500206787222543731L;
20 |
21 | /**
22 | * 提交是否成功
23 | */
24 | private Boolean success;
25 |
26 | /**
27 | * 提交失败时,使用该节点提交
28 | */
29 | private Long leaderId;
30 | }
31 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/resources/spring/spring-beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/raft-state-machine/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-state-machine
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-core-model
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/config/RaftNodeModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.config;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: raft 节点配置
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 9:17 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class RaftNodeModel extends BaseToString {
17 | private static final long serialVersionUID = -2921302830680312336L;
18 |
19 | /**
20 | * rpc server id
21 | */
22 | private Long serverId;
23 |
24 | /**
25 | * rpc通讯的ip
26 | */
27 | private String ip;
28 |
29 | /**
30 | * rpc通讯的端口
31 | */
32 | private Integer port;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/util/EntryUtil.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.util;
2 |
3 | import top.datadriven.raft.core.model.constant.CommonConstant;
4 | import top.datadriven.raft.facade.model.LogEntryModel;
5 |
6 | /**
7 | * @description: 条目相关工具类
8 | * @author: jiayancheng
9 | * @email: jiayancheng@foxmail.com
10 | * @datetime: 2020/4/18 7:17 下午
11 | * @version: 1.0.0
12 | */
13 | public class EntryUtil {
14 | /**
15 | * 获取初始term,即第0个entry
16 | *
17 | * @return entry
18 | */
19 | public static LogEntryModel getInitEntry() {
20 | LogEntryModel logEntryModel = new LogEntryModel();
21 | logEntryModel.setIndex(CommonConstant.INIT_INDEX);
22 | logEntryModel.setTerm(CommonConstant.INIT_TERM);
23 | return logEntryModel;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/constant/CommonConstant.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.constant;
2 |
3 | /**
4 | * @description: 常量
5 | * @author: jiayancheng
6 | * @email: jiayancheng@foxmail.com
7 | * @datetime: 2018/2/22 上午10:50
8 | * @version: 1.0.0
9 | */
10 | public class CommonConstant {
11 |
12 | /**
13 | * 通知channel 标志
14 | */
15 | public static final String CHANNEL_FLAG = "FLAG";
16 |
17 | /**
18 | * 初始term值
19 | */
20 | public static final Long INIT_TERM = 0L;
21 |
22 | /**
23 | * 初始index
24 | */
25 | public static final Long INIT_INDEX = 0L;
26 |
27 | /**
28 | * 心跳间隔时间,单位ms
29 | */
30 | public static final int HEARTBEAT_INTERVAL = 1000;
31 |
32 | /**
33 | * 数据分割标识
34 | */
35 | public static final String DATA_SPLIT_FLAG = " ";
36 |
37 | }
--------------------------------------------------------------------------------
/raft-facade/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | top.datadriven.raft
8 | raft-facade
9 | 1.0.1.20200413
10 |
11 |
12 |
13 | org.apache.commons
14 | commons-lang3
15 | 3.4
16 |
17 |
18 | org.projectlombok
19 | lombok
20 | 1.16.6
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/model/LeaderStateModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | import java.util.Map;
8 |
9 | /**
10 | * @description: leader状态数据
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/13 8:50 下午
14 | * @version: 1.0.0
15 | */
16 | @Getter
17 | @Setter
18 | public class LeaderStateModel extends BaseToString {
19 | private static final long serialVersionUID = -8118337834135107508L;
20 |
21 | /**
22 | * 需要给follower复制的下一条目的索引值(针对每一个follower)
23 | * 初始值为leader的最大index+1
24 | */
25 | private Map nextIndex;
26 |
27 | /**
28 | * 已经赋值给follower的最高索引(针对每一个follower)
29 | * 作用:当一半以上follower存在时,leader用来commit数据
30 | */
31 | private Map matchIndex;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/ServerStateFactoryImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import lombok.Setter;
4 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
5 | import top.datadriven.raft.core.service.transformer.ServerStateFactory;
6 | import top.datadriven.raft.core.service.transformer.ServerStateTransformer;
7 |
8 | import java.util.Map;
9 |
10 | /**
11 | * @description: 状态执行器 工厂
12 | * @author: jiayancheng
13 | * @email: jiayancheng@foxmail.com
14 | * @datetime: 2020/4/18 10:05 下午
15 | * @version: 1.0.0
16 | */
17 | public class ServerStateFactoryImpl implements ServerStateFactory {
18 | @Setter
19 | private Map stateMap;
20 |
21 | @Override
22 | public ServerStateTransformer getByType(ServerStateEnum currentState) {
23 | return stateMap.get(currentState);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/AppendEntriesResponse.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: 附加日志
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 9:21 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class AppendEntriesResponse extends BaseToString {
17 | private static final long serialVersionUID = 3110625658565930253L;
18 |
19 | public AppendEntriesResponse(Long term, Boolean success) {
20 | this.term = term;
21 | this.success = success;
22 | }
23 |
24 | /**
25 | * 当前term,for leader update itself
26 | */
27 | private Long term;
28 |
29 | /**
30 | * 跟随者包含了 匹配上 prevLogIndex 和 prevLogTerm 的日志时为真
31 | */
32 | private Boolean success;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/VoteRequest.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | /**
8 | * @description: 投票请求对象
9 | * @author: jiayancheng
10 | * @email: jiayancheng@foxmail.com
11 | * @datetime: 2020/4/13 9:20 下午
12 | * @version: 1.0.0
13 | */
14 | @Getter
15 | @Setter
16 | public class VoteRequest extends BaseToString {
17 | private static final long serialVersionUID = -355819569838316428L;
18 |
19 | /**
20 | * candidate 的任期号
21 | */
22 | private Long term;
23 |
24 | /**
25 | * 请求选票的candidate id
26 | */
27 | private Long candidateId;
28 |
29 | /**
30 | * candidate的最后一条 log entry 的index
31 | */
32 | private Long lastLogIndex;
33 |
34 | /**
35 | * candidate的最后一条 log entry 的term
36 | */
37 | private Long lastLogTerm;
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/api/RaftFacade.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.api;
2 |
3 |
4 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
5 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
6 | import top.datadriven.raft.facade.model.VoteRequest;
7 | import top.datadriven.raft.facade.model.VoteResponse;
8 |
9 | /**
10 | * @description: raft 门面:接受其他server的请求
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/14 11:03 下午
14 | * @version: 1.0.0
15 | */
16 | public interface RaftFacade {
17 |
18 | /**
19 | * 接受请求:投票
20 | *
21 | * @param voteRequest 请求参数
22 | * @return 结果
23 | */
24 | VoteResponse requestVote(VoteRequest voteRequest);
25 |
26 | /**
27 | * 接受请求:附加日志
28 | *
29 | * @param appendEntriesRequest 请求
30 | * @return 结果
31 | */
32 | AppendEntriesResponse appendEntries(AppendEntriesRequest appendEntriesRequest);
33 | }
34 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/ServerStateTransformerStarterImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import org.springframework.stereotype.Service;
4 | import top.datadriven.raft.core.service.transformer.ServerStateTransformer;
5 | import top.datadriven.raft.core.service.transformer.ServerStateTransformerStarter;
6 |
7 | import javax.annotation.Resource;
8 |
9 | /**
10 | * @description: 节点状态转换器启动(即角色转换状态机)
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/18 8:41 下午
14 | * @version: 1.0.0
15 | */
16 | @Service
17 | public class ServerStateTransformerStarterImpl implements ServerStateTransformerStarter {
18 |
19 | @Resource(name = "followerStateImpl")
20 | private ServerStateTransformer serverStateTransformer;
21 |
22 | @Override
23 | public void start() {
24 | // follower 为入口
25 | serverStateTransformer.execute();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/convertor/FollowerConvertor.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.convertor;
2 |
3 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
4 | import top.datadriven.raft.core.model.model.RaftCoreModel;
5 |
6 | /**
7 | * @description: follower 转换
8 | * @author: jiayancheng
9 | * @email: jiayancheng@foxmail.com
10 | * @datetime: 2020/4/21 11:16 下午
11 | * @version: 1.0.0
12 | */
13 | public class FollowerConvertor {
14 | /**
15 | * 转换为follower: 设置相关变量
16 | * 说明:加锁在调用方完成
17 | *
18 | * @param term 需要转换为的term
19 | * @param coreModel 核心对象
20 | */
21 | public static void convert2Follower(Long term,
22 | RaftCoreModel coreModel) {
23 | coreModel.setServerStateEnum(ServerStateEnum.FOLLOWER);
24 | coreModel.getPersistentState().setVotedFor(null);
25 | coreModel.getPersistentState().setCurrentTerm(term);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/component/AppendEntriesComponent.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.component;
2 |
3 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
4 |
5 | /**
6 | * @description: 附件日志条目服务
7 | * @author: jiayancheng
8 | * @email: jiayancheng@foxmail.com
9 | * @datetime: 2020/4/14 11:23 下午
10 | * @version: 1.0.0
11 | */
12 | public interface AppendEntriesComponent {
13 |
14 | /**
15 | * 广播附加日志条目(或者心跳)
16 | * 备注:不需要等待结果
17 | * *
18 | * * 一旦成为领导人:发送空的附加日志 RPC(心跳)给其他所有的服务器;在一定的空余时间之后不停的重复发送,以阻止跟随者超时(5.2 节)
19 | * * 备注:及时入参LogEntry为空也要发起rpc请求
20 | */
21 | void broadcastAppendEntries();
22 |
23 | /**
24 | * 发起请求:附加日志
25 | * *
26 | * 如果对于一个跟随者,最后日志条目的索引值大于等于 nextIndex,那么:发送从 nextIndex 开始的所有日志条目:
27 | * - 如果成功:更新相应跟随者的 nextIndex 和 matchIndex
28 | * - 如果因为日志不一致而失败,减少 nextIndex 重试
29 | *
30 | * @param serverId 请求的server的id
31 | * @param appendEntriesRequest 请求
32 | */
33 | void requestAppendEntries(Long serverId, AppendEntriesRequest appendEntriesRequest);
34 | }
35 |
--------------------------------------------------------------------------------
/raft-config-loader/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-config-loader
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-core-model
19 |
20 |
21 |
22 |
23 | org.yaml
24 | snakeyaml
25 |
26 |
27 | org.javassist
28 | javassist
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/resources/spring/spring-beans.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/raft-test/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-test
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-integration
19 |
20 |
21 | top.datadriven.raft
22 | raft-facade
23 |
24 |
25 |
26 |
27 |
28 | org.springframework
29 | spring-test
30 |
31 |
32 |
--------------------------------------------------------------------------------
/raft-web/src/main/resources/spring/spring-content.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | classpath:props/raft.properties
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/raft-integration/src/main/java/top/datadriven/raft/integration/RaftClient.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.integration;
2 |
3 |
4 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
5 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
6 | import top.datadriven.raft.facade.model.VoteRequest;
7 | import top.datadriven.raft.facade.model.VoteResponse;
8 |
9 | /**
10 | * @description: raft 发起请求
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/14 11:03 下午
14 | * @version: 1.0.0
15 | */
16 | public interface RaftClient {
17 |
18 | /**
19 | * 发起请求:投票
20 | *
21 | * @param remoteServerId 远程服务的server id
22 | * @param voteRequest 请求参数
23 | * @return 结果
24 | */
25 | VoteResponse requestVote(Long remoteServerId,
26 | VoteRequest voteRequest);
27 |
28 | /**
29 | * 发起请求:附加日志
30 | *
31 | * @param remoteServerId 远程服务的server id
32 | * @param appendEntriesRequest 请求
33 | * @return 结果
34 | */
35 | AppendEntriesResponse appendEntries(Long remoteServerId,
36 | AppendEntriesRequest appendEntriesRequest);
37 | }
38 |
--------------------------------------------------------------------------------
/raft-web/src/main/resources/props/raft.properties:
--------------------------------------------------------------------------------
1 | ## private key
2 | app.db.private.key=MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOfenMCSxKL9bZJEfEa1A5XNx79C8Tdsz9JC0oIH9YTYPD6ZW78kebUuvmQqMKP6rUvDtspZ5g3KiQcoY51IUlEmZOkg0EmsqA8A36x2uYKP+wSAPc/FkrDKL4VCDIskm2hJSGUMB5QDdcoMVBNMmP42R3FzmRQsEfU6n8rJnfEtAgMBAAECgYEAkFi+nwf/kDRS5S7rax0/SSAdTM1A269KxWvCHx8TUotHHfVc72amuguKjVLSixMAlV0Wy2wh0s4WdjVHpl+iliC17sqyJoatAhSDDRpim0xEtnb6pS0mBffb/wvPYk4anqQTDQDKhcLuo6Yp1BWY4bqZaKjs8XdVadlX2LkrskUCQQD/AC73q3ED1TCpad718Y/XUaPnlkcOhEAtPM3VnDALq6vBUCwn7fn70F+MaTGrp2k8abrqDJXKOXtOHNvyb5B3AkEA6Mc5RNc1YZpZDulkB4zS70a3WHBXVBdWLbx3YrXg3sO4picWUyu5yHhnef3BBdpgTbHYYXf/iCjSes9ofMK4ewJBAIFxomXvDWuYqR8Wsyu99/qhYsaIroFb+Qf9ua8ZnfoOpx12iTOrxh5h5F7ud1xfmzgjo9JzmQYSr9kzJSOoJnkCQE53uepm0WvRZ+wK6NlSs1hNckixtf52z2ojeesgfGkbeQcpbfEjcEEPtXH+BC9A6e3G4bYZiV4QxML5X7OOwDkCQQCsddzxJAg7h3ucfLkdF9s5qoQzgh1j3+o6gNDDGLtLFA+1Nmn1urbOoXV+BLD2SGeC+Xefl5dXihqomg1meLis
3 |
4 | # dubbo
5 | zk.url=localhost:2181
6 | #logger
7 | raft.log.home=/data/appLogs
8 | raft.log.level=DEBUG
9 | raft.log.day.count=30
10 | # cache
11 | raft.cache.spec=maximumSize=10000
12 | # task
13 | # 初次执行,延迟多少ms:30s
14 | raft.cache.task.initialdelay=30000
15 | # 间隔多少ms执行一次:1min
16 | raft.cache.task.fixedrate=60000
17 | # others
--------------------------------------------------------------------------------
/raft-web/src/main/resources/spring/spring-mvc.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/config/ConfigModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.config;
2 |
3 | import com.google.common.collect.Lists;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 | import top.datadriven.raft.facade.base.BaseToString;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * @description: 配置model
12 | * @author: jiayancheng
13 | * @email: jiayancheng@foxmail.com
14 | * @datetime: 2020/4/13 9:15 下午
15 | * @version: 1.0.0
16 | */
17 | @Getter
18 | @Setter
19 | public class ConfigModel extends BaseToString {
20 | private static final long serialVersionUID = 7825937885052180314L;
21 |
22 | private RaftNodeModel localNode;
23 | private List allNodes;
24 |
25 | /**
26 | * 获取当前server的id
27 | *
28 | * @return id
29 | */
30 | public Long getCurrentServerId() {
31 | return localNode.getServerId();
32 | }
33 |
34 | /**
35 | * 获取所有远程服务器配置
36 | */
37 | public List getRemoteNodes() {
38 | List remoteNodes = Lists.newArrayList();
39 | remoteNodes.addAll(allNodes);
40 | remoteNodes.removeIf(node -> node.getServerId().equals(localNode.getServerId()));
41 | return remoteNodes;
42 | }
43 |
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/raft-facade/src/main/java/top/datadriven/raft/facade/model/AppendEntriesRequest.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.facade.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.facade.base.BaseToString;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @description: 附加日志
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/13 9:21 下午
14 | * @version: 1.0.0
15 | */
16 | @Getter
17 | @Setter
18 | public class AppendEntriesRequest extends BaseToString {
19 | private static final long serialVersionUID = 3705288786110190835L;
20 |
21 | /**
22 | * leader任期号
23 | */
24 | private Long term;
25 |
26 | /**
27 | * leader id,so follower can redirect clients
28 | * 当client请求到follower时,用于给client返回 leader id
29 | */
30 | private Long leaderId;
31 |
32 | /**
33 | * 上条日志的 index
34 | */
35 | private Long preLogIndex;
36 |
37 | /**
38 | * 上条日志的 term
39 | */
40 | private Long preLogTerm;
41 |
42 | /**
43 | * 请求的日志条目
44 | * (empty for heartbeat; may send more than one for efficiency)
45 | */
46 | private List logEntries;
47 |
48 | /**
49 | * leader’s commitIndex
50 | */
51 | private Long leaderCommit;
52 | }
53 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/enums/OptionEnum.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.enums;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | /**
9 | * @description: 操作枚举
10 | * @author: jiayancheng
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2020/4/13 下午9:37
13 | * @version: 1.0.0
14 | */
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | public enum OptionEnum {
17 |
18 | /**
19 | * 操作
20 | */
21 | GET("GET", "获取"),
22 | ADD("ADD", "添加,存在的话则更新"),
23 | DEL("DEL", "删除"),
24 | ;
25 |
26 | /**
27 | * 枚举code
28 | */
29 | @Getter
30 | private String code;
31 |
32 | /**
33 | * 描述
34 | */
35 | @Getter
36 | private String desc;
37 |
38 | /**
39 | * 根据code获取枚举【不忽略大小写】
40 | *
41 | * @param code 枚举code
42 | * @return 枚举
43 | */
44 | public static OptionEnum getByCode(String code) {
45 | for (OptionEnum anEnum : OptionEnum.values()) {
46 | if (StringUtils.equals(anEnum.getCode(), code)) {
47 | return anEnum;
48 | }
49 | }
50 | throw new RuntimeException("code不存在");
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/raft-config-loader/src/main/java/top/datadriven/raft/config/loader/ConfigLoader.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.config.loader;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import org.springframework.stereotype.Service;
5 | import org.yaml.snakeyaml.Yaml;
6 | import top.datadriven.raft.core.model.config.ConfigModel;
7 |
8 | import java.io.BufferedReader;
9 |
10 | /**
11 | * @description: 配置加载器
12 | * @author: jiayancheng
13 | * @email: jiayancheng@foxmail.com
14 | * @datetime: 2020/4/26 12:15 上午
15 | * @version: 1.0.0
16 | */
17 | @Service
18 | public class ConfigLoader {
19 | private static final ConfigModel CONFIG_MODEL;
20 |
21 | static {
22 | BufferedReader bufferedReader = FileUtil.getReader(
23 | "server-rpc-config.yml",
24 | "UTF-8");
25 | Yaml yaml = new Yaml();
26 | CONFIG_MODEL = yaml.loadAs(bufferedReader, ConfigModel.class);
27 | }
28 |
29 | /**
30 | * 加载配置信息
31 | *
32 | * @return 配置
33 | */
34 | public static ConfigModel load() {
35 | return CONFIG_MODEL;
36 | }
37 |
38 | /**
39 | * 获取server的总数量
40 | * 逻辑:自身1个+远程个数
41 | *
42 | * @return 数量
43 | */
44 | public static Integer getServerCount() {
45 | return CONFIG_MODEL.getAllNodes().size();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/ServerStateTransformer.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer;
2 |
3 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
4 | import top.datadriven.raft.core.model.model.RaftCoreModel;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * @description: 节点状态转换器(即角色转换状态机)
10 | * @author: jiayancheng
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2020/4/14 11:18 下午
13 | * @version: 1.0.0
14 | */
15 | public interface ServerStateTransformer {
16 |
17 | /**
18 | * 开始执行
19 | * 启动后,在满足相应条件后,会自动在各个状态之间进行转换
20 | */
21 | void execute();
22 |
23 | /**
24 | * 获取当前状态
25 | *
26 | * @return 当前状态
27 | */
28 | ServerStateEnum getCurrentState();
29 |
30 | /**
31 | * 获取后续可能的状态
32 | *
33 | * @return 状态
34 | */
35 | List getNextStates();
36 |
37 | /**
38 | * 前置校验:校验通过才能进入当前状态。
39 | * 当前默认逻辑即可满足要求,后续如果需要特殊逻辑,子类覆盖即可。
40 | *
41 | * @return true:校验通过;false:校验不通过,不能进入该状态
42 | */
43 | default Boolean preCheck() {
44 | return RaftCoreModel.getSingleton().getServerStateEnum() == getCurrentState();
45 | }
46 |
47 | /**
48 | * 进入当前状态前,需要做的事情
49 | */
50 | void preDo();
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/raft-integration/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-integration
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-core-model
19 |
20 |
21 | top.datadriven.raft
22 | raft-facade
23 |
24 |
25 | top.datadriven.raft
26 | raft-config-loader
27 |
28 |
29 |
30 |
31 |
32 | com.alibaba
33 | dubbo
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/enums/ServerStateEnum.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.enums;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | /**
9 | * @description: 节点状态(角色)
10 | * @author: jiayancheng
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2020/4/13 下午9:37
13 | * @version: 1.0.0
14 | */
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | public enum ServerStateEnum {
17 |
18 | /**
19 | * 节点状态(角色)
20 | */
21 | LEADER("LEADER", "主节点"),
22 | CANDIDATE("CANDIDATE", "候选节点"),
23 | FOLLOWER("FOLLOWER", "从节点"),;
24 |
25 | /**
26 | * 枚举code
27 | */
28 | @Getter
29 | private String code;
30 |
31 | /**
32 | * 描述
33 | */
34 | @Getter
35 | private String desc;
36 |
37 | /**
38 | * 根据code获取枚举【不忽略大小写】
39 | *
40 | * @param code 枚举code
41 | * @return 枚举
42 | */
43 | public ServerStateEnum getByCode(String code) {
44 | for (ServerStateEnum anEnum : ServerStateEnum.values()) {
45 | if (StringUtils.equals(anEnum.getCode(), code)) {
46 | return anEnum;
47 | }
48 | }
49 | throw new RuntimeException("code不存在");
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/util/CommonUtil.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.util;
2 |
3 | import cn.hutool.core.util.RandomUtil;
4 | import com.google.common.collect.Lists;
5 | import top.datadriven.raft.core.model.constant.CommonConstant;
6 |
7 | import java.util.List;
8 |
9 | /**
10 | * @description: 通用工具类
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/18 10:35 下午
14 | * @version: 1.0.0
15 | */
16 | public class CommonUtil {
17 | /**
18 | * 获取start到end倍数的heartbeatInterval时间
19 | */
20 | public static int getInterval(int start, int end) {
21 | return RandomUtil.randomInt(start * CommonConstant.HEARTBEAT_INTERVAL, end * CommonConstant.HEARTBEAT_INTERVAL);
22 | }
23 |
24 | /**
25 | * 获取多数节点的梳理:超过一半
26 | */
27 | public static int getMostCount(int allServerCount) {
28 | return allServerCount / 2 + 1;
29 | }
30 |
31 | /**
32 | * 根据separatorChars进行分割str
33 | *
34 | * @param str 要分割的字符串
35 | * @param separatorChars 分割字符
36 | * @return 分割结果
37 | */
38 | public static List split(String str, String separatorChars) {
39 | int index = str.indexOf(separatorChars);
40 | return Lists.newArrayList(
41 | str.substring(0, index),
42 | str.substring(index + 1)
43 | );
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/AbstractServerStateTransformer.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
4 | import top.datadriven.raft.core.service.transformer.ServerStateFactory;
5 | import top.datadriven.raft.core.service.transformer.ServerStateTransformer;
6 |
7 | import javax.annotation.Resource;
8 |
9 | /**
10 | * @description: 抽象类
11 | * @author: jiayancheng
12 | * @email: jiayancheng@foxmail.com
13 | * @datetime: 2020/4/28 8:43 下午
14 | * @version: 1.0.0
15 | */
16 | public abstract class AbstractServerStateTransformer implements ServerStateTransformer {
17 | @Resource
18 | private ServerStateFactory serverStateFactory;
19 |
20 | /**
21 | * 依次获取下一个状态,如果满足前置校验,则进入下一个状态
22 | */
23 | public void executeNext() {
24 | for (ServerStateEnum nextState : getNextStates()) {
25 | ServerStateTransformer nextTransformer = serverStateFactory.getByType(nextState);
26 | //如果满足前置校验,则进入下一个状态
27 | if (nextTransformer.preCheck()) {
28 | //进入状态前的准备工作
29 | nextTransformer.preDo();
30 | //执行下一状态
31 | nextTransformer.execute();
32 | //只执行第一个匹配到的,理论上会在下一个状态实现中进行后续的跳转,后续state不会再执行;此处break只做标识使用
33 | break;
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/java/top/datadriven/raft/biz/service/impl/api/impl/RaftFacadeImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.biz.service.impl.api.impl;
2 |
3 | import org.springframework.stereotype.Component;
4 | import top.datadriven.raft.core.service.service.AppendEntriesService;
5 | import top.datadriven.raft.core.service.service.VoteService;
6 | import top.datadriven.raft.facade.api.RaftFacade;
7 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
8 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
9 | import top.datadriven.raft.facade.model.VoteRequest;
10 | import top.datadriven.raft.facade.model.VoteResponse;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * @description: raft facade
16 | * @author: jiayancheng
17 | * @email: jiayancheng@foxmail.com
18 | * @datetime: 2020/4/22 9:01 下午
19 | * @version: 1.0.0
20 | */
21 | @Component
22 | public class RaftFacadeImpl implements RaftFacade {
23 |
24 | @Resource
25 | private VoteService voteService;
26 |
27 | @Resource
28 | private AppendEntriesService appendEntriesService;
29 |
30 | @Override
31 | public VoteResponse requestVote(VoteRequest voteRequest) {
32 | return voteService.receiveVote(voteRequest);
33 | }
34 |
35 | @Override
36 | public AppendEntriesResponse appendEntries(AppendEntriesRequest appendEntriesRequest) {
37 | return appendEntriesService.receiveAppendEntries(appendEntriesRequest);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/exception/ErrorCodeEnum.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.exception;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Getter;
6 | import org.apache.commons.lang3.StringUtils;
7 |
8 | /**
9 | * @description: 错误码枚举
10 | * @author: jiayancheng
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2018/3/9 上午9:55
13 | * @version: 1.0.0
14 | */
15 | @AllArgsConstructor(access = AccessLevel.PRIVATE)
16 | public enum ErrorCodeEnum {
17 |
18 | /**
19 | * 系统异常
20 | */
21 | SYSTEM_ERROR("SYSTEM_ERROR", "系统异常"),
22 |
23 | NOT_SUPPORT_TYPE("NOT_SUPPORT_TYPE", "不支持的类型"),
24 |
25 | PARAM_ERROR("PARAM_ERROR", "参数异常"),
26 |
27 | CHANNEL_ERROR("CHANNEL_ERROR", "channel通知异常"),
28 |
29 | DATA_NOT_EXIT("DATA_NOT_EXIT", "数据不存在"),
30 |
31 | ;
32 |
33 | /**
34 | * 枚举code
35 | */
36 | @Getter
37 | private final String code;
38 |
39 | /**
40 | * 枚举说明
41 | */
42 | @Getter
43 | private final String desc;
44 |
45 | /**
46 | * 根据code获取枚举【不忽略大小写】
47 | *
48 | * @param code 枚举code
49 | * @return 枚举
50 | */
51 | public ErrorCodeEnum getByCode(String code) {
52 | for (ErrorCodeEnum anEnum : ErrorCodeEnum.values()) {
53 | if (StringUtils.equals(anEnum.getCode(), code)) {
54 | return anEnum;
55 | }
56 | }
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-biz-service-impl
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-facade
19 |
20 |
21 | top.datadriven.raft
22 | raft-core-service
23 |
24 |
25 |
26 |
27 |
28 | com.github.sgroschupf
29 | zkclient
30 |
31 |
32 | com.alibaba
33 | dubbo
34 |
35 |
36 | io.netty
37 | netty
38 |
39 |
40 | org.javassist
41 | javassist
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/raft-core-service/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-core-service
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-integration
19 |
20 |
21 | top.datadriven.raft
22 | raft-config-loader
23 |
24 |
25 | top.datadriven.raft
26 | raft-core-model
27 |
28 |
29 | top.datadriven.raft
30 | raft-state-machine
31 |
32 |
33 |
34 |
35 | org.springframework
36 | spring-context
37 |
38 |
39 | org.springframework
40 | spring-core
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/java/top/datadriven/raft/biz/service/impl/component/RaftStarter.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.biz.service.impl.component;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.stereotype.Component;
5 | import top.datadriven.raft.biz.service.impl.provider.DubboServiceRegister;
6 | import top.datadriven.raft.core.model.exception.RaftException;
7 | import top.datadriven.raft.core.service.handler.StateMachineHandler;
8 | import top.datadriven.raft.core.service.transformer.ServerStateTransformerStarter;
9 |
10 | import javax.annotation.PostConstruct;
11 | import javax.annotation.Resource;
12 |
13 | /**
14 | * @description: raft 启动器
15 | * @author: jiayancheng
16 | * @email: jiayancheng@foxmail.com
17 | * @datetime: 2020/4/18 5:36 下午
18 | * @version: 1.0.0
19 | */
20 | @Slf4j
21 | @Component
22 | public class RaftStarter {
23 |
24 | @Resource
25 | private ServerStateTransformerStarter serverStateTransformerStarter;
26 |
27 | @Resource
28 | private StateMachineHandler stateMachineHandler;
29 |
30 | @Resource
31 | private DubboServiceRegister dubboServiceRegister;
32 |
33 | /**
34 | * 这里的启动有先后顺序
35 | */
36 | @PostConstruct
37 | public void start() {
38 | try {
39 | //1. 启动dubbo服务
40 | dubboServiceRegister.registry();
41 |
42 | //2. 启动server 状态流转
43 | serverStateTransformerStarter.start();
44 |
45 | //3. 启动状态机
46 | stateMachineHandler.commit2Apply();
47 | } catch (RaftException raftException) {
48 | log.error(raftException.getErrorMsg(), raftException);
49 | } catch (Throwable t) {
50 | log.error(t.getMessage(), t);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/raft-web/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | spring-mvc
31 | org.springframework.web.servlet.DispatcherServlet
32 |
33 | contextConfigLocation
34 | classpath*:spring/spring-content.xml
35 |
36 | 0
37 |
38 |
39 | spring-mvc
40 | /
41 |
42 |
--------------------------------------------------------------------------------
/raft-web/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | ### set log levels ###
2 | #log4j.rootLogger = INFO , stdout , D , E
3 | log4j.rootLogger = INFO , stdout
4 |
5 | ### output to the console ###
6 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender
7 | log4j.appender.stdout.Target = System.out
8 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
9 | #log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n
10 | log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n
11 |
12 | ### Output to the log file ###
13 | log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
14 | #log4j.appender.D.File = ${mytest_one.root}/WEB-INF/logs/error.log
15 | log4j.appender.D.Append = true
16 | log4j.appender.D.Threshold = ERROR
17 | log4j.appender.D.layout = org.apache.log4j.PatternLayout
18 | log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
19 | log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender
20 | log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd
21 | #log4j.appender.ServerDailyRollingFile.File=${mytest_one.root}/WEB-INF/logs/error.log
22 | log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout
23 | log4j.appender.ServerDailyRollingFile.layout.ConversionPattern= %-d{yyyy-MM-dd HH\:mm\:ss} [ %t\:%r ] - [ %p ] %m%n
24 | log4j.appender.ServerDailyRollingFile.Append=true
25 |
26 |
27 | log4j.logger.com.ibatis=INFO
28 | log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=INFO
29 | log4j.logger.com.ibatis.common.jdbc.ScriptRunner=INFO
30 | log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=INFO
31 | log4j.logger.org.mybatis=INFO
32 | log4j.logger.java.sql.Connection=INFO
33 | log4j.logger.java.sql.Statement=INFO
34 | log4j.logger.java.sql.PreparedStatement=INFO,stdout
35 | com.ng.mapper=INFO
36 |
--------------------------------------------------------------------------------
/raft-integration/src/main/java/top/datadriven/raft/integration/impl/RaftClientImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.integration.impl;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.stereotype.Service;
5 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
6 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
7 | import top.datadriven.raft.facade.model.VoteRequest;
8 | import top.datadriven.raft.facade.model.VoteResponse;
9 | import top.datadriven.raft.integration.RaftClient;
10 | import top.datadriven.raft.integration.consumer.DubboServiceConsumer;
11 |
12 | import javax.annotation.Resource;
13 |
14 | /**
15 | * @description: raft 发起请求
16 | * @author: jiayancheng
17 | * @email: jiayancheng@foxmail.com
18 | * @datetime: 2020/4/26 8:18 下午
19 | * @version: 1.0.0
20 | */
21 | @Slf4j
22 | @Service
23 | public class RaftClientImpl implements RaftClient {
24 | @Resource
25 | private DubboServiceConsumer dubboServiceConsumer;
26 |
27 | @Override
28 | public VoteResponse requestVote(Long remoteServerId,
29 | VoteRequest voteRequest) {
30 | try {
31 | return dubboServiceConsumer
32 | .getFacade(remoteServerId)
33 | .requestVote(voteRequest);
34 | } catch (Throwable t) {
35 | log.error("投票失败", t);
36 | return new VoteResponse(0L, Boolean.FALSE);
37 | }
38 | }
39 |
40 | @Override
41 | public AppendEntriesResponse appendEntries(Long remoteServerId,
42 | AppendEntriesRequest appendEntriesRequest) {
43 | try {
44 | return dubboServiceConsumer
45 | .getFacade(remoteServerId)
46 | .appendEntries(appendEntriesRequest);
47 | } catch (Throwable t) {
48 | log.error("附加日志失败", t);
49 | return new AppendEntriesResponse(0L, Boolean.FALSE);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/pool/RaftThreadPool.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.pool;
2 |
3 | import com.google.common.util.concurrent.ListenableFuture;
4 | import com.google.common.util.concurrent.ListeningExecutorService;
5 | import com.google.common.util.concurrent.MoreExecutors;
6 | import com.google.common.util.concurrent.ThreadFactoryBuilder;
7 |
8 | import java.util.concurrent.*;
9 |
10 | /**
11 | * @description:
12 | * @author: jiayancheng
13 | * @email: jiayancheng@foxmail.com
14 | * @datetime: 2020/4/19 5:50 下午
15 | * @version: 1.0.0
16 | */
17 | public class RaftThreadPool {
18 | /**
19 | * 线程工厂,提供创建新线程的功能。
20 | */
21 | private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
22 | .setNameFormat("raft-pool-%d").build();
23 |
24 | /**
25 | * 线程池
26 | */
27 | private static final ExecutorService executorService = new ThreadPoolExecutor(
28 | // 核心线程数,默认情况下核心线程会一直存活
29 | 8,
30 | //线程池所能容纳的最大线程数。
31 | 12,
32 | //非核心线程的闲置超时时间,超过这个时间就会被回收。
33 | 10,
34 | TimeUnit.SECONDS,
35 | //线程池中的任务队列.(超过核心线程的数量,被放到这里)
36 | new LinkedBlockingDeque<>(1024),
37 | //线程工厂,提供创建新线程的功能。
38 | THREAD_FACTORY,
39 | //当达到最大线程数,且队列已满情况下,执行拒绝策略:抛异常
40 | new ThreadPoolExecutor.AbortPolicy());
41 |
42 | /**
43 | * 定义一个线程池,用于处理所有任务
44 | */
45 | final static ListeningExecutorService listeningExecutorService
46 | = MoreExecutors.listeningDecorator(executorService);
47 |
48 | /**
49 | * 执行任务
50 | */
51 | public static void execute(Runnable runnable) {
52 | executorService.execute(runnable);
53 | }
54 |
55 | /**
56 | * 执行任务
57 | */
58 | public static ListenableFuture execute(Callable callable) {
59 | return listeningExecutorService.submit(callable);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/raft-state-machine/src/main/java/top/datadriven/raft/state/machine/impl/KvStateMachineImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.state.machine.impl;
2 |
3 | import com.google.common.collect.Maps;
4 | import lombok.extern.log4j.Log4j;
5 | import org.springframework.stereotype.Service;
6 | import top.datadriven.raft.core.model.constant.CommonConstant;
7 | import top.datadriven.raft.core.model.enums.OptionEnum;
8 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
9 | import top.datadriven.raft.core.model.exception.RaftException;
10 | import top.datadriven.raft.core.model.util.CommonUtil;
11 | import top.datadriven.raft.facade.model.LogEntryModel;
12 | import top.datadriven.raft.state.machine.StateMachine;
13 |
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * @description: key-value 的状态机实现
19 | * @author: jiayancheng
20 | * @email: jiayancheng@foxmail.com
21 | * @datetime: 2020/4/26 9:24 下午
22 | * @version: 1.0.0
23 | */
24 | @Log4j
25 | @Service
26 | public class KvStateMachineImpl implements StateMachine {
27 |
28 | private final Map kvMap = Maps.newHashMap();
29 |
30 | @Override
31 | public void execute(LogEntryModel logEntryModel) {
32 | OptionEnum optionEnum = OptionEnum.getByCode(logEntryModel.getOption());
33 | // PUT 操作时,有key和value两个值
34 | List kvData = CommonUtil.split(logEntryModel.getData(), CommonConstant.DATA_SPLIT_FLAG);
35 | switch (optionEnum) {
36 | case GET:
37 | log.info("GET option :" + kvData);
38 | break;
39 | case ADD:
40 | log.info("ADD option :" + kvData);
41 | kvMap.put(kvData.get(0), kvData.get(1));
42 | break;
43 | case DEL:
44 | log.info("DEL option :" + kvData);
45 | kvMap.remove(kvData.get(0));
46 | break;
47 | default:
48 | throw new RaftException(ErrorCodeEnum.NOT_SUPPORT_TYPE, "不支持的类型:" + optionEnum);
49 | }
50 |
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/handler/impl/StateMachineHandlerImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.handler.impl;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.stereotype.Service;
5 | import top.datadriven.raft.core.model.model.RaftCoreModel;
6 | import top.datadriven.raft.core.model.model.ServerStateModel;
7 | import top.datadriven.raft.core.service.handler.StateMachineHandler;
8 | import top.datadriven.raft.facade.model.LogEntryModel;
9 | import top.datadriven.raft.state.machine.StateMachine;
10 |
11 | import javax.annotation.Resource;
12 | import java.util.List;
13 | import java.util.concurrent.locks.Lock;
14 |
15 | /**
16 | * @description: 状态机控制器
17 | * @author: jiayancheng
18 | * @email: jiayancheng@foxmail.com
19 | * @datetime: 2020/4/20 11:09 下午
20 | * @version: 1.0.0
21 | */
22 | @Slf4j
23 | @Service
24 | public class StateMachineHandlerImpl implements StateMachineHandler {
25 | @Resource
26 | private StateMachine stateMachine;
27 |
28 | @Override
29 | public void commit2Apply() {
30 |
31 | while (!Thread.currentThread().isInterrupted()) {
32 | Lock lock = RaftCoreModel.getLock();
33 | lock.lock();
34 | try {
35 | //0.数据准备
36 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
37 | List entries = coreModel.getPersistentState().getLogEntries();
38 | ServerStateModel serverState = coreModel.getServerState();
39 |
40 | //1.阻塞等待 take:若队列为空,发生阻塞,等待有元素。
41 | coreModel.getCommitChannel().take();
42 |
43 | //2.接到通知后,apply 到状态机:将logs[lastApplied+1, commitIndex] apply
44 | for (long i = serverState.getLastApplied() + 1; i <= serverState.getCommitIndex(); i++) {
45 | stateMachine.execute(entries.get((int) i));
46 | serverState.setLastApplied(serverState.getLastApplied() + 1);
47 | }
48 | } catch (Exception e) {
49 | log.error("commit2Apply error", e);
50 | } finally {
51 | lock.unlock();
52 | }
53 | }
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/model/PersistentStateModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import top.datadriven.raft.core.model.constant.CommonConstant;
6 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
7 | import top.datadriven.raft.core.model.exception.RaftException;
8 | import top.datadriven.raft.facade.base.BaseToString;
9 | import top.datadriven.raft.facade.model.LogEntryModel;
10 |
11 | import java.util.List;
12 |
13 | /**
14 | * @description: 持久化的状态数据
15 | * @author: jiayancheng
16 | * @email: jiayancheng@foxmail.com
17 | * @datetime: 2020/4/13 8:44 下午
18 | * @version: 1.0.0
19 | */
20 | @Getter
21 | @Setter
22 | public class PersistentStateModel extends BaseToString {
23 | private static final long serialVersionUID = 614565993931190984L;
24 |
25 | private Long currentTerm;
26 |
27 | /**
28 | * 在 currentTerm 获得选票的serverId。如果没有投票则为null
29 | * 改变 votedFor 的两种情况:
30 | * * 一是 Follower/Candidate 超时变为 Candidate 时,term 会增加 1,这时候先无脑投自己(rf.votedFor = rf.me),然后发起选举;
31 | * * 二是在收到其他 Peer 的 RPC 时(包括 Request 和 Reply),发现别人 term 高,变为 Follower 时,也需要及时清空自己之前投票结果(rf.votedFor = null)以使本轮次可以继续投票。
32 | */
33 | private Long votedFor;
34 |
35 | private List logEntries;
36 |
37 | /*==============================辅助函数=============================*/
38 |
39 | /**
40 | * 获取最后一条写入的entry
41 | */
42 | public LogEntryModel getLastEntry() {
43 | return logEntries.get(logEntries.size() - 1);
44 | }
45 |
46 |
47 | /**
48 | * 获取倒数第二条写入的entry
49 | */
50 | public LogEntryModel getPreEntry() {
51 | //只有1条记录(初始记录)时,直接返回该记录
52 | if (logEntries.size() == 1
53 | && logEntries.get(0).getIndex().equals(CommonConstant.INIT_INDEX)) {
54 | return logEntries.get(0);
55 | }
56 | return logEntries.get(logEntries.size() - 2);
57 | }
58 |
59 | /**
60 | * 根据index获取term,不存在则抛异常
61 | */
62 | public Long getTermByIndex(Long index) {
63 | if (getLastEntry().getIndex() > index) {
64 | throw new RaftException(ErrorCodeEnum.DATA_NOT_EXIT, "index过大,当前server不存在");
65 | }
66 | return logEntries.get(Math.toIntExact(index)).getTerm();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/java/top/datadriven/raft/biz/service/impl/api/impl/OperationFacadeImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.biz.service.impl.api.impl;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.springframework.stereotype.Service;
5 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
6 | import top.datadriven.raft.core.model.model.PersistentStateModel;
7 | import top.datadriven.raft.core.model.model.RaftCoreModel;
8 | import top.datadriven.raft.facade.api.OperationFacade;
9 | import top.datadriven.raft.facade.model.LogEntryModel;
10 | import top.datadriven.raft.facade.model.SubmitResponse;
11 |
12 | import java.util.List;
13 | import java.util.concurrent.locks.Lock;
14 |
15 | /**
16 | * @description: 客户端的操作接口
17 | * @author: jiayancheng
18 | * @email: jiayancheng@foxmail.com
19 | * @datetime: 2020/5/3 3:22 下午
20 | * @version: 1.0.0
21 | */
22 | @Slf4j
23 | @Service
24 | public class OperationFacadeImpl implements OperationFacade {
25 | @Override
26 | public SubmitResponse submitData(String option, String data) {
27 | Lock lock = RaftCoreModel.getLock();
28 | lock.lock();
29 | try {
30 | //0.数据准备
31 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
32 | PersistentStateModel persistentState = coreModel.getPersistentState();
33 | List entries = persistentState.getLogEntries();
34 |
35 | //1.为leader时,则附加日志
36 | if (coreModel.getServerStateEnum() == ServerStateEnum.LEADER) {
37 | LogEntryModel logEntry = new LogEntryModel();
38 | logEntry.setData(data);
39 | logEntry.setOption(option);
40 | logEntry.setTerm(persistentState.getCurrentTerm());
41 | logEntry.setIndex(persistentState.getLastEntry().getIndex() + 1);
42 | entries.add(logEntry);
43 | return new SubmitResponse(Boolean.TRUE, null);
44 | }
45 | //2.非leader时,返回失败
46 | else {
47 | return new SubmitResponse(Boolean.FALSE, coreModel.getLeaderId());
48 | }
49 | } catch (Exception e) {
50 | log.error("submitData error", e);
51 | return new SubmitResponse(Boolean.FALSE, null);
52 | } finally {
53 | lock.unlock();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/FollowerStateImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import com.google.common.collect.Lists;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.springframework.stereotype.Service;
7 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
8 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
9 | import top.datadriven.raft.core.model.exception.RaftException;
10 | import top.datadriven.raft.core.model.model.RaftCoreModel;
11 | import top.datadriven.raft.core.model.util.CommonUtil;
12 |
13 | import java.util.List;
14 | import java.util.concurrent.TimeUnit;
15 |
16 | /**
17 | * @description: follower状态
18 | * @author: jiayancheng
19 | * @email: jiayancheng@foxmail.com
20 | * @datetime: 2020/4/15 11:36 下午
21 | * @version: 1.0.0
22 | */
23 | @Slf4j
24 | @Service(value = "followerStateImpl")
25 | public class FollowerStateImpl extends AbstractServerStateTransformer {
26 |
27 | @Override
28 | public void execute() {
29 | while (!Thread.currentThread().isInterrupted()) {
30 | log.info("当前为Follower:" + RaftCoreModel.getSingleton());
31 | String channelFlag;
32 | try {
33 | //1.每过6~10个心跳时间获取一次心跳标志
34 | channelFlag = RaftCoreModel.getSingleton()
35 | .getHeartbeatChannel()
36 | .poll(CommonUtil.getInterval(6, 10), TimeUnit.MILLISECONDS);
37 | } catch (InterruptedException e) {
38 | throw new RaftException(ErrorCodeEnum.CHANNEL_ERROR, "定时获取channelFlag异常");
39 | }
40 |
41 | //2. 判断通知结果
42 | //如果心跳超时(结果为空),进行下一个状态;否则继续循环
43 | if (StringUtils.isBlank(channelFlag)) {
44 | RaftCoreModel.getSingleton().setServerStateEnum(ServerStateEnum.CANDIDATE);
45 | }
46 |
47 | //3.执行后续节点
48 | executeNext();
49 | }
50 | }
51 |
52 | @Override
53 | public void preDo() {
54 | // do nothing
55 | }
56 |
57 | @Override
58 | public List getNextStates() {
59 | return Lists.newArrayList(
60 | ServerStateEnum.CANDIDATE
61 | );
62 | }
63 |
64 | @Override
65 | public ServerStateEnum getCurrentState() {
66 | return ServerStateEnum.FOLLOWER;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/exception/RaftException.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.exception;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 |
6 | /**
7 | * @description: 决策异常
8 | * @author: jiayancheng
9 | * @email: jiayancheng@foxmail.com
10 | * @datetime: 2018/3/9 上午10:01
11 | * @version: 1.0.0
12 | */
13 | @Getter
14 | @Setter
15 | public class RaftException extends RuntimeException {
16 |
17 | private static final long serialVersionUID = 6100413904443364600L;
18 |
19 | /**
20 | * 错误码枚举
21 | */
22 | private ErrorCodeEnum errorCodeEnum;
23 |
24 | /**
25 | * 详细错误信息
26 | */
27 | private String detailErrorMsg;
28 |
29 |
30 | /**
31 | * 原始的异常信息
32 | */
33 | private Throwable originalThrowable;
34 |
35 |
36 | public RaftException(ErrorCodeEnum errorCodeEnum) {
37 | super(errorCodeEnum.getCode());
38 | this.errorCodeEnum = errorCodeEnum;
39 | }
40 |
41 | public RaftException(ErrorCodeEnum errorCodeEnum, String detailErrorMsg) {
42 | super(errorCodeEnum.getCode());
43 | this.errorCodeEnum = errorCodeEnum;
44 | this.detailErrorMsg = detailErrorMsg;
45 | }
46 |
47 | public RaftException(ErrorCodeEnum errorCodeEnum, Throwable originalThrowable) {
48 | super(errorCodeEnum.getCode(), originalThrowable);
49 | this.errorCodeEnum = errorCodeEnum;
50 | this.originalThrowable = originalThrowable;
51 | }
52 |
53 | public RaftException(ErrorCodeEnum errorCodeEnum, String detailErrorMsg, Throwable originalThrowable) {
54 | super(errorCodeEnum.getCode(), originalThrowable);
55 | this.errorCodeEnum = errorCodeEnum;
56 | this.detailErrorMsg = detailErrorMsg;
57 | this.originalThrowable = originalThrowable;
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | StringBuilder sb = new StringBuilder();
63 | sb.append(getClass().getName()).append(", ");
64 | sb.append(this.errorCodeEnum.getCode()).append(", ");
65 | sb.append(this.errorCodeEnum.getDesc()).append(", ");
66 | sb.append(this.detailErrorMsg);
67 | return String.valueOf(sb);
68 | }
69 |
70 | public String getErrorMsg() {
71 | StringBuilder sb = new StringBuilder();
72 | sb.append(this.errorCodeEnum.getDesc()).append(", ");
73 | sb.append(this.detailErrorMsg);
74 | return String.valueOf(sb);
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/util/SpringContextUtil.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.util;
2 | import org.springframework.beans.BeansException;
3 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
4 | import org.springframework.context.ApplicationContext;
5 | import org.springframework.context.ApplicationContextAware;
6 | import org.springframework.stereotype.Component;
7 |
8 | /**
9 | * @description: 获取spring容器
10 | * @author: jiayancheng && zhangchi
11 | * @email: jiayancheng@foxmail.com
12 | * @datetime: 2018/2/23 下午7:19
13 | * @version: 1.0.0
14 | */
15 | @Component
16 | public class SpringContextUtil implements ApplicationContextAware {
17 | /**
18 | * Spring应用上下文环境
19 | */
20 | private static ApplicationContext applicationContext;
21 |
22 | /**
23 | * 实现ApplicationContextAware接口的回调方法,设置上下文环境
24 | *
25 | * @param applicationContext 重写ApplicationContextAware中的setApplicationContext
26 | */
27 | @Override
28 | public void setApplicationContext(ApplicationContext applicationContext) {
29 | SpringContextUtil.setApp(applicationContext);
30 | }
31 |
32 | /**
33 | * @return ApplicationContext
34 | */
35 | public static ApplicationContext getApplicationContext() {
36 | return applicationContext;
37 | }
38 |
39 | /**
40 | * 为了规避findbugs的Write to static field from instance method潜在bug,
41 | * setApplicationContext实例化SpringContextUtil通过该方法为静态变量applicationContext赋值。
42 | *
43 | * @param applicationContext 上下文
44 | */
45 | public static void setApp(ApplicationContext applicationContext) {
46 | SpringContextUtil.applicationContext = applicationContext;
47 | }
48 |
49 | /**
50 | * 根据类名获取类对象
51 | * @param name 类名
52 | * @param 对象
53 | * @return 返回对象
54 | * @throws BeansException 抛出获取类对象异常
55 | */
56 | public static T getBean(String name) throws BeansException {
57 | return (T) applicationContext.getBean(name);
58 | }
59 |
60 | /**
61 | * 根据class对象获取bean
62 | *
63 | * @param clazz class信息
64 | * @param 对象
65 | * @return 返回类对象
66 | */
67 | public static T getBeanOfType(Class clazz) {
68 | return (T) applicationContext.getBeansOfType(clazz);
69 | }
70 |
71 | /**
72 | * 返回依赖注入的对象工程
73 | *
74 | * @return AutowireCapableBeanFactory
75 | */
76 | public static AutowireCapableBeanFactory getAutowire() {
77 | return applicationContext.getAutowireCapableBeanFactory();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/CandidateStateImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import com.google.common.collect.Lists;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.stereotype.Service;
6 | import top.datadriven.raft.config.loader.ConfigLoader;
7 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
8 | import top.datadriven.raft.core.model.model.PersistentStateModel;
9 | import top.datadriven.raft.core.model.model.RaftCoreModel;
10 | import top.datadriven.raft.core.service.component.VoteComponent;
11 |
12 | import javax.annotation.Resource;
13 | import java.util.List;
14 | import java.util.concurrent.locks.Lock;
15 |
16 | /**
17 | * @description: candidate状态
18 | * @author: jiayancheng
19 | * @email: jiayancheng@foxmail.com
20 | * @datetime: 2020/4/15 11:36 下午
21 | * @version: 1.0.0
22 | */
23 | @Slf4j
24 | @Service(value = "candidateStateImpl")
25 | public class CandidateStateImpl extends AbstractServerStateTransformer {
26 |
27 | @Resource
28 | private VoteComponent voteComponent;
29 |
30 | @Override
31 | public void execute() {
32 | log.info("当前为 Candidate:" + RaftCoreModel.getSingleton());
33 | Lock lock = RaftCoreModel.getLock();
34 | lock.lock();
35 | try {
36 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
37 | PersistentStateModel persistentState = coreModel.getPersistentState();
38 |
39 | //1.给自己投票:投一票、term+1
40 | persistentState.setVotedFor(ConfigLoader.load().getCurrentServerId());
41 | persistentState.setCurrentTerm(persistentState.getCurrentTerm() + 1);
42 | coreModel.setVoteCount(1L);
43 |
44 | //2.candidate发起投票(广播):使用CountDownLatch实现
45 | Boolean voteResult = voteComponent.broadcastVote();
46 |
47 | //3.根据投票结果进行设置
48 | if (voteResult) {
49 | coreModel.setServerStateEnum(ServerStateEnum.LEADER);
50 | }
51 | } finally {
52 | lock.unlock();
53 | }
54 |
55 | //4.执行后续节点
56 | executeNext();
57 |
58 | }
59 |
60 | @Override
61 | public void preDo() {
62 | // do nothing
63 | }
64 |
65 | @Override
66 | public List getNextStates() {
67 | return Lists.newArrayList(
68 | ServerStateEnum.CANDIDATE,
69 | ServerStateEnum.LEADER,
70 | ServerStateEnum.FOLLOWER
71 | );
72 | }
73 |
74 | @Override
75 | public ServerStateEnum getCurrentState() {
76 | return ServerStateEnum.CANDIDATE;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/raft-integration/src/main/java/top/datadriven/raft/integration/consumer/impl/DubboServiceConsumerImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.integration.consumer.impl;
2 |
3 | import com.alibaba.dubbo.config.ApplicationConfig;
4 | import com.alibaba.dubbo.config.ReferenceConfig;
5 | import com.google.common.collect.Maps;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.stereotype.Service;
8 | import top.datadriven.raft.config.loader.ConfigLoader;
9 | import top.datadriven.raft.core.model.config.RaftNodeModel;
10 | import top.datadriven.raft.facade.api.RaftFacade;
11 | import top.datadriven.raft.integration.consumer.DubboServiceConsumer;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * @description: dubbo 服务消费
19 | * @author: jiayancheng
20 | * @email: jiayancheng@foxmail.com
21 | * @datetime: 2020/4/26 8:23 下午
22 | * @version: 1.0.0
23 | */
24 | @Slf4j
25 | @Service
26 | public class DubboServiceConsumerImpl implements DubboServiceConsumer {
27 |
28 | private final Map raftFacadeMap = Maps.newHashMap();
29 |
30 | @PostConstruct
31 | public void init() {
32 | log.info("开始加载远程facade服务..");
33 |
34 | try {
35 | //0.获取远程节点配置
36 | List remoteNodes = ConfigLoader.load().getRemoteNodes();
37 |
38 | for (RaftNodeModel remoteNode : remoteNodes) {
39 | //1.当前应用配置【dubbo】
40 | ApplicationConfig application = new ApplicationConfig();
41 | application.setName("simple-raft-consumer");
42 |
43 | //2.引用远程服务
44 | // ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
45 | ReferenceConfig reference = new ReferenceConfig<>();
46 | reference.setApplication(application);
47 | reference.setInterface(RaftFacade.class);
48 | reference.setUrl("dubbo://" + remoteNode.getIp() + ":" + remoteNode.getPort());
49 |
50 |
51 | //3.和本地bean一样使用xxxService
52 | // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
53 | RaftFacade raftFacade = reference.get();
54 | raftFacadeMap.put(remoteNode.getServerId(), raftFacade);
55 | }
56 | log.info("加载完成远程facade服务:" + remoteNodes.toString());
57 | } catch (Throwable t) {
58 | log.error("加载远程facade服务失败", t);
59 | }
60 | }
61 |
62 | @Override
63 | public RaftFacade getFacade(Long serverId) {
64 | if (raftFacadeMap.get(serverId) == null) {
65 | init();
66 | }
67 | return raftFacadeMap.get(serverId);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/util/AssertUtil.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.util;
2 |
3 | import com.alibaba.fastjson.util.TypeUtils;
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.springframework.util.CollectionUtils;
6 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
7 | import top.datadriven.raft.core.model.exception.RaftException;
8 |
9 | import java.util.List;
10 | import java.util.Objects;
11 |
12 | /**
13 | * @description: 工具类
14 | * @author: jiayancheng
15 | * @email: jiayancheng@foxmail.com
16 | * @datetime: 2018/2/9 下午2:47
17 | * @version: 1.0.0
18 | */
19 | public class AssertUtil {
20 | /**
21 | * str 不能为空
22 | *
23 | * @param str 校验参数
24 | * @param errorDesc 异常描述
25 | */
26 | public static void notBlank(String str, String errorDesc) {
27 | if (StringUtils.isBlank(str)) {
28 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
29 | }
30 | }
31 |
32 | /**
33 | * str 必须能为空
34 | *
35 | * @param str 校验参数
36 | * @param errorDesc 异常描述
37 | */
38 | public static void mustBeBlank(String str, String errorDesc) {
39 | if (StringUtils.isNotBlank(str)) {
40 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
41 | }
42 | }
43 |
44 | /**
45 | * list 不能为空
46 | *
47 | * @param list 校验参数
48 | * @param errorDesc 异常描述
49 | */
50 | public static void notEmpty(List list, String errorDesc) {
51 | if (CollectionUtils.isEmpty(list)) {
52 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
53 | }
54 | }
55 |
56 | /**
57 | * list 必须能为空
58 | *
59 | * @param list 校验参数
60 | * @param errorDesc 异常描述
61 | */
62 | public static void mustBeEmpty(List list, String errorDesc) {
63 | if (!CollectionUtils.isEmpty(list)) {
64 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
65 | }
66 | }
67 |
68 | /**
69 | * 不能为空
70 | *
71 | * @param ob 校验参数
72 | * @param errorDesc 异常描述
73 | */
74 | public static void notNull(Object ob, String errorDesc) {
75 | if (Objects.isNull(ob)) {
76 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
77 | }
78 | }
79 |
80 | /**
81 | * 不能为false
82 | *
83 | * @param ob 校验参数
84 | * @param errorDesc 异常描述
85 | */
86 | public static void assertTrue(Object ob, String errorDesc) {
87 | if (!TypeUtils.castToBoolean(ob)) {
88 | throw new RaftException(ErrorCodeEnum.PARAM_ERROR, errorDesc);
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/raft-biz-service-impl/src/main/java/top/datadriven/raft/biz/service/impl/provider/impl/DubboServiceRegisterImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.biz.service.impl.provider.impl;
2 |
3 | import com.alibaba.dubbo.config.ApplicationConfig;
4 | import com.alibaba.dubbo.config.ProtocolConfig;
5 | import com.alibaba.dubbo.config.RegistryConfig;
6 | import com.alibaba.dubbo.config.ServiceConfig;
7 | import lombok.extern.log4j.Log4j;
8 | import org.springframework.stereotype.Component;
9 | import top.datadriven.raft.biz.service.impl.provider.DubboServiceRegister;
10 | import top.datadriven.raft.config.loader.ConfigLoader;
11 | import top.datadriven.raft.core.model.config.RaftNodeModel;
12 | import top.datadriven.raft.facade.api.OperationFacade;
13 | import top.datadriven.raft.facade.api.RaftFacade;
14 |
15 | import javax.annotation.Resource;
16 |
17 | /**
18 | * @description: 注册当前server的dubbo服务
19 | * @author: jiayancheng
20 | * @email: jiayancheng@foxmail.com
21 | * @datetime: 2020/4/25 11:28 下午
22 | * @version: 1.0.0
23 | */
24 | @Log4j
25 | @Component
26 | public class DubboServiceRegisterImpl implements DubboServiceRegister {
27 |
28 | @Resource
29 | private RaftFacade raftFacade;
30 |
31 | @Resource
32 | private OperationFacade operationFacade;
33 |
34 | @Override
35 | public void registry() {
36 | log.info("RaftFacade的dubbo服务:开始注册...");
37 | registryByName(raftFacade, RaftFacade.class);
38 | registryByName(operationFacade, OperationFacade.class);
39 | log.info("RaftFacade的dubbo服务:完成注册。");
40 | }
41 |
42 | private void registryByName(T t, Class> interfaceClass) {
43 | //1. 获取当前server配置
44 | RaftNodeModel currentServerConfig = ConfigLoader.load().getLocalNode();
45 |
46 | //2.当前应用配置
47 | ApplicationConfig application = new ApplicationConfig();
48 | application.setName("simple-raft-provider");
49 |
50 | //3.服务提供者协议配置
51 | ProtocolConfig protocol = new ProtocolConfig();
52 | protocol.setName("dubbo");
53 | protocol.setPort(currentServerConfig.getPort());
54 |
55 | //4.不使用注册中心
56 | RegistryConfig registry = new RegistryConfig();
57 | registry.setRegister(Boolean.FALSE);
58 |
59 | //5.服务提供者暴露服务配置
60 | // 注意:ServiceConfig为重对象,内部封装了与注册中心的连接,以及开启服务端口
61 | // 此实例很重,封装了与注册中心的连接,请自行缓存,否则可能造成内存和连接泄漏
62 | ServiceConfig service = new ServiceConfig<>();
63 | service.setApplication(application);
64 | // 多个协议可以用setProtocols()
65 | service.setProtocol(protocol);
66 | service.setInterface(interfaceClass);
67 | service.setRef(t);
68 | service.setRegistry(registry);
69 |
70 | //6.暴露及注册服务
71 | service.export();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/raft-common-util/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-common-util
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | org.projectlombok
21 | lombok
22 |
23 |
24 |
25 |
26 | org.apache.commons
27 | commons-lang3
28 |
29 |
30 |
31 |
32 | com.google.guava
33 | guava
34 |
35 |
36 |
37 |
38 | com.alibaba
39 | fastjson
40 |
41 |
42 |
43 |
44 | org.springframework
45 | spring-context
46 |
47 |
48 | org.springframework
49 | spring-core
50 |
51 |
52 | org.springframework
53 | spring-beans
54 |
55 |
56 | org.springframework
57 | spring-jdbc
58 |
59 |
60 | org.springframework
61 | spring-aop
62 |
63 |
64 | org.springframework
65 | spring-webmvc
66 |
67 |
68 |
69 |
70 | org.aspectj
71 | aspectjweaver
72 |
73 |
74 |
75 |
76 | junit
77 | junit
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/raft-web/src/main/java/top/datadriven/raft/web/OptionFacadeTest.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.web;
2 |
3 | import com.alibaba.dubbo.config.ApplicationConfig;
4 | import com.alibaba.dubbo.config.ReferenceConfig;
5 | import com.google.common.collect.Maps;
6 | import lombok.extern.slf4j.Slf4j;
7 | import top.datadriven.raft.config.loader.ConfigLoader;
8 | import top.datadriven.raft.core.model.config.RaftNodeModel;
9 | import top.datadriven.raft.core.model.enums.OptionEnum;
10 | import top.datadriven.raft.facade.api.OperationFacade;
11 | import top.datadriven.raft.facade.model.SubmitResponse;
12 |
13 | import java.util.List;
14 | import java.util.Map;
15 |
16 | /**
17 | * @description: 费者启动类 参考:http://dubbo.apache.org/zh-cn/docs/user/configuration/api.html
18 | * @author: jiayancheng
19 | * @email: jiayancheng@foxmail.com
20 | * @datetime: 2020/4/25 3:59 下午
21 | * @version: 1.0.0
22 | */
23 | @Slf4j
24 | public class OptionFacadeTest {
25 | private final Map operationFacadeMap = Maps.newHashMap();
26 |
27 | public static void main(String[] args) {
28 | String data = "key1 value1";
29 | OptionFacadeTest optionFacade = new OptionFacadeTest();
30 | for (RaftNodeModel node : ConfigLoader.load().getAllNodes()) {
31 | OperationFacade operationFacade = optionFacade.getFacade(node.getServerId());
32 | SubmitResponse ret = operationFacade.submitData(OptionEnum.ADD.getCode(), data);
33 | if (!ret.getSuccess() && ret.getLeaderId() != null) {
34 | OperationFacade operationFacadeLeader = optionFacade.getFacade(node.getServerId());
35 | SubmitResponse retLeader = operationFacadeLeader.submitData(OptionEnum.ADD.getCode(), data);
36 | if (retLeader.getSuccess()) {
37 | break;
38 | }
39 | }
40 |
41 | }
42 | }
43 |
44 |
45 | public void init() {
46 | log.info("开始加载远程facade服务..");
47 |
48 | try {
49 | //0.获取远程节点配置
50 | List allNodes = ConfigLoader.load().getAllNodes();
51 |
52 | for (RaftNodeModel node : allNodes) {
53 | //1.当前应用配置【dubbo】
54 | ApplicationConfig application = new ApplicationConfig();
55 | application.setName("simple-raft-submit-consumer");
56 |
57 | //2.引用远程服务
58 | // ReferenceConfig实例很重,封装了与注册中心的连接以及与提供者的连接,请自行缓存,否则可能造成内存和连接泄漏
59 | ReferenceConfig reference = new ReferenceConfig<>();
60 | reference.setApplication(application);
61 | reference.setInterface(OperationFacade.class);
62 | reference.setUrl("dubbo://" + node.getIp() + ":" + node.getPort());
63 |
64 |
65 | //3.和本地bean一样使用xxxService
66 | // 注意:此代理对象内部封装了所有通讯细节,对象较重,请缓存复用
67 | OperationFacade operationFacade = reference.get();
68 | operationFacadeMap.put(node.getServerId(), operationFacade);
69 | }
70 | log.info("加载完成远程facade服务:" + allNodes.toString());
71 | } catch (Throwable t) {
72 | log.error("加载远程facade服务失败", t);
73 | }
74 | }
75 |
76 | public OperationFacade getFacade(Long serverId) {
77 | if (operationFacadeMap.get(serverId) == null) {
78 | init();
79 | }
80 | return operationFacadeMap.get(serverId);
81 | }
82 | }
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/transformer/impl/LeaderStateImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.transformer.impl;
2 |
3 | import com.google.common.collect.Lists;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.stereotype.Service;
6 | import top.datadriven.raft.config.loader.ConfigLoader;
7 | import top.datadriven.raft.core.model.config.ConfigModel;
8 | import top.datadriven.raft.core.model.config.RaftNodeModel;
9 | import top.datadriven.raft.core.model.constant.CommonConstant;
10 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
11 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
12 | import top.datadriven.raft.core.model.exception.RaftException;
13 | import top.datadriven.raft.core.model.model.LeaderStateModel;
14 | import top.datadriven.raft.core.model.model.RaftCoreModel;
15 | import top.datadriven.raft.core.service.component.AppendEntriesComponent;
16 |
17 | import javax.annotation.Resource;
18 | import java.util.List;
19 | import java.util.concurrent.locks.Lock;
20 |
21 | /**
22 | * @description: leader 状态
23 | * @author: jiayancheng
24 | * @email: jiayancheng@foxmail.com
25 | * @datetime: 2020/4/15 11:35 下午
26 | * @version: 1.0.0
27 | */
28 | @Slf4j
29 | @Service(value = "leaderStateImpl")
30 | public class LeaderStateImpl extends AbstractServerStateTransformer {
31 |
32 | @Resource
33 | private AppendEntriesComponent appendEntriesComponent;
34 |
35 | /**
36 | * 每隔heartbeat时间,发送一次广播
37 | * 备注1:如果接收到的 RPC 请求或响应中,任期号T > currentTerm,那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)
38 | * ------发生在:vote、 appendEntries的四个过程中
39 | */
40 | @Override
41 | public void execute() {
42 | while (!Thread.currentThread().isInterrupted()) {
43 | log.info("当前为 Leader:" + RaftCoreModel.getSingleton());
44 |
45 | //1.广播appendEntries或者心跳(entry为空)
46 | appendEntriesComponent.broadcastAppendEntries();
47 |
48 | //2.休眠 heartbeat
49 | try {
50 | //noinspection BusyWait
51 | Thread.sleep(CommonConstant.HEARTBEAT_INTERVAL);
52 | } catch (InterruptedException e) {
53 | throw new RaftException(ErrorCodeEnum.SYSTEM_ERROR, "sleep异常");
54 | }
55 |
56 | //3.执行后续节点
57 | executeNext();
58 | }
59 | }
60 |
61 | /**
62 | * 变成leader前,需要设置nextIndex和matchIndex
63 | */
64 | @Override
65 | public void preDo() {
66 | Lock lock = RaftCoreModel.getLock();
67 | lock.lock();
68 | try {
69 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
70 | LeaderStateModel leaderState = coreModel.getLeaderState();
71 |
72 | ConfigModel config = ConfigLoader.load();
73 | for (RaftNodeModel node : config.getAllNodes()) {
74 | //对于每一个服务器,需要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加一)
75 | leaderState.getNextIndex().put(node.getServerId(),
76 | coreModel.getPersistentState().getLastEntry().getIndex() + 1);
77 | leaderState.getMatchIndex().put(node.getServerId(), 0L);
78 | }
79 | } finally {
80 | lock.unlock();
81 | }
82 | }
83 |
84 | @Override
85 | public List getNextStates() {
86 | return Lists.newArrayList(
87 | ServerStateEnum.FOLLOWER
88 | );
89 | }
90 |
91 | @Override
92 | public ServerStateEnum getCurrentState() {
93 | return ServerStateEnum.LEADER;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/raft-core-model/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | top.datadriven.raft
7 | raft-simple
8 | 1.0-SNAPSHOT
9 |
10 | 4.0.0
11 |
12 | raft-core-model
13 |
14 |
15 |
16 |
17 | top.datadriven.raft
18 | raft-facade
19 |
20 |
21 |
22 |
23 |
24 | org.projectlombok
25 | lombok
26 |
27 |
28 | org.apache.commons
29 | commons-lang3
30 |
31 |
32 | com.google.guava
33 | guava
34 |
35 |
36 |
37 | com.alibaba
38 | fastjson
39 |
40 |
41 |
42 | org.springframework
43 | spring-context
44 |
45 |
46 | org.springframework
47 | spring-core
48 |
49 |
50 | org.springframework
51 | spring-beans
52 |
53 |
54 | org.springframework
55 | spring-jdbc
56 |
57 |
58 | org.springframework
59 | spring-aop
60 |
61 |
62 | org.springframework
63 | spring-webmvc
64 |
65 |
66 | cn.hutool
67 | hutool-all
68 |
69 |
70 |
71 |
72 | org.slf4j
73 | slf4j-api
74 |
75 |
76 | org.slf4j
77 | log4j-over-slf4j
78 |
79 |
80 | ch.qos.logback
81 | logback-classic
82 |
83 |
84 | ch.qos.logback
85 | logback-core
86 |
87 |
88 | org.logback-extensions
89 | logback-ext-spring
90 |
91 |
92 | org.slf4j
93 | jcl-over-slf4j
94 |
95 |
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/raft-web/src/main/resources/spring/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ${log_pattern}
17 | ${log_charset}
18 |
19 |
20 |
21 |
22 |
23 |
24 | ERROR
25 |
26 | ${log_path}/raft_error.log
27 | true
28 |
29 | ${log_pattern}
30 | ${log_charset}
31 |
32 |
33 | ${log_path}/raft_error.%d{yyyy-MM-dd}.%i.log.zip
34 |
35 | ${log_max_file_size}
36 |
37 |
38 |
39 |
40 |
41 | ${log_path}/raft_app.log
42 | true
43 |
44 | ${log_pattern}
45 | ${log_charset}
46 |
47 |
48 | ${log_path}/raft_app.%d{yyyy-MM-dd}.%i.log.zip
49 | ${raft.log.day.count}
50 |
51 | ${log_max_file_size}
52 |
53 |
54 |
55 |
56 |
57 |
58 | 0
59 | 1000
60 |
61 | true
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/service/impl/VoteServiceImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.service.impl;
2 |
3 | import org.springframework.stereotype.Service;
4 | import top.datadriven.raft.core.model.model.PersistentStateModel;
5 | import top.datadriven.raft.core.model.model.RaftCoreModel;
6 | import top.datadriven.raft.core.service.service.VoteService;
7 | import top.datadriven.raft.core.service.transformer.convertor.FollowerConvertor;
8 | import top.datadriven.raft.facade.model.LogEntryModel;
9 | import top.datadriven.raft.facade.model.VoteRequest;
10 | import top.datadriven.raft.facade.model.VoteResponse;
11 |
12 | import java.util.concurrent.locks.Lock;
13 |
14 | /**
15 | * @description: 接受投票服务
16 | * @author: jiayancheng
17 | * @email: jiayancheng@foxmail.com
18 | * @datetime: 2020/4/19 3:38 下午
19 | * @version: 1.0. 0
20 | */
21 | @Service
22 | public class VoteServiceImpl implements VoteService {
23 | @Override
24 | public VoteResponse receiveVote(VoteRequest voteRequest) {
25 | Lock lock = RaftCoreModel.getLock();
26 | lock.lock();
27 |
28 | try {
29 | //0.数据准备
30 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
31 | PersistentStateModel persistentState = coreModel.getPersistentState();
32 | Long term = voteRequest.getTerm();
33 | Long currentTerm = persistentState.getCurrentTerm();
34 |
35 | //1. 如果term < currentTerm返回 false (5.2 节)
36 | if (term < currentTerm) {
37 | return new VoteResponse(currentTerm, Boolean.FALSE);
38 | }
39 |
40 | //2.如果接收到的 RPC 请求或响应中,任期号T > currentTerm,
41 | // 那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)
42 | if (term > currentTerm) {
43 | FollowerConvertor.convert2Follower(term, coreModel);
44 | }
45 |
46 | //3. 如果 votedFor 为空或者为 candidateId,并且候选人的日志
47 | // 至少和自己一样新,那么就投票给他(5.2 节,5.4 节)
48 | if (upToDate(voteRequest, persistentState)
49 | && notVoteOther(voteRequest, persistentState)) {
50 | //设置leaderId
51 | coreModel.setLeaderId(voteRequest.getCandidateId());
52 | return new VoteResponse(currentTerm, Boolean.TRUE);
53 | } else {
54 | return new VoteResponse(currentTerm, Boolean.FALSE);
55 | }
56 |
57 | } finally {
58 | lock.unlock();
59 | }
60 | }
61 |
62 | /**
63 | * votedFor 为空或者为 candidateId(还没投票给其他节点),则为true
64 | * 逻辑:voteFor为空或者已经投票给请求的candidate了
65 | */
66 | private boolean notVoteOther(VoteRequest voteRequest,
67 | PersistentStateModel persistentState) {
68 | return persistentState.getVotedFor() == null
69 | || persistentState.getVotedFor().equals(voteRequest.getCandidateId());
70 | }
71 |
72 | /**
73 | * Raft 通过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。
74 | * 比较逻辑:如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。
75 | * ********如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。
76 | *
77 | * @return ret 候选人的日志至少和自己一样新,则为true;否则为false
78 | */
79 | private boolean upToDate(VoteRequest voteRequest,
80 | PersistentStateModel persistentState) {
81 | //数据准备
82 | LogEntryModel lastLogEntry = persistentState.getLastEntry();
83 | Long requestLastLogTerm = voteRequest.getLastLogTerm();
84 | Long requestLastLogIndex = voteRequest.getLastLogIndex();
85 | Long currentLastLogTerm = lastLogEntry.getTerm();
86 | Long currentLastLogIndex = lastLogEntry.getIndex();
87 |
88 | //逻辑判断
89 | //如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。
90 | if (!currentLastLogTerm.equals(requestLastLogTerm)) {
91 | return requestLastLogTerm > currentLastLogTerm;
92 | }
93 | //如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。
94 | return requestLastLogIndex >= currentLastLogIndex;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/raft-core-model/src/main/java/top/datadriven/raft/core/model/model/RaftCoreModel.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.model.model;
2 |
3 | import com.google.common.collect.Lists;
4 | import com.google.common.collect.Maps;
5 | import lombok.Getter;
6 | import lombok.Setter;
7 | import top.datadriven.raft.core.model.constant.CommonConstant;
8 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
9 | import top.datadriven.raft.core.model.util.EntryUtil;
10 | import top.datadriven.raft.facade.base.BaseToString;
11 |
12 | import java.util.concurrent.LinkedBlockingQueue;
13 | import java.util.concurrent.locks.Condition;
14 | import java.util.concurrent.locks.Lock;
15 | import java.util.concurrent.locks.ReentrantLock;
16 |
17 | /**
18 | * @description: raft 核心类
19 | * @author: jiayancheng
20 | * @email: jiayancheng@foxmail.com
21 | * @datetime: 2020/4/13 8:23 下午
22 | * @version: 1.0.0
23 | */
24 | @Getter
25 | @Setter
26 | public class RaftCoreModel extends BaseToString {
27 | private static final long serialVersionUID = -4367174548580870292L;
28 |
29 | /**
30 | * 饿汉模式单例
31 | */
32 | private static RaftCoreModel raftCoreModel = new RaftCoreModel();
33 |
34 | /**
35 | * 构造函数,单例,不允许外部实例化。
36 | * 构造函数中,初始化数据。
37 | */
38 | private RaftCoreModel() {
39 | serverStateEnum = ServerStateEnum.FOLLOWER;
40 |
41 | // persistent state
42 | persistentState = new PersistentStateModel();
43 | persistentState.setCurrentTerm(CommonConstant.INIT_TERM);
44 | //第一个entry初始化为0
45 | persistentState.setLogEntries(Lists.newArrayList(EntryUtil.getInitEntry()));
46 |
47 | // volatile state (server)
48 | serverState = new ServerStateModel();
49 | serverState.setCommitIndex(0L);
50 | serverState.setLastApplied(0L);
51 |
52 | //volatile state (leader)
53 | leaderState = new LeaderStateModel();
54 | leaderState.setMatchIndex(Maps.newHashMap());
55 | leaderState.setNextIndex(Maps.newHashMap());
56 | }
57 |
58 | /**
59 | * 当前server的状态
60 | */
61 | private ServerStateEnum serverStateEnum;
62 |
63 | /**
64 | * 持久化数据
65 | */
66 | private PersistentStateModel persistentState;
67 |
68 | /**
69 | * server状态数据
70 | */
71 | private ServerStateModel serverState;
72 |
73 | /**
74 | * leader专有的状态数据
75 | */
76 | private LeaderStateModel leaderState;
77 |
78 | /**
79 | * candidate数据:candidate获得的投票数
80 | */
81 | private Long voteCount;
82 |
83 | /**
84 | * 当前的leaderId:针对非Leader节点
85 | */
86 | private Long leaderId;
87 |
88 |
89 | /*==============================异步通知channel=============================*/
90 | /**
91 | * 心跳超时控制:用来判断Follower态是否心跳超时
92 | * 实现方式:长度为1的阻塞队列,follower定时从里面取出标志数据。当收到投票请求或者appendEntries请求时,放入1次标志位。
93 | * follower时:放入速度>取出速度。
94 | * queue方法使用:https://blog.csdn.net/z69183787/article/details/46986823
95 | */
96 | private LinkedBlockingQueue heartbeatChannel = new LinkedBlockingQueue<>(1);
97 |
98 | /**
99 | * 用于commit entry 通知
100 | */
101 | private LinkedBlockingQueue commitChannel = new LinkedBlockingQueue<>(1);
102 |
103 | /*==============================锁=============================*/
104 | /**
105 | * 共用一把锁
106 | * 说明:这里为了实现简单,不再使用细粒度的锁。当然,后面可以优化为使用更细粒度的锁。
107 | */
108 | private static Lock lock = new ReentrantLock();
109 |
110 | public static Condition condition = lock.newCondition();
111 |
112 |
113 |
114 | /*================================辅助函数=================================*/
115 |
116 | /**
117 | * 获取单例对象
118 | */
119 | public static RaftCoreModel getSingleton() {
120 | return raftCoreModel;
121 | }
122 |
123 | /**
124 | * 共用一把锁
125 | * 说明:这里为了实现简单,不再使用细粒度的锁。当然,后面可以优化为使用更细粒度的锁。
126 | */
127 | public static Lock getLock() {
128 | return lock;
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/service/impl/AppendEntriesServiceImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.service.impl;
2 |
3 | import cn.hutool.core.util.NumberUtil;
4 | import org.springframework.stereotype.Service;
5 | import top.datadriven.raft.core.model.constant.CommonConstant;
6 | import top.datadriven.raft.core.model.model.PersistentStateModel;
7 | import top.datadriven.raft.core.model.model.RaftCoreModel;
8 | import top.datadriven.raft.core.model.model.ServerStateModel;
9 | import top.datadriven.raft.core.service.service.AppendEntriesService;
10 | import top.datadriven.raft.core.service.transformer.convertor.FollowerConvertor;
11 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
12 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
13 | import top.datadriven.raft.facade.model.LogEntryModel;
14 |
15 | import java.util.concurrent.locks.Lock;
16 |
17 | /**
18 | * @description: 接收附件日志条目服务
19 | * @author: jiayancheng
20 | * @email: jiayancheng@foxmail.com
21 | * @datetime: 2020/4/20 7:54 下午
22 | * @version: 1.0.0
23 | */
24 | @Service
25 | public class AppendEntriesServiceImpl implements AppendEntriesService {
26 | @Override
27 | public AppendEntriesResponse receiveAppendEntries(AppendEntriesRequest request) {
28 | Lock lock = RaftCoreModel.getLock();
29 | lock.lock();
30 | try {
31 | //0.数据准备
32 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
33 | PersistentStateModel persistentState = coreModel.getPersistentState();
34 | ServerStateModel serverState = coreModel.getServerState();
35 | Long term = request.getTerm();
36 | Long currentTerm = persistentState.getCurrentTerm();
37 | LogEntryModel lastEntry = persistentState.getLastEntry();
38 |
39 | //1.如果 leader.term < server.currentTerm 就返回 false (5.1 节)
40 | if (term < currentTerm) {
41 | return new AppendEntriesResponse(currentTerm, Boolean.FALSE);
42 | }
43 |
44 | //2.放入心跳标识
45 | coreModel.getHeartbeatChannel().offer(CommonConstant.CHANNEL_FLAG);
46 |
47 | //3.如果接收到的 RPC 请求或响应中,任期号term > currentTerm,那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)
48 | // 如图7的文字描述的(f)
49 | if (term > currentTerm) {
50 | FollowerConvertor.convert2Follower(term, coreModel);
51 | }
52 |
53 | //4.日志太新(leader的上一条日志大于当前server的最新日志index,实际应该等于):告诉leader更新index
54 | // 如图7的(b)(e)
55 | if (request.getPreLogIndex() > lastEntry.getIndex()) {
56 | return new AppendEntriesResponse(currentTerm, Boolean.FALSE);
57 | }
58 |
59 | //5.如果当前server日志在 prevLogIndex 位置处的日志条目的任期号logTerm和 prevLogTerm 不匹配,则返回 false(5.3 节)
60 | //第4步保证了当前server日志在 prevLogIndex 位置处的日志条目非空
61 | // 如图7的(e)(f)
62 | if (!persistentState.getTermByIndex(request.getPreLogIndex())
63 | .equals(request.getPreLogTerm())) {
64 | return new AppendEntriesResponse(currentTerm, Boolean.FALSE);
65 | }
66 |
67 | //6.如果已经存在的日志条目和新的产生冲突(索引值相同但是任期号不同),删除这一条和之后所有的 (5.3 节)
68 | //如图7的(f)
69 | persistentState.getLogEntries()
70 | .removeIf(entry -> entry.getIndex() > request.getPreLogIndex());
71 |
72 | //7.如果新条目在日志中不存在,则添加
73 | // 如图7的(a)(b)
74 | if (!request.getLogEntries().isEmpty()) {
75 | persistentState.getLogEntries().addAll(request.getLogEntries());
76 | }
77 |
78 | //8.如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个
79 | // 如图7的(b)的第二次appendEntry
80 | if (request.getLeaderCommit() > serverState.getCommitIndex()) {
81 | serverState.setCommitIndex(NumberUtil.min(request.getLeaderCommit(), lastEntry.getIndex()));
82 | }
83 |
84 | //9.通知状态机的日志更新状态流转,commit -->apply (第8步相当于是通过设置commitIndex,commit了数据)
85 | coreModel.getCommitChannel().offer(CommonConstant.CHANNEL_FLAG);
86 |
87 | return new AppendEntriesResponse(currentTerm, Boolean.TRUE);
88 | } finally {
89 | lock.unlock();
90 | }
91 |
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/raft-web/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | top.datadriven.raft
5 | raft-simple
6 | 1.0-SNAPSHOT
7 |
8 | 4.0.0
9 | raft-web
10 | war
11 | raft-web Maven Webapp
12 | http://maven.apache.org
13 |
14 |
15 |
16 | top.datadriven.raft
17 | raft-biz-service-impl
18 |
19 |
20 |
21 |
22 |
23 | javax.servlet
24 | javax.servlet-api
25 |
26 |
27 |
28 |
29 | org.springframework
30 | spring-context
31 |
32 |
33 | org.springframework
34 | spring-aop
35 |
36 |
37 | org.springframework
38 | spring-tx
39 |
40 |
41 | org.springframework
42 | spring-context-support
43 |
44 |
45 | org.springframework
46 | spring-jdbc
47 |
48 |
49 | org.springframework
50 | spring-web
51 |
52 |
53 | org.springframework
54 | spring-webmvc
55 |
56 |
57 | org.springframework
58 | spring-aspects
59 |
60 |
61 | org.springframework
62 | spring-beans
63 |
64 |
65 | org.springframework
66 | spring-core
67 |
68 |
69 | org.springframework
70 | spring-jms
71 |
72 |
73 | org.springframework
74 | spring-expression
75 |
76 |
77 | org.springframework
78 | spring-instrument
79 |
80 |
81 | org.springframework
82 | spring-orm
83 |
84 |
85 | org.springframework
86 | spring-oxm
87 |
88 |
89 | org.springframework
90 | spring-test
91 |
92 |
93 | org.springframework.amqp
94 | spring-rabbit
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | org.eclipse.jetty
105 | jetty-maven-plugin
106 | 9.4.5.v20170502
107 |
108 |
109 | 8809
110 |
111 | 10
112 |
113 | /
114 |
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-war-plugin
121 | 2.6
122 |
123 |
124 | ${basedir}/src/main/webapp
125 |
126 | src/main/webapp/WEB-INF/web.xml
127 | raft-simple
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/component/impl/VoteComponentImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.component.impl;
2 |
3 | import com.google.common.util.concurrent.FutureCallback;
4 | import com.google.common.util.concurrent.Futures;
5 | import com.google.common.util.concurrent.ListenableFuture;
6 | import com.google.common.util.concurrent.MoreExecutors;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.checkerframework.checker.nullness.compatqual.NullableDecl;
9 | import org.springframework.stereotype.Component;
10 | import top.datadriven.raft.config.loader.ConfigLoader;
11 | import top.datadriven.raft.core.model.config.ConfigModel;
12 | import top.datadriven.raft.core.model.config.RaftNodeModel;
13 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
14 | import top.datadriven.raft.core.model.exception.ErrorCodeEnum;
15 | import top.datadriven.raft.core.model.exception.RaftException;
16 | import top.datadriven.raft.core.model.model.PersistentStateModel;
17 | import top.datadriven.raft.core.model.model.RaftCoreModel;
18 | import top.datadriven.raft.core.model.util.CommonUtil;
19 | import top.datadriven.raft.core.service.component.VoteComponent;
20 | import top.datadriven.raft.core.service.pool.RaftThreadPool;
21 | import top.datadriven.raft.core.service.transformer.convertor.FollowerConvertor;
22 | import top.datadriven.raft.facade.model.LogEntryModel;
23 | import top.datadriven.raft.facade.model.VoteRequest;
24 | import top.datadriven.raft.facade.model.VoteResponse;
25 | import top.datadriven.raft.integration.RaftClient;
26 |
27 | import javax.annotation.Resource;
28 | import java.util.concurrent.CountDownLatch;
29 | import java.util.concurrent.TimeUnit;
30 | import java.util.concurrent.locks.Lock;
31 |
32 | /**
33 | * @description: 投票
34 | * @author: jiayancheng
35 | * @email: jiayancheng@foxmail.com
36 | * @datetime: 2020/4/19 3:27 下午
37 | * @version: 1.0.0
38 | */
39 | @Slf4j
40 | @Component
41 | public class VoteComponentImpl implements VoteComponent {
42 | @Resource
43 | private RaftClient raftClient;
44 |
45 |
46 | @Override
47 | public Boolean broadcastVote() {
48 | Lock lock = RaftCoreModel.getLock();
49 | VoteRequest voteRequest = new VoteRequest();
50 | ConfigModel configModel = ConfigLoader.load();
51 | lock.lock();
52 | try {
53 | //0.数据准备
54 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
55 | PersistentStateModel persistentState = coreModel.getPersistentState();
56 | LogEntryModel lastLogEntry = persistentState.getLastEntry();
57 |
58 | //1. 组装入参
59 | voteRequest.setTerm(persistentState.getCurrentTerm());
60 | voteRequest.setCandidateId(configModel.getCurrentServerId());
61 | voteRequest.setLastLogIndex(lastLogEntry.getIndex());
62 | voteRequest.setLastLogTerm(lastLogEntry.getTerm());
63 | } finally {
64 | lock.unlock();
65 | }
66 |
67 | //2.线程池发起请求
68 | // return multiThreadMode(voteRequest, configModel);
69 | return singleThreadMode(voteRequest, configModel);
70 | }
71 |
72 | /**
73 | * 多线程模式执行
74 | */
75 | private Boolean multiThreadMode(VoteRequest voteRequest, ConfigModel configModel) {
76 | //1.一半以上节点成功,通过countDownLatch来获取
77 | CountDownLatch countDownLatch = new CountDownLatch(CommonUtil.getMostCount(ConfigLoader.getServerCount()));
78 | for (RaftNodeModel remoteNode : configModel.getRemoteNodes()) {
79 |
80 | //2. 请求一台服务
81 | ListenableFuture listenableFuture = RaftThreadPool
82 | .execute(() -> requestVote(remoteNode.getServerId(), voteRequest)
83 | );
84 |
85 | //3.增加回调方法。
86 | //noinspection UnstableApiUsage
87 | Futures.addCallback(listenableFuture, new FutureCallback() {
88 | @Override
89 | public void onSuccess(@NullableDecl Boolean result) {
90 | //如果执行成功则减一
91 | if (result != null && result) {
92 | countDownLatch.countDown();
93 | }
94 | }
95 |
96 | @Override
97 | public void onFailure(@SuppressWarnings("NullableProblems") Throwable throwable) {
98 | log.warn("投票异常", throwable);
99 | }
100 | // MoreExecutors.directExecutor()返回guava默认的Executor
101 | }, MoreExecutors.directExecutor());
102 | }
103 |
104 | //4.等待1s,获取执行结果。全部执行完成(一半以上server),则为true
105 | try {
106 | return countDownLatch.await(1, TimeUnit.SECONDS);
107 | } catch (InterruptedException e) {
108 | throw new RaftException(ErrorCodeEnum.SYSTEM_ERROR, "countDownLatch await error");
109 | }
110 | }
111 |
112 | /**
113 | * 单线程模式执行:方便调试
114 | */
115 | private Boolean singleThreadMode(VoteRequest voteRequest, ConfigModel configModel) {
116 | int successCount = 1;
117 | for (RaftNodeModel remoteNode : configModel.getRemoteNodes()) {
118 | Boolean response = requestVote(remoteNode.getServerId(), voteRequest);
119 | if (response) {
120 | successCount++;
121 | }
122 | if (successCount >= CommonUtil.getMostCount(ConfigLoader.getServerCount())) {
123 | return Boolean.TRUE;
124 | }
125 | }
126 | return Boolean.FALSE;
127 | }
128 |
129 | @Override
130 | public Boolean requestVote(Long remoteServerId, VoteRequest voteRequest) {
131 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
132 | PersistentStateModel persistentState = coreModel.getPersistentState();
133 | Long currentTerm = persistentState.getCurrentTerm();
134 |
135 | //1.发起请求
136 | VoteResponse response = raftClient.requestVote(remoteServerId, voteRequest);
137 |
138 | //2.状态发生变化或者term发生变化,则不作处理。(发送请求的过程过了一段时间,所以需要重新判断一下)
139 | if (coreModel.getServerStateEnum() != ServerStateEnum.CANDIDATE
140 | || !voteRequest.getTerm().equals(currentTerm)) {
141 | return Boolean.FALSE;
142 | }
143 |
144 | //3.如果response的term大于currentTerm,则转换为follower
145 | if (response.getTerm() > currentTerm) {
146 | FollowerConvertor.convert2Follower(response.getTerm(), coreModel);
147 | return Boolean.FALSE;
148 | }
149 | //4.返回投票结果
150 | else {
151 | return response.getVoteGranted();
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/raft-core-service/src/main/java/top/datadriven/raft/core/service/component/impl/AppendEntriesComponentImpl.java:
--------------------------------------------------------------------------------
1 | package top.datadriven.raft.core.service.component.impl;
2 |
3 | import cn.hutool.core.collection.CollectionUtil;
4 | import com.google.common.collect.Lists;
5 | import org.springframework.stereotype.Component;
6 | import top.datadriven.raft.config.loader.ConfigLoader;
7 | import top.datadriven.raft.core.model.config.ConfigModel;
8 | import top.datadriven.raft.core.model.config.RaftNodeModel;
9 | import top.datadriven.raft.core.model.constant.CommonConstant;
10 | import top.datadriven.raft.core.model.enums.ServerStateEnum;
11 | import top.datadriven.raft.core.model.model.LeaderStateModel;
12 | import top.datadriven.raft.core.model.model.PersistentStateModel;
13 | import top.datadriven.raft.core.model.model.RaftCoreModel;
14 | import top.datadriven.raft.core.model.model.ServerStateModel;
15 | import top.datadriven.raft.core.model.util.CommonUtil;
16 | import top.datadriven.raft.core.service.component.AppendEntriesComponent;
17 | import top.datadriven.raft.core.service.pool.RaftThreadPool;
18 | import top.datadriven.raft.core.service.transformer.convertor.FollowerConvertor;
19 | import top.datadriven.raft.facade.model.AppendEntriesRequest;
20 | import top.datadriven.raft.facade.model.AppendEntriesResponse;
21 | import top.datadriven.raft.facade.model.LogEntryModel;
22 | import top.datadriven.raft.integration.RaftClient;
23 |
24 | import javax.annotation.Resource;
25 | import java.util.List;
26 | import java.util.Map;
27 | import java.util.concurrent.locks.Lock;
28 |
29 | /**
30 | * @description: 附件日志条目服务
31 | * @author: jiayancheng
32 | * @email: jiayancheng@foxmail.com
33 | * @datetime: 2020/4/20 7:53 下午
34 | * @version: 1.0.0
35 | */
36 | @Component
37 | public class AppendEntriesComponentImpl implements AppendEntriesComponent {
38 |
39 | @Resource
40 | private RaftClient raftClient;
41 |
42 | @Override
43 | public void broadcastAppendEntries() {
44 | ConfigModel configModel = ConfigLoader.load();
45 | Long leaderId = configModel.getLocalNode().getServerId();
46 |
47 | Lock lock = RaftCoreModel.getLock();
48 | lock.lock();
49 | try {
50 | //0.数据准备
51 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
52 | PersistentStateModel persistentState = coreModel.getPersistentState();
53 | Long currentTerm = persistentState.getCurrentTerm();
54 | ServerStateModel serverState = coreModel.getServerState();
55 | Long commitIndex = serverState.getCommitIndex();
56 | Long lastApplied = serverState.getLastApplied();
57 | Map matchIndex = coreModel.getLeaderState().getMatchIndex();
58 |
59 | //1.找到N
60 | Long indexMaxN = getMaxN(persistentState, commitIndex,
61 | lastApplied, matchIndex, configModel);
62 | if (indexMaxN >= commitIndex) {
63 | //1.1 重置commitIndex
64 | serverState.setCommitIndex(indexMaxN);
65 | //1.2 通知 将新的index apply 到状态机
66 | coreModel.getCommitChannel().offer(CommonConstant.CHANNEL_FLAG);
67 | }
68 |
69 | //2.针对followers: 组装入参 --> 多线程发起请求
70 | for (RaftNodeModel remoteNode : configModel.getRemoteNodes()) {
71 | //2.1 组装请求入参
72 | AppendEntriesRequest request = new AppendEntriesRequest();
73 | request.setTerm(currentTerm);
74 | request.setLeaderId(leaderId);
75 | request.setPreLogIndex(persistentState.getPreEntry().getIndex());
76 | request.setPreLogTerm(persistentState.getPreEntry().getTerm());
77 | request.setLeaderCommit(commitIndex);
78 | //复制leader的log entry
79 | request.setLogEntries(getNextEntries(remoteNode));
80 |
81 | //2.2 线程池 异步发起单个请求
82 | RaftThreadPool.execute(() -> requestAppendEntries(remoteNode.getServerId(), request));
83 | }
84 |
85 | } finally {
86 | lock.unlock();
87 | }
88 | }
89 |
90 | /**
91 | * 获取下一批 日志条目
92 | */
93 | private List getNextEntries(RaftNodeModel remoteNode) {
94 | //1.数据准备
95 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
96 | LeaderStateModel leaderState = coreModel.getLeaderState();
97 | PersistentStateModel persistentState = coreModel.getPersistentState();
98 | List logEntries = persistentState.getLogEntries();
99 |
100 | //2.获取开始和结束索引
101 | int nextIndex = Math.toIntExact(leaderState.getNextIndex().get(remoteNode.getServerId()));
102 | int lastLogIndex = Math.toIntExact(persistentState.getLastEntry().getIndex());
103 | //3.索引不符合预期(已经大于等于最大日志)时,发心跳空包
104 | if (nextIndex > lastLogIndex) {
105 | return Lists.newArrayList();
106 | }
107 | return logEntries.subList(nextIndex, lastLogIndex + 1);
108 | }
109 |
110 |
111 | @Override
112 | public void requestAppendEntries(Long serverId, AppendEntriesRequest request) {
113 | //1.发起RPC请求
114 | AppendEntriesResponse response = raftClient.appendEntries(serverId, request);
115 |
116 | Lock lock = RaftCoreModel.getLock();
117 | lock.lock();
118 | try {
119 | //0.数据准备
120 | RaftCoreModel coreModel = RaftCoreModel.getSingleton();
121 | PersistentStateModel persistentState = coreModel.getPersistentState();
122 | LogEntryModel lastEntry = persistentState.getLastEntry();
123 | Long currentTerm = persistentState.getCurrentTerm();
124 | Map matchIndex = coreModel.getLeaderState().getMatchIndex();
125 |
126 | //2. 当前节点被废黜,或任期号变更了,不对回复值做处理
127 | if (coreModel.getServerStateEnum() != ServerStateEnum.LEADER
128 | || !currentTerm.equals(request.getTerm())) {
129 | return;
130 | }
131 |
132 | //3.Follower发送了更新的任期号,则leader将自己降为Follower
133 | if (response.getTerm() > currentTerm) {
134 | FollowerConvertor.convert2Follower(response.getTerm(), coreModel);
135 | }
136 |
137 | //4. 判断结果,为true: 更新nextIndex和matchIndex
138 | //更新逻辑:nextIndex为最后一条日志+1,matchIndex为nextIndex-1
139 | Map nextIndex = coreModel.getLeaderState().getNextIndex();
140 | List requestLogs = request.getLogEntries();
141 | if (response.getSuccess() && CollectionUtil.isNotEmpty(requestLogs)) {
142 | long next = requestLogs.get(requestLogs.size() - 1).getIndex() + 1;
143 | nextIndex.put(serverId, next);
144 | matchIndex.put(serverId, nextIndex.get(serverId) - 1);
145 | }
146 | // 为false: nextIndex减一
147 | if (!response.getSuccess() && nextIndex.get(serverId) > 1) {
148 | nextIndex.put(serverId, nextIndex.get(serverId) - 1);
149 | }
150 | } finally {
151 | lock.unlock();
152 | }
153 | }
154 |
155 | /**
156 | * 找到N
157 | * 1.如果存在一个满足N > commitIndex的 N,并且大多数(一半以上)的 matchIndex[i] ≥ N成立,
158 | * 并且log[N].term == currentTerm成立, 那么令 commitIndex 等于这个 N(论文 5.3 和 5.4 节)。
159 | * 比如,图6中,令leader commitIndex=5,那么找到的N=7
160 | * 前者是一半节点匹配后才能提交;后者[log.get(i - baseIndex).getLogTerm() == currentTerm]是防止非本此term的日志覆盖(5.4.2) 比如,在图8(c)中,S1在term=4时为leader,此时,虽然已经复制了index=2的一半以上节点,但是该term=2,非当前term,不能用来提交。
161 | */
162 | private Long getMaxN(PersistentStateModel persistentState, Long commitIndex,
163 | Long lastApplied, Map matchIndex,
164 | ConfigModel configModel) {
165 | //1.数据准备
166 | Long indexMaxN = commitIndex;
167 | Long currentTerm = persistentState.getCurrentTerm();
168 | List logEntries = persistentState.getLogEntries();
169 |
170 | //2.找到N
171 | for (long indexN = commitIndex + 1; indexN <= lastApplied; indexN++) {
172 | //当前节点已存在,所以初始值为1
173 | int matchServerCount = 1;
174 | for (RaftNodeModel remoteNode : configModel.getRemoteNodes()) {
175 | //2.1 matchIndex[i] ≥ N成立,则加一
176 | if (matchIndex.get(remoteNode.getServerId()) >= indexN) {
177 | matchServerCount++;
178 | }
179 | }
180 | //2.2 判断是否一半以上成立, 并且log[N].term == currentTerm成立
181 | if (matchServerCount >= CommonUtil.getMostCount(ConfigLoader.getServerCount())
182 | && logEntries.get((int) indexN).getTerm().equals(currentTerm)) {
183 | indexMaxN = indexN;
184 | }
185 | }
186 | return indexMaxN;
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # raft Java实现的详细设计文档
2 |
3 | 原文地址:https://www.yuque.com/aidt/tq712e/bhhyno
4 |
5 | # 概述
6 | 第一期,只实现raft一致性算法的核心功能,先不实现集群成员变化、日志压缩等功能。
7 | 本文为raft实现的设计文档,对raft算法进行抽象,将关键逻辑用图形和表格梳理清楚,从而给使用Java代码进行实现提供设计文档。
8 | # 主要概念
9 |
10 | - server:服务器,可能为leader、candidate、follower中的任意一方
11 | - leader:主节点
12 | - candidate:候选节点
13 | - follower:从节点
14 | - RPC:远程过程调用,在这里指通信接口
15 | - election:选举leader的过程
16 | - client:客户端,发起请求,传输数据的使用方
17 | - entry:条目,=term + 状态机指令(数据) + logIndex(日志索引)。
18 | - term:任期号,即一个数字。如下图,多个term组成了整个生命周期的时间轴。
19 |
20 | 
21 |
22 |
23 | # 关键设计
24 | ## 领域模型
25 | ### term介绍
26 | 核心的数据结构即为entry,多个entry组成了本地的数据模型,如下图:
27 | 
28 |
29 | - term:任期号
30 | - index:日志索引,从1开始递增
31 | - data:保存的数据
32 |
33 | 体现在一个集群中,则如下图:
34 | 
35 | ### 整体模型
36 | 
37 | ## 用例图
38 | 
39 | ## 模块划分
40 | 
41 | ## 关键类设计
42 | ### 整体类图如下
43 | 
44 | ### Server状态流转类图
45 | 
46 | ## server状态流转
47 | 
48 | 备注:跟随者只响应来自其他服务器的请求。如果跟随者接收不到消息,那么他就会变成候选人并发起一次选举。获得集群中大多数选票的候选人将成为领导者。在一个任期内,领导人一直都会是领导人直到自己宕机了。
49 | ## Entry状态转换
50 | 从客户端submit到最终apply到状态机:
51 | 
52 | # 核心实现流程
53 | ## leader选举
54 | ### follower投票
55 | 
56 | 约束转换:
57 |
58 |
59 | 1. 如果term < currentTerm返回 false (5.2 节)
60 | 1. 如果 votedFor 为空或者为 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)
61 | 1. 如果接收到的 RPC 请求或响应中,任期号T > currentTerm,那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)
62 | ### candidate发起投票(广播)
63 | 
64 | ### candidate发起投票(单个)
65 | 
66 | 通过ifLeaderChannel方式通知状态转换模块,由candidate转换为leader
67 | ## 日志复制
68 | ### follower接受条目
69 | 
70 | 
71 |
72 |
73 | 
74 | ### leader请求条目(广播)
75 | 
76 | 
77 | 
78 | ### leader请求条目(单个)
79 | 
80 | ## 客户端submit
81 | ### 接收客户端submit
82 | 
83 | ### 客户端submit
84 | 
85 |
86 |
87 | ## 提交过程:append-commit-apply-sm
88 | ### append-commit-apply-sm概述
89 | 
90 | ### commit2Apply
91 | 
92 | ### 状态机的一种实现(apply2sm)
93 | 
94 |
95 |
96 | ## server角色流转的详细实现
97 | 基于上面的server状态流转的状态机,详细描述如下:
98 | 
99 | # 实现参考
100 | [MIT 6.824 2020 Raft 实现细节汇总](https://zhuanlan.zhihu.com/p/39105353)
101 |
102 |
103 | # 其他
104 | ## 通信模块
105 | dubbo参考:
106 |
107 | - [Dubbo不使用zk](https://www.jianshu.com/p/f5ddbde05813)
108 | - [dubbo-quick-start](http://dubbo.apache.org/zh-cn/docs/user/quick-start.html)
109 |
110 | grpc参考:
111 |
112 | - [google grpc 快速入门](https://www.jianshu.com/p/ff354ccbde08)
113 | - [grpc Java Quick Start](https://grpc.io/docs/quickstart/java/)
114 |
115 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | UTF-8
9 | 5.2.5.RELEASE
10 | 1.2.0.RELEASE
11 | 4.2.5.RELEASE
12 | 4.2.5.RELEASE
13 | 1.7.21
14 | 1.7.21
15 | 1.1.8
16 | 1.16.6
17 | 4.12
18 | 3.4
19 | 3.5.1
20 | 5.1.38
21 | 3.4.1
22 | 1.3.0
23 | 1.2.46
24 | 24.0-jre
25 | 2.4.0.Final
26 | 3.0.1
27 | 3.21.0-GA
28 | 2.5.2
29 | 0.1
30 | 3.9.9.Final
31 | 2.19.1
32 | 2.8.2
33 | 3.5.1
34 | 1.8
35 | 2.6
36 | 0.1.4
37 |
38 |
39 | top.datadriven.raft
40 | raft-simple
41 | pom
42 | 1.0-SNAPSHOT
43 |
44 |
45 | raft-facade
46 | raft-biz-service-impl
47 | raft-integration
48 | raft-config-loader
49 | raft-state-machine
50 | raft-common-util
51 | raft-core-model
52 | raft-core-service
53 | raft-web
54 | raft-test
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | top.datadriven.raft
64 | raft-biz-service-impl
65 | 1.0-SNAPSHOT
66 |
67 |
68 | top.datadriven.raft
69 | raft-integration
70 | 1.0-SNAPSHOT
71 |
72 |
73 | top.datadriven.raft
74 | raft-config-loader
75 | 1.0-SNAPSHOT
76 |
77 |
78 | top.datadriven.raft
79 | raft-runner-mysql
80 | 1.0-SNAPSHOT
81 |
82 |
83 | top.datadriven.raft
84 | raft-state-machine
85 | 1.0-SNAPSHOT
86 |
87 |
88 | top.datadriven.raft
89 | raft-common-util
90 | 1.0-SNAPSHOT
91 |
92 |
93 | top.datadriven.raft
94 | raft-core-model
95 | 1.0-SNAPSHOT
96 |
97 |
98 | top.datadriven.raft
99 | raft-core-service
100 | 1.0-SNAPSHOT
101 |
102 |
103 | top.datadriven.raft
104 | raft-facade
105 | 1.0.1.20200413
106 |
107 |
108 | top.datadriven.raft
109 | raft-test
110 | 1.0-SNAPSHOT
111 |
112 |
113 | top.datadriven.raft
114 | raft-web
115 | 1.0-SNAPSHOT
116 |
117 |
118 |
119 |
120 | junit
121 | junit
122 | ${junit.version}
123 |
124 |
125 |
126 |
127 | org.springframework
128 | spring-context
129 | ${spring.version}
130 |
131 |
132 | org.springframework
133 | spring-aop
134 | ${spring.version}
135 |
136 |
137 | org.springframework
138 | spring-tx
139 | ${spring.version}
140 |
141 |
142 | org.springframework
143 | spring-context-support
144 | ${spring.version}
145 |
146 |
147 | org.springframework
148 | spring-jdbc
149 | ${spring.version}
150 |
151 |
152 | org.springframework
153 | spring-web
154 | ${spring.version}
155 |
156 |
157 | org.springframework
158 | spring-webmvc
159 | ${spring.version}
160 |
161 |
162 | org.springframework
163 | spring-aspects
164 | ${spring.version}
165 |
166 |
167 | org.springframework
168 | spring-beans
169 | ${spring.version}
170 |
171 |
172 | org.springframework
173 | spring-core
174 | ${spring.version}
175 |
176 |
177 | org.springframework
178 | spring-jms
179 | ${spring.version}
180 |
181 |
182 | org.springframework
183 | spring-expression
184 | ${spring.version}
185 |
186 |
187 | org.springframework
188 | spring-instrument
189 | ${spring.version}
190 |
191 |
192 | org.springframework
193 | spring-orm
194 | ${spring.version}
195 |
196 |
197 | org.springframework
198 | spring-oxm
199 | ${spring.version}
200 |
201 |
202 | org.springframework
203 | spring-test
204 | ${spring.version}
205 | test
206 |
207 |
208 | org.springframework.amqp
209 | spring-rabbit
210 | ${spring.amqp.version}
211 |
212 |
213 |
214 |
215 |
216 | org.aspectj
217 | aspectjweaver
218 | 1.8.13
219 |
220 |
221 |
222 | org.slf4j
223 | slf4j-api
224 | ${slf4j.version}
225 |
226 |
227 | org.slf4j
228 | log4j-over-slf4j
229 | ${log4j-over-slf4j.version}
230 |
231 |
232 | ch.qos.logback
233 | logback-classic
234 | ${logback.version}
235 |
236 |
237 | ch.qos.logback
238 | logback-core
239 | ${logback.version}
240 |
241 |
242 | org.logback-extensions
243 | logback-ext-spring
244 | ${logback.spring.version}
245 |
246 |
247 | org.slf4j
248 | jcl-over-slf4j
249 | 1.7.12
250 |
251 |
252 |
253 | org.apache.commons
254 | commons-lang3
255 | ${common-lang3.version}
256 |
257 |
258 |
259 | org.projectlombok
260 | lombok
261 | ${lombok.version}
262 |
263 |
264 |
265 |
266 | com.alibaba
267 | druid
268 | 1.1.7
269 |
270 |
271 |
272 |
273 | com.alibaba
274 | fastjson
275 | ${fastjson.version}
276 |
277 |
278 |
279 | com.google.guava
280 | guava
281 | ${guava.version}
282 |
283 |
284 |
285 |
286 | javax.servlet
287 | javax.servlet-api
288 | ${servlet.version}
289 |
290 |
291 |
292 | io.netty
293 | netty
294 | 3.9.9.Final
295 |
296 |
297 |
298 |
299 | com.github.sgroschupf
300 | zkclient
301 | ${zkclient.version}
302 |
303 |
304 | com.alibaba
305 | dubbo
306 | ${dubbo.version}
307 |
308 |
309 | org.springframework
310 | spring
311 |
312 |
313 |
314 |
315 |
316 |
317 | cn.hutool
318 | hutool-all
319 | 5.2.0
320 |
321 |
322 |
323 |
324 | org.yaml
325 | snakeyaml
326 | 1.25
327 |
328 |
329 | org.javassist
330 | javassist
331 | 3.25.0-GA
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 | org.apache.maven.plugins
343 | maven-surefire-plugin
344 | ${maven-surefire-plugin.version}
345 |
346 |
347 |
348 | org.apache.maven.plugins
349 | maven-compiler-plugin
350 | ${maven-compiler-plugin.version}
351 |
352 | UTF-8
353 |
354 | true
355 | 256m
356 | 256m
357 |
358 | ${jdk.version}
359 |
360 | ${jdk.version}
361 |
362 |
363 |
364 | org.apache.maven.plugins
365 | maven-war-plugin
366 | ${maven-war-plugin.version}
367 |
368 |
369 |
370 |
371 |
372 |
373 | org.apache.maven.plugins
374 | maven-resources-plugin
375 | 3.0.2
376 |
377 | UTF-8
378 |
379 |
380 |
381 |
382 | org.apache.maven.plugins
383 | maven-surefire-plugin
384 |
385 |
386 |
387 |
388 |
389 |
390 | org.apache.maven.plugins
391 | maven-compiler-plugin
392 |
393 |
394 |
395 |
396 |
--------------------------------------------------------------------------------