├── .gitignore ├── LICENSE ├── README.md ├── api ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── daydreamer │ │ └── raft │ │ └── api │ │ ├── callback │ │ └── CommitHook.java │ │ ├── collection │ │ ├── MemorySafeLinkedBlockingQueue.java │ │ ├── RejectPolicy.java │ │ └── impl │ │ │ ├── AbortRejectPolicy.java │ │ │ ├── DiscardOldestRejectPolicy.java │ │ │ └── DiscardRejectPolicy.java │ │ ├── entity │ │ ├── Request.java │ │ ├── Response.java │ │ ├── base │ │ │ ├── CommittedResponse.java │ │ │ ├── ErrorResponse.java │ │ │ ├── LogEntry.java │ │ │ ├── MemberChangeEntry.java │ │ │ └── Payload.java │ │ ├── constant │ │ │ ├── LogType.java │ │ │ ├── MemberChange.java │ │ │ └── ResponseCode.java │ │ ├── request │ │ │ ├── AppendEntriesRequest.java │ │ │ ├── EntryCommittedRequest.java │ │ │ ├── HeartbeatRequest.java │ │ │ ├── PrevoteRequest.java │ │ │ ├── VoteCommitRequest.java │ │ │ └── VoteRequest.java │ │ └── response │ │ │ ├── AppendEntriesResponse.java │ │ │ ├── ClientErrorResponse.java │ │ │ ├── EntryCommittedResponse.java │ │ │ ├── HeartbeatResponse.java │ │ │ ├── PrevoteResponse.java │ │ │ ├── ServerErrorResponse.java │ │ │ ├── VoteCommitResponse.java │ │ │ └── VoteResponse.java │ │ ├── exception │ │ └── InvalidResponseException.java │ │ └── grpc │ │ ├── Message.java │ │ ├── MessageOrBuilder.java │ │ ├── RequestRpc.java │ │ └── RequesterGrpc.java │ └── proto │ └── grpc.proto ├── common ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── daydreamer │ │ └── raft │ │ └── common │ │ ├── annotation │ │ ├── SPI.java │ │ ├── SPIIgnoredInject.java │ │ ├── SPIImplement.java │ │ ├── SPIMethodInit.java │ │ └── SPISetter.java │ │ ├── constant │ │ └── LogConstant.java │ │ ├── entity │ │ ├── Holder.java │ │ ├── RaftConfig.java │ │ └── SimpleFuture.java │ │ ├── filter │ │ ├── LogFilter.java │ │ └── impl │ │ │ └── FilterChain.java │ │ ├── loader │ │ ├── GroupAware.java │ │ ├── RaftServiceLoader.java │ │ ├── ServiceFactory.java │ │ └── impl │ │ │ ├── AdaptiveServiceFactory.java │ │ │ ├── ConfigServiceFactory.java │ │ │ └── SPIServiceFactory.java │ │ ├── service │ │ ├── ActiveProperties.java │ │ └── PropertiesReader.java │ │ ├── threadpool │ │ ├── ThreadPoolFactory.java │ │ └── impl │ │ │ ├── AdaptiveThreadPoolFactory.java │ │ │ └── CacheThreadPoolFactory.java │ │ └── utils │ │ ├── MD5Utils.java │ │ └── MsgUtils.java │ └── resources │ ├── META-INF │ └── raft │ │ ├── com.daydreamer.raft.common.filter.LogFilter │ │ ├── com.daydreamer.raft.common.loader.ServiceFactory │ │ └── com.daydreamer.raft.common.threadpool.ThreadPoolFactory │ └── log4j.properties ├── example ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── daydreamer │ │ └── raft │ │ └── example │ │ ├── LeaderElectionExample.java │ │ ├── LogAppendExample.java │ │ └── MemberChangeExample.java │ └── resources │ ├── example-server0.properties │ ├── example-server1.properties │ └── example-server2.properties ├── persistence ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── daydreamer │ │ └── persistence │ │ ├── PersistenceService.java │ │ └── impl │ │ └── FileSystemPersistence.java │ └── resources │ └── META-INF │ └── raft │ └── org.daydreamer.persistence.PersistenceService ├── pom.xml ├── protocol ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── daydreamer │ │ │ └── raft │ │ │ └── protocol │ │ │ ├── chain │ │ │ ├── LogPostProcessor.java │ │ │ ├── LogPostProcessorHolder.java │ │ │ └── impl │ │ │ │ ├── MemberChangeLogPostProcessor.java │ │ │ │ └── PersistentLogPostProcessor.java │ │ │ ├── constant │ │ │ ├── LogErrorCode.java │ │ │ ├── NodeRole.java │ │ │ ├── NodeStatus.java │ │ │ └── RaftProperty.java │ │ │ ├── core │ │ │ ├── AbstractFollowerNotifier.java │ │ │ ├── AbstractRaftServer.java │ │ │ ├── CommitHookManager.java │ │ │ ├── LogSender.java │ │ │ ├── Protocol.java │ │ │ ├── RaftMemberManager.java │ │ │ └── impl │ │ │ │ ├── AsynCommitHookManager.java │ │ │ │ ├── DefaultLogSender.java │ │ │ │ ├── GrpcFollowerNotifier.java │ │ │ │ ├── GrpcRaftServer.java │ │ │ │ ├── GrpcRequestServerCore.java │ │ │ │ ├── MemberManager.java │ │ │ │ ├── RaftPropertiesReader.java │ │ │ │ └── RaftProtocol.java │ │ │ ├── entity │ │ │ └── Member.java │ │ │ ├── exception │ │ │ └── LogException.java │ │ │ ├── filter │ │ │ └── InnerLogFilter.java │ │ │ ├── handler │ │ │ ├── RequestHandler.java │ │ │ ├── RequestHandlerHolder.java │ │ │ └── impl │ │ │ │ ├── AppendEntriesRequestHandler.java │ │ │ │ ├── DefaultRequestHandler.java │ │ │ │ ├── HeartbeatRequestHandler.java │ │ │ │ ├── LogCommittedRequestHandler.java │ │ │ │ ├── PrevoteRequestHandler.java │ │ │ │ ├── VoteCommitRequestHandler.java │ │ │ │ └── VoteRequestHandler.java │ │ │ └── storage │ │ │ ├── ReplicatedStateMachine.java │ │ │ └── impl │ │ │ ├── DelegateReplicatedStateMachine.java │ │ │ └── MemoryReplicatedStateMachine.java │ └── resources │ │ └── META-INF │ │ └── raft │ │ ├── com.daydreamer.raft.common.filter.LogFilter │ │ ├── com.daydreamer.raft.protocol.chain.LogPostProcessor │ │ ├── com.daydreamer.raft.protocol.core.AbstractFollowerNotifier │ │ ├── com.daydreamer.raft.protocol.core.AbstractRaftServer │ │ ├── com.daydreamer.raft.protocol.core.LogSender │ │ ├── com.daydreamer.raft.protocol.core.RaftMemberManager │ │ ├── com.daydreamer.raft.protocol.handler.RequestHandler │ │ └── com.daydreamer.raft.protocol.storage.ReplicatedStateMachine │ └── test │ └── java │ └── com │ └── daydreamer │ └── raft │ └── protocol │ └── storage │ └── impl │ └── MemoryReplicatedStateMachineTest.java └── transport ├── pom.xml └── src └── main ├── java └── com │ └── daydreamer │ └── raft │ └── transport │ ├── connection │ ├── Closeable.java │ ├── Connection.java │ ├── ResponseCallBack.java │ └── impl │ │ ├── grpc │ │ └── GrpcConnection.java │ │ └── http │ │ └── HttpConnection.java │ ├── constant │ └── ResponseRepository.java │ └── factory │ ├── ConnectionFactory.java │ └── impl │ └── GrpcConnectionFactory.java └── resources └── META-INF └── raft └── com.daydreamer.raft.transport.factory.ConnectionFactory /.gitignore: -------------------------------------------------------------------------------- 1 | # Except this file !.gitignore 2 | .classpath 3 | .project 4 | .settings 5 | target 6 | .idea 7 | .vscode 8 | .DS_Store 9 | .factorypath 10 | /logs 11 | *.iml 12 | *.log 13 | node_modules 14 | test/derby.log 15 | derby.log 16 | work 17 | test/logs 18 | derby.log 19 | yarn.lock 20 | .flattened-pom.xml 21 | -------------------------------------------------------------------------------- /api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | raft 7 | org.daydreamer 8 | 1.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | api 13 | ${ddr-raft.version} 14 | 15 | 16 | 1.8 17 | 1.8 18 | 2.6 19 | 2.8.6 20 | 1.30.2 21 | 3.16.3 22 | 1.17.0 23 | 1.3.2 24 | 25 | 26 | 27 | 28 | io.grpc 29 | grpc-netty-shaded 30 | ${grpc.version} 31 | 32 | 33 | io.grpc 34 | grpc-protobuf 35 | ${grpc.version} 36 | 37 | 38 | io.grpc 39 | grpc-stub 40 | ${grpc.version} 41 | 42 | 43 | com.google.api.grpc 44 | proto-google-common-protos 45 | ${proto-common.version} 46 | 47 | 48 | com.google.protobuf 49 | protobuf-java 50 | ${proto.version} 51 | 52 | 53 | io.grpc 54 | protoc-gen-grpc-java 55 | ${grpc.version} 56 | pom 57 | 58 | 59 | javax.annotation 60 | javax.annotation-api 61 | ${javax.version} 62 | 63 | 64 | 65 | 66 | 67 | 68 | kr.motd.maven 69 | os-maven-plugin 70 | 1.6.2 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/callback/CommitHook.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.callback; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | 5 | /** 6 | * @author Daydreamer 7 | *

8 | * it will be invoked after log committed 9 | */ 10 | public interface CommitHook { 11 | 12 | /** 13 | * it will be invoked after log committed 14 | * 15 | * @param logEntry committed log 16 | */ 17 | void handleCommittedLog(LogEntry logEntry); 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/collection/MemorySafeLinkedBlockingQueue.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.collection; 2 | 3 | import com.daydreamer.raft.api.collection.impl.AbortRejectPolicy; 4 | 5 | import java.util.Collection; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * @author Daydreamer 11 | */ 12 | public class MemorySafeLinkedBlockingQueue extends LinkedBlockingQueue { 13 | 14 | /** 15 | * default reject policy 16 | */ 17 | private static final RejectPolicy DEFAULT_REJECT_POLICY = new AbortRejectPolicy<>(); 18 | 19 | /** 20 | * default min memory at least 21 | */ 22 | private static final long DEFAULT_MIN_MEMORY_AT_LEAST = 200 * 1024; 23 | 24 | /** 25 | * leave 200m at least 26 | */ 27 | private long minMemoryAtLeast = DEFAULT_MIN_MEMORY_AT_LEAST; 28 | 29 | /** 30 | * reject policy 31 | */ 32 | private RejectPolicy rejectPolicy = DEFAULT_REJECT_POLICY; 33 | 34 | public MemorySafeLinkedBlockingQueue(long minMemoryAtLeast) { 35 | this.minMemoryAtLeast = minMemoryAtLeast; 36 | } 37 | 38 | public MemorySafeLinkedBlockingQueue() { 39 | } 40 | 41 | public MemorySafeLinkedBlockingQueue(RejectPolicy rejectPolicy) { 42 | this.rejectPolicy = rejectPolicy; 43 | } 44 | 45 | public MemorySafeLinkedBlockingQueue(long minMemoryAtLeast, RejectPolicy rejectPolicy) { 46 | this.minMemoryAtLeast = minMemoryAtLeast; 47 | this.rejectPolicy = rejectPolicy; 48 | } 49 | 50 | public MemorySafeLinkedBlockingQueue(int capacity, long minMemoryAtLeast, RejectPolicy rejectPolicy) { 51 | super(capacity); 52 | this.minMemoryAtLeast = minMemoryAtLeast; 53 | this.rejectPolicy = rejectPolicy; 54 | } 55 | 56 | public MemorySafeLinkedBlockingQueue(Collection c, long minMemoryAtLeast, RejectPolicy rejectPolicy) { 57 | super(c); 58 | this.minMemoryAtLeast = minMemoryAtLeast; 59 | this.rejectPolicy = rejectPolicy; 60 | } 61 | 62 | /** 63 | * is there free memory 64 | * 65 | * @return whether allow to operate 66 | */ 67 | private boolean hasFreeMemory() { 68 | return Runtime.getRuntime().freeMemory() > minMemoryAtLeast; 69 | } 70 | 71 | @Override 72 | public void put(final E e) throws InterruptedException { 73 | if (hasFreeMemory()) { 74 | super.put(e); 75 | } else { 76 | rejectPolicy.reject(e, this); 77 | } 78 | } 79 | 80 | public long getMinMemoryAtLeast() { 81 | return minMemoryAtLeast; 82 | } 83 | 84 | public void setMinMemoryAtLeast(long minMemoryAtLeast) { 85 | this.minMemoryAtLeast = minMemoryAtLeast; 86 | } 87 | 88 | @Override 89 | public boolean offer(final E e, final long timeout, final TimeUnit unit) throws InterruptedException { 90 | if (!hasFreeMemory()) { 91 | return false; 92 | } 93 | return super.offer(e, timeout, unit); 94 | } 95 | 96 | @Override 97 | public boolean offer(final E e) { 98 | if (!hasFreeMemory()) { 99 | rejectPolicy.reject(e, this); 100 | return false; 101 | } 102 | return super.offer(e); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/collection/RejectPolicy.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.collection; 2 | 3 | import java.util.Queue; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public interface RejectPolicy { 9 | 10 | void reject(E ele, Queue queue); 11 | } 12 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/collection/impl/AbortRejectPolicy.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.collection.impl; 2 | 3 | import com.daydreamer.raft.api.collection.RejectPolicy; 4 | 5 | import java.util.Queue; 6 | import java.util.concurrent.RejectedExecutionException; 7 | 8 | /** 9 | * @author Daydreamer 10 | */ 11 | public class AbortRejectPolicy implements RejectPolicy { 12 | 13 | @Override 14 | public void reject(E ele, Queue queue) { 15 | throw new RejectedExecutionException("No more memory can be used!"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/collection/impl/DiscardOldestRejectPolicy.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.collection.impl; 2 | 3 | import com.daydreamer.raft.api.collection.RejectPolicy; 4 | 5 | import java.util.Queue; 6 | 7 | /** 8 | * @author Daydreamer 9 | */ 10 | public class DiscardOldestRejectPolicy implements RejectPolicy { 11 | 12 | @Override 13 | public void reject(E ele, Queue queue) { 14 | queue.poll(); 15 | queue.add(ele); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/collection/impl/DiscardRejectPolicy.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.collection.impl; 2 | 3 | import com.daydreamer.raft.api.collection.RejectPolicy; 4 | 5 | import java.util.Queue; 6 | 7 | /** 8 | * @author Daydreamer 9 | */ 10 | public class DiscardRejectPolicy implements RejectPolicy { 11 | @Override 12 | public void reject(E ele, Queue queue) { 13 | // nothing to do 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/Request.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.concurrent.atomic.AtomicLong; 7 | 8 | /** 9 | * @author Daydreamer 10 | * 11 | * payload of request 12 | */ 13 | public abstract class Request implements Serializable { 14 | 15 | private static final long serialVersionUID = 11988178431L; 16 | 17 | /** 18 | * next request id 19 | */ 20 | private static final AtomicLong NEXT_REQUEST_ID = new AtomicLong(0); 21 | 22 | /** 23 | * request id 24 | */ 25 | private long requestId = NEXT_REQUEST_ID.getAndIncrement(); 26 | 27 | /** 28 | * data header 29 | */ 30 | private Map headers = new HashMap(); 31 | 32 | public Map getHeaders() { 33 | return headers; 34 | } 35 | 36 | public long getRequestId() { 37 | return requestId; 38 | } 39 | 40 | public void setHeaders(Map headers) { 41 | this.headers = headers; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/Response.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity; 2 | 3 | 4 | import com.daydreamer.raft.api.entity.constant.ResponseCode; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author Daydreamer 10 | * 11 | * payload of response 12 | */ 13 | public abstract class Response implements Serializable { 14 | 15 | private int resultCode = ResponseCode.SUCCESS_CODE; 16 | 17 | private String message; 18 | 19 | private String requestId; 20 | 21 | public int getResultCode() { 22 | return resultCode; 23 | } 24 | 25 | public void setResultCode(int resultCode) { 26 | this.resultCode = resultCode; 27 | } 28 | 29 | public String getMessage() { 30 | return message; 31 | } 32 | 33 | public void setMessage(String message) { 34 | this.message = message; 35 | } 36 | 37 | public String getRequestId() { 38 | return requestId; 39 | } 40 | 41 | public void setRequestId(String requestId) { 42 | this.requestId = requestId; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/base/CommittedResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.base; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public abstract class CommittedResponse extends Response { 9 | 10 | private boolean accepted; 11 | 12 | public CommittedResponse(boolean accepted) { 13 | this.accepted = accepted; 14 | } 15 | 16 | public boolean isAccepted() { 17 | return accepted; 18 | } 19 | 20 | public void setAccepted(boolean accepted) { 21 | this.accepted = accepted; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/base/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.base; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public abstract class ErrorResponse extends Response { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/base/LogEntry.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.base; 2 | 3 | import java.io.Serializable; 4 | import java.util.Objects; 5 | 6 | /** 7 | * @author Daydreamer 8 | */ 9 | public class LogEntry implements Serializable { 10 | 11 | /** 12 | * term 13 | */ 14 | private int term; 15 | 16 | /** 17 | * log id 18 | */ 19 | private long logId; 20 | 21 | /** 22 | * data 23 | */ 24 | private Payload payload; 25 | 26 | public LogEntry(int term, long logId, Payload payload) { 27 | this.term = term; 28 | this.logId = logId; 29 | this.payload = payload; 30 | } 31 | 32 | public LogEntry(int term, long logId) { 33 | this.term = term; 34 | this.logId = logId; 35 | } 36 | 37 | public int getTerm() { 38 | return term; 39 | } 40 | 41 | public void setTerm(int term) { 42 | this.term = term; 43 | } 44 | 45 | public long getLogId() { 46 | return logId; 47 | } 48 | 49 | public void setLogId(long logId) { 50 | this.logId = logId; 51 | } 52 | 53 | public Payload getPayload() { 54 | return payload; 55 | } 56 | 57 | public void setPayload(Payload payload) { 58 | this.payload = payload; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (!(o instanceof LogEntry)) { 67 | return false; 68 | } 69 | LogEntry logEntry = (LogEntry) o; 70 | return term == logEntry.term && logId == logEntry.logId; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(term, logId); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "LogEntry{" + "term=" + term + ", logId=" + logId + ", log type=" + payload.getLogType() + '}'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/base/MemberChangeEntry.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.base; 2 | 3 | import com.daydreamer.raft.api.entity.constant.MemberChange; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author Daydreamer 9 | */ 10 | public class MemberChangeEntry implements Serializable { 11 | 12 | private String address; 13 | 14 | private MemberChange memberChange; 15 | 16 | public MemberChangeEntry(String address, MemberChange memberChange) { 17 | this.address = address; 18 | this.memberChange = memberChange; 19 | } 20 | 21 | public String getAddress() { 22 | return address; 23 | } 24 | 25 | public void setAddress(String address) { 26 | this.address = address; 27 | } 28 | 29 | public MemberChange getMemberChange() { 30 | return memberChange; 31 | } 32 | 33 | public void setMemberChange(MemberChange memberChange) { 34 | this.memberChange = memberChange; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/base/Payload.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.base; 2 | 3 | import com.daydreamer.raft.api.entity.constant.LogType; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Daydreamer 10 | */ 11 | public class Payload implements Serializable { 12 | 13 | private Map metadata; 14 | 15 | private LogType logType; 16 | 17 | public Payload() { 18 | } 19 | 20 | public Payload(Map object, LogType logType) { 21 | this.metadata = object; 22 | this.logType = logType; 23 | } 24 | 25 | public Map getMetadata() { 26 | return metadata; 27 | } 28 | 29 | public void setMetadata(Map metadata) { 30 | this.metadata = metadata; 31 | } 32 | 33 | public LogType getLogType() { 34 | return logType; 35 | } 36 | 37 | public void setLogType(LogType logType) { 38 | this.logType = logType; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/constant/LogType.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | *

6 | * type of raft log 7 | */ 8 | public enum LogType { 9 | 10 | /** 11 | * read log 12 | */ 13 | READ, 14 | 15 | /** 16 | * write log 17 | */ 18 | WRITE, 19 | 20 | /** 21 | * no-op 22 | */ 23 | NO_OP, 24 | 25 | /** 26 | * member change 27 | */ 28 | MEMBER_CHANGE 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/constant/MemberChange.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public enum MemberChange { 7 | 8 | /** 9 | * add 10 | */ 11 | ADD, 12 | 13 | /** 14 | * remove 15 | */ 16 | REMOVE 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/constant/ResponseCode.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | * 6 | * reponse code of response 7 | */ 8 | public class ResponseCode { 9 | 10 | private ResponseCode() {} 11 | 12 | /** 13 | * success 14 | */ 15 | public static final int SUCCESS_CODE = 200; 16 | 17 | /** 18 | * error from client 19 | */ 20 | public static final int ERROR_CLIENT = 400; 21 | 22 | /** 23 | * error from server 24 | */ 25 | public static final int ERROR_SERVER = 500; 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/AppendEntriesRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.base.LogEntry; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author Daydreamer 10 | */ 11 | public class AppendEntriesRequest extends Request { 12 | 13 | /** 14 | * last committed or uncommitted log id 15 | */ 16 | private long lastLogId; 17 | 18 | /** 19 | * last committed or uncommitted log term 20 | */ 21 | private int lastTerm; 22 | 23 | /** 24 | * log from last committed log in follower to current committed log in leader 25 | */ 26 | private List logEntries; 27 | 28 | /** 29 | * current term 30 | */ 31 | private int currentTerm; 32 | 33 | /** 34 | * current committed or uncommitted log id 35 | */ 36 | private long currentLogId; 37 | 38 | /** 39 | * whether it is payload 40 | */ 41 | private boolean payload; 42 | 43 | public List getLogEntries() { 44 | return logEntries; 45 | } 46 | 47 | public void setLogEntries(List logEntries) { 48 | this.logEntries = logEntries; 49 | } 50 | 51 | public int getCurrentTerm() { 52 | return currentTerm; 53 | } 54 | 55 | public void setCurrentTerm(int currentTerm) { 56 | this.currentTerm = currentTerm; 57 | } 58 | 59 | public long getCurrentLogId() { 60 | return currentLogId; 61 | } 62 | 63 | public void setCurrentLogId(long currentLogId) { 64 | this.currentLogId = currentLogId; 65 | } 66 | 67 | public boolean isPayload() { 68 | return payload; 69 | } 70 | 71 | public void setPayload(boolean payload) { 72 | this.payload = payload; 73 | } 74 | 75 | public List getLogEntry() { 76 | return logEntries; 77 | } 78 | 79 | public void setLogEntry(List logEntries) { 80 | this.logEntries = logEntries; 81 | } 82 | 83 | public long getLastLogId() { 84 | return lastLogId; 85 | } 86 | 87 | public void setLastLogId(long lastLogId) { 88 | this.lastLogId = lastLogId; 89 | } 90 | 91 | public int getLastTerm() { 92 | return lastTerm; 93 | } 94 | 95 | public void setLastTerm(int lastTerm) { 96 | this.lastTerm = lastTerm; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/EntryCommittedRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class EntryCommittedRequest extends Request { 9 | 10 | private long logId; 11 | 12 | private int term; 13 | 14 | public EntryCommittedRequest(long logId, int term) { 15 | this.logId = logId; 16 | this.term = term; 17 | } 18 | 19 | public long getLogId() { 20 | return logId; 21 | } 22 | 23 | public void setLogId(long logId) { 24 | this.logId = logId; 25 | } 26 | 27 | public int getTerm() { 28 | return term; 29 | } 30 | 31 | public void setTerm(int term) { 32 | this.term = term; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/HeartbeatRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | 5 | /** 6 | * @author Daydreamer 7 | * 8 | * heart beat 9 | */ 10 | public class HeartbeatRequest extends Request { 11 | 12 | private int term; 13 | 14 | private long logId; 15 | 16 | public HeartbeatRequest(int term, long logId) { 17 | this.term = term; 18 | this.logId = logId; 19 | } 20 | 21 | public long getLogId() { 22 | return logId; 23 | } 24 | 25 | public void setLogId(long logId) { 26 | this.logId = logId; 27 | } 28 | 29 | public int getTerm() { 30 | return term; 31 | } 32 | 33 | public void setTerm(int term) { 34 | this.term = term; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/PrevoteRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class PrevoteRequest extends Request { 9 | 10 | /** 11 | * term from client 12 | */ 13 | private int term; 14 | 15 | /** 16 | * log index 17 | */ 18 | private long logIndex; 19 | 20 | public PrevoteRequest() { 21 | } 22 | 23 | public PrevoteRequest(int term, long logIndex) { 24 | this.term = term; 25 | this.logIndex = logIndex; 26 | } 27 | 28 | public long getLogIndex() { 29 | return logIndex; 30 | } 31 | 32 | public void setLogIndex(long logIndex) { 33 | this.logIndex = logIndex; 34 | } 35 | 36 | public int getTerm() { 37 | return term; 38 | } 39 | 40 | public void setTerm(int term) { 41 | this.term = term; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/VoteCommitRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | 5 | /** 6 | * @author Daydreamer 7 | * 8 | * if success to get half of all, then commit and tell follower 9 | */ 10 | public class VoteCommitRequest extends Request { 11 | 12 | private int term; 13 | 14 | private long logId; 15 | 16 | public VoteCommitRequest() { 17 | } 18 | 19 | public VoteCommitRequest(int term, long logId) { 20 | this.term = term; 21 | this.logId = logId; 22 | } 23 | 24 | public int getTerm() { 25 | return term; 26 | } 27 | 28 | public void setTerm(int term) { 29 | this.term = term; 30 | } 31 | 32 | public long getLogId() { 33 | return logId; 34 | } 35 | 36 | public void setLogId(long logId) { 37 | this.logId = logId; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/request/VoteRequest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.request; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | 5 | /** 6 | * @author Daydreamer 7 | * 8 | * request to vote 9 | */ 10 | public class VoteRequest extends Request { 11 | 12 | private int term; 13 | 14 | private long logIndex; 15 | 16 | public VoteRequest() { 17 | } 18 | 19 | public VoteRequest(int term, long logIndex) { 20 | this.term = term; 21 | this.logIndex = logIndex; 22 | } 23 | 24 | public int getTerm() { 25 | return term; 26 | } 27 | 28 | public void setTerm(int term) { 29 | this.term = term; 30 | } 31 | 32 | public long getLogIndex() { 33 | return logIndex; 34 | } 35 | 36 | public void setLogIndex(long logIndex) { 37 | this.logIndex = logIndex; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/AppendEntriesResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class AppendEntriesResponse extends Response { 9 | 10 | /** 11 | * whether accepted 12 | */ 13 | private boolean accepted; 14 | 15 | public AppendEntriesResponse(boolean accepted) { 16 | this.accepted = accepted; 17 | } 18 | 19 | public boolean isAccepted() { 20 | return accepted; 21 | } 22 | 23 | public void setAccepted(boolean accepted) { 24 | this.accepted = accepted; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/ClientErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.base.ErrorResponse; 4 | 5 | 6 | /** 7 | * @author Daydreamer 8 | * 9 | * Client error 10 | */ 11 | public class ClientErrorResponse extends ErrorResponse { 12 | 13 | public ClientErrorResponse(String msg, int code) { 14 | super.setMessage(msg); 15 | super.setResultCode(code); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/EntryCommittedResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.base.CommittedResponse; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class EntryCommittedResponse extends CommittedResponse { 9 | 10 | public EntryCommittedResponse(boolean accepted) { 11 | super(accepted); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/HeartbeatResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class HeartbeatResponse extends Response { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/PrevoteResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class PrevoteResponse extends Response { 9 | 10 | private boolean agree; 11 | 12 | public PrevoteResponse() { 13 | } 14 | 15 | public PrevoteResponse(boolean agree) { 16 | this.agree = agree; 17 | } 18 | 19 | public boolean isAgree() { 20 | return agree; 21 | } 22 | 23 | public void setAgree(boolean agree) { 24 | this.agree = agree; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/ServerErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.base.ErrorResponse; 4 | 5 | /** 6 | * @author Daydreamer 7 | * 8 | * Server error, often unknown error 9 | */ 10 | public class ServerErrorResponse extends ErrorResponse { 11 | public ServerErrorResponse(String msg, int code) { 12 | super.setMessage(msg); 13 | super.setResultCode(code); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/VoteCommitResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.base.CommittedResponse; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class VoteCommitResponse extends CommittedResponse { 9 | 10 | public VoteCommitResponse(boolean accepted) { 11 | super(accepted); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/entity/response/VoteResponse.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.entity.response; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | * 8 | * response to vote 9 | */ 10 | public class VoteResponse extends Response { 11 | 12 | /** 13 | * whether to vote for client 14 | */ 15 | private boolean isVoted; 16 | 17 | public VoteResponse(boolean isVoted) { 18 | this.isVoted = isVoted; 19 | } 20 | 21 | public boolean isVoted() { 22 | return isVoted; 23 | } 24 | 25 | public void setVoted(boolean voted) { 26 | isVoted = voted; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "VoteResponse{" + "isVoted=" + isVoted + '}'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/exception/InvalidResponseException.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.api.exception; 2 | 3 | import com.daydreamer.raft.api.entity.base.ErrorResponse; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public class InvalidResponseException extends Exception { 9 | 10 | private ErrorResponse errorResponse; 11 | 12 | public InvalidResponseException(ErrorResponse errorResponse) { 13 | this.errorResponse = errorResponse; 14 | } 15 | 16 | public InvalidResponseException(String message, Throwable cause, ErrorResponse errorResponse) { 17 | super(message, cause); 18 | this.errorResponse = errorResponse; 19 | } 20 | 21 | public InvalidResponseException(String message, ErrorResponse errorResponse) { 22 | super(message); 23 | this.errorResponse = errorResponse; 24 | } 25 | 26 | public ErrorResponse getErrorResponse() { 27 | return errorResponse; 28 | } 29 | 30 | public void setErrorResponse(ErrorResponse errorResponse) { 31 | this.errorResponse = errorResponse; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/grpc/MessageOrBuilder.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: grpc.proto 3 | 4 | package com.daydreamer.raft.api.grpc; 5 | 6 | public interface MessageOrBuilder extends 7 | // @@protoc_insertion_point(interface_extends:Message) 8 | com.google.protobuf.MessageOrBuilder { 9 | 10 | /** 11 | *

12 |    * payload
13 |    * 
14 | * 15 | * string data = 1; 16 | * @return The data. 17 | */ 18 | String getData(); 19 | /** 20 | *
21 |    * payload
22 |    * 
23 | * 24 | * string data = 1; 25 | * @return The bytes for data. 26 | */ 27 | com.google.protobuf.ByteString 28 | getDataBytes(); 29 | 30 | /** 31 | *
32 |    * conn id
33 |    * 
34 | * 35 | * string id = 2; 36 | * @return The id. 37 | */ 38 | String getId(); 39 | /** 40 | *
41 |    * conn id
42 |    * 
43 | * 44 | * string id = 2; 45 | * @return The bytes for id. 46 | */ 47 | com.google.protobuf.ByteString 48 | getIdBytes(); 49 | 50 | /** 51 | *
52 |    * request or response type
53 |    * 
54 | * 55 | * string type = 3; 56 | * @return The type. 57 | */ 58 | String getType(); 59 | /** 60 | *
61 |    * request or response type
62 |    * 
63 | * 64 | * string type = 3; 65 | * @return The bytes for type. 66 | */ 67 | com.google.protobuf.ByteString 68 | getTypeBytes(); 69 | } 70 | -------------------------------------------------------------------------------- /api/src/main/java/com/daydreamer/raft/api/grpc/RequestRpc.java: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: grpc.proto 3 | 4 | package com.daydreamer.raft.api.grpc; 5 | 6 | public final class RequestRpc { 7 | private RequestRpc() {} 8 | public static void registerAllExtensions( 9 | com.google.protobuf.ExtensionRegistryLite registry) { 10 | } 11 | 12 | public static void registerAllExtensions( 13 | com.google.protobuf.ExtensionRegistry registry) { 14 | registerAllExtensions( 15 | (com.google.protobuf.ExtensionRegistryLite) registry); 16 | } 17 | static final com.google.protobuf.Descriptors.Descriptor 18 | internal_static_Message_descriptor; 19 | static final 20 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable 21 | internal_static_Message_fieldAccessorTable; 22 | 23 | public static com.google.protobuf.Descriptors.FileDescriptor 24 | getDescriptor() { 25 | return descriptor; 26 | } 27 | private static com.google.protobuf.Descriptors.FileDescriptor 28 | descriptor; 29 | static { 30 | String[] descriptorData = { 31 | "\n\ngrpc.proto\032\031google/protobuf/any.proto\"" + 32 | "1\n\007Message\022\014\n\004data\030\001 \001(\t\022\n\n\002id\030\002 \001(\t\022\014\n\004" + 33 | "type\030\003 \001(\t2,\n\tRequester\022\037\n\007request\022\010.Mes" + 34 | "sage\032\010.Message\"\000B2\n\"com.daydreamer.raft." + 35 | "transport.grpcB\nRequestRpcP\001b\006proto3" 36 | }; 37 | descriptor = com.google.protobuf.Descriptors.FileDescriptor 38 | .internalBuildGeneratedFileFrom(descriptorData, 39 | new com.google.protobuf.Descriptors.FileDescriptor[] { 40 | com.google.protobuf.AnyProto.getDescriptor(), 41 | }); 42 | internal_static_Message_descriptor = 43 | getDescriptor().getMessageTypes().get(0); 44 | internal_static_Message_fieldAccessorTable = new 45 | com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( 46 | internal_static_Message_descriptor, 47 | new String[] { "Data", "Id", "Type", }); 48 | com.google.protobuf.AnyProto.getDescriptor(); 49 | } 50 | 51 | // @@protoc_insertion_point(outer_class_scope) 52 | } 53 | -------------------------------------------------------------------------------- /api/src/main/proto/grpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/any.proto"; 4 | 5 | option java_multiple_files = true; 6 | option java_package = "com.daydreamer.raft.transport.grpc"; 7 | option java_outer_classname = "RequestRpc"; 8 | 9 | /** 10 | service 11 | */ 12 | service Requester { 13 | rpc request (Message) returns (Message){ 14 | 15 | } 16 | } 17 | 18 | /** 19 | base message 20 | */ 21 | message Message { 22 | string data = 1; // payload 23 | string id = 2; // conn id 24 | string type = 3; // request or response type 25 | } 26 | 27 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | raft 7 | org.daydreamer 8 | 1.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | common 13 | ${ddr-raft.version} 14 | 15 | 16 | 2.6 17 | 2.8.6 18 | 5.5.2 19 | 1.7.21 20 | 21 | 22 | 23 | 24 | commons-lang 25 | commons-lang 26 | ${commons-lang.version} 27 | 28 | 29 | com.google.code.gson 30 | gson 31 | ${gson.version} 32 | 33 | 34 | org.daydreamer 35 | api 36 | ${ddr-raft.version} 37 | 38 | 39 | 40 | 41 | org.junit.jupiter 42 | junit-jupiter-engine 43 | ${junit.version} 44 | 45 | 46 | 47 | 48 | org.slf4j 49 | slf4j-log4j12 50 | ${slf4j.version} 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/annotation/SPI.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | @Documented 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE}) 11 | public @interface SPI { 12 | 13 | /** 14 | * default implement key 15 | * 16 | * @return default implement key 17 | */ 18 | String value(); 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/annotation/SPIIgnoredInject.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.annotation; 2 | 3 | /** 4 | * @author Daydreamer 5 | *

6 | * Ignore setter for setter method 7 | */ 8 | public @interface SPIIgnoredInject { 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/annotation/SPIImplement.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | @Documented 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE}) 11 | public @interface SPIImplement { 12 | 13 | /** 14 | * key of current implement 15 | * 16 | * @return key 17 | */ 18 | String value(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/annotation/SPIMethodInit.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.annotation; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * @author Daydreamer 8 | *

9 | * Invoke after properties set 10 | */ 11 | @Documented 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.METHOD}) 14 | public @interface SPIMethodInit { 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/annotation/SPISetter.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | @Documented 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.METHOD}) 11 | public @interface SPISetter { 12 | 13 | /** 14 | * dependency key 15 | * 16 | * @return key 17 | */ 18 | String value(); 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/constant/LogConstant.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public class LogConstant { 7 | 8 | private LogConstant() {} 9 | 10 | /** 11 | * inner log which will not trigger hook 12 | */ 13 | public static final String INNER_LOG_TAG = "ddr.log.inner.filter.tag"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/entity/Holder.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.entity; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public class Holder { 7 | 8 | private Object object; 9 | 10 | private Class clazz; 11 | 12 | public Holder() { 13 | } 14 | 15 | public Holder(Class clazz) { 16 | this.clazz = clazz; 17 | } 18 | 19 | public Holder(Object object, Class clazz) { 20 | this.object = object; 21 | this.clazz = clazz; 22 | } 23 | 24 | public Object getObject() { 25 | return object; 26 | } 27 | 28 | public void setObject(Object object) { 29 | this.object = object; 30 | } 31 | 32 | public Class getClazz() { 33 | return clazz; 34 | } 35 | 36 | public void setClazz(Class clazz) { 37 | this.clazz = clazz; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/entity/RaftConfig.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.entity; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | import com.daydreamer.raft.common.service.ActiveProperties; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author Daydreamer 10 | *

11 | * config about raft 12 | *

13 | * voteBaseTime + ramdom() < heartInterval < abnormalActiveInterval 14 | */ 15 | @SPI("raftConfig") 16 | public class RaftConfig implements ActiveProperties { 17 | 18 | /** 19 | * member ip exclude current node 20 | */ 21 | private List memberAddresses; 22 | 23 | /** 24 | * server ip 25 | */ 26 | private String serverAddr; 27 | 28 | /** 29 | * current node will tell follower to keep if current node is leader and timeout 30 | */ 31 | private int heartInterval = 1000; 32 | 33 | /** 34 | * current node will ask votes if timeout 35 | */ 36 | private int abnormalActiveInterval = 10000; 37 | 38 | /** 39 | * base interval between two elections 40 | */ 41 | private int voteBaseTime = 3000; 42 | 43 | /** 44 | * base wait time in candidate 45 | */ 46 | private int candidateStatusTimeout = 5000; 47 | 48 | /** 49 | * write fail, then retry writeRetryTimes 50 | */ 51 | private int writeRetryTimes = 2; 52 | 53 | /** 54 | * reject any write request if current node is follower 55 | */ 56 | private boolean followerRejectWrite = false; 57 | 58 | /** 59 | * the count of core thread for default thread pool 60 | * {@link com.daydreamer.raft.common.threadpool.impl.CacheThreadPoolFactory} 61 | */ 62 | private int defaultThreadPoolCoreThread = 2; 63 | 64 | /** 65 | * the count of max thread for default thread pool 66 | * {@link com.daydreamer.raft.common.threadpool.impl.CacheThreadPoolFactory} 67 | */ 68 | private int defaultThreadPoolMaxThread = 2; 69 | 70 | /** 71 | * log data dir 72 | */ 73 | private String dataDir = System.getProperty("user.home"); 74 | 75 | /** 76 | * persistent log 77 | */ 78 | private boolean persistent = false; 79 | 80 | public boolean isFollowerRejectWrite() { 81 | return followerRejectWrite; 82 | } 83 | 84 | public void setFollowerRejectWrite(boolean followerRejectWrite) { 85 | this.followerRejectWrite = followerRejectWrite; 86 | } 87 | 88 | public int getDefaultThreadPoolCoreThread() { 89 | return defaultThreadPoolCoreThread; 90 | } 91 | 92 | public void setDefaultThreadPoolCoreThread(int defaultThreadPoolCoreThread) { 93 | this.defaultThreadPoolCoreThread = defaultThreadPoolCoreThread; 94 | } 95 | 96 | public String getDataDir() { 97 | return dataDir; 98 | } 99 | 100 | public boolean isPersistent() { 101 | return persistent; 102 | } 103 | 104 | public void setPersistent(boolean persistent) { 105 | this.persistent = persistent; 106 | } 107 | 108 | public void setDataDir(String dataDir) { 109 | this.dataDir = dataDir; 110 | } 111 | 112 | public int getDefaultThreadPoolMaxThread() { 113 | return defaultThreadPoolMaxThread; 114 | } 115 | 116 | public void setDefaultThreadPoolMaxThread(int defaultThreadPoolMaxThread) { 117 | this.defaultThreadPoolMaxThread = defaultThreadPoolMaxThread; 118 | } 119 | 120 | public String getServerAddr() { 121 | return serverAddr; 122 | } 123 | 124 | public void setServerAddr(String serverAddr) { 125 | this.serverAddr = serverAddr; 126 | } 127 | 128 | public int getWriteRetryTimes() { 129 | return writeRetryTimes; 130 | } 131 | 132 | public void setWriteRetryTimes(int writeRetryTimes) { 133 | this.writeRetryTimes = writeRetryTimes; 134 | } 135 | 136 | public int getCandidateStatusTimeout() { 137 | return candidateStatusTimeout; 138 | } 139 | 140 | public void setCandidateStatusTimeout(int candidateStatusTimeout) { 141 | this.candidateStatusTimeout = candidateStatusTimeout; 142 | } 143 | 144 | public int getVoteBaseTime() { 145 | return voteBaseTime; 146 | } 147 | 148 | public void setVoteBaseTime(int voteBaseTime) { 149 | this.voteBaseTime = voteBaseTime; 150 | } 151 | 152 | public int getAbnormalActiveInterval() { 153 | return abnormalActiveInterval; 154 | } 155 | 156 | public void setAbnormalActiveInterval(int abnormalActiveInterval) { 157 | this.abnormalActiveInterval = abnormalActiveInterval; 158 | } 159 | 160 | public List getMemberAddresses() { 161 | return memberAddresses; 162 | } 163 | 164 | public void setMemberAddresses(List memberAddresses) { 165 | this.memberAddresses = memberAddresses; 166 | } 167 | 168 | public int getHeartInterval() { 169 | return heartInterval; 170 | } 171 | 172 | public void setHeartInterval(int heartInterval) { 173 | this.heartInterval = heartInterval; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/entity/SimpleFuture.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.entity; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Future; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.locks.Condition; 10 | import java.util.concurrent.locks.ReentrantLock; 11 | 12 | /** 13 | * @author Daydreamer 14 | *

15 | * future 16 | */ 17 | public class SimpleFuture implements Future { 18 | 19 | private T data; 20 | 21 | private Exception throwable; 22 | 23 | /** 24 | * single thread pool 25 | */ 26 | private ExecutorService executor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(), 27 | r -> { 28 | Thread thread = new Thread(r); 29 | thread.setName("Future-Thread"); 30 | thread.setDaemon(true); 31 | return thread; 32 | }); 33 | 34 | private volatile boolean isCancel = false; 35 | 36 | private volatile boolean finish = false; 37 | 38 | public SimpleFuture(Callable callable) { 39 | Runnable task = () -> { 40 | try { 41 | data = callable.call(); 42 | } catch (Exception e) { 43 | // nothing to do 44 | throwable = e; 45 | } 46 | finish = true; 47 | // close 48 | executor.shutdown(); 49 | }; 50 | executor.execute(task); 51 | } 52 | 53 | @Override 54 | public synchronized boolean cancel(boolean mayInterruptIfRunning) { 55 | if (isCancel) { 56 | return isCancel; 57 | } 58 | isCancel = mayInterruptIfRunning; 59 | notifyAll(); 60 | // clear 61 | executor.shutdown(); 62 | return isCancel; 63 | } 64 | 65 | @Override 66 | public boolean isCancelled() { 67 | return isCancel; 68 | } 69 | 70 | @Override 71 | public boolean isDone() { 72 | return finish; 73 | } 74 | 75 | @Override 76 | public synchronized T get() throws InterruptedException { 77 | while (!finish) { 78 | wait(200); 79 | } 80 | return data; 81 | } 82 | 83 | @Override 84 | public synchronized T get(long timeout, TimeUnit unit) throws InterruptedException { 85 | long begin = System.currentTimeMillis(); 86 | long remain = unit.toMillis(timeout); 87 | while (remain > 0 && !finish) { 88 | wait(200); 89 | remain = remain - (System.currentTimeMillis() - begin); 90 | } 91 | return data; 92 | } 93 | 94 | /** 95 | * if exception 96 | * 97 | * @return exception 98 | */ 99 | public synchronized Exception getException() throws InterruptedException { 100 | while (!finish) { 101 | wait(200); 102 | } 103 | return throwable; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/filter/LogFilter.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.filter; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.api.callback.CommitHook; 5 | import com.daydreamer.raft.common.annotation.SPI; 6 | 7 | /** 8 | * @author Daydreamer 9 | *

10 | * it will be invoked after log committed, before {@link CommitHook} 11 | */ 12 | @SPI("filterChain") 13 | public interface LogFilter { 14 | 15 | /** 16 | * filter log 17 | * 18 | * @param logEntry log 19 | * @return whether to filter current log 20 | */ 21 | boolean filter(LogEntry logEntry); 22 | } 23 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/filter/impl/FilterChain.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.filter.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.filter.LogFilter; 7 | import com.daydreamer.raft.common.loader.GroupAware; 8 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @SPIImplement("filterChain") 14 | public class FilterChain implements LogFilter, GroupAware { 15 | 16 | private final List filters = new ArrayList<>(); 17 | 18 | private String groupKey; 19 | 20 | @SPIMethodInit 21 | private void init() { 22 | List all = RaftServiceLoader.getLoader(groupKey, LogFilter.class).getAll(); 23 | if (all != null) { 24 | filters.addAll(all); 25 | filters.remove(this); 26 | } 27 | } 28 | 29 | @Override 30 | public void setGroupKey(String key) { 31 | this.groupKey = key; 32 | } 33 | 34 | @Override 35 | public boolean filter(LogEntry logEntry) { 36 | for (LogFilter filter : filters) { 37 | // if anyone return false, then reject 38 | if (!filter.filter(logEntry)) { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/loader/GroupAware.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.loader; 2 | 3 | /** 4 | * @author Daydreamer 5 | *

6 | * In order to get group key 7 | */ 8 | public interface GroupAware { 9 | 10 | void setGroupKey(String key); 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/loader/ServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.loader; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | @SPI("adaptiveServiceFactory") 9 | public interface ServiceFactory extends GroupAware{ 10 | 11 | T getDependency(Class type, String name); 12 | } 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/loader/impl/AdaptiveServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.loader.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 5 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 6 | import com.daydreamer.raft.common.loader.ServiceFactory; 7 | import java.util.List; 8 | 9 | /** 10 | * @author Daydreamer 11 | */ 12 | @SPIImplement("adaptiveServiceFactory") 13 | public class AdaptiveServiceFactory implements ServiceFactory { 14 | 15 | private List serviceFactories; 16 | 17 | private String groupKey; 18 | 19 | public AdaptiveServiceFactory() { 20 | 21 | } 22 | 23 | @SPIMethodInit 24 | private void init() { 25 | RaftServiceLoader loader = RaftServiceLoader.getLoader(groupKey, ServiceFactory.class); 26 | serviceFactories = loader.getAll(); 27 | // remove current 28 | serviceFactories.remove(this); 29 | } 30 | 31 | @Override 32 | public T getDependency(Class type, String name) { 33 | for (ServiceFactory serviceFactory : serviceFactories) { 34 | T dependency = serviceFactory.getDependency(type, name); 35 | if (dependency != null) { 36 | return dependency; 37 | } 38 | } 39 | return null; 40 | } 41 | 42 | @Override 43 | public void setGroupKey(String key) { 44 | this.groupKey = key; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/loader/impl/ConfigServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.loader.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.common.loader.ServiceFactory; 5 | import com.daydreamer.raft.common.service.ActiveProperties; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * @author Daydreamer 12 | */ 13 | @SPIImplement("configServiceFactory") 14 | public class ConfigServiceFactory implements ServiceFactory { 15 | 16 | private final Map map = new ConcurrentHashMap<>(); 17 | 18 | private String groupKey; 19 | 20 | @Override 21 | public T getDependency(Class type, String name) { 22 | if (map.containsKey(name) && map.get(name).getClass().equals(type)) { 23 | return type.cast(map.get(name)); 24 | } 25 | return null; 26 | } 27 | 28 | /** 29 | * add property 30 | * 31 | * @param name property name 32 | * @param properties property 33 | */ 34 | public void addProperty(String name, ActiveProperties properties) { 35 | map.putIfAbsent(name, properties); 36 | } 37 | 38 | @Override 39 | public void setGroupKey(String key) { 40 | this.groupKey = key; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/loader/impl/SPIServiceFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.loader.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.common.loader.GroupAware; 5 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 6 | import com.daydreamer.raft.common.loader.ServiceFactory; 7 | 8 | import java.lang.reflect.Modifier; 9 | 10 | /** 11 | * @author Daydreamer 12 | */ 13 | @SPIImplement("spiServiceFactory") 14 | public class SPIServiceFactory implements ServiceFactory { 15 | 16 | private String groupKey; 17 | 18 | @Override 19 | public T getDependency(Class type, String name) { 20 | // if interface or abstract class, then try to find 21 | if (Modifier.isInterface(type.getModifiers()) 22 | || Modifier.isAbstract(type.getModifiers())) { 23 | return RaftServiceLoader.getLoader(groupKey, type).getInstance(name); 24 | } 25 | // no found 26 | return null; 27 | } 28 | 29 | @Override 30 | public void setGroupKey(String key) { 31 | this.groupKey = key; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/service/ActiveProperties.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.service; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public interface ActiveProperties { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/service/PropertiesReader.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.service; 2 | 3 | import com.daydreamer.raft.common.utils.MD5Utils; 4 | import org.apache.log4j.Logger; 5 | 6 | import java.io.BufferedInputStream; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.Objects; 12 | import java.util.Properties; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.LinkedBlockingQueue; 15 | import java.util.concurrent.ThreadPoolExecutor; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * @author Daydreamer 20 | */ 21 | public abstract class PropertiesReader { 22 | 23 | private static final Logger LOGGER = Logger.getLogger(PropertiesReader.class); 24 | 25 | /** 26 | * target file 27 | */ 28 | private String filePath; 29 | 30 | /** 31 | * executor 32 | */ 33 | private ExecutorService executorService; 34 | 35 | /** 36 | * properties loader 37 | */ 38 | private T properties; 39 | 40 | /** 41 | * file hash 42 | */ 43 | private String hash = ""; 44 | 45 | /** 46 | * whether open 47 | */ 48 | private boolean open; 49 | 50 | public PropertiesReader(String filePath, T properties, boolean open) { 51 | this.filePath = filePath; 52 | this.properties = properties; 53 | this.open = open; 54 | if (this.open) { 55 | executorService = new ThreadPoolExecutor(1, 1, 1000, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(), r -> { 56 | Thread thread = new Thread(r); 57 | thread.setDaemon(true); 58 | thread.setName("Watch-Dog-Thread-For-File: " + filePath); 59 | thread.setUncaughtExceptionHandler((t, e) -> { 60 | LOGGER.error("Fail to execute watch job, because: " + e.getLocalizedMessage()); 61 | }); 62 | return thread; 63 | }); 64 | // init 65 | init(); 66 | } 67 | } 68 | 69 | public boolean isOpen() { 70 | return open; 71 | } 72 | 73 | /** 74 | * init method 75 | */ 76 | private void init() { 77 | // load 78 | load(); 79 | // add job 80 | executorService.execute(this::changeDetectJob); 81 | } 82 | 83 | /** 84 | * load properties 85 | * 86 | * @return properties 87 | */ 88 | private Properties load() { 89 | Properties p = new Properties(); 90 | InputStream in = null; 91 | try { 92 | // load 93 | File file = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(filePath)).getFile()); 94 | if (!file.exists()) { 95 | throw new IllegalArgumentException("[PropertiesReader] - Cannot find property file, bad path: " + filePath); 96 | } 97 | in = new BufferedInputStream(new FileInputStream(file)); 98 | p.load(in); 99 | // populate 100 | populateProperties(p, properties); 101 | // calculate hash 102 | hash = MD5Utils.getFileMD5String(file); 103 | } catch (Exception e) { 104 | LOGGER.error("Fail to load properties, because: " + e.getLocalizedMessage()); 105 | } finally { 106 | if (in != null) { 107 | try { 108 | in.close(); 109 | } catch (IOException e) { 110 | // nothing to do 111 | } 112 | } 113 | } 114 | return p; 115 | } 116 | 117 | 118 | /** 119 | * watch job 120 | */ 121 | private void changeDetectJob() { 122 | File file = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(filePath)).getFile()); 123 | if (!file.exists()) { 124 | throw new IllegalStateException("[PropertiesReader] - Properties loss, file name: " + filePath); 125 | } 126 | try { 127 | String strHash = MD5Utils.getFileMD5String(file); 128 | // if change 129 | if (!strHash.equals(hash)) { 130 | populateProperties(load(), properties); 131 | } 132 | } catch (IOException e) { 133 | throw new IllegalStateException("[PropertiesReader] - Properties loss, file name: " + filePath); 134 | } 135 | } 136 | 137 | /** 138 | * properties 139 | * 140 | * @return properties 141 | */ 142 | public T getProperties() { 143 | return properties; 144 | } 145 | 146 | /** 147 | * close 148 | */ 149 | public void close() { 150 | if (executorService != null) { 151 | executorService.shutdown(); 152 | } 153 | } 154 | 155 | /** 156 | * base on the properties to do some 157 | * 158 | * @param properties properties 159 | * @param activeProperties active properties 160 | */ 161 | public abstract void populateProperties(Properties properties, T activeProperties); 162 | } 163 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/threadpool/ThreadPoolFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.threadpool; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | 5 | import java.util.concurrent.Executor; 6 | 7 | /** 8 | * @author Daydreamer 9 | */ 10 | @SPI("adaptiveThreadPoolFactory") 11 | public interface ThreadPoolFactory { 12 | 13 | /** 14 | * max priority 15 | */ 16 | int MAX_PRIORITY = Integer.MAX_VALUE; 17 | 18 | /** 19 | * min priority 20 | */ 21 | int MIN_PRIORITY = Integer.MIN_VALUE; 22 | 23 | /** 24 | * get {@link Executor} by key 25 | * 26 | * @param key key 27 | * @return Executor 28 | */ 29 | Executor getExecutor(Object key); 30 | 31 | /** 32 | * the larger order, the faster invoke 33 | * 34 | * @return order 35 | */ 36 | int getOrder(); 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/threadpool/impl/AdaptiveThreadPoolFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.threadpool.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 5 | import com.daydreamer.raft.common.loader.GroupAware; 6 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 7 | import com.daydreamer.raft.common.threadpool.ThreadPoolFactory; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | import java.util.concurrent.Executor; 13 | 14 | /** 15 | * @author Daydreamer 16 | */ 17 | @SPIImplement("adaptiveThreadPoolFactory") 18 | public class AdaptiveThreadPoolFactory implements ThreadPoolFactory, GroupAware { 19 | 20 | private final List threadPoolFactories = new ArrayList<>(); 21 | 22 | private String groupKey; 23 | 24 | @SPIMethodInit 25 | private void init() { 26 | // add all thread pool 27 | List all = RaftServiceLoader 28 | .getLoader(groupKey, ThreadPoolFactory.class) 29 | .getAll(); 30 | threadPoolFactories.addAll(all); 31 | threadPoolFactories.remove(this); 32 | threadPoolFactories.sort(Comparator.comparingInt(ThreadPoolFactory::getOrder)); 33 | } 34 | 35 | @Override 36 | public Executor getExecutor(Object key) { 37 | for (ThreadPoolFactory threadPoolFactory : threadPoolFactories) { 38 | Executor executor = threadPoolFactory.getExecutor(key); 39 | if (executor != null) { 40 | return executor; 41 | } 42 | } 43 | return null; 44 | } 45 | 46 | @Override 47 | public int getOrder() { 48 | return ThreadPoolFactory.MAX_PRIORITY; 49 | } 50 | 51 | public void setGroupKey(String groupKey) { 52 | this.groupKey = groupKey; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/threadpool/impl/CacheThreadPoolFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.threadpool.impl; 2 | 3 | import com.daydreamer.raft.api.collection.MemorySafeLinkedBlockingQueue; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.entity.RaftConfig; 6 | import com.daydreamer.raft.common.threadpool.ThreadPoolFactory; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.Executor; 11 | import java.util.concurrent.ThreadPoolExecutor; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | @SPIImplement("cacheThreadPoolFactory") 15 | public class CacheThreadPoolFactory implements ThreadPoolFactory { 16 | 17 | private Map executors = new ConcurrentHashMap<>(); 18 | 19 | private RaftConfig raftConfig; 20 | 21 | public CacheThreadPoolFactory() { 22 | // add hook method to shut down all 23 | Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown)); 24 | } 25 | 26 | @Override 27 | public Executor getExecutor(Object key) { 28 | Executor executor = executors.get(key); 29 | if (executor == null) { 30 | executor = new ThreadPoolExecutor(raftConfig.getDefaultThreadPoolCoreThread(), 31 | raftConfig.getDefaultThreadPoolMaxThread(), 32 | Integer.MAX_VALUE, TimeUnit.MICROSECONDS, 33 | new MemorySafeLinkedBlockingQueue<>(), 34 | (run) -> { 35 | Thread thread = new Thread(run); 36 | thread.setName("[CacheThreadPool] - Thread: " + thread.hashCode()); 37 | return thread; 38 | }, new ThreadPoolExecutor.AbortPolicy()); 39 | executors.putIfAbsent(key, executor); 40 | } 41 | return executors.get(key); 42 | } 43 | 44 | @Override 45 | public int getOrder() { 46 | return ThreadPoolFactory.MIN_PRIORITY; 47 | } 48 | 49 | private void shutdown() { 50 | executors.values().forEach((e) -> { 51 | if (e instanceof ThreadPoolExecutor) { 52 | ((ThreadPoolExecutor) e).shutdown(); 53 | } 54 | }); 55 | } 56 | 57 | public void setRaftConfig(RaftConfig activeProperties) { 58 | this.raftConfig = activeProperties; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/utils/MD5Utils.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.utils; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.nio.MappedByteBuffer; 7 | import java.nio.channels.FileChannel; 8 | import java.security.MessageDigest; 9 | import java.security.NoSuchAlgorithmException; 10 | import org.apache.log4j.Logger; 11 | 12 | /** 13 | * @author Daydreamer 14 | */ 15 | public class MD5Utils { 16 | 17 | private static final Logger LOGGER = Logger.getLogger(MD5Utils.class); 18 | 19 | private MD5Utils() { 20 | } 21 | 22 | private static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 23 | 24 | private static MessageDigest messagedigest = null; 25 | 26 | static { 27 | try { 28 | messagedigest = MessageDigest.getInstance("MD5"); 29 | } catch (NoSuchAlgorithmException nsaex) { 30 | LOGGER.error("Not support MD5 algorithm, cannot detect file change!"); 31 | } 32 | } 33 | 34 | public static String getFileMD5String(File file) throws IOException { 35 | FileInputStream in = new FileInputStream(file); 36 | FileChannel ch = in.getChannel(); 37 | MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); 38 | messagedigest.update(byteBuffer); 39 | return bufferToHex(messagedigest.digest()); 40 | } 41 | 42 | private static String bufferToHex(byte bytes[]) { 43 | return bufferToHex(bytes, 0, bytes.length); 44 | } 45 | 46 | private static String bufferToHex(byte bytes[], int m, int n) { 47 | StringBuffer stringbuffer = new StringBuffer(2 * n); 48 | int k = m + n; 49 | for (int l = m; l < k; l++) { 50 | appendHexPair(bytes[l], stringbuffer); 51 | } 52 | return stringbuffer.toString(); 53 | } 54 | 55 | private static void appendHexPair(byte bt, StringBuffer stringbuffer) { 56 | char c0 = hexDigits[(bt & 0xf0) >> 4]; 57 | char c1 = hexDigits[bt & 0xf]; 58 | stringbuffer.append(c0); 59 | stringbuffer.append(c1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /common/src/main/java/com/daydreamer/raft/common/utils/MsgUtils.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.common.utils; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.api.grpc.Message; 6 | import com.google.gson.Gson; 7 | 8 | import java.io.File; 9 | import java.io.Serializable; 10 | import java.util.Map; 11 | import java.util.Objects; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * @author Daydreamer 16 | */ 17 | public class MsgUtils { 18 | 19 | private static final String REQUEST_PACKAGE = "com/daydreamer/raft/api/entity/request/"; 20 | 21 | private static final String RESPONSE_PACKAGE = "com/daydreamer/raft/api/entity/response/"; 22 | 23 | private static final String CLASS_FORMAT = ".class"; 24 | 25 | private static final String FILE_SEPARATOR = "/"; 26 | 27 | private static final String EMPTY = ""; 28 | 29 | private static final String PACKAGE_SEPARATOR = "."; 30 | 31 | private static final Map> ENTITY_MAP = new ConcurrentHashMap<>(); 32 | 33 | static { 34 | // load request 35 | load(REQUEST_PACKAGE); 36 | // load response 37 | load(RESPONSE_PACKAGE); 38 | } 39 | 40 | /** 41 | * load entity type from package 42 | * 43 | * @param path base package 44 | */ 45 | public static void load(String path) { 46 | try { 47 | String classPre = path.replaceAll(FILE_SEPARATOR, PACKAGE_SEPARATOR); 48 | // load instance 49 | ClassLoader classLoader = MsgUtils.class.getClassLoader(); 50 | File file = new File(Objects.requireNonNull(classLoader.getResource(path)).getFile()); 51 | File[] files = file.listFiles(); 52 | if (files != null) { 53 | for (File child : files) { 54 | String clazzName = classPre + child.getName().replace(CLASS_FORMAT, EMPTY); 55 | Class clazz = Class.forName(clazzName); 56 | ENTITY_MAP.put(clazzName, clazz); 57 | } 58 | } 59 | } catch (Exception e) { 60 | throw new IllegalStateException("Can not load base entity, because ", e); 61 | } 62 | } 63 | 64 | public static Request convertRequest(Message message) { 65 | String type = message.getType(); 66 | Class targetClazz = ENTITY_MAP.get(type); 67 | return (Request) convert(message, targetClazz); 68 | } 69 | 70 | public static Response convertResponse(Message message) { 71 | String type = message.getType(); 72 | Class targetClazz = ENTITY_MAP.get(type); 73 | return (Response) convert(message, targetClazz); 74 | } 75 | 76 | private static Object convert(Message message, Class targetClazz) { 77 | String data = message.getData(); 78 | try { 79 | Gson gson = new Gson(); 80 | return gson.fromJson(data, targetClazz); 81 | } catch (Exception e) { 82 | throw new IllegalArgumentException("Cannot covert instance, because " + e.getLocalizedMessage()); 83 | } 84 | } 85 | 86 | public static Message convertMsg(Serializable serializable) { 87 | Gson gson = new Gson(); 88 | String data = gson.toJson(serializable); 89 | String type = serializable.getClass().getName(); 90 | return Message.newBuilder().setData(data).setType(type).build(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/raft/com.daydreamer.raft.common.filter.LogFilter: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.common.filter.impl.FilterChain -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/raft/com.daydreamer.raft.common.loader.ServiceFactory: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.common.loader.impl.SPIServiceFactory 2 | com.daydreamer.raft.common.loader.impl.AdaptiveServiceFactory 3 | com.daydreamer.raft.common.loader.impl.ConfigServiceFactory -------------------------------------------------------------------------------- /common/src/main/resources/META-INF/raft/com.daydreamer.raft.common.threadpool.ThreadPoolFactory: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.common.threadpool.impl.AdaptiveThreadPoolFactory 2 | com.daydreamer.raft.common.threadpool.impl.CacheThreadPoolFactory -------------------------------------------------------------------------------- /common/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | ### set log levels ### 2 | log4j.rootLogger = info,stdout,D,E 3 | 4 | ### console ### 5 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 6 | log4j.appender.stdout.Target = System.out 7 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 8 | log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %p [%c] %m%n 9 | 10 | ### log file ### 11 | log4j.appender.D = org.apache.log4j.DailyRollingFileAppender 12 | log4j.appender.D.File = logs/log.log 13 | log4j.appender.D.Append = true 14 | log4j.appender.D.Threshold = DEBUG 15 | log4j.appender.D.layout = org.apache.log4j.PatternLayout 16 | log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n 17 | 18 | ### save in file if error ### 19 | log4j.appender.E = org.apache.log4j.DailyRollingFileAppender 20 | log4j.appender.E.File = logs/error.log 21 | log4j.appender.E.Append = true 22 | log4j.appender.E.Threshold = ERROR 23 | log4j.appender.E.layout = org.apache.log4j.PatternLayout 24 | log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | raft 7 | org.daydreamer 8 | 1.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | example 13 | ${ddr-raft.version} 14 | 15 | 16 | 17 | protocol 18 | org.daydreamer 19 | ${ddr-raft.version} 20 | 21 | 22 | -------------------------------------------------------------------------------- /example/src/main/java/com/daydreamer/raft/example/LeaderElectionExample.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.example; 2 | 3 | import com.daydreamer.raft.protocol.core.Protocol; 4 | import com.daydreamer.raft.protocol.core.impl.RaftProtocol; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | /** 11 | * @author Daydreamer 12 | */ 13 | public class LeaderElectionExample { 14 | 15 | public static void main(String[] args) throws InterruptedException { 16 | /* 17 | * start three application with different config to simulate cluster 18 | * then they will elect for leader 19 | */ 20 | List serverList = Stream 21 | .of(new RaftProtocol("example-server0.properties"), 22 | new RaftProtocol("example-server1.properties"), 23 | new RaftProtocol("example-server2.properties")) 24 | .collect(Collectors.toList()); 25 | // start all 26 | serverList.forEach(Protocol::run); 27 | Thread.sleep(120 * 1000); 28 | // stop all 29 | serverList.forEach(Protocol::close); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/src/main/java/com/daydreamer/raft/example/LogAppendExample.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.example; 2 | 3 | import com.daydreamer.raft.api.entity.base.Payload; 4 | import com.daydreamer.raft.api.entity.constant.LogType; 5 | import com.daydreamer.raft.protocol.core.Protocol; 6 | import com.daydreamer.raft.protocol.core.impl.RaftProtocol; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.UUID; 12 | import java.util.stream.Collectors; 13 | import java.util.stream.Stream; 14 | 15 | /** 16 | * @author Daydreamer 17 | */ 18 | public class LogAppendExample { 19 | 20 | public static void main(String[] args) throws InterruptedException { 21 | List cluster = getCluster(); 22 | // add listener for each server 23 | for (Protocol protocol : cluster) { 24 | protocol.addListener(UUID.randomUUID().toString(), 25 | logEntry -> System.out.println("Apply Log: " + logEntry)); 26 | } 27 | // start 28 | cluster.forEach(Protocol::run); 29 | // append log if wait until 30 sec 30 | Thread appendLogThread = new Thread(getTask(cluster)); 31 | appendLogThread.start(); 32 | // wait for appending 33 | Thread.sleep(60 * 1000); 34 | // close 35 | cluster.forEach(Protocol::close); 36 | } 37 | 38 | /** 39 | * job to append log 40 | * 41 | * @return task 42 | */ 43 | public static Runnable getTask(List protocols) { 44 | return () -> { 45 | try { 46 | // wait for cluster ready (to do leader election) 47 | Thread.sleep(30 * 1000); 48 | } catch (InterruptedException e) { 49 | // nothing to do 50 | } 51 | System.out.println("Try to write!"); 52 | // try to write 53 | for (Protocol protocol : protocols) { 54 | // find leader 55 | try { 56 | Map metadata = new HashMap<>(1); 57 | metadata.put("key", "Hello raft!"); 58 | boolean write = protocol.write(new Payload(metadata, LogType.WRITE)); 59 | System.out.println("write result:" + write); 60 | } catch (IllegalStateException e) { 61 | // it not leader 62 | } catch (Exception e) { 63 | // nothing to do 64 | } 65 | } 66 | }; 67 | } 68 | 69 | /** 70 | * get cluster 71 | * 72 | * @return cluster 73 | */ 74 | public static List getCluster() { 75 | /* 76 | * start three application with different config to simulate cluster 77 | * then they will elect for leader 78 | */ 79 | return Stream.of(new RaftProtocol("example-server0.properties"), 80 | new RaftProtocol("example-server1.properties"), 81 | new RaftProtocol("example-server2.properties")) 82 | .collect(Collectors.toList()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/src/main/java/com/daydreamer/raft/example/MemberChangeExample.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.example; 2 | 3 | import com.daydreamer.raft.api.entity.base.MemberChangeEntry; 4 | import com.daydreamer.raft.api.entity.constant.MemberChange; 5 | import com.daydreamer.raft.protocol.core.Protocol; 6 | import com.daydreamer.raft.protocol.core.impl.RaftProtocol; 7 | import com.daydreamer.raft.common.entity.RaftConfig; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | /** 15 | * @author Daydreamer 16 | */ 17 | public class MemberChangeExample { 18 | 19 | public static void main(String[] args) throws InterruptedException { 20 | List protocols = createRaftProtocol(); 21 | protocols.forEach(Protocol::run); 22 | Thread thread = new Thread(getTask(protocols)); 23 | thread.start(); 24 | // wait for member changing, remove node: 127.0.0.1:17999 25 | Thread.sleep(60 * 1000); 26 | protocols.forEach(Protocol::close); 27 | } 28 | 29 | /** 30 | * job to append log 31 | * 32 | * @return task 33 | */ 34 | public static Runnable getTask(List protocols) { 35 | return () -> { 36 | try { 37 | // wait for cluster ready (to do leader election) 38 | Thread.sleep(30 * 1000); 39 | } catch (InterruptedException e) { 40 | // nothing to do 41 | } 42 | System.out.println("Try to change member, remove server: 127.0.0.1:17999"); 43 | // try to write 44 | for (Protocol protocol : protocols) { 45 | // find leader 46 | try { 47 | MemberChangeEntry action = new MemberChangeEntry("127.0.0.1:17999", MemberChange.REMOVE); 48 | boolean success = protocol.memberChange(action); 49 | System.out.println("operation result: " + success); 50 | } catch (IllegalStateException e) { 51 | // it not leader 52 | } catch (Exception e) { 53 | // nothing to do 54 | } 55 | } 56 | }; 57 | } 58 | 59 | public static List createRaftProtocol() { 60 | List protocols = new ArrayList<>(); 61 | List raftConfig = createRaftConfig(); 62 | for (RaftConfig config : raftConfig) { 63 | protocols.add(new RaftProtocol(config)); 64 | } 65 | return protocols; 66 | } 67 | 68 | 69 | public static List createRaftConfig() { 70 | // server01 71 | RaftConfig raftConfig1 = new RaftConfig(); 72 | raftConfig1.setServerAddr("127.0.0.1:17999"); 73 | raftConfig1.setMemberAddresses(Stream.of("127.0.0.1:18999", "127.0.0.1:19999").collect(Collectors.toList())); 74 | // server02 75 | RaftConfig raftConfig2 = new RaftConfig(); 76 | raftConfig2.setServerAddr("127.0.0.1:18999"); 77 | raftConfig2.setMemberAddresses(Stream.of("127.0.0.1:17999", "127.0.0.1:19999").collect(Collectors.toList())); 78 | // server03 79 | RaftConfig raftConfig3 = new RaftConfig(); 80 | raftConfig3.setServerAddr("127.0.0.1:19999"); 81 | raftConfig3.setMemberAddresses(Stream.of("127.0.0.1:17999", "127.0.0.1:18999").collect(Collectors.toList())); 82 | return Stream.of(raftConfig1, raftConfig2,raftConfig3).collect(Collectors.toList()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/src/main/resources/example-server0.properties: -------------------------------------------------------------------------------- 1 | # server port 2 | ddr.raft.server-address=127.0.0.1:18899 3 | # heartbeat internal between leader and followers 4 | ddr.raft.heartInterval=1000 5 | # cluster member exclude current node 6 | ddr.raft.members-addresses=127.0.0.1:17799, 127.0.0.1:19999 7 | # heartbeat internal between leader and followers too long, follower will try to re-elect 8 | ddr.raft.leader-inactive-timeout=3000 9 | # interval between two elections, just base time not real time 10 | ddr.raft.voteBaseTime=2000 11 | # candidate will back status to follower if timeout 12 | ddr.raft.candidate-wait-timeout=3000 13 | # whether to persist log to disk, default false 14 | # ddr.raft.persistent-log=false 15 | # log persistence dir, default ${user.home} 16 | # ddr.raft.data.dir= 17 | 18 | -------------------------------------------------------------------------------- /example/src/main/resources/example-server1.properties: -------------------------------------------------------------------------------- 1 | # server port 2 | ddr.raft.server-address=127.0.0.1:17799 3 | # heartbeat internal between leader and followers 4 | ddr.raft.heartInterval=1000 5 | # cluster member exclude current node 6 | ddr.raft.members-addresses=127.0.0.1:18899, 127.0.0.1:19999 7 | # heartbeat internal between leader and followers too long, follower will try to re-elect 8 | ddr.raft.leader-inactive-timeout=3000 9 | # interval between two elections, just base time not real time 10 | ddr.raft.voteBaseTime=2000 11 | # candidate will back status to follower if timeout 12 | ddr.raft.candidate-wait-timeout=3000 13 | # whether to persist log to disk, default false 14 | # ddr.raft.persistent-log=false 15 | # log persistence dir, default ${user.home} 16 | # ddr.raft.data.dir= -------------------------------------------------------------------------------- /example/src/main/resources/example-server2.properties: -------------------------------------------------------------------------------- 1 | # server port 2 | ddr.raft.server-address=127.0.0.1:19999 3 | # heartbeat internal between leader and followers 4 | ddr.raft.heartInterval=1000 5 | # cluster member exclude current node 6 | ddr.raft.members-addresses=127.0.0.1:17799, 127.0.0.1:18899 7 | # heartbeat internal between leader and followers too long, follower will try to re-elect 8 | ddr.raft.leader-inactive-timeout=3000 9 | # interval between two elections, just base time not real time 10 | ddr.raft.voteBaseTime=2000 11 | # candidate will back status to follower if timeout 12 | ddr.raft.candidate-wait-timeout=3000 13 | # whether to persist log to disk, default false 14 | # ddr.raft.persistent-log=false 15 | # log persistence dir, default ${user.home} 16 | # ddr.raft.data.dir= -------------------------------------------------------------------------------- /persistence/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.daydreamer 8 | raft 9 | 1.1-SNAPSHOT 10 | 11 | 12 | persistence 13 | ${ddr-raft.version} 14 | 15 | 16 | 11 17 | 11 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.daydreamer 24 | common 25 | ${ddr-raft.version} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /persistence/src/main/java/org/daydreamer/persistence/PersistenceService.java: -------------------------------------------------------------------------------- 1 | package org.daydreamer.persistence; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPI; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author Daydreamer 10 | *

11 | * log storage 12 | */ 13 | @SPI("fileSystem") 14 | public interface PersistenceService { 15 | 16 | /** 17 | * write lof if commit 18 | * 19 | * @param logEntry committed log 20 | */ 21 | boolean write(LogEntry logEntry); 22 | 23 | /** 24 | * read all committed log 25 | * 26 | * @return all committed logs 27 | */ 28 | List read(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /persistence/src/main/java/org/daydreamer/persistence/impl/FileSystemPersistence.java: -------------------------------------------------------------------------------- 1 | package org.daydreamer.persistence.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.entity.RaftConfig; 7 | import org.daydreamer.persistence.PersistenceService; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.*; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.LinkedList; 15 | import java.util.List; 16 | 17 | @SPIImplement("fileSystem") 18 | public class FileSystemPersistence implements PersistenceService { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemPersistence.class); 21 | 22 | /** 23 | * config 24 | */ 25 | private RaftConfig raftConfig; 26 | 27 | private static final String DIR_FILE = "ddr_raft"; 28 | 29 | private static final String LOG_STORAGE_FILE = "ddr_raft_log_data"; 30 | 31 | private ObjectOutputStream logAppendStream; 32 | 33 | /** 34 | * whether enable persistence 35 | */ 36 | private boolean enabled; 37 | 38 | @SPIMethodInit 39 | private void init() { 40 | try { 41 | // create if not exist 42 | enabled = raftConfig.isPersistent(); 43 | // load 44 | if (enabled) { 45 | File baseDir = new File(raftConfig.getDataDir()); 46 | if (!baseDir.exists()) { 47 | throw new IllegalArgumentException("Not specify the data dir or data dir is not exists!"); 48 | } 49 | // create dir 50 | File dataDir = new File(raftConfig.getDataDir() + File.separator + DIR_FILE); 51 | if (!dataDir.exists()) { 52 | createNewDir(dataDir); 53 | } 54 | String address = raftConfig.getServerAddr().replace(":", "_"); 55 | File dataFile = new File(raftConfig.getDataDir() + File.separator + DIR_FILE 56 | + File.separator + LOG_STORAGE_FILE + "_" + address); 57 | if (!dataFile.exists()) { 58 | createNewFile(dataFile); 59 | } 60 | // append to log file 61 | logAppendStream = new ObjectOutputStream(new FileOutputStream(dataFile, true)); 62 | } 63 | } catch (Exception e) { 64 | throw new IllegalStateException(e); 65 | } 66 | } 67 | 68 | public void createNewFile(File file) throws IOException { 69 | boolean newFile = file.createNewFile(); 70 | if (!newFile) { 71 | throw new IllegalStateException("Cannot create file for log storage!"); 72 | } 73 | } 74 | 75 | public void createNewDir(File file) { 76 | boolean mkdir = file.mkdir(); 77 | if (!mkdir) { 78 | throw new IllegalStateException("Cannot create dir for data dir!"); 79 | } 80 | } 81 | 82 | @Override 83 | public boolean write(LogEntry logEntry) { 84 | // if persistent 85 | try { 86 | if (this.enabled) { 87 | logAppendStream.writeObject(logEntry); 88 | logAppendStream.flush(); 89 | } 90 | return true; 91 | } catch (Exception e) { 92 | LOGGER.error("Fail to persist log: {} to disk", logEntry); 93 | } 94 | return false; 95 | } 96 | 97 | @Override 98 | public List read() { 99 | LinkedList res = new LinkedList<>(); 100 | if (this.enabled) { 101 | try { 102 | String address = raftConfig.getServerAddr().replace(":", "_"); 103 | File dataFile = new File(raftConfig.getDataDir() + File.separator + DIR_FILE 104 | + File.separator + LOG_STORAGE_FILE + "_" + address); 105 | ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(dataFile)); 106 | while (true) { 107 | try { 108 | Object o = objectInputStream.readObject(); 109 | if (o instanceof LogEntry) { 110 | res.addLast((LogEntry) o); 111 | } 112 | } catch (EOFException e) { 113 | // nothing to load 114 | return res; 115 | } 116 | } 117 | } catch (Exception e) { 118 | LOGGER.error("Fail to load log snapshot file, because {}", e.getLocalizedMessage()); 119 | return Collections.emptyList(); 120 | } 121 | } 122 | return res; 123 | } 124 | 125 | public void setRaftConfig(RaftConfig raftConfig) { 126 | this.raftConfig = raftConfig; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /persistence/src/main/resources/META-INF/raft/org.daydreamer.persistence.PersistenceService: -------------------------------------------------------------------------------- 1 | org.daydreamer.persistence.impl.FileSystemPersistence -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.daydreamer 8 | raft 9 | pom 10 | 1.1-SNAPSHOT 11 | 12 | 13 | transport 14 | common 15 | protocol 16 | example 17 | api 18 | persistence 19 | 20 | 21 | 22 | 1.8 23 | 1.8 24 | 1.1-SNAPSHOT 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-compiler-plugin 32 | 3.1 33 | 34 | ${maven.compiler.target} 35 | ${maven.compiler.source} 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /protocol/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | raft 7 | org.daydreamer 8 | 1.1-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | protocol 13 | ${ddr-raft.version} 14 | 15 | 16 | 17 | org.daydreamer 18 | common 19 | ${ddr-raft.version} 20 | 21 | 22 | 23 | org.daydreamer 24 | transport 25 | ${ddr-raft.version} 26 | 27 | 28 | 29 | org.daydreamer 30 | persistence 31 | ${ddr-raft.version} 32 | 33 | 34 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/chain/LogPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.chain; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPI; 5 | 6 | /** 7 | * @author Daydreamer 8 | * 9 | * LogEntry -append-> LogPostProcessor::handleAfterAppend -commit-> LogPostProcessor::handleAfterCommit 10 | */ 11 | @SPI("logPostProcessor") 12 | public interface LogPostProcessor { 13 | 14 | /** 15 | * post process log entry 16 | * 17 | * @param logEntry log entry 18 | * @return whether continue to append 19 | */ 20 | default boolean handleBeforeAppend(LogEntry logEntry) { 21 | return true; 22 | } 23 | 24 | /** 25 | * post process log entry 26 | * 27 | * @param logEntry log entry 28 | */ 29 | default void handleAfterAppend(LogEntry logEntry) { 30 | 31 | } 32 | 33 | /** 34 | * post process log entry 35 | * 36 | * @param logEntry log entry 37 | */ 38 | default void handleAfterCommit(LogEntry logEntry) { 39 | 40 | } 41 | 42 | /** 43 | * post process log entry 44 | * 45 | * @param logEntry log entry 46 | * @return whether continue to commit 47 | */ 48 | default boolean handleBeforeCommit(LogEntry logEntry) { 49 | return true; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/chain/LogPostProcessorHolder.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.chain; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.loader.GroupAware; 7 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import java.util.*; 11 | 12 | /** 13 | * @author Daydreamer 14 | *

15 | * registry for {@link LogPostProcessor} 16 | */ 17 | @SPIImplement("logPostProcessor") 18 | public class LogPostProcessorHolder implements LogPostProcessor, GroupAware { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(LogPostProcessorHolder.class); 21 | 22 | private final List postProcessors = new ArrayList<>(); 23 | 24 | private String groupKey; 25 | 26 | public LogPostProcessorHolder() { 27 | 28 | } 29 | 30 | @SPIMethodInit 31 | private void init() { 32 | List all = RaftServiceLoader.getLoader(groupKey, LogPostProcessor.class).getAll(); 33 | postProcessors.addAll(all); 34 | postProcessors.remove(this); 35 | } 36 | 37 | /** 38 | * get all processors 39 | * 40 | * @return all processors 41 | */ 42 | public List getPostProcessors() { 43 | return Collections.unmodifiableList(postProcessors); 44 | } 45 | 46 | /** 47 | * register new processor 48 | * 49 | * @param logPostProcessor new processor 50 | */ 51 | public synchronized void register(LogPostProcessor logPostProcessor) { 52 | postProcessors.add(logPostProcessor); 53 | } 54 | 55 | @Override 56 | public boolean handleBeforeAppend(LogEntry logEntry) { 57 | boolean continueNext = true; 58 | for (int i = 0; continueNext && i < postProcessors.size(); i++) { 59 | try { 60 | continueNext = postProcessors.get(i).handleBeforeAppend(logEntry); 61 | } catch (Exception e) { 62 | LOGGER.error("Fail to handle before appending, because {}, log: {}", e.getMessage(), logEntry); 63 | } 64 | } 65 | return continueNext; 66 | } 67 | 68 | @Override 69 | public void handleAfterAppend(LogEntry logEntry) { 70 | for (LogPostProcessor postProcessor : postProcessors) { 71 | try { 72 | postProcessor.handleBeforeAppend(logEntry); 73 | } catch (Exception e) { 74 | LOGGER.error("Fail to handle after appending, because {}, log: {}", e.getMessage(), logEntry); 75 | } 76 | } 77 | } 78 | 79 | @Override 80 | public void handleAfterCommit(LogEntry logEntry) { 81 | for (LogPostProcessor postProcessor : postProcessors) { 82 | try { 83 | postProcessor.handleAfterCommit(logEntry); 84 | } catch (Exception e) { 85 | LOGGER.error("Fail to handle after committing, because {}, log: {}", e.getMessage(), logEntry); 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public boolean handleBeforeCommit(LogEntry logEntry) { 92 | boolean continueNext = true; 93 | for (int i = 0; continueNext && i < postProcessors.size(); i++) { 94 | try { 95 | continueNext = postProcessors.get(i).handleBeforeCommit(logEntry); 96 | } catch (Exception e) { 97 | LOGGER.error("Fail to handle before committing, because {}, log: {}", e.getMessage(), logEntry); 98 | } 99 | } 100 | return continueNext; 101 | } 102 | 103 | @Override 104 | public void setGroupKey(String key) { 105 | this.groupKey = key; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/chain/impl/MemberChangeLogPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.chain.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.api.entity.base.Payload; 5 | import com.daydreamer.raft.api.entity.constant.LogType; 6 | import com.daydreamer.raft.api.entity.constant.MemberChange; 7 | import com.daydreamer.raft.common.annotation.SPIImplement; 8 | import com.daydreamer.raft.protocol.chain.LogPostProcessor; 9 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 10 | 11 | /** 12 | * @author Daydreamer 13 | *

14 | * handle if member change log append or commit 15 | *

16 | * Leader has the activity to change memeber. 17 | * First, leader will try to append memeber change log(may be add a new memeber or 18 | * remove a member) to other node. 19 | * Then, leader will try to apply new member list after majority append successfully. 20 | * Last, leader will try to commit member change log, and tell follower to do as self. 21 | *

22 | * If leader down before finish appending member change log, this member change request will be abandon. 23 | * If leader down after finish appending member change log. then follower will do continue if it becomes 24 | * leader because of no-op log. 25 | *

26 | * especially, member will enter member change state if it append a log for member changing, 27 | * it will not accept any member change request unless cover the old member change log 28 | */ 29 | @SPIImplement("memberChangeLogPostProcessor") 30 | public class MemberChangeLogPostProcessor implements LogPostProcessor { 31 | 32 | private volatile long lastMemberChangeLogIndex = -1; 33 | 34 | private RaftMemberManager raftMemberManager; 35 | 36 | private String MEMBER_CHANGE_TYPE = "memberChange"; 37 | 38 | private String ADDRESS = "address"; 39 | 40 | @Override 41 | public synchronized boolean handleBeforeAppend(LogEntry logEntry) { 42 | // if not member change log, pass 43 | if (!LogType.MEMBER_CHANGE.equals(logEntry.getPayload().getLogType())) { 44 | return true; 45 | } 46 | // if member change log, try to check whether cover 47 | if (lastMemberChangeLogIndex == -1) { 48 | return true; 49 | } 50 | // cover 51 | return lastMemberChangeLogIndex <= logEntry.getLogId(); 52 | } 53 | 54 | @Override 55 | public synchronized void handleAfterAppend(LogEntry logEntry) { 56 | // if not member change log, pass 57 | if (!LogType.MEMBER_CHANGE.equals(logEntry.getPayload().getLogType())) { 58 | return; 59 | } 60 | // update log index 61 | lastMemberChangeLogIndex = logEntry.getLogId(); 62 | } 63 | 64 | @Override 65 | public synchronized void handleAfterCommit(LogEntry logEntry) { 66 | // if not member change log, pass 67 | if (!LogType.MEMBER_CHANGE.equals(logEntry.getPayload().getLogType())) { 68 | return; 69 | } 70 | // change member list 71 | Payload payload = logEntry.getPayload(); 72 | // type 73 | MemberChange memberChange = MemberChange.valueOf(payload.getMetadata().get(MEMBER_CHANGE_TYPE)); 74 | String addr = payload.getMetadata().get(ADDRESS); 75 | if (MemberChange.ADD.equals(memberChange)) { 76 | raftMemberManager.addNewMember(addr); 77 | } else if (MemberChange.REMOVE.equals(memberChange)) { 78 | // check whether down current node 79 | if (addr.equals(raftMemberManager.getSelf().getAddress())) { 80 | raftMemberManager.removeSelf(); 81 | } else { 82 | raftMemberManager.removeMember(addr); 83 | } 84 | } 85 | lastMemberChangeLogIndex = -1; 86 | // TODO update config 87 | 88 | } 89 | 90 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 91 | this.raftMemberManager = raftMemberManager; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/chain/impl/PersistentLogPostProcessor.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.chain.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.loader.GroupAware; 7 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 8 | import com.daydreamer.raft.protocol.chain.LogPostProcessor; 9 | import org.daydreamer.persistence.PersistenceService; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * @author Daydreamer 15 | */ 16 | @SPIImplement("persistentLogPostProcessor") 17 | public class PersistentLogPostProcessor implements LogPostProcessor, GroupAware { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(PersistentLogPostProcessor.class); 20 | 21 | private PersistenceService persistenceService; 22 | 23 | private String groupKey; 24 | 25 | @SPIMethodInit 26 | private void init() { 27 | persistenceService = RaftServiceLoader.getLoader(groupKey, PersistenceService.class).getDefault(); 28 | } 29 | 30 | @Override 31 | public boolean handleBeforeCommit(LogEntry logEntry) { 32 | return persistenceService.write(logEntry); 33 | } 34 | 35 | @Override 36 | public void setGroupKey(String key) { 37 | this.groupKey = key; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/constant/LogErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public class LogErrorCode { 7 | 8 | private LogErrorCode() {} 9 | 10 | /** 11 | * cause when commit log but last uncommitted log id is too less 12 | */ 13 | public static final int UNCOMMITTED_LOG_TO_LESS = 1; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/constant/NodeRole.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | * 6 | * status of member node 7 | */ 8 | public enum NodeRole { 9 | 10 | /** 11 | * leader 12 | */ 13 | LEADER, 14 | 15 | /** 16 | * follower 17 | */ 18 | FOLLOWER, 19 | 20 | /** 21 | * candidate 22 | */ 23 | CANDIDATE 24 | 25 | } 26 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/constant/NodeStatus.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | * 6 | * health status of node 7 | */ 8 | public enum NodeStatus { 9 | 10 | /** 11 | * healthy 12 | */ 13 | UP, 14 | 15 | /** 16 | * unhealthy 17 | */ 18 | DOWN 19 | 20 | } 21 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/constant/RaftProperty.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.constant; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public class RaftProperty { 7 | 8 | private RaftProperty() {} 9 | 10 | public static final String MEMBER_ADDRESSES = "ddr.raft.members-addresses"; 11 | 12 | public static final String LEADER_HEARTBEAT = "ddr.raft.heartInterval"; 13 | 14 | public static final String ABNORMAL_LEADER_ACTIVE_INTERNAL = "ddr.raft.leader-inactive-timeout"; 15 | 16 | public static final String VOTE_BASE_TIME = "ddr.raft.voteBaseTime"; 17 | 18 | public static final String CANDIDATE_WAIT_TIMEOUT = "ddr.raft.candidate-wait-timeout"; 19 | 20 | public static final String WRITE_RETRY_TIMES_IF_FAIL = "ddr.raft.write-retry-times"; 21 | 22 | public static final String SERVER_ADDR = "ddr.raft.server-address"; 23 | 24 | public static final String REJECT_WRITE_IF_FOLLOWER = "ddr.raft.reject-write-if-follower"; 25 | 26 | public static final String DEFAULT_THREAD_POOL_CORE = "ddr.raft.thread-pool.default.core"; 27 | 28 | public static final String DEFAULT_THREAD_POOL_MAX = "ddr.raft.thread-pool.default.max"; 29 | 30 | public static final String LOG_DATA_DIR = "ddr.raft.data.dir"; 31 | 32 | public static final String LOG_PERSISTENT = "ddr.raft.persistent-log"; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/AbstractFollowerNotifier.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 5 | import com.daydreamer.raft.common.entity.RaftConfig; 6 | import com.daydreamer.raft.transport.connection.Closeable; 7 | 8 | import java.util.concurrent.ScheduledThreadPoolExecutor; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | 12 | /** 13 | * @author Daydreamer 14 | *

15 | * It is a manager to retain connection 16 | */ 17 | @SPI("abstractFollowerNotifier") 18 | public abstract class AbstractFollowerNotifier implements Closeable { 19 | 20 | /** 21 | * member manager 22 | */ 23 | protected RaftMemberManager raftMemberManager; 24 | 25 | /** 26 | * config 27 | */ 28 | protected RaftConfig raftConfig; 29 | 30 | /** 31 | * whether init 32 | */ 33 | protected AtomicBoolean isInit = new AtomicBoolean(false); 34 | 35 | /** 36 | * executor for schedule 37 | */ 38 | protected ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, r -> { 39 | Thread thread = new Thread(r); 40 | thread.setName("refresh-active-time-thread"); 41 | thread.setDaemon(true); 42 | return thread; 43 | }); 44 | 45 | /** 46 | * init notifier 47 | */ 48 | @SPIMethodInit 49 | public synchronized void init() { 50 | if (isInit.get()) { 51 | return; 52 | } 53 | executor.scheduleAtFixedRate(this::keepFollowers, raftConfig.getHeartInterval(), raftConfig.getHeartInterval(), TimeUnit.MICROSECONDS); 54 | // other init operation 55 | doInit(); 56 | // finish 57 | isInit.set(true); 58 | } 59 | 60 | /** 61 | * init 62 | */ 63 | protected void doInit() { 64 | // nothing to do default 65 | } 66 | 67 | /** 68 | * remind follower to keep current node as leader 69 | * 70 | */ 71 | protected abstract void keepFollowers(); 72 | } 73 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/CommitHookManager.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core; 2 | 3 | import com.daydreamer.raft.api.callback.CommitHook; 4 | import com.daydreamer.raft.api.entity.base.LogEntry; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.filter.LogFilter; 7 | import com.daydreamer.raft.common.loader.GroupAware; 8 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 9 | import com.daydreamer.raft.protocol.chain.LogPostProcessor; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | /** 17 | * @author Daydreamer 18 | */ 19 | public abstract class CommitHookManager implements LogPostProcessor, GroupAware { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(CommitHookManager.class); 22 | 23 | protected String groupKey; 24 | 25 | protected final Map hooks = new ConcurrentHashMap<>(); 26 | 27 | protected LogFilter logFilter; 28 | 29 | @SPIMethodInit 30 | private void init() { 31 | logFilter = RaftServiceLoader.getLoader(groupKey, LogFilter.class).getDefault(); 32 | } 33 | 34 | /** 35 | * register new hook for key 36 | * 37 | * @param key key 38 | * @param hook hook 39 | */ 40 | public synchronized boolean register(Object key, CommitHook hook) { 41 | CommitHook commitHooks = hooks.get(key); 42 | if (commitHooks == null) { 43 | hooks.putIfAbsent(key, hook); 44 | return true; 45 | } 46 | return false; 47 | } 48 | 49 | /** 50 | * unregister hook 51 | * 52 | * @param key hook key 53 | */ 54 | public synchronized void removeHook(Object key) { 55 | hooks.remove(key); 56 | } 57 | 58 | /** 59 | * commit log 60 | * 61 | * @param logEntry log 62 | */ 63 | public abstract void commit(LogEntry logEntry); 64 | 65 | @Override 66 | public void handleAfterCommit(LogEntry logEntry) { 67 | try { 68 | // if it does not allow to filter 69 | if (logFilter.filter(logEntry)) { 70 | commit(logEntry); 71 | } 72 | } catch (Throwable e) { 73 | LOGGER.error("Fail to do hooks for committed log: " + logEntry + ", " 74 | + "because: " + e.getLocalizedMessage()); 75 | } 76 | } 77 | 78 | @Override 79 | public void setGroupKey(String key) { 80 | this.groupKey = key; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/LogSender.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.api.entity.base.LogEntry; 6 | import com.daydreamer.raft.common.annotation.SPI; 7 | import com.daydreamer.raft.protocol.entity.Member; 8 | 9 | import java.util.List; 10 | import java.util.function.Predicate; 11 | 12 | /** 13 | * @author Daydreamer 14 | */ 15 | @SPI("logSender") 16 | public interface LogSender { 17 | 18 | /** 19 | * append log 20 | * 21 | * @param member member 22 | * @param logEntry newest log 23 | * @throws Exception Exception when appending 24 | * @return whether success 25 | */ 26 | boolean appendLog(Member member, LogEntry logEntry) throws Exception; 27 | 28 | /** 29 | * committed 30 | * 31 | * @param request request 32 | * @param member member 33 | * @return whether success 34 | * @throws Exception Exception when committing 35 | */ 36 | boolean commit(Member member, Request request) throws Exception; 37 | 38 | /** 39 | * send request to all members 40 | * 41 | * @param request request 42 | * @param predicate condition success 43 | * @param members members 44 | * @return whether success half of all 45 | * @throws Exception exception 46 | */ 47 | boolean batchRequestMembers(Request request, List members, Predicate predicate) throws Exception; 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core; 2 | 3 | import com.daydreamer.raft.api.callback.CommitHook; 4 | import com.daydreamer.raft.api.entity.base.MemberChangeEntry; 5 | import com.daydreamer.raft.api.entity.base.Payload; 6 | 7 | /** 8 | * @author Daydreamer 9 | *

10 | * protocol 11 | */ 12 | public interface Protocol { 13 | 14 | /** 15 | * write 16 | * 17 | * @param payload data 18 | * @return whether committed 19 | * @throws Exception abnormal cluster 20 | */ 21 | boolean write(Payload payload) throws Exception; 22 | 23 | /** 24 | * read 25 | */ 26 | void read(); 27 | 28 | /** 29 | * member change 30 | * 31 | * @param memberChangeEntry which member change 32 | * @throws Exception Exception 33 | * @return whether change successfully 34 | */ 35 | boolean memberChange(MemberChangeEntry memberChangeEntry) throws Exception; 36 | 37 | /** 38 | * add the hook method to invoke after log committed 39 | * 40 | * @param key key 41 | * @param commitHook commitHook 42 | */ 43 | boolean addListener(Object key, CommitHook commitHook); 44 | 45 | /** 46 | * remove the hook method to invoke after log committed 47 | * 48 | * @param key key 49 | */ 50 | void removeListener(Object key); 51 | 52 | /** 53 | * start 54 | */ 55 | void run(); 56 | 57 | /** 58 | * stop protocol running 59 | */ 60 | void close(); 61 | } 62 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/RaftMemberManager.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | import com.daydreamer.raft.protocol.entity.Member; 5 | import com.daydreamer.raft.transport.connection.Closeable; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author Daydreamer 11 | *

12 | * It is used to hold the message of member 13 | */ 14 | @SPI("raftMemberManager") 15 | public interface RaftMemberManager extends Closeable { 16 | 17 | /** 18 | * init manager 19 | */ 20 | void init(); 21 | 22 | /** 23 | * get all members 24 | * 25 | * @return all members 26 | */ 27 | List getAllMember(); 28 | 29 | /** 30 | * get all normal members it is valid if current node is leader 31 | * 32 | * @return normal members 33 | */ 34 | List getActiveMember(); 35 | 36 | /** 37 | * add a new member may success if current node is leader, fail if follower 38 | * 39 | * @param addr new member 40 | * @return whether add successfully 41 | */ 42 | boolean addNewMember(String addr); 43 | 44 | /** 45 | * remove a existed member 46 | * 47 | * @param id member addr 48 | * @return whether remove successfully 49 | */ 50 | boolean removeMember(String id); 51 | 52 | /** 53 | * get self as member 54 | * 55 | * @return self as member 56 | */ 57 | Member getSelf(); 58 | 59 | /** 60 | * get member by id 61 | * 62 | * @param id id of member 63 | * @return member 64 | */ 65 | Member getMemberById(String id); 66 | 67 | /** 68 | * whether current node is leader 69 | * 70 | * @return whether current node is leader 71 | */ 72 | boolean isLeader(); 73 | 74 | /** 75 | * whether current node leave 76 | * 77 | * @return leave 78 | */ 79 | boolean isSelfLeave(); 80 | 81 | /** 82 | * remove self 83 | */ 84 | void removeSelf(); 85 | } 86 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/AsynCommitHookManager.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.api.callback.CommitHook; 4 | import com.daydreamer.raft.api.entity.base.LogEntry; 5 | import com.daydreamer.raft.common.annotation.SPIImplement; 6 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 7 | import com.daydreamer.raft.common.loader.GroupAware; 8 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 9 | import com.daydreamer.raft.common.threadpool.ThreadPoolFactory; 10 | import com.daydreamer.raft.protocol.core.CommitHookManager; 11 | 12 | import java.util.concurrent.Executor; 13 | 14 | /** 15 | * @author Daydreamer 16 | */ 17 | @SPIImplement("asynCommitHookManager") 18 | public class AsynCommitHookManager extends CommitHookManager implements GroupAware { 19 | 20 | /** 21 | * job executor 22 | */ 23 | private Executor executor; 24 | 25 | 26 | @SPIMethodInit 27 | private void init() { 28 | this.executor = RaftServiceLoader 29 | .getLoader(groupKey, ThreadPoolFactory.class) 30 | .getDefault() 31 | .getExecutor(AsynCommitHookManager.class); 32 | } 33 | 34 | @Override 35 | public void commit(LogEntry logEntry) { 36 | executor.execute(() -> { 37 | for (CommitHook commitHook : hooks.values()) { 38 | commitHook.handleCommittedLog(logEntry); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/DefaultLogSender.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.api.entity.base.CommittedResponse; 6 | import com.daydreamer.raft.api.entity.base.LogEntry; 7 | import com.daydreamer.raft.api.entity.request.AppendEntriesRequest; 8 | import com.daydreamer.raft.api.entity.response.AppendEntriesResponse; 9 | import com.daydreamer.raft.api.entity.response.ServerErrorResponse; 10 | import com.daydreamer.raft.common.annotation.SPIImplement; 11 | import com.daydreamer.raft.protocol.core.LogSender; 12 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 13 | import com.daydreamer.raft.protocol.entity.Member; 14 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 15 | import com.daydreamer.raft.transport.connection.Connection; 16 | import com.daydreamer.raft.transport.connection.ResponseCallBack; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.concurrent.CountDownLatch; 23 | import java.util.concurrent.Future; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | import java.util.function.Predicate; 27 | 28 | /** 29 | * @author Daydreamer 30 | */ 31 | @SPIImplement("logSender") 32 | public class DefaultLogSender implements LogSender { 33 | 34 | private Logger LOGGER = LoggerFactory.getLogger(DefaultLogSender.class.getSimpleName()); 35 | 36 | private RaftMemberManager raftMemberManager; 37 | 38 | private ReplicatedStateMachine replicatedStateMachine; 39 | 40 | public DefaultLogSender() { 41 | 42 | } 43 | 44 | @Override 45 | public synchronized boolean appendLog(Member member, LogEntry logEntry) throws Exception { 46 | // append log request 47 | ArrayList logEntries = new ArrayList(); 48 | logEntries.add(logEntry); 49 | AppendEntriesRequest originRequest = buildAppendLogRequest(logEntries); 50 | Connection connection = member.getConnection(); 51 | if (connection != null) { 52 | AppendEntriesResponse response = new AppendEntriesResponse(false); 53 | // need to syn log 54 | while (!response.isAccepted()) { 55 | member.setLogId(originRequest.getLastLogId() - 1); 56 | Future future = connection.request(originRequest); 57 | // block wait 58 | Response commonResponse = future.get(); 59 | // member may down 60 | // copy log next time 61 | if (commonResponse == null) { 62 | return false; 63 | } 64 | // if submit log has committed 65 | if (commonResponse instanceof ServerErrorResponse) { 66 | LOGGER.error("Node has committed the log, member: " + member.getAddress()); 67 | return false; 68 | } else if (commonResponse instanceof AppendEntriesResponse) { 69 | response = (AppendEntriesResponse) commonResponse; 70 | } 71 | // find last log 72 | if (originRequest.getLastLogId() - 1 < 0) { 73 | originRequest.setLastTerm(0); 74 | originRequest.setLastLogId(-1); 75 | continue; 76 | } 77 | LogEntry lastLog = replicatedStateMachine.getLogById(originRequest.getLastLogId() - 1); 78 | int lastTerm = 0; 79 | long lastLogId = -1; 80 | if (originRequest.getLastLogId() - 2 >= 0) { 81 | LogEntry lastLastLog = replicatedStateMachine.getLogById(originRequest.getLastLogId() - 2); 82 | lastLogId = lastLastLog.getLogId(); 83 | lastTerm = lastLastLog.getTerm(); 84 | } 85 | originRequest.setLastTerm(lastTerm); 86 | originRequest.setLastLogId(lastLogId); 87 | originRequest.getLogEntries().add(0, lastLog); 88 | } 89 | return true; 90 | } 91 | return false; 92 | } 93 | 94 | /** 95 | * build request 96 | * 97 | * @param logEntries log entries 98 | */ 99 | private AppendEntriesRequest buildAppendLogRequest(List logEntries) { 100 | Member self = raftMemberManager.getSelf(); 101 | AppendEntriesRequest appendEntriesRequest = new AppendEntriesRequest(); 102 | appendEntriesRequest.setCurrentLogId(self.getLogId()); 103 | appendEntriesRequest.setCurrentTerm(self.getTerm()); 104 | appendEntriesRequest.setLogEntries(logEntries); 105 | appendEntriesRequest.setPayload(true); 106 | long lastLogId = logEntries.get(0).getLogId() - 1; 107 | int lastTerm = 0; 108 | if (lastLogId >= 0) { 109 | LogEntry lastLog = replicatedStateMachine.getLogById(lastLogId); 110 | lastTerm = lastLog.getTerm(); 111 | } 112 | appendEntriesRequest.setLastLogId(lastLogId); 113 | appendEntriesRequest.setLastTerm(lastTerm); 114 | return appendEntriesRequest; 115 | } 116 | 117 | @Override 118 | public synchronized boolean commit(Member member, Request request) throws Exception { 119 | Connection connection = member.getConnection(); 120 | if (connection != null) { 121 | Response response = connection.request(request, 2500); 122 | if (response instanceof CommittedResponse) { 123 | return ((CommittedResponse) response).isAccepted(); 124 | } 125 | } 126 | return false; 127 | } 128 | 129 | @Override 130 | public boolean batchRequestMembers(Request request, List members, Predicate predicate) 131 | throws Exception { 132 | // begin to request 133 | AtomicInteger count = new AtomicInteger(1); 134 | CountDownLatch countDownLatch = new CountDownLatch(members.size()); 135 | for (Member member : members) { 136 | try { 137 | Connection connection = member.getConnection(); 138 | connection.request(request, new ResponseCallBack() { 139 | 140 | @Override 141 | public void onSuccess(Response response) { 142 | if (predicate.test(response)) { 143 | count.incrementAndGet(); 144 | } 145 | countDownLatch.countDown(); 146 | } 147 | 148 | @Override 149 | public void onFail(Exception e) { 150 | // nothing to do 151 | countDownLatch.countDown(); 152 | } 153 | 154 | @Override 155 | public void onTimeout() { 156 | // nothing to do 157 | } 158 | }); 159 | } catch (Exception e) { 160 | // nothing to do 161 | } 162 | } 163 | countDownLatch.await(members.size() * 10000L, TimeUnit.MICROSECONDS); 164 | return count.get() > (raftMemberManager.getAllMember().size() + 1) / 2; 165 | } 166 | 167 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 168 | this.raftMemberManager = raftMemberManager; 169 | } 170 | 171 | public void setReplicatedStateMachine(ReplicatedStateMachine replicatedStateMachine) { 172 | this.replicatedStateMachine = replicatedStateMachine; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/GrpcFollowerNotifier.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.protocol.constant.NodeStatus; 5 | import com.daydreamer.raft.protocol.core.AbstractFollowerNotifier; 6 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 7 | import com.daydreamer.raft.common.entity.RaftConfig; 8 | import com.daydreamer.raft.protocol.entity.Member; 9 | import com.daydreamer.raft.transport.connection.Connection; 10 | import com.daydreamer.raft.transport.connection.ResponseCallBack; 11 | import com.daydreamer.raft.api.entity.Response; 12 | import com.daydreamer.raft.api.entity.request.HeartbeatRequest; 13 | 14 | import java.util.List; 15 | import java.util.concurrent.CountDownLatch; 16 | import java.util.concurrent.TimeUnit; 17 | import org.apache.log4j.Logger; 18 | 19 | /** 20 | * @author Daydreamer 21 | *

22 | * It is a implmement to retain grpc connection. 23 | */ 24 | @SPIImplement("abstractFollowerNotifier") 25 | public class GrpcFollowerNotifier extends AbstractFollowerNotifier { 26 | 27 | private static final Logger LOGGER = Logger.getLogger(GrpcFollowerNotifier.class); 28 | 29 | public GrpcFollowerNotifier() { 30 | 31 | } 32 | 33 | @Override 34 | public void keepFollowers() { 35 | try { 36 | // if current node is leader 37 | // tell follower to keep 38 | if (raftMemberManager.isLeader() && !executor.isShutdown()) { 39 | List allMember = raftMemberManager.getAllMember(); 40 | // not active a period time 41 | CountDownLatch countDownLatch = new CountDownLatch(allMember.size()); 42 | allMember.forEach(member -> { 43 | try { 44 | long lastActiveTime = member.getLastActiveTime(); 45 | long currentTime = System.currentTimeMillis(); 46 | if (currentTime - lastActiveTime > raftConfig.getHeartInterval()) { 47 | Connection connection = member.getConnection(); 48 | if (connection != null) { 49 | Member self = raftMemberManager.getSelf(); 50 | connection.request(new HeartbeatRequest(self.getTerm(), self.getLogId()), 51 | 2000, new ResponseCallBack() { 52 | 53 | @Override 54 | public void onSuccess(Response response) { 55 | member.setStatus(NodeStatus.UP); 56 | countDownLatch.countDown(); 57 | } 58 | 59 | @Override 60 | public void onFail(Exception e) { 61 | member.setStatus(NodeStatus.DOWN); 62 | countDownLatch.countDown(); 63 | } 64 | 65 | @Override 66 | public void onTimeout() { 67 | countDownLatch.countDown(); 68 | } 69 | 70 | }); 71 | } 72 | } 73 | } catch (Exception e) { 74 | LOGGER.error("Schedule error when check connection, because " + e 75 | .getLocalizedMessage() + ". current member" + member.getAddress()); 76 | } 77 | }); 78 | // wait for response 79 | countDownLatch.await(1500L * allMember.size(), TimeUnit.MILLISECONDS); 80 | } 81 | } catch (Exception e) { 82 | // nothing to do 83 | } 84 | } 85 | 86 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 87 | this.raftMemberManager = raftMemberManager; 88 | } 89 | 90 | public void setRaftConfig(RaftConfig raftConfig) { 91 | this.raftConfig = raftConfig; 92 | } 93 | 94 | @Override 95 | public void close() { 96 | executor.shutdownNow(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/GrpcRaftServer.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.api.entity.request.PrevoteRequest; 4 | import com.daydreamer.raft.api.entity.response.PrevoteResponse; 5 | import com.daydreamer.raft.common.annotation.SPIImplement; 6 | import com.daydreamer.raft.common.loader.GroupAware; 7 | import com.daydreamer.raft.protocol.core.AbstractRaftServer; 8 | import com.daydreamer.raft.protocol.entity.Member; 9 | import com.daydreamer.raft.api.entity.request.VoteCommitRequest; 10 | import com.daydreamer.raft.api.entity.request.VoteRequest; 11 | import com.daydreamer.raft.api.entity.response.VoteCommitResponse; 12 | import com.daydreamer.raft.api.entity.response.VoteResponse; 13 | import com.daydreamer.raft.protocol.handler.RequestHandlerHolder; 14 | import io.grpc.Server; 15 | import io.grpc.ServerBuilder; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * @author Daydreamer 23 | */ 24 | @SPIImplement("abstractRaftServer") 25 | public class GrpcRaftServer extends AbstractRaftServer implements GroupAware { 26 | 27 | private static final Logger LOGGER = LoggerFactory.getLogger(GrpcRaftServer.class); 28 | 29 | /** 30 | * server 31 | */ 32 | private Server server; 33 | 34 | private String groupKey; 35 | 36 | public GrpcRaftServer() { 37 | } 38 | 39 | @Override 40 | public Member getSelf() { 41 | return raftMemberManager.getSelf(); 42 | } 43 | 44 | @Override 45 | protected void doStartServer() { 46 | try { 47 | int port = raftMemberManager.getSelf().getPort(); 48 | server = ServerBuilder.forPort(port).addService(new GrpcRequestServerCore(new RequestHandlerHolder(groupKey))).build() 49 | .start(); 50 | LOGGER.info("Server started, listening on port: " + port); 51 | } catch (Exception e) { 52 | throw new IllegalStateException( 53 | "[GrpcRaftServer] - Fail to init server, because " + e.getLocalizedMessage()); 54 | } 55 | } 56 | 57 | @Override 58 | public boolean requestVote() throws Exception { 59 | // request vote 60 | Member self = raftMemberManager.getSelf(); 61 | // refresh candidate 62 | refreshCandidateActive(); 63 | // if success half of all 64 | // then commit 65 | if (!logSender.batchRequestMembers(new VoteRequest(self.getTerm(), self.getLogId()), raftMemberManager.getAllMember(), response -> { 66 | // nothing to do 67 | return ((VoteResponse) response).isVoted(); 68 | })) { 69 | return false; 70 | } 71 | return logSender 72 | .batchRequestMembers(new VoteCommitRequest(self.getTerm(), self.getLogId()), raftMemberManager.getAllMember(), response -> { 73 | // nothing to do 74 | return ((VoteCommitResponse) response).isAccepted(); 75 | }); 76 | } 77 | 78 | @Override 79 | protected boolean prevote() throws Exception { 80 | // refresh candidate 81 | refreshCandidateActive(); 82 | Member self = raftMemberManager.getSelf(); 83 | PrevoteRequest prevoteRequest = new PrevoteRequest(self.getTerm(), self.getLogId()); 84 | return logSender.batchRequestMembers(prevoteRequest, raftMemberManager.getAllMember(), (response) -> { 85 | if (response instanceof PrevoteResponse) { 86 | return ((PrevoteResponse) response).isAgree(); 87 | } 88 | return false; 89 | }); 90 | } 91 | 92 | @Override 93 | public boolean isLeader() { 94 | return raftMemberManager.isLeader(); 95 | } 96 | 97 | @Override 98 | public void close() { 99 | try { 100 | // close super 101 | super.close(); 102 | // close sub 103 | if (server != null) { 104 | server.shutdown().awaitTermination(30, TimeUnit.SECONDS); 105 | } 106 | } catch (Exception e) { 107 | throw new IllegalStateException( 108 | "[GrpcRaftServer] - Fail to close server, because " + e.getLocalizedMessage()); 109 | } 110 | } 111 | 112 | @Override 113 | public void setGroupKey(String key) { 114 | this.groupKey = key; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/GrpcRequestServerCore.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.api.grpc.Message; 4 | import com.daydreamer.raft.api.grpc.RequesterGrpc; 5 | import com.daydreamer.raft.common.utils.MsgUtils; 6 | import com.daydreamer.raft.api.entity.Response; 7 | import com.daydreamer.raft.protocol.handler.RequestHandlerHolder; 8 | import io.grpc.stub.StreamObserver; 9 | 10 | /** 11 | * @author Daydreamer 12 | * 13 | * GrpcServer core 14 | */ 15 | public class GrpcRequestServerCore extends RequesterGrpc.RequesterImplBase { 16 | 17 | private RequestHandlerHolder requestHandlerHolder; 18 | 19 | public GrpcRequestServerCore(RequestHandlerHolder requestHandlerHolder) { 20 | this.requestHandlerHolder = requestHandlerHolder; 21 | } 22 | 23 | @Override 24 | public void request(Message msg, StreamObserver responseObserver) { 25 | // handle 26 | Response response = requestHandlerHolder.handle(MsgUtils.convertRequest(msg)); 27 | // response 28 | Message back = MsgUtils.convertMsg(response); 29 | responseObserver.onNext(back); 30 | responseObserver.onCompleted(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/MemberManager.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 5 | import com.daydreamer.raft.common.loader.GroupAware; 6 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 7 | import com.daydreamer.raft.protocol.constant.NodeRole; 8 | import com.daydreamer.raft.protocol.constant.NodeStatus; 9 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 10 | import com.daydreamer.raft.common.entity.RaftConfig; 11 | import com.daydreamer.raft.protocol.entity.Member; 12 | import com.daydreamer.raft.transport.connection.Connection; 13 | import com.daydreamer.raft.transport.connection.impl.grpc.GrpcConnection; 14 | import com.daydreamer.raft.api.grpc.RequesterGrpc; 15 | import com.daydreamer.raft.transport.factory.ConnectionFactory; 16 | import io.grpc.ManagedChannel; 17 | import io.grpc.ManagedChannelBuilder; 18 | import org.apache.commons.lang.StringUtils; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * @author Daydreamer 29 | *

30 | * storge member 31 | */ 32 | @SPIImplement("raftMemberManager") 33 | public class MemberManager implements RaftMemberManager, GroupAware { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(MemberManager.class.getSimpleName()); 36 | 37 | private static final String DEFAULT_CONNECTION_KEY = "grpc"; 38 | 39 | private static final String IP_PORT_ADDR_FORMAT = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}:[0-9]{2,5}"; 40 | 41 | private RaftConfig raftConfig; 42 | 43 | private Member self; 44 | 45 | private List members = new ArrayList<>(); 46 | 47 | private String groupKey; 48 | 49 | private ConnectionFactory connectionFactory; 50 | 51 | public MemberManager() { 52 | 53 | } 54 | 55 | /** 56 | * init current node member 57 | */ 58 | public void initSelf() { 59 | try { 60 | String serverAddr = raftConfig.getServerAddr(); 61 | if (StringUtils.isBlank(serverAddr)) { 62 | throw new IllegalStateException("Server address is not specified!"); 63 | } 64 | Member tmp = buildRawMember(serverAddr); 65 | tmp.setTerm(0); 66 | tmp.setLogId(-1); 67 | tmp.setStatus(NodeStatus.UP); 68 | self = tmp; 69 | } catch (Exception e) { 70 | throw new IllegalStateException("[MemberManager] - Fail to init self message, because: " + e.getLocalizedMessage()); 71 | } 72 | } 73 | 74 | @Override 75 | @SPIMethodInit 76 | public void init() { 77 | // init self 78 | initSelf(); 79 | // get connection factory 80 | connectionFactory = RaftServiceLoader.getLoader(groupKey, ConnectionFactory.class).getInstance(DEFAULT_CONNECTION_KEY); 81 | // load member 82 | List memberAddresses = raftConfig.getMemberAddresses(); 83 | for (String addr : memberAddresses) { 84 | Member member = buildRawMember(addr); 85 | members.add(member); 86 | } 87 | // create connection 88 | for (Member member : members) { 89 | member.setConnection(createConnection(member)); 90 | } 91 | } 92 | 93 | /** 94 | * get raw member 95 | * 96 | * @param addr member address 97 | * @return new member 98 | */ 99 | private Member buildRawMember(String addr) { 100 | Member member = new Member(); 101 | member.setStatus(NodeStatus.DOWN); 102 | member.setRole(NodeRole.FOLLOWER); 103 | member.setAddress(addr); 104 | if (addr.matches(IP_PORT_ADDR_FORMAT)) { 105 | String[] split = addr.split(":"); 106 | member.setIp(split[0]); 107 | member.setPort(Integer.parseInt(split[1])); 108 | } 109 | return member; 110 | } 111 | 112 | /** 113 | * create connection 114 | * 115 | * @param member target host 116 | * @return conn 117 | */ 118 | private Connection createConnection(Member member) { 119 | return connectionFactory.getConnection(member.getIp(), member.getPort(), null); 120 | } 121 | 122 | @Override 123 | public List getAllMember() { 124 | return Collections.unmodifiableList(members); 125 | } 126 | 127 | @Override 128 | public List getActiveMember() { 129 | return members.stream().filter(member -> NodeStatus.UP.equals(member.getStatus())).collect(Collectors.toList()); 130 | } 131 | 132 | @Override 133 | public boolean addNewMember(String addr) { 134 | Member member = buildRawMember(addr); 135 | member.setConnection(createConnection(member)); 136 | members.add(member); 137 | LOGGER.info("Add new member, member: {}, current members list: {}", member, members); 138 | return true; 139 | } 140 | 141 | @Override 142 | public boolean removeMember(String id) { 143 | boolean remove = members.removeIf(member -> { 144 | if (member.getAddress().equals(id)) { 145 | member.getConnection().close(); 146 | return true; 147 | } 148 | return false; 149 | }); 150 | if (remove) { 151 | LOGGER.info("Remove member: {}, current members list: {}", id, members); 152 | } 153 | return remove; 154 | } 155 | 156 | @Override 157 | public Member getSelf() { 158 | return self; 159 | } 160 | 161 | @Override 162 | public Member getMemberById(String id) { 163 | return null; 164 | } 165 | 166 | @Override 167 | public boolean isLeader() { 168 | return NodeRole.LEADER.equals(self.getRole()); 169 | } 170 | 171 | @Override 172 | public boolean isSelfLeave() { 173 | return members.isEmpty(); 174 | } 175 | 176 | @Override 177 | public void removeSelf() { 178 | close(); 179 | members.clear(); 180 | LOGGER.info("Current member: {} has leave cluster", self.getAddress()); 181 | // down to follower 182 | self.setRole(NodeRole.FOLLOWER); 183 | } 184 | 185 | @Override 186 | public void close() { 187 | getAllMember().forEach(member -> { 188 | if (member.getConnection() != null) { 189 | member.getConnection().close(); 190 | } 191 | }); 192 | } 193 | 194 | public void setRaftConfig(RaftConfig raftConfig) { 195 | this.raftConfig = raftConfig; 196 | } 197 | 198 | @Override 199 | public void setGroupKey(String key) { 200 | this.groupKey = key; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/core/impl/RaftPropertiesReader.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.core.impl; 2 | 3 | import com.daydreamer.raft.common.service.PropertiesReader; 4 | import com.daydreamer.raft.protocol.constant.RaftProperty; 5 | import com.daydreamer.raft.common.entity.RaftConfig; 6 | import org.apache.commons.lang.StringUtils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Properties; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | import java.util.function.Supplier; 13 | 14 | import org.apache.log4j.Logger; 15 | 16 | /** 17 | * @author Daydreamer 18 | */ 19 | public class RaftPropertiesReader extends PropertiesReader { 20 | 21 | private static final Logger LOGGER = Logger.getLogger(RaftPropertiesReader.class); 22 | 23 | private static final AtomicBoolean init = new AtomicBoolean(false); 24 | 25 | public RaftPropertiesReader(String filePath) { 26 | super(filePath, new RaftConfig(), true); 27 | } 28 | 29 | public RaftPropertiesReader(RaftConfig raftConfig) { 30 | super(null, raftConfig, false); 31 | } 32 | 33 | @Override 34 | public void populateProperties(Properties properties, RaftConfig activeProperties) { 35 | try { 36 | String members = properties.getProperty(RaftProperty.MEMBER_ADDRESSES); 37 | if (StringUtils.isNotBlank(members) && activeProperties.getMemberAddresses() == null) { 38 | String[] split = members.split(", "); 39 | List addresses = new ArrayList<>(split.length); 40 | for (String addr : split) { 41 | addresses.add(addr); 42 | } 43 | activeProperties.setMemberAddresses(addresses); 44 | } else { 45 | LOGGER.warn("Current version don't allow to change members, excuse please!"); 46 | } 47 | String heartbeat = properties.getProperty(RaftProperty.LEADER_HEARTBEAT); 48 | if (StringUtils.isNotBlank(heartbeat)) { 49 | int h = Integer.parseInt(heartbeat); 50 | activeProperties.setHeartInterval(h); 51 | } 52 | String abnormalInternal = properties.getProperty(RaftProperty.ABNORMAL_LEADER_ACTIVE_INTERNAL); 53 | if (StringUtils.isNotBlank(abnormalInternal)) { 54 | int h = Integer.parseInt(abnormalInternal); 55 | activeProperties.setAbnormalActiveInterval(h); 56 | } 57 | String voteBaseTime = properties.getProperty(RaftProperty.VOTE_BASE_TIME); 58 | if (StringUtils.isNotBlank(voteBaseTime)) { 59 | int h = Integer.parseInt(voteBaseTime); 60 | activeProperties.setVoteBaseTime(h); 61 | } 62 | String candidateTimeout = properties.getProperty(RaftProperty.CANDIDATE_WAIT_TIMEOUT); 63 | if (candidateTimeout != null) { 64 | int h = Integer.parseInt(candidateTimeout); 65 | activeProperties.setCandidateStatusTimeout(h); 66 | } 67 | String retryWrite = properties.getProperty(RaftProperty.WRITE_RETRY_TIMES_IF_FAIL); 68 | if (StringUtils.isNotBlank(retryWrite)) { 69 | int rw = Integer.parseInt(retryWrite); 70 | activeProperties.setWriteRetryTimes(rw); 71 | } 72 | String serverAddr = properties.getProperty(RaftProperty.SERVER_ADDR); 73 | if (StringUtils.isNotBlank(serverAddr) && activeProperties.getServerAddr() == null) { 74 | activeProperties.setServerAddr(serverAddr); 75 | } else { 76 | LOGGER.warn("Server address cannot be changed while running!"); 77 | } 78 | String rejectWrite = properties.getProperty(RaftProperty.REJECT_WRITE_IF_FOLLOWER); 79 | if (StringUtils.isNotBlank(rejectWrite)) { 80 | activeProperties.setFollowerRejectWrite(!rejectWrite.contains("false")); 81 | } 82 | String defaultPoolCore = properties.getProperty(RaftProperty.DEFAULT_THREAD_POOL_CORE); 83 | String defaultPoolMax = properties.getProperty(RaftProperty.DEFAULT_THREAD_POOL_MAX); 84 | if (StringUtils.isNotBlank(defaultPoolCore) && StringUtils.isNotBlank(defaultPoolMax)) { 85 | activeProperties.setDefaultThreadPoolMaxThread(Integer.parseInt(defaultPoolMax)); 86 | activeProperties.setDefaultThreadPoolCoreThread(Integer.parseInt(defaultPoolCore)); 87 | } 88 | String enablePersistenceStr = properties.getProperty(RaftProperty.LOG_PERSISTENT); 89 | boolean enablePersistence = getValue(() -> Boolean.valueOf(enablePersistenceStr), 90 | true, "Fail to load enablePersistence"); 91 | activeProperties.setPersistent(enablePersistence); 92 | String dataDir = properties.getProperty(RaftProperty.LOG_DATA_DIR); 93 | if (!init.get() && dataDir != null) { 94 | activeProperties.setDataDir(dataDir); 95 | } 96 | init.set(true); 97 | } catch (Exception e) { 98 | e.printStackTrace(); 99 | LOGGER.error("Fail to update properties, because: " + e.getMessage()); 100 | } 101 | } 102 | 103 | private static T getValue(Supplier supplier, T defaultVal, String errorMsg) { 104 | try { 105 | return supplier.get(); 106 | } catch (Exception e) { 107 | LOGGER.error(errorMsg + ", because" + e.getLocalizedMessage()); 108 | return defaultVal; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/entity/Member.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.entity; 2 | 3 | import com.daydreamer.raft.protocol.constant.NodeRole; 4 | import com.daydreamer.raft.protocol.constant.NodeStatus; 5 | import com.daydreamer.raft.transport.connection.Connection; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | import java.util.concurrent.atomic.AtomicReference; 10 | 11 | /** 12 | * @author Daydreamer 13 | *

14 | * It is the mapping of member node 15 | */ 16 | public class Member { 17 | 18 | /** 19 | * member id 20 | */ 21 | private String memberId; 22 | 23 | /** 24 | * ip 25 | */ 26 | private String ip; 27 | 28 | /** 29 | * port 30 | */ 31 | private int port; 32 | 33 | /** 34 | * ip:port or domain 35 | */ 36 | private String address; 37 | 38 | /** 39 | * last active time 40 | */ 41 | private long lastActiveTime; 42 | 43 | /** 44 | * term of member 45 | */ 46 | private AtomicInteger term = new AtomicInteger(0); 47 | 48 | /** 49 | * id in term 50 | */ 51 | private AtomicLong logId = new AtomicLong(0); 52 | 53 | /** 54 | * role 55 | */ 56 | private AtomicReference role = new AtomicReference<>(NodeRole.FOLLOWER); 57 | 58 | /** 59 | * healthy or not 60 | */ 61 | private AtomicReference status = new AtomicReference<>(NodeStatus.DOWN); 62 | 63 | /** 64 | * conn 65 | */ 66 | private Connection connection; 67 | 68 | public void setTerm(int term) { 69 | this.term.set(term); 70 | } 71 | 72 | public void setLogId(long logId) { 73 | this.logId.set(logId); 74 | } 75 | 76 | public long getLastActiveTime() { 77 | return lastActiveTime; 78 | } 79 | 80 | public void setLastActiveTime(long lastActiveTime) { 81 | this.lastActiveTime = lastActiveTime; 82 | } 83 | 84 | public Connection getConnection() { 85 | return connection; 86 | } 87 | 88 | public void setConnection(Connection connection) { 89 | this.connection = connection; 90 | } 91 | 92 | public NodeStatus getStatus() { 93 | return status.get(); 94 | } 95 | 96 | public String getMemberId() { 97 | return memberId; 98 | } 99 | 100 | public void setMemberId(String memberId) { 101 | this.memberId = memberId; 102 | } 103 | 104 | public NodeRole getRole() { 105 | return role.get(); 106 | } 107 | 108 | public boolean setRole(NodeRole newRole, NodeRole oldRole) { 109 | return this.role.compareAndSet(oldRole, newRole); 110 | } 111 | 112 | public boolean setStatus(NodeStatus newStatus, NodeStatus expected) { 113 | return this.status.compareAndSet(expected, newStatus); 114 | } 115 | 116 | public void setStatus(NodeStatus newStatus) { 117 | this.status.set(newStatus); 118 | } 119 | 120 | public void setRole(NodeRole newRole) { 121 | this.role.set(newRole); 122 | } 123 | 124 | public int getTerm() { 125 | return term.get(); 126 | } 127 | 128 | public void increaseTerm() { 129 | this.term.incrementAndGet(); 130 | } 131 | 132 | public long getLogId() { 133 | return logId.get(); 134 | } 135 | 136 | public void increaseLogId() { 137 | this.logId.incrementAndGet(); 138 | } 139 | 140 | public String getIp() { 141 | return ip; 142 | } 143 | 144 | public void setIp(String ip) { 145 | this.ip = ip; 146 | } 147 | 148 | public int getPort() { 149 | return port; 150 | } 151 | 152 | public void setPort(int port) { 153 | this.port = port; 154 | } 155 | 156 | public String getAddress() { 157 | return address; 158 | } 159 | 160 | public void setAddress(String address) { 161 | this.address = address; 162 | } 163 | 164 | @Override 165 | public String toString() { 166 | return "Member{" + "address='" + address + '\'' + '}'; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/exception/LogException.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.exception; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public class LogException extends Exception { 7 | 8 | /** 9 | * {@link com.daydreamer.raft.protocol.constant.LogErrorCode} 10 | */ 11 | public int errorCode; 12 | 13 | public LogException(int errorCode) { 14 | this.errorCode = errorCode; 15 | } 16 | 17 | public LogException(String message, int errorCode) { 18 | super(message); 19 | this.errorCode = errorCode; 20 | } 21 | 22 | public LogException(String message, Throwable cause, int errorCode) { 23 | super(message, cause); 24 | this.errorCode = errorCode; 25 | } 26 | 27 | public LogException(Throwable cause, int errorCode) { 28 | super(cause); 29 | this.errorCode = errorCode; 30 | } 31 | 32 | public LogException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int errorCode) { 33 | super(message, cause, enableSuppression, writableStackTrace); 34 | this.errorCode = errorCode; 35 | } 36 | 37 | public int getErrorCode() { 38 | return errorCode; 39 | } 40 | 41 | public void setErrorCode(int errorCode) { 42 | this.errorCode = errorCode; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/filter/InnerLogFilter.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.filter; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.api.entity.base.Payload; 5 | import com.daydreamer.raft.common.filter.LogFilter; 6 | import com.daydreamer.raft.common.annotation.SPIImplement; 7 | import com.daydreamer.raft.common.constant.LogConstant; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author Daydreamer 13 | */ 14 | @SPIImplement("innerLogFilter") 15 | public class InnerLogFilter implements LogFilter { 16 | 17 | @Override 18 | public boolean filter(LogEntry logEntry) { 19 | Payload payload = logEntry.getPayload(); 20 | if (payload == null) { 21 | return true; 22 | } 23 | Map metadata = payload.getMetadata(); 24 | if (metadata == null) { 25 | return true; 26 | } 27 | String val = metadata.get(LogConstant.INNER_LOG_TAG); 28 | return val == null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/RequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.common.annotation.SPI; 6 | 7 | /** 8 | * @author Daydreamer 9 | * 10 | * It is a handler to handle request from client 11 | */ 12 | @SPI("defaultRequestHandler") 13 | public interface RequestHandler { 14 | 15 | /** 16 | * handle the request from other node 17 | * 18 | * @param request request 19 | * @return response 20 | */ 21 | T handle(S request); 22 | 23 | /** 24 | * get data source 25 | * 26 | * @return request 27 | */ 28 | Class getSource(); 29 | } 30 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/RequestHandlerHolder.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler; 2 | 3 | 4 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 5 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 6 | import com.daydreamer.raft.transport.constant.ResponseRepository; 7 | import com.daydreamer.raft.api.entity.Request; 8 | import com.daydreamer.raft.api.entity.Response; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @author Daydreamer 15 | *

16 | * request handler registry 17 | */ 18 | @SuppressWarnings("all") 19 | public class RequestHandlerHolder { 20 | 21 | /** 22 | * registry 23 | */ 24 | public final Map, RequestHandler> registry = new ConcurrentHashMap<>(); 25 | 26 | private final String groupKey; 27 | 28 | public RequestHandlerHolder(String groupKey) { 29 | this.groupKey = groupKey; 30 | init(); 31 | } 32 | 33 | private void init() { 34 | try { 35 | RaftServiceLoader loader = RaftServiceLoader.getLoader(groupKey, RequestHandler.class); 36 | for (RequestHandler handler : loader.getAll()) { 37 | registry.put(handler.getSource(), handler); 38 | } 39 | } catch (Exception e) { 40 | throw new IllegalStateException("Can not load base handler for request", e); 41 | } 42 | } 43 | 44 | /** 45 | * delegate the request to handler 46 | * 47 | * @param request request 48 | * @return response 49 | */ 50 | public Response handle(Request request) { 51 | RequestHandler handler = registry.get(request.getClass()); 52 | // if cannot find handler 53 | if (handler == null) { 54 | return ResponseRepository.NOT_HANDLER_FOUND; 55 | } 56 | return handler.handle(request); 57 | } 58 | 59 | /** 60 | * register new handler 61 | * 62 | * @param handler handler 63 | */ 64 | public void register(RequestHandler handler) { 65 | if (!registry.containsKey(handler.getSource())) { 66 | registry.put(handler.getSource(), (RequestHandler) handler); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/AppendEntriesRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | import com.daydreamer.raft.api.entity.base.LogEntry; 5 | import com.daydreamer.raft.api.entity.constant.ResponseCode; 6 | import com.daydreamer.raft.api.entity.request.AppendEntriesRequest; 7 | import com.daydreamer.raft.api.entity.response.AppendEntriesResponse; 8 | import com.daydreamer.raft.api.entity.response.ServerErrorResponse; 9 | import com.daydreamer.raft.common.annotation.SPIImplement; 10 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 11 | import com.daydreamer.raft.protocol.handler.RequestHandler; 12 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 13 | import org.apache.log4j.Logger; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * @author Daydreamer 19 | */ 20 | @SPIImplement("appendEntriesRequestHandler") 21 | public class AppendEntriesRequestHandler 22 | implements RequestHandler{ 23 | 24 | private static final Logger LOGGER = Logger.getLogger(AppendEntriesRequestHandler.class); 25 | 26 | /** 27 | * log repository 28 | */ 29 | private ReplicatedStateMachine replicatedStateMachine; 30 | 31 | /** 32 | * raft member manager 33 | */ 34 | private RaftMemberManager raftMemberManager; 35 | 36 | @Override 37 | public synchronized Response handle(AppendEntriesRequest request) { 38 | try { 39 | // check log id and leaderLastLogTerm 40 | int leaderLastLogTerm = request.getLastTerm(); 41 | long leaderLastLogId = request.getLastLogId(); 42 | // if first log 43 | List logEntries = request.getLogEntries(); 44 | if (leaderLastLogId == -1) { 45 | if (replicatedStateMachine.getLastCommittedLogId() == -1) { 46 | boolean append = false; 47 | for (LogEntry logEntry : logEntries) { 48 | append = replicatedStateMachine.append(logEntry); 49 | if (!append) { 50 | break; 51 | } 52 | } 53 | if (append) { 54 | // update log index 55 | raftMemberManager.getSelf().setLogId(logEntries.get(logEntries.size() - 1).getLogId()); 56 | } 57 | return new AppendEntriesResponse(append); 58 | } else { 59 | LOGGER.error("First log has committed, cannot cover!"); 60 | return new ServerErrorResponse("Log has committed", ResponseCode.ERROR_CLIENT); 61 | } 62 | } 63 | // try to find 64 | LogEntry lastLog = replicatedStateMachine.getLogById(leaderLastLogId); 65 | // if uncommitted, try to append 66 | if (replicatedStateMachine.getLastCommittedLogId() < logEntries.get(logEntries.size() - 1).getLogId()) { 67 | // if found, then append 68 | if (lastLog != null && lastLog.getTerm() == leaderLastLogTerm) { 69 | // append all 70 | boolean append = false; 71 | for (LogEntry logEntry : logEntries) { 72 | append = replicatedStateMachine.append(logEntry); 73 | if (!append) { 74 | break; 75 | } 76 | } 77 | // update log index 78 | raftMemberManager.getSelf().setLogId(logEntries.get(logEntries.size() - 1).getLogId()); 79 | return new AppendEntriesResponse(append); 80 | } 81 | // cannot found, then ask leader to syn ahead 82 | else { 83 | return new AppendEntriesResponse(false); 84 | } 85 | } 86 | // if has committed 87 | else { 88 | return new AppendEntriesResponse(true); 89 | } 90 | } catch (Exception e) { 91 | // nothing to do 92 | LogEntry lastCommittedLog = replicatedStateMachine 93 | .getCommittedLog(replicatedStateMachine.getLastCommittedLogId()); 94 | LOGGER.error("Fail to append log, leader last term: " + request.getLastTerm() + ", leader last log id: " 95 | + request.getLastLogId() + ", current node committed log term: " + lastCommittedLog.getTerm() 96 | + ", current node committed log id: " + lastCommittedLog.getLogId()); 97 | return new ServerErrorResponse(e.getMessage(), ResponseCode.ERROR_SERVER); 98 | } 99 | } 100 | 101 | @Override 102 | public Class getSource() { 103 | return AppendEntriesRequest.class; 104 | } 105 | 106 | public void setReplicatedStateMachine(ReplicatedStateMachine replicatedStateMachine) { 107 | this.replicatedStateMachine = replicatedStateMachine; 108 | } 109 | 110 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 111 | this.raftMemberManager = raftMemberManager; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/DefaultRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.common.annotation.SPIImplement; 6 | import com.daydreamer.raft.protocol.handler.RequestHandler; 7 | import com.daydreamer.raft.transport.constant.ResponseRepository; 8 | 9 | /** 10 | * @author Daydreamer 11 | */ 12 | @SPIImplement("defaultRequestHandler") 13 | public class DefaultRequestHandler implements RequestHandler { 14 | 15 | @Override 16 | public Response handle(Request request) { 17 | return ResponseRepository.SERVER_UNKNOWN_ERROR; 18 | } 19 | 20 | @Override 21 | public Class getSource() { 22 | return Request.class; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/HeartbeatRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.protocol.core.AbstractRaftServer; 5 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 6 | import com.daydreamer.raft.protocol.handler.RequestHandler; 7 | import com.daydreamer.raft.api.entity.request.HeartbeatRequest; 8 | import com.daydreamer.raft.api.entity.response.HeartbeatResponse; 9 | 10 | /** 11 | * @author Daydreamer 12 | */ 13 | @SPIImplement("heartbeatRequestHandler") 14 | public class HeartbeatRequestHandler 15 | implements RequestHandler { 16 | 17 | /** 18 | * raftServer 19 | */ 20 | private AbstractRaftServer raftServer; 21 | 22 | /** 23 | * raftMemberManager 24 | */ 25 | private RaftMemberManager raftMemberManager; 26 | 27 | @Override 28 | public HeartbeatResponse handle(HeartbeatRequest request) { 29 | int term = request.getTerm(); 30 | long logId = request.getLogId(); 31 | // if term lower 32 | if (term < raftMemberManager.getSelf().getTerm()) { 33 | return new HeartbeatResponse(); 34 | } 35 | // if log id lower 36 | if (logId < raftMemberManager.getSelf().getLogId()) { 37 | return new HeartbeatResponse(); 38 | } 39 | // let current node to be follower 40 | // refresh leader active time if normal cluster 41 | raftServer.refreshLeaderActive(); 42 | // refresh term 43 | raftMemberManager.getSelf().setTerm(request.getTerm()); 44 | return new HeartbeatResponse(); 45 | } 46 | 47 | @Override 48 | public Class getSource() { 49 | return HeartbeatRequest.class; 50 | } 51 | 52 | public void setAbstractRaftServer(AbstractRaftServer raftServer) { 53 | this.raftServer = raftServer; 54 | } 55 | 56 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 57 | this.raftMemberManager = raftMemberManager; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/LogCommittedRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | import com.daydreamer.raft.api.entity.base.LogEntry; 5 | import com.daydreamer.raft.api.entity.constant.ResponseCode; 6 | import com.daydreamer.raft.api.entity.request.EntryCommittedRequest; 7 | import com.daydreamer.raft.api.entity.response.EntryCommittedResponse; 8 | import com.daydreamer.raft.api.entity.response.ServerErrorResponse; 9 | import com.daydreamer.raft.common.annotation.SPIImplement; 10 | import com.daydreamer.raft.protocol.handler.RequestHandler; 11 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 12 | 13 | import org.apache.log4j.Logger; 14 | 15 | /** 16 | * @author Daydreamer 17 | */ 18 | @SPIImplement("logCommittedRequestHandler") 19 | public class LogCommittedRequestHandler 20 | implements RequestHandler { 21 | 22 | private static final Logger LOGGER = Logger.getLogger(LogCommittedRequestHandler.class); 23 | 24 | private ReplicatedStateMachine replicatedStateMachine; 25 | 26 | @Override 27 | public synchronized Response handle(EntryCommittedRequest request) { 28 | try { 29 | long logId = request.getLogId(); 30 | int term = request.getTerm(); 31 | // commit if not committed 32 | if (replicatedStateMachine.getLastUncommittedLogId() >= request.getLogId()) { 33 | LogEntry logEntry = replicatedStateMachine.getLogById(logId); 34 | if (logEntry != null && logEntry.getTerm() == term) { 35 | replicatedStateMachine.commit(term, logId); 36 | } 37 | } 38 | return new EntryCommittedResponse(true); 39 | } catch (Exception e) { 40 | LOGGER.error("Fail to commit log, because: "+ e.getMessage()); 41 | return new ServerErrorResponse(e.getMessage(), ResponseCode.ERROR_SERVER); 42 | } 43 | } 44 | 45 | @Override 46 | public Class getSource() { 47 | return EntryCommittedRequest.class; 48 | } 49 | 50 | public void setReplicatedStateMachine(ReplicatedStateMachine replicatedStateMachine) { 51 | this.replicatedStateMachine = replicatedStateMachine; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/PrevoteRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.api.entity.request.PrevoteRequest; 4 | import com.daydreamer.raft.api.entity.response.PrevoteResponse; 5 | import com.daydreamer.raft.common.annotation.SPIImplement; 6 | import com.daydreamer.raft.protocol.core.AbstractRaftServer; 7 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 8 | import com.daydreamer.raft.protocol.handler.RequestHandler; 9 | 10 | /** 11 | * @author Daydreamer 12 | */ 13 | @SPIImplement("prevoteRequestHandler") 14 | public class PrevoteRequestHandler implements RequestHandler { 15 | 16 | /** 17 | * raft members 18 | */ 19 | private RaftMemberManager raftMemberManager; 20 | 21 | /** 22 | * raft server 23 | */ 24 | private AbstractRaftServer raftServer; 25 | 26 | /** 27 | * if accepted 28 | */ 29 | private static final PrevoteResponse ACCEPTED = new PrevoteResponse(true); 30 | 31 | /** 32 | * if reject 33 | */ 34 | private static final PrevoteResponse REJECT = new PrevoteResponse(false); 35 | 36 | @Override 37 | public PrevoteResponse handle(PrevoteRequest request) { 38 | // reject vote if leader existed 39 | if (raftServer.leaderExisted()) { 40 | return REJECT; 41 | } 42 | // if term current node larger, then disagree 43 | int clientTerm = request.getTerm(); 44 | long clientLogIdx = request.getLogIndex(); 45 | // if more log 46 | if (clientTerm >= raftMemberManager.getSelf().getTerm() 47 | && clientLogIdx >= raftMemberManager.getSelf().getLogId()) { 48 | return ACCEPTED; 49 | } 50 | // or reject 51 | return REJECT; 52 | } 53 | 54 | @Override 55 | public Class getSource() { 56 | return PrevoteRequest.class; 57 | } 58 | 59 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 60 | this.raftMemberManager = raftMemberManager; 61 | } 62 | 63 | public void setAbstractRaftServer(AbstractRaftServer abstractRaftServer) { 64 | this.raftServer = abstractRaftServer; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/VoteCommitRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.protocol.core.AbstractRaftServer; 5 | import com.daydreamer.raft.protocol.handler.RequestHandler; 6 | import com.daydreamer.raft.api.entity.request.VoteCommitRequest; 7 | import com.daydreamer.raft.api.entity.response.VoteCommitResponse; 8 | 9 | /** 10 | * @author Daydreamer 11 | */ 12 | @SPIImplement("voteCommitRequestHandler") 13 | public class VoteCommitRequestHandler implements RequestHandler { 14 | 15 | /** 16 | * raftServer 17 | */ 18 | private AbstractRaftServer raftServer; 19 | 20 | @Override 21 | public VoteCommitResponse handle(VoteCommitRequest request) { 22 | // judge the request whether from last term current node has voted 23 | if (request.getTerm() == raftServer.getLastTermCurrentNodeHasVoted()) { 24 | // refresh leader active time 25 | raftServer.refreshLeaderActive(); 26 | return new VoteCommitResponse(true); 27 | } 28 | // reject if current node has largest term and log id 29 | if (request.getTerm() < raftServer.getSelf().getTerm()) { 30 | return new VoteCommitResponse(false); 31 | } 32 | if (request.getLogId() < raftServer.getSelf().getLogId()) { 33 | return new VoteCommitResponse(false); 34 | } 35 | // refresh leader active time 36 | raftServer.refreshLeaderActive(); 37 | return new VoteCommitResponse(true); 38 | } 39 | 40 | @Override 41 | public Class getSource() { 42 | return VoteCommitRequest.class; 43 | } 44 | 45 | public void setAbstractRaftServer(AbstractRaftServer abstractRaftServer) { 46 | this.raftServer = abstractRaftServer; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/handler/impl/VoteRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.handler.impl; 2 | 3 | import com.daydreamer.raft.common.annotation.SPIImplement; 4 | import com.daydreamer.raft.protocol.core.AbstractRaftServer; 5 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 6 | import com.daydreamer.raft.protocol.handler.RequestHandler; 7 | import com.daydreamer.raft.api.entity.request.VoteRequest; 8 | import com.daydreamer.raft.api.entity.response.VoteResponse; 9 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 10 | 11 | /** 12 | * @author Daydreamer 13 | *

14 | * vote request handler 15 | */ 16 | @SPIImplement("voteRequestHandler") 17 | @SuppressWarnings("all") 18 | public class VoteRequestHandler implements RequestHandler { 19 | 20 | /** 21 | * if reject 22 | */ 23 | private static final VoteResponse REJECT = new VoteResponse(false); 24 | 25 | /** 26 | * if accept 27 | */ 28 | private static final VoteResponse ACCEPTED = new VoteResponse(true); 29 | 30 | /** 31 | * raftMemberManager 32 | */ 33 | private RaftMemberManager raftMemberManager; 34 | 35 | /** 36 | * raftServer 37 | */ 38 | private AbstractRaftServer abstractRaftServer; 39 | 40 | /** 41 | * log storage 42 | */ 43 | private ReplicatedStateMachine replicatedStateMachine; 44 | 45 | @Override 46 | public synchronized VoteResponse handle(VoteRequest request) { 47 | // reject vote if leader existed 48 | if (abstractRaftServer.leaderExisted()) { 49 | return REJECT; 50 | } 51 | // if current node has voted this term, then reject 52 | if (request.getTerm() <= abstractRaftServer.getLastTermCurrentNodeHasVoted()) { 53 | return REJECT; 54 | } 55 | // determine whether to vote base on lastVotedTerm 56 | int term = request.getTerm(); 57 | long logIndex = request.getLogIndex(); 58 | // wait log committed finish 59 | synchronized (replicatedStateMachine) { 60 | // lower term 61 | if (term < raftMemberManager.getSelf().getLogId()) { 62 | return REJECT; 63 | } 64 | // update last term 65 | abstractRaftServer.refreshLastVotedTerm(request.getTerm()); 66 | // lower log id 67 | if (logIndex < raftMemberManager.getSelf().getLogId()) { 68 | return REJECT; 69 | } 70 | // refresh may be leader active time 71 | abstractRaftServer.refreshLeaderActive(); 72 | return ACCEPTED; 73 | } 74 | } 75 | 76 | @Override 77 | public Class getSource() { 78 | return VoteRequest.class; 79 | } 80 | 81 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 82 | this.raftMemberManager = raftMemberManager; 83 | } 84 | 85 | public void setAbstractRaftServer(AbstractRaftServer abstractRaftServer) { 86 | this.abstractRaftServer = abstractRaftServer; 87 | } 88 | 89 | public void setReplicatedStateMachine(ReplicatedStateMachine replicatedStateMachine) { 90 | this.replicatedStateMachine = replicatedStateMachine; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/storage/ReplicatedStateMachine.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.storage; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPI; 5 | import com.daydreamer.raft.protocol.exception.LogException; 6 | import com.daydreamer.raft.transport.connection.Closeable; 7 | 8 | /** 9 | * @author Daydreamer 10 | *

11 | * log storage 12 | */ 13 | @SPI("replicatedStateMachine") 14 | public interface ReplicatedStateMachine extends Closeable { 15 | 16 | /** 17 | * commit 18 | * 19 | * @param term term 20 | * @param logId log id 21 | * @return whether success, fail if new log id is not linked to last log id 22 | * @throws LogException LogException 23 | */ 24 | boolean commit(int term, long logId) throws LogException; 25 | 26 | /** 27 | * append log 28 | * 29 | * @param logEntry new log 30 | * @return whether success, fail if new log id is not linked to last log id 31 | * @throws LogException LogException 32 | */ 33 | boolean append(LogEntry logEntry) throws LogException; 34 | 35 | /** 36 | * get committed log by id 37 | * 38 | * @param logId log id 39 | * @return log 40 | */ 41 | LogEntry getCommittedLog(long logId); 42 | 43 | /** 44 | * get uncommitted log by id 45 | * 46 | * @param logId log id 47 | * @return log 48 | */ 49 | LogEntry getUncommittedLog(long logId); 50 | 51 | /** 52 | * get last committed log id 53 | * 54 | * @return log id 55 | */ 56 | long getLastCommittedLogId(); 57 | 58 | /** 59 | * get log by id 60 | * 61 | * @param logId log id 62 | * @return get log 63 | */ 64 | LogEntry getLogById(long logId); 65 | 66 | /** 67 | * get last uncommitted log id 68 | * 69 | * @return last uncommitted log id 70 | */ 71 | long getLastUncommittedLogId(); 72 | } 73 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/storage/impl/DelegateReplicatedStateMachine.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.storage.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.common.annotation.SPIMethodInit; 6 | import com.daydreamer.raft.common.loader.GroupAware; 7 | import com.daydreamer.raft.common.loader.RaftServiceLoader; 8 | import com.daydreamer.raft.protocol.chain.LogPostProcessor; 9 | import com.daydreamer.raft.protocol.core.RaftMemberManager; 10 | import com.daydreamer.raft.protocol.exception.LogException; 11 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | 19 | /** 20 | * @author Daydreamer 21 | */ 22 | @SPIImplement("replicatedStateMachine") 23 | public class DelegateReplicatedStateMachine implements ReplicatedStateMachine, GroupAware { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(DelegateReplicatedStateMachine.class); 26 | 27 | private static final String DEFAULT_INSTANCE_NAME = "memoryReplicatedStateMachine"; 28 | 29 | /** 30 | * raftMemberManager 31 | */ 32 | private RaftMemberManager raftMemberManager; 33 | 34 | /** 35 | * storageRepository 36 | */ 37 | private ReplicatedStateMachine replicatedStateMachine; 38 | 39 | /** 40 | * logPostProcessorHolder 41 | */ 42 | private LogPostProcessor logPostProcessor; 43 | 44 | private String groupKey; 45 | 46 | public DelegateReplicatedStateMachine() { 47 | 48 | } 49 | 50 | @SPIMethodInit 51 | private void init() { 52 | this.replicatedStateMachine = RaftServiceLoader.getLoader(groupKey, ReplicatedStateMachine.class) 53 | .getInstance(DEFAULT_INSTANCE_NAME); 54 | } 55 | 56 | @Override 57 | public boolean commit(int term, long logId) throws LogException { 58 | // no uncommitted log 59 | if (getLastUncommittedLogId() == -1) { 60 | return false; 61 | } 62 | // has committed 63 | if (getLastCommittedLogId() == logId) { 64 | return true; 65 | } 66 | // get uncommitted log until logId 67 | List logReadyToCommit = getUncommittedLogUntil(logId); 68 | boolean continueOp = true; 69 | for (int i = 0; continueOp && i < logReadyToCommit.size(); i++) { 70 | continueOp = logPostProcessor.handleBeforeCommit(logReadyToCommit.get(i)); 71 | } 72 | // if false 73 | if (!continueOp) { 74 | return false; 75 | } 76 | // get log ready to commit 77 | boolean commit = replicatedStateMachine.commit(term, logId); 78 | if (commit) { 79 | for (LogEntry log : logReadyToCommit) { 80 | logPostProcessor.handleAfterCommit(log); 81 | } 82 | LOGGER.info("Member: " + raftMemberManager.getSelf().getAddress() + ", " + replicatedStateMachine 83 | .getLogById(logId) + " commit finish!"); 84 | } 85 | return commit; 86 | } 87 | 88 | /** 89 | * get log until logIndex 90 | * 91 | * @param unCommittedLogIndex last log index 92 | * @return log entries 93 | */ 94 | private List getUncommittedLogUntil(long unCommittedLogIndex) { 95 | List logEntries = new ArrayList<>(); 96 | long logIndex = getLastCommittedLogId() + 1; 97 | while (logIndex <= unCommittedLogIndex) { 98 | try { 99 | logEntries.add(getLogById(logIndex)); 100 | } catch (Exception e) { 101 | LOGGER.info("Fail to get log, because {}, log index: {}", e.getMessage(), logIndex); 102 | } 103 | logIndex++; 104 | } 105 | return logEntries; 106 | } 107 | 108 | @Override 109 | public boolean append(LogEntry logEntry) throws LogException { 110 | long lastUncommittedLogIndex = getLastUncommittedLogId(); 111 | // filter 112 | if (!logPostProcessor.handleBeforeAppend(logEntry)) { 113 | return false; 114 | } 115 | boolean append = replicatedStateMachine.append(logEntry); 116 | if (append) { 117 | long logIndex = lastUncommittedLogIndex + 1; 118 | while (logIndex <= getLastUncommittedLogId()) { 119 | logPostProcessor.handleAfterAppend(logEntry); 120 | logIndex++; 121 | } 122 | LOGGER.info("Member: " + raftMemberManager.getSelf().getAddress() + ", " + logEntry + " append finish!"); 123 | } 124 | return append; 125 | } 126 | 127 | @Override 128 | public LogEntry getCommittedLog(long logId) { 129 | return replicatedStateMachine.getCommittedLog(logId); 130 | } 131 | 132 | @Override 133 | public LogEntry getUncommittedLog(long logId) { 134 | return replicatedStateMachine.getUncommittedLog(logId); 135 | } 136 | 137 | @Override 138 | public long getLastCommittedLogId() { 139 | return replicatedStateMachine.getLastCommittedLogId(); 140 | } 141 | 142 | @Override 143 | public LogEntry getLogById(long logId) { 144 | return replicatedStateMachine.getLogById(logId); 145 | } 146 | 147 | @Override 148 | public long getLastUncommittedLogId() { 149 | return replicatedStateMachine.getLastUncommittedLogId(); 150 | } 151 | 152 | @Override 153 | public void close() { 154 | replicatedStateMachine.close(); 155 | } 156 | 157 | public RaftMemberManager getRaftMemberManager() { 158 | return raftMemberManager; 159 | } 160 | 161 | public void setRaftMemberManager(RaftMemberManager raftMemberManager) { 162 | this.raftMemberManager = raftMemberManager; 163 | } 164 | 165 | public void setLogPostProcessor(LogPostProcessor logPostProcessor) { 166 | this.logPostProcessor = logPostProcessor; 167 | } 168 | 169 | @Override 170 | public void setGroupKey(String key) { 171 | this.groupKey = key; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /protocol/src/main/java/com/daydreamer/raft/protocol/storage/impl/MemoryReplicatedStateMachine.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.storage.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.protocol.exception.LogException; 6 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * @author Daydreamer 14 | *

15 | * storage in memory. its max log id is Integer.MAX_VALUE - 1 16 | */ 17 | @SPIImplement("memoryReplicatedStateMachine") 18 | public class MemoryReplicatedStateMachine implements ReplicatedStateMachine { 19 | 20 | /** 21 | * uncommitted logs 22 | */ 23 | private List logEntriesList = Collections.synchronizedList(new ArrayList<>()); 24 | 25 | /** 26 | * last committed log 27 | */ 28 | private int lastCommittedLogIndex = -1; 29 | 30 | /** 31 | * last committed log id 32 | */ 33 | private long lastCommittedLogId = -1; 34 | 35 | /** 36 | * last uncommitted log id 37 | */ 38 | private long lastUncommittedLogId = -1; 39 | 40 | /** 41 | * last uncommitted log index 42 | */ 43 | private int lastUncommittedLogIndex = -1; 44 | 45 | @Override 46 | public synchronized boolean commit(int term, long logId) throws LogException { 47 | // get last uncommitted log 48 | LogEntry lastUncommittedLog = logEntriesList.get(lastUncommittedLogIndex); 49 | // if log id smaller, normal 50 | if (lastUncommittedLog.getLogId() >= logId) { 51 | int committedLogIndex = lastCommittedLogIndex; 52 | committedLogIndex++; 53 | // find log 54 | while (logEntriesList.get(committedLogIndex).getLogId() != logId) { 55 | committedLogIndex++; 56 | } 57 | lastCommittedLogId = logEntriesList.get(committedLogIndex).getLogId(); 58 | lastCommittedLogIndex = committedLogIndex; 59 | return true; 60 | } 61 | // need more log 62 | return false; 63 | } 64 | 65 | @Override 66 | public synchronized boolean append(LogEntry logEntry) throws LogException { 67 | // if empty 68 | if (lastUncommittedLogIndex == -1) { 69 | logEntriesList.add(logEntry); 70 | lastUncommittedLogIndex++; 71 | lastUncommittedLogId = logEntry.getLogId(); 72 | return true; 73 | } 74 | // find suitable index 75 | if (logEntry.getLogId() <= lastCommittedLogId) { 76 | throw new IllegalArgumentException( 77 | "log id: " + logEntry.getLogId() + " has committed, which cannot be modified!"); 78 | } 79 | // not linked 80 | if (logEntry.getLogId() > lastUncommittedLogId + 1) { 81 | return false; 82 | } 83 | int index = lastUncommittedLogIndex; 84 | while (index > lastCommittedLogIndex 85 | && logEntriesList.get(index).getLogId() + 1 != logEntry.getLogId()) { 86 | index--; 87 | } 88 | if (index == lastUncommittedLogIndex) { 89 | logEntriesList.add(logEntry); 90 | lastUncommittedLogId = logEntry.getLogId(); 91 | lastUncommittedLogIndex = index + 1; 92 | } else if (index < lastUncommittedLogIndex) { 93 | // remove old 94 | logEntriesList.add(index + 1, logEntry); 95 | logEntriesList.remove(index + 2); 96 | } 97 | return true; 98 | } 99 | 100 | @Override 101 | public synchronized LogEntry getCommittedLog(long logId) { 102 | // not found 103 | if (lastCommittedLogIndex == -1) { 104 | return null; 105 | } 106 | int index = lastCommittedLogIndex; 107 | while (index >= 0 && logEntriesList.get(index).getLogId() != logId) { 108 | index--; 109 | } 110 | return index == -1 ? null : logEntriesList.get(index); 111 | } 112 | 113 | @Override 114 | public LogEntry getUncommittedLog(long logId) { 115 | if (logEntriesList.size() == 0) { 116 | return null; 117 | } 118 | int index = logEntriesList.size() - 1; 119 | // if not committed anything 120 | if (lastCommittedLogIndex == -1) { 121 | while (index >= 0 && logEntriesList.get(index).getLogId() != logId) { 122 | index--; 123 | } 124 | return index == -1 ? null : logEntriesList.get(index); 125 | } 126 | // if has committed logs 127 | else { 128 | while (index > lastCommittedLogIndex && logEntriesList.get(index).getLogId() != logId) { 129 | index--; 130 | } 131 | return index == lastCommittedLogIndex ? null : logEntriesList.get(index); 132 | } 133 | } 134 | 135 | @Override 136 | public synchronized long getLastCommittedLogId() { 137 | return lastCommittedLogId; 138 | } 139 | 140 | @Override 141 | public LogEntry getLogById(long logId) { 142 | if (lastUncommittedLogIndex == -1) { 143 | return null; 144 | } 145 | if (lastUncommittedLogId >= logId) { 146 | int index = lastUncommittedLogIndex; 147 | while (index >= 0 && logEntriesList.get(index).getLogId() != logId) { 148 | index--; 149 | } 150 | return index == -1 ? null : logEntriesList.get(index); 151 | } 152 | return null; 153 | } 154 | 155 | @Override 156 | public synchronized long getLastUncommittedLogId() { 157 | return lastUncommittedLogId; 158 | } 159 | 160 | @Override 161 | public void close() { 162 | // nothing to do 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.common.filter.LogFilter: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.filter.InnerLogFilter -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.chain.LogPostProcessor: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.chain.LogPostProcessorHolder 2 | com.daydreamer.raft.protocol.chain.impl.MemberChangeLogPostProcessor 3 | com.daydreamer.raft.protocol.core.impl.AsynCommitHookManager 4 | com.daydreamer.raft.protocol.chain.impl.PersistentLogPostProcessor -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.core.AbstractFollowerNotifier: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.core.impl.GrpcFollowerNotifier -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.core.AbstractRaftServer: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.core.impl.GrpcRaftServer -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.core.LogSender: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.core.impl.DefaultLogSender -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.core.RaftMemberManager: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.core.impl.MemberManager -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.handler.RequestHandler: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.handler.impl.AppendEntriesRequestHandler 2 | com.daydreamer.raft.protocol.handler.impl.HeartbeatRequestHandler 3 | com.daydreamer.raft.protocol.handler.impl.LogCommittedRequestHandler 4 | com.daydreamer.raft.protocol.handler.impl.PrevoteRequestHandler 5 | com.daydreamer.raft.protocol.handler.impl.VoteCommitRequestHandler 6 | com.daydreamer.raft.protocol.handler.impl.VoteRequestHandler 7 | com.daydreamer.raft.protocol.handler.impl.DefaultRequestHandler -------------------------------------------------------------------------------- /protocol/src/main/resources/META-INF/raft/com.daydreamer.raft.protocol.storage.ReplicatedStateMachine: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.protocol.storage.impl.DelegateReplicatedStateMachine 2 | com.daydreamer.raft.protocol.storage.impl.MemoryReplicatedStateMachine -------------------------------------------------------------------------------- /protocol/src/test/java/com/daydreamer/raft/protocol/storage/impl/MemoryReplicatedStateMachineTest.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.protocol.storage.impl; 2 | 3 | import com.daydreamer.raft.api.entity.base.LogEntry; 4 | import com.daydreamer.raft.protocol.exception.LogException; 5 | import com.daydreamer.raft.protocol.storage.ReplicatedStateMachine; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.junit.jupiter.api.Test; 8 | 9 | class MemoryReplicatedStateMachineTest { 10 | 11 | @Test 12 | void committedAppend() throws LogException { 13 | ReplicatedStateMachine replicatedStateMachine = new MemoryReplicatedStateMachine(); 14 | LogEntry logEntry = new LogEntry(0, 0); 15 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 16 | logEntry = new LogEntry(0, 1); 17 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 18 | logEntry = new LogEntry(0, 2); 19 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 20 | logEntry = new LogEntry(0, 3); 21 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 22 | logEntry = new LogEntry(1, 4); 23 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 24 | // committed all 25 | Assertions.assertTrue(replicatedStateMachine.commit(1, 4)); 26 | // committed again 27 | Assertions.assertTrue(replicatedStateMachine.commit(1, 4)); 28 | 29 | // committed fail 30 | Assertions.assertFalse(replicatedStateMachine.commit(1, 5)); 31 | 32 | // append more 33 | logEntry = new LogEntry(1, 5); 34 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 35 | logEntry = new LogEntry(1, 6); 36 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 37 | // committed all 38 | Assertions.assertTrue(replicatedStateMachine.commit(1, 6)); 39 | } 40 | 41 | @Test 42 | void notCommittedAppend() throws LogException { 43 | ReplicatedStateMachine replicatedStateMachine = new MemoryReplicatedStateMachine(); 44 | LogEntry logEntry = new LogEntry(0, 0); 45 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 46 | logEntry = new LogEntry(0, 1); 47 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 48 | logEntry = new LogEntry(0, 2); 49 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 50 | logEntry = new LogEntry(0, 3); 51 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 52 | logEntry = new LogEntry(1, 4); 53 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 54 | 55 | // append fail 56 | logEntry = new LogEntry(1, 6); 57 | Assertions.assertFalse(replicatedStateMachine.append(logEntry)); 58 | logEntry = new LogEntry(1, 7); 59 | Assertions.assertFalse(replicatedStateMachine.append(logEntry)); 60 | logEntry = new LogEntry(1, 8); 61 | Assertions.assertFalse(replicatedStateMachine.append(logEntry)); 62 | 63 | // append true 64 | logEntry = new LogEntry(2, 5); 65 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 66 | logEntry = new LogEntry(2, 6); 67 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 68 | 69 | // cover 70 | logEntry = new LogEntry(1, 5); 71 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 72 | logEntry = new LogEntry(1, 6); 73 | Assertions.assertTrue(replicatedStateMachine.append(logEntry)); 74 | } 75 | } -------------------------------------------------------------------------------- /transport/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | raft 7 | org.daydreamer 8 | 1.1-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | transport 13 | ${ddr-raft.version} 14 | 15 | 16 | 17 | org.daydreamer 18 | common 19 | ${ddr-raft.version} 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/connection/Closeable.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.connection; 2 | 3 | /** 4 | * @author Daydreamer 5 | */ 6 | public interface Closeable { 7 | 8 | /** 9 | * close 10 | */ 11 | void close(); 12 | } 13 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/connection/Connection.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.connection; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | 6 | import java.util.concurrent.Future; 7 | import java.util.concurrent.TimeoutException; 8 | 9 | /** 10 | * @author Daydreamer 11 | */ 12 | public abstract class Connection implements Closeable { 13 | 14 | private String id; 15 | 16 | public Connection(String id) { 17 | this.id = id; 18 | } 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public void setId(String id) { 25 | this.id = id; 26 | } 27 | 28 | /** 29 | * send request 30 | * 31 | * @param request request 32 | * @param timeout timeout 33 | * @throws TimeoutException exception if time out 34 | * @return future 35 | */ 36 | public abstract Response request(Request request, long timeout) throws Exception; 37 | 38 | /** 39 | * send asyn 40 | * 41 | * @param request data 42 | * @return future 43 | */ 44 | public abstract Future request(Request request) throws Exception; 45 | 46 | /** 47 | * call back allow if response 48 | * 49 | * @param request request 50 | * @param timeout timeout 51 | * @param callBack call back 52 | * @throws Exception exception 53 | */ 54 | public abstract void request(Request request, long timeout, ResponseCallBack callBack) throws Exception; 55 | 56 | /** 57 | * call back allow if response 58 | * 59 | * @param request request 60 | * @param callBack call back 61 | * @throws Exception exception 62 | */ 63 | public abstract void request(Request request, ResponseCallBack callBack) throws Exception; 64 | } 65 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/connection/ResponseCallBack.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.connection; 2 | 3 | import com.daydreamer.raft.api.entity.Response; 4 | 5 | /** 6 | * @author Daydreamer 7 | */ 8 | public interface ResponseCallBack { 9 | 10 | /** 11 | * do for success 12 | * 13 | * @param response response 14 | */ 15 | void onSuccess(Response response); 16 | 17 | /** 18 | * do for failure 19 | * 20 | * @param e exception 21 | */ 22 | void onFail(Exception e); 23 | 24 | /** 25 | * do for timeout 26 | */ 27 | void onTimeout(); 28 | } 29 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/connection/impl/grpc/GrpcConnection.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.connection.impl.grpc; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.api.entity.base.ErrorResponse; 6 | import com.daydreamer.raft.api.exception.InvalidResponseException; 7 | import com.daydreamer.raft.api.grpc.Message; 8 | import com.daydreamer.raft.api.grpc.RequesterGrpc; 9 | import com.daydreamer.raft.common.entity.SimpleFuture; 10 | import com.daydreamer.raft.common.utils.MsgUtils; 11 | import com.daydreamer.raft.transport.connection.Connection; 12 | import com.daydreamer.raft.transport.connection.ResponseCallBack; 13 | import io.grpc.ManagedChannel; 14 | 15 | import java.util.concurrent.Future; 16 | import java.util.concurrent.TimeUnit; 17 | import java.util.concurrent.TimeoutException; 18 | 19 | /** 20 | * @author Daydreamer 21 | *

22 | * client to request 23 | */ 24 | public class GrpcConnection extends Connection { 25 | 26 | private RequesterGrpc.RequesterBlockingStub requesterBlockingStub; 27 | 28 | public GrpcConnection(String id, RequesterGrpc.RequesterBlockingStub requesterFutureStub) { 29 | super(id); 30 | this.requesterBlockingStub = requesterFutureStub; 31 | } 32 | 33 | @Override 34 | public Response request(Request request, long timeout) throws Exception { 35 | Message msg = MsgUtils.convertMsg(request); 36 | SimpleFuture responseFuture = new SimpleFuture<>(() -> { 37 | Message responseMsg = requesterBlockingStub.request(msg); 38 | return MsgUtils.convertResponse(responseMsg); 39 | }); 40 | try { 41 | Response response = responseFuture.get(timeout, TimeUnit.MICROSECONDS); 42 | if (response != null) { 43 | return response; 44 | } else { 45 | throw new TimeoutException(); 46 | } 47 | } catch (InterruptedException e) { 48 | // nothing to do 49 | } catch (Exception e) { 50 | throw responseFuture.getException(); 51 | } 52 | return null; 53 | } 54 | 55 | @Override 56 | public Future request(Request request) { 57 | Message msg = MsgUtils.convertMsg(request); 58 | return new SimpleFuture<>(() -> { 59 | Message responseMsg = requesterBlockingStub.request(msg); 60 | return MsgUtils.convertResponse(responseMsg); 61 | }); 62 | } 63 | 64 | @Override 65 | public void request(Request request, long timeout, ResponseCallBack callBack) { 66 | new SimpleFuture<>(() -> { 67 | Response result = null; 68 | try { 69 | Future future = request(request); 70 | Response response = future.get(timeout, TimeUnit.MICROSECONDS); 71 | if (response instanceof ErrorResponse) { 72 | callBack.onFail(new InvalidResponseException((ErrorResponse) response)); 73 | } 74 | callBack.onSuccess(response); 75 | } catch (TimeoutException te) { 76 | callBack.onTimeout(); 77 | } catch (Exception e) { 78 | callBack.onFail(e); 79 | } 80 | return result; 81 | }); 82 | } 83 | 84 | @Override 85 | public void request(Request request, ResponseCallBack callBack) throws Exception { 86 | new SimpleFuture<>(() -> { 87 | Response result = null; 88 | try { 89 | Message responseMsg = requesterBlockingStub.request(MsgUtils.convertMsg(request)); 90 | Response response = MsgUtils.convertResponse(responseMsg); 91 | if (response instanceof ErrorResponse) { 92 | callBack.onFail(new InvalidResponseException((ErrorResponse) response)); 93 | } 94 | callBack.onSuccess(response); 95 | } catch (Exception e) { 96 | callBack.onFail(e); 97 | } 98 | return result; 99 | }); 100 | } 101 | 102 | @Override 103 | public void close() { 104 | try { 105 | ((ManagedChannel) requesterBlockingStub.getChannel()).awaitTermination(100, TimeUnit.MICROSECONDS); 106 | } catch (InterruptedException e) { 107 | // nothing to do 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/connection/impl/http/HttpConnection.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.connection.impl.http; 2 | 3 | import com.daydreamer.raft.api.entity.Request; 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.transport.connection.Connection; 6 | import com.daydreamer.raft.transport.connection.ResponseCallBack; 7 | 8 | import java.util.concurrent.Future; 9 | 10 | /** 11 | * @author Daydreamer 12 | * 13 | * TODO No plan at the moment 14 | */ 15 | public class HttpConnection extends Connection { 16 | 17 | public HttpConnection(String id) { 18 | super(id); 19 | throw new UnsupportedOperationException("No plain current version"); 20 | } 21 | 22 | @Override 23 | public Response request(Request request, long timeout) throws Exception { 24 | return null; 25 | } 26 | 27 | @Override 28 | public Future request(Request request) throws Exception { 29 | return null; 30 | } 31 | 32 | @Override 33 | public void request(Request request, long timeout, ResponseCallBack callBack) throws Exception { 34 | 35 | } 36 | 37 | @Override 38 | public void request(Request request, ResponseCallBack callBack) throws Exception { 39 | 40 | } 41 | 42 | @Override 43 | public void close() { 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/constant/ResponseRepository.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.constant; 2 | 3 | 4 | import com.daydreamer.raft.api.entity.Response; 5 | import com.daydreamer.raft.api.entity.constant.ResponseCode; 6 | import com.daydreamer.raft.api.entity.response.ClientErrorResponse; 7 | import com.daydreamer.raft.api.entity.response.ServerErrorResponse; 8 | 9 | 10 | /** 11 | * @author Daydreamer 12 | * 13 | * Easy to get static response 14 | */ 15 | public class ResponseRepository { 16 | 17 | public static final Response NOT_HANDLER_FOUND = new ClientErrorResponse("Not handler found", ResponseCode.ERROR_CLIENT); 18 | 19 | public static final Response SERVER_UNKNOWN_ERROR = new ServerErrorResponse("Unknown error from server", ResponseCode.ERROR_SERVER); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/factory/ConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.factory; 2 | 3 | import com.daydreamer.raft.common.annotation.SPI; 4 | import com.daydreamer.raft.transport.connection.Connection; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * @author Daydreamer 10 | *

11 | * Factory to get connection 12 | */ 13 | @SPI("grpc") 14 | public interface ConnectionFactory { 15 | 16 | /** 17 | * get connection 18 | * 19 | * @return get connection 20 | */ 21 | Connection getConnection(String ip, Integer port, Map metadata); 22 | } 23 | -------------------------------------------------------------------------------- /transport/src/main/java/com/daydreamer/raft/transport/factory/impl/GrpcConnectionFactory.java: -------------------------------------------------------------------------------- 1 | package com.daydreamer.raft.transport.factory.impl; 2 | 3 | import com.daydreamer.raft.api.grpc.RequesterGrpc; 4 | import com.daydreamer.raft.common.annotation.SPIImplement; 5 | import com.daydreamer.raft.transport.connection.Connection; 6 | import com.daydreamer.raft.transport.connection.impl.grpc.GrpcConnection; 7 | import com.daydreamer.raft.transport.factory.ConnectionFactory; 8 | import io.grpc.ManagedChannel; 9 | import io.grpc.ManagedChannelBuilder; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * @author Daydreamer 15 | */ 16 | @SPIImplement("grpc") 17 | public class GrpcConnectionFactory implements ConnectionFactory { 18 | 19 | private static final String SEPARATOR = ":"; 20 | 21 | @Override 22 | public Connection getConnection(String ip, Integer port, Map metadata) { 23 | // init channel 24 | ManagedChannel channel = ManagedChannelBuilder.forAddress(ip, port) 25 | .usePlaintext() 26 | .build(); 27 | // init service rpc Stub 28 | RequesterGrpc.RequesterBlockingStub blockingStub = RequesterGrpc.newBlockingStub(channel); 29 | return new GrpcConnection(ip + SEPARATOR + port, blockingStub); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /transport/src/main/resources/META-INF/raft/com.daydreamer.raft.transport.factory.ConnectionFactory: -------------------------------------------------------------------------------- 1 | com.daydreamer.raft.transport.factory.impl.GrpcConnectionFactory --------------------------------------------------------------------------------