├── .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 | 17 | 18 | 19 | 20 | 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 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1585474228592-d639c3f1-ac06-4166-befe-9429dcebbeb7.png#align=left&display=inline&height=204&margin=%5Bobject%20Object%5D&name=image.png&originHeight=204&originWidth=519&size=34126&status=done&style=none&width=519) 21 | 22 | 23 | # 关键设计 24 | ## 领域模型 25 | ### term介绍 26 | 核心的数据结构即为entry,多个entry组成了本地的数据模型,如下图: 27 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1585476025778-c1e4ec11-71ab-4d0f-ab83-b4b2c432ed75.png#align=left&display=inline&height=171&margin=%5Bobject%20Object%5D&name=image.png&originHeight=276&originWidth=720&size=84196&status=done&style=none&width=447) 28 | 29 | - term:任期号 30 | - index:日志索引,从1开始递增 31 | - data:保存的数据 32 | 33 | 体现在一个集群中,则如下图: 34 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1585474097159-58d6df44-a5da-4feb-ba0b-44d6a2463cc8.png#align=left&display=inline&height=367&margin=%5Bobject%20Object%5D&name=image.png&originHeight=475&originWidth=633&size=207483&status=done&style=none&width=489) 35 | ### 整体模型 36 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1586963816465-2bebb66d-58e7-40e7-bec2-79500db38ee1.png#align=left&display=inline&height=460&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1164&originWidth=1556&size=202312&status=done&style=none&width=615) 37 | ## 用例图 38 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1586964166688-32fdd9d9-14d8-4f3c-9b09-5da8757565bf.png#align=left&display=inline&height=603&margin=%5Bobject%20Object%5D&name=image.png&originHeight=834&originWidth=707&size=197509&status=done&style=none&width=511) 39 | ## 模块划分 40 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1586963892263-490ac7fb-d470-4924-9f00-3b5f7e6f79c6.png#align=left&display=inline&height=413&margin=%5Bobject%20Object%5D&name=image.png&originHeight=938&originWidth=1336&size=89828&status=done&style=none&width=588) 41 | ## 关键类设计 42 | ### 整体类图如下 43 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1586963936053-179c57ae-3942-4f3e-8537-257573dc8108.png#align=left&display=inline&height=550&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1100&originWidth=1252&size=150070&status=done&style=none&width=626) 44 | ### Server状态流转类图 45 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1586964012137-d5f3472c-790c-4eb9-b626-f9d9747d043a.png#align=left&display=inline&height=215&margin=%5Bobject%20Object%5D&name=image.png&originHeight=430&originWidth=1102&size=41898&status=done&style=none&width=551) 46 | ## server状态流转 47 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560247069-8cc2d1cb-988b-4008-adcf-74e9685a88cd.png#align=left&display=inline&height=198&margin=%5Bobject%20Object%5D&name=image.png&originHeight=620&originWidth=1710&size=87332&status=done&style=none&width=546) 48 | 备注:跟随者只响应来自其他服务器的请求。如果跟随者接收不到消息,那么他就会变成候选人并发起一次选举。获得集群中大多数选票的候选人将成为领导者。在一个任期内,领导人一直都会是领导人直到自己宕机了。 49 | ## Entry状态转换 50 | 从客户端submit到最终apply到状态机: 51 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588561273413-1aa6fdd3-c4e5-4128-a68e-3d97af7041bc.png#align=left&display=inline&height=394&margin=%5Bobject%20Object%5D&name=image.png&originHeight=788&originWidth=678&size=52898&status=done&style=none&width=339) 52 | # 核心实现流程 53 | ## leader选举 54 | ### follower投票 55 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560317486-05008a80-634e-4153-a836-1227e237fcd8.png#align=left&display=inline&height=402&margin=%5Bobject%20Object%5D&name=image.png&originHeight=804&originWidth=1194&size=69838&status=done&style=none&width=597) 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 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560360242-e5e401a7-0747-4c48-bf67-9914930b3c60.png#align=left&display=inline&height=440&margin=%5Bobject%20Object%5D&name=image.png&originHeight=880&originWidth=486&size=50172&status=done&style=none&width=243) 64 | ### candidate发起投票(单个) 65 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560414102-242ad7b4-b094-4704-a3b4-c86f334d951b.png#align=left&display=inline&height=331&margin=%5Bobject%20Object%5D&name=image.png&originHeight=804&originWidth=1250&size=83555&status=done&style=none&width=515) 66 | 通过ifLeaderChannel方式通知状态转换模块,由candidate转换为leader 67 | ## 日志复制 68 | ### follower接受条目 69 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560546366-aded0a23-15d0-4503-8e6e-4f214db2ee31.png#align=left&display=inline&height=514&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1028&originWidth=1392&size=179589&status=done&style=none&width=696) 70 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560571684-123e283d-8a72-4cdf-858d-50a5f5faa647.png#align=left&display=inline&height=474&margin=%5Bobject%20Object%5D&name=image.png&originHeight=948&originWidth=1422&size=158037&status=done&style=none&width=711) 71 | 72 | 73 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560599239-4aa0ced5-b121-45b2-95c9-5cb73a39b4bd.png#align=left&display=inline&height=385&margin=%5Bobject%20Object%5D&name=image.png&originHeight=770&originWidth=1078&size=239430&status=done&style=none&width=539) 74 | ### leader请求条目(广播) 75 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560642248-cd8285cd-56c6-4168-b636-3804e82a903a.png#align=left&display=inline&height=592&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1184&originWidth=1654&size=221583&status=done&style=none&width=827) 76 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560659662-1c350154-3707-463a-ad32-f00ddc4128ff.png#align=left&display=inline&height=308&margin=%5Bobject%20Object%5D&name=image.png&originHeight=616&originWidth=812&size=244968&status=done&style=none&width=406) 77 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560676249-c15a7e6a-fc3c-4365-9182-7f73e85e67d8.png#align=left&display=inline&height=247&margin=%5Bobject%20Object%5D&name=image.png&originHeight=494&originWidth=754&size=212343&status=done&style=none&width=377) 78 | ### leader请求条目(单个) 79 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560737487-8b2a5da6-fede-49a3-ab42-4c6007a54ca7.png#align=left&display=inline&height=558&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1116&originWidth=1290&size=191715&status=done&style=none&width=645) 80 | ## 客户端submit 81 | ### 接收客户端submit 82 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560781329-b5486a0f-a054-4f33-8223-ed8e4875a1e2.png#align=left&display=inline&height=479&margin=%5Bobject%20Object%5D&name=image.png&originHeight=958&originWidth=1198&size=148591&status=done&style=none&width=599) 83 | ### 客户端submit 84 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560813733-db5652c1-07ee-4371-91c6-5e21db22a9ad.png#align=left&display=inline&height=382&margin=%5Bobject%20Object%5D&name=image.png&originHeight=764&originWidth=946&size=94464&status=done&style=none&width=473) 85 | 86 | 87 | ## 提交过程:append-commit-apply-sm 88 | ### append-commit-apply-sm概述 89 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560867276-96000036-756e-40f4-b4ca-1c5f5925d731.png#align=left&display=inline&height=418&margin=%5Bobject%20Object%5D&name=image.png&originHeight=836&originWidth=944&size=66754&status=done&style=none&width=472) 90 | ### commit2Apply 91 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588560975605-4912b7df-28bc-4a1f-ad99-d377c9cbfdf3.png#align=left&display=inline&height=555&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1110&originWidth=1266&size=119373&status=done&style=none&width=633) 92 | ### 状态机的一种实现(apply2sm) 93 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588561029493-e8623425-2a19-4446-884a-aaee79211fd2.png#align=left&display=inline&height=447&margin=%5Bobject%20Object%5D&name=image.png&originHeight=894&originWidth=1232&size=110272&status=done&style=none&width=616) 94 | 95 | 96 | ## server角色流转的详细实现 97 | 基于上面的server状态流转的状态机,详细描述如下: 98 | ![image.png](https://cdn.nlark.com/yuque/0/2020/png/129807/1588561076821-3994d2d2-c1f7-48a0-bf62-23d559dd78c1.png#align=left&display=inline&height=544&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1088&originWidth=1786&size=199293&status=done&style=none&width=893) 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 | --------------------------------------------------------------------------------