client.submit(new PutCommand("foo", "Hello world!"));

ServerContext

connection.handler(CommandRequest.class, request -> state.command(request));

State.command

ReserveState开始,会把command forward到leader,只有leader可以处理command

@Override
public CompletableFuture<CommandResponse> command(CommandRequest request) {
context.checkThread();
logRequest(request); if (context.getLeader() == null) {
return CompletableFuture.completedFuture(logResponse(CommandResponse.builder()
.withStatus(Response.Status.ERROR)
.withError(CopycatError.Type.NO_LEADER_ERROR)
.build()));
} else {
return this.<CommandRequest, CommandResponse>forward(request)
.exceptionally(error -> CommandResponse.builder()
.withStatus(Response.Status.ERROR)
.withError(CopycatError.Type.NO_LEADER_ERROR)
.build())
.thenApply(this::logResponse);
}
}

LeaderState.Command

public CompletableFuture<CommandResponse> command(final CommandRequest request) {
context.checkThread();
logRequest(request); // Get the client's server session. If the session doesn't exist, return an unknown session error.
ServerSessionContext session = context.getStateMachine().executor().context().sessions().getSession(request.session());
if (session == null) { //如果session不存在,无法处理该command
return CompletableFuture.completedFuture(logResponse(CommandResponse.builder()
.withStatus(Response.Status.ERROR)
.withError(CopycatError.Type.UNKNOWN_SESSION_ERROR)
.build()));
} ComposableFuture<CommandResponse> future = new ComposableFuture<>();
sequenceCommand(request, session, future);
return future;
}

sequenceCommand

/**
* Sequences the given command to the log.
*/
private void sequenceCommand(CommandRequest request, ServerSessionContext session, CompletableFuture<CommandResponse> future) {
// If the command is LINEARIZABLE and the session's current sequence number is less then one prior to the request
// sequence number, queue this request for handling later. We want to handle command requests in the order in which
// they were sent by the client. Note that it's possible for the session sequence number to be greater than the request
// sequence number. In that case, it's likely that the command was submitted more than once to the
// cluster, and the command will be deduplicated once applied to the state machine.
if (request.sequence() > session.nextRequestSequence()) { //session中的request需要按sequence执行,所以如果request的sequence num大于我们期望的,说明这个request需要等之前的request先执行
// If the request sequence number is more than 1k requests above the last sequenced request, reject the request.
// The client should resubmit a request that fails with a COMMAND_ERROR.
if (request.sequence() - session.getRequestSequence() > MAX_REQUEST_QUEUE_SIZE) { //如果request的sequence大的太多,和当前sequence比,大100以上
future.complete(CommandResponse.builder()
.withStatus(Response.Status.ERROR)
.withError(CopycatError.Type.COMMAND_ERROR) //拒绝该command
.build());
}
// Register the request in the request queue if it's not too far ahead of the current sequence number.
else {
session.registerRequest(request.sequence(), () -> applyCommand(request, session, future)); //放入queue等待
}
} else {
applyCommand(request, session, future); //apply该command
}
}

如果command的request比期望的大,

session.registerRequest

ServerSessionContext

  ServerSessionContext registerRequest(long sequence, Runnable runnable) {
commands.put(sequence, runnable);
return this;
}

可以看到会把sequence id和对于的function注册到commands里面,这里就是applyCommand

问题这个commands会在什么时候被触发执行,

ServerSessionContext setRequestSequence(long request) {
if (request > this.requestSequence) {
this.requestSequence = request; // When the request sequence number is incremented, get the next queued request callback and call it.
// This will allow the command request to be evaluated in sequence.
Runnable command = this.commands.remove(nextRequestSequence());
if (command != null) {
command.run();
}
}
return this;
}

在setRequestSequence的时候,

当set的时候,去commands里面看下,是否有下一个request在等待,如果有直接执行掉

applyCommand

/**
* Applies the given command to the log.
*/
private void applyCommand(CommandRequest request, ServerSessionContext session, CompletableFuture<CommandResponse> future) {
final Command command = request.command(); final long term = context.getTerm();
final long timestamp = System.currentTimeMillis();
final long index; // Create a CommandEntry and append it to the log.
try (CommandEntry entry = context.getLog().create(CommandEntry.class)) {
entry.setTerm(term)
.setSession(request.session())
.setTimestamp(timestamp)
.setSequence(request.sequence())
.setCommand(command);
index = context.getLog().append(entry); //把CommandEntry写入log
LOGGER.debug("{} - Appended {}", context.getCluster().member().address(), entry);
} // Replicate the command to followers.
appendCommand(index, future); // Set the last processed request for the session. This will cause sequential command callbacks to be executed.
session.setRequestSequence(request.sequence()); //更新session的sequence,这里会试图去check session.commands是否有next request
}

appendCommand

/**
* Sends append requests for a command to followers.
*/
private void appendCommand(long index, CompletableFuture<CommandResponse> future) {
appender.appendEntries(index).whenComplete((commitIndex, commitError) -> { //appendEntries到该index
context.checkThread();
if (isOpen()) {
if (commitError == null) {
applyCommand(index, future); //如果成功,applyCommand
} else {
future.complete(logResponse(CommandResponse.builder()
.withStatus(Response.Status.ERROR)
.withError(CopycatError.Type.INTERNAL_ERROR)
.build()));
}
}
});
}

applyCommand,函数名不能换换吗

  /**
* Applies a command to the state machine.
*/
private void applyCommand(long index, CompletableFuture<CommandResponse> future) {
context.getStateMachine().<ServerStateMachine.Result>apply(index).whenComplete((result, error) -> {
if (isOpen()) {
completeOperation(result, CommandResponse.builder(), error, future);
}
});
}

apply,我收到command首先要把它写到log里面,然后同步给follower,最终,需要去执行command,比如修改状态机里面的值,a=1

ServerContext.getStateMachine(),返回

private ServerStateMachine stateMachine;
 
这里调用ServerStateMachine.apply(index)
调用apply(entry)
调用apply((CommandEntry) entry)
private CompletableFuture<Result> apply(CommandEntry entry) {
final CompletableFuture<Result> future = new CompletableFuture<>();
final ThreadContext context = ThreadContext.currentContextOrThrow(); //这里保留当前thread的引用 // First check to ensure that the session exists.
ServerSessionContext session = executor.context().sessions().getSession(entry.getSession()); // If the session is null, return an UnknownSessionException. Commands applied to the state machine must
// have a session. We ensure that session register/unregister entries are not compacted from the log
// until all associated commands have been cleaned.
if (session == null) { //session不存在
log.release(entry.getIndex());
return Futures.exceptionalFuture(new UnknownSessionException("unknown session: " + entry.getSession()));
}
// If the session is not in an active state, return an UnknownSessionException. Sessions are retained in the
// session registry until all prior commands have been released by the state machine, but new commands can
// only be applied for sessions in an active state.
else if (!session.state().active()) { //session的状态非active
log.release(entry.getIndex());
return Futures.exceptionalFuture(new UnknownSessionException("inactive session: " + entry.getSession()));
}
// If the command's sequence number is less than the next session sequence number then that indicates that
// we've received a command that was previously applied to the state machine. Ensure linearizability by
// returning the cached response instead of applying it to the user defined state machine.
else if (entry.getSequence() > 0 && entry.getSequence() < session.nextCommandSequence()) { //已经apply过的entry
// Ensure the response check is executed in the state machine thread in order to ensure the
// command was applied, otherwise there will be a race condition and concurrent modification issues.
long sequence = entry.getSequence(); // Switch to the state machine thread and get the existing response.
executor.executor().execute(() -> sequenceCommand(sequence, session, future, context)); //直接返回之前apply的结果
return future;
}
// If we've made it this far, the command must have been applied in the proper order as sequenced by the
// session. This should be the case for most commands applied to the state machine.
else {
// Allow the executor to execute any scheduled events.
long index = entry.getIndex();
long sequence = entry.getSequence(); // Calculate the updated timestamp for the command.
long timestamp = executor.timestamp(entry.getTimestamp()); // Execute the command in the state machine thread. Once complete, the CompletableFuture callback will be completed
// in the state machine thread. Register the result in that thread and then complete the future in the caller's thread.
ServerCommit commit = commits.acquire(entry, session, timestamp); //这里有个ServerCommitPool的实现,为了避免反复生成ServerCommit对象,直接从pool里面拿一个,用完放回去
executor.executor().execute(() -> executeCommand(index, sequence, timestamp, commit, session, future, context)); // Update the last applied index prior to the command sequence number. This is necessary to ensure queries sequenced
// at this index receive the index of the command.
setLastApplied(index); // Update the session timestamp and command sequence number. This is done in the caller's thread since all
// timestamp/index/sequence checks are done in this thread prior to executing operations on the state machine thread.
session.setTimestamp(timestamp).setCommandSequence(sequence);
return future;
}
}
executeCommand
ServerCommit commit = commits.acquire(entry, session, timestamp);
executor.executor().execute(() -> executeCommand(index, sequence, timestamp, commit, session, future, context));

注意这里有两个线程,

一个是context,是

ThreadContext threadContext

用来响应server请求的

还有一个是executor里面的stateContext,用来改变stateMachine的状态的

所以这里是用executor来执行executeCommand,但把ThreadContext传入

/**
* Executes a state machine command.
*/
private void executeCommand(long index, long sequence, long timestamp, ServerCommit commit, ServerSessionContext session, CompletableFuture<Result> future, ThreadContext context) { // Trigger scheduled callbacks in the state machine.
executor.tick(index, timestamp); // Update the state machine context with the commit index and local server context. The synchronous flag
// indicates whether the server expects linearizable completion of published events. Events will be published
// based on the configured consistency level for the context.
executor.init(commit.index(), commit.time(), ServerStateMachineContext.Type.COMMAND); // Store the event index to return in the command response.
long eventIndex = session.getEventIndex(); try {
// Execute the state machine operation and get the result.
Object output = executor.executeOperation(commit); // Once the operation has been applied to the state machine, commit events published by the command.
// The state machine context will build a composite future for events published to all sessions.
executor.commit(); // Store the result for linearizability and complete the command.
Result result = new Result(index, eventIndex, output);
session.registerResult(sequence, result); // 缓存执行结果
context.executor().execute(() -> future.complete(result)); // complete future,表示future执行结束
} catch (Exception e) {
// If an exception occurs during execution of the command, store the exception.
Result result = new Result(index, eventIndex, e);
session.registerResult(sequence, result);
context.executor().execute(() -> future.complete(result));
}
}
ServerStateMachineExecutor.tick
根据时间,去触发scheduledTasks中已经到时间的task
 
ServerStateMachineExecutor.init
更新state machine的context
void init(long index, Instant instant, ServerStateMachineContext.Type type) {
context.update(index, instant, type);
} //ServerStateMachineContext
void update(long index, Instant instant, Type type) {
this.index = index;
this.type = type;
clock.set(instant);
}
 
ServerStateMachineExecutor.executeOperation
<T extends Operation<U>, U> U executeOperation(Commit commit) {

    // Get the function registered for the operation. If no function is registered, attempt to
// use a global function if available.
Function function = operations.get(commit.type()); //从operations找到type对应的function if (function == null) {
// If no operation function was found for the class, try to find an operation function
// registered with a parent class.
for (Map.Entry<Class, Function> entry : operations.entrySet()) {
if (entry.getKey().isAssignableFrom(commit.type())) { //如果注册的type是commit.type的父类
function = entry.getValue();
break;
}
} // If a parent operation function was found, store the function for future reference.
if (function != null) {
operations.put(commit.type(), function);
}
} if (function == null) {
throw new IllegalStateException("unknown state machine operation: " + commit.type());
} else {
// Execute the operation. If the operation return value is a Future, await the result,
// otherwise immediately complete the execution future.
try {
return (U) function.apply(commit); //真正执行function
} catch (Exception e) {
throw new ApplicationException(e, "An application error occurred");
}
}
}
 
 
RequestSequence 和 CommandSequence有什么不同的,看看都在什么地方用了?
 

RequestSequence

Set

ServerStateMachine

private CompletableFuture<Void> apply(KeepAliveEntry entry) {
//…
  // Update the session keep alive index for log cleaning.
session.setKeepAliveIndex(entry.getIndex()).setRequestSequence(commandSequence);
}

LeaderState

private void applyCommand(CommandRequest request, ServerSessionContext session, CompletableFuture<CommandResponse> future) {
//……
  // Set the last processed request for the session. This will cause sequential command callbacks to be executed.
session.setRequestSequence(request.sequence());
}
 

Get

ServerSessionContext.setCommandSequence
// If the request sequence number is less than the applied sequence number, update the request
// sequence number. This is necessary to ensure that if the local server is a follower that is
// later elected leader, its sequences are consistent for commands.
if (sequence > requestSequence) {
// Only attempt to trigger command callbacks if any are registered.
if (!this.commands.isEmpty()) {
// For each request sequence number, a command callback completing the command submission may exist.
for (long i = this.requestSequence + 1; i <= sequence; i++) {
this.requestSequence = i;
Runnable command = this.commands.remove(i);
if (command != null) {
command.run();
}
}
} else {
this.requestSequence = sequence;
}
}

LeaderState

/**
* Sequences the given command to the log.
*/
private void sequenceCommand(CommandRequest request, ServerSessionContext session, CompletableFuture<CommandResponse> future) {
// If the command is LINEARIZABLE and the session's current sequence number is less then one prior to the request
// sequence number, queue this request for handling later. We want to handle command requests in the order in which
// they were sent by the client. Note that it's possible for the session sequence number to be greater than the request
// sequence number. In that case, it's likely that the command was submitted more than once to the
// cluster, and the command will be deduplicated once applied to the state machine.
if (request.sequence() > session.nextRequestSequence()) {
// If the request sequence number is more than 1k requests above the last sequenced request, reject the request.
// The client should resubmit a request that fails with a COMMAND_ERROR.
if (request.sequence() - session.getRequestSequence() > MAX_REQUEST_QUEUE_SIZE) {

CommandSequence

Set

ServerSessionContext.setCommandSequence

for (long i = commandSequence + 1; i <= sequence; i++) {
commandSequence = i;
List<Runnable> queries = this.sequenceQueries.remove(commandSequence);
if (queries != null) {
for (Runnable query : queries) {
query.run();
}
queries.clear();
queriesPool.add(queries);
}
}

ServerStateMachine

private CompletableFuture<Result> apply(CommandEntry entry)
// Update the session timestamp and command sequence number. This is done in the caller's thread since all
// timestamp/index/sequence checks are done in this thread prior to executing operations on the state machine thread.
session.setTimestamp(timestamp).setCommandSequence(sequence);

Get

LeaderState

sequenceLinearizableQuery, sequenceBoundedLinearizableQuery
/**
* Sequences a bounded linearizable query.
*/
private void sequenceBoundedLinearizableQuery(QueryEntry entry, ServerSessionContext session, CompletableFuture<QueryResponse> future) {
// If the query's sequence number is greater than the session's current sequence number, queue the request for
// handling once the state machine is caught up.
if (entry.getSequence() > session.getCommandSequence()) {
session.registerSequenceQuery(entry.getSequence(), () -> applyQuery(entry, future));
} else {
applyQuery(entry, future);
}
}

PassiveState

private void sequenceQuery(QueryEntry entry, ServerSessionContext session, CompletableFuture<QueryResponse> future) {
// If the query's sequence number is greater than the session's current sequence number, queue the request for
// handling once the state machine is caught up.
if (entry.getSequence() > session.getCommandSequence()) {
session.registerSequenceQuery(entry.getSequence(), () -> indexQuery(entry, future));
} else {
indexQuery(entry, future);
}
}

Copycat - command的更多相关文章

  1. Copycat - StateMachine

    看下用户注册StateMachine的过程, CopycatServer.Builder builder = CopycatServer.builder(address); builder.withS ...

  2. Copycat - Overview

    Copycat’s primary role is as a framework for building highly consistent, fault-tolerant replicated s ...

  3. Copycat - MemberShip

    https://github.com/atomix/copycat   http://atomix.io/copycat/docs/membership/   为了便于实现,Copycat把membe ...

  4. ifconfig: command not found(CentOS专版,其他的可以参考)

    ifconfig: command not found 查看path配置(echo相当于c中的printf,C#中的Console.WriteLine) echo $PATH 解决方案1:先看看是不是 ...

  5. scp报错 -bash: scp: command not found

    环境:RHEL6.5 使用scp命令报错: [root@oradb23 media]# scp /etc/hosts oradb24:/etc/ -bash: scp: command not fou ...

  6. ENode框架单台机器在处理Command时的设计思路

    设计目标 尽量快的处理命令和事件,保证吞吐量: 处理完一个命令后不需要等待命令产生的事件持久化完成就能处理下一个命令,从而保证领域内的业务逻辑处理不依赖于持久化IO,实现真正的in-memory: 保 ...

  7. 设计模式(六):控制台中的“命令模式”(Command Pattern)

    今天的博客中就来系统的整理一下“命令模式”.说到命令模式,我就想起了控制台(Console)中的命令.无论是Windows操作系统(cmd.exe)还是Linux操作系统(命令行式shell(Comm ...

  8. GET command找不到

    谷歌的: On running a cronjob with get command, I was getting the following error. /bin/sh: GET: command ...

  9. source /etc/profile报错-bash: id:command is not found

    由于误操作导致 source /etc/profile 报错 -bash: id:command is not found 此时,linux下很多命令到不能能用,包括vi ls 等... 可以使用 e ...

随机推荐

  1. JAVA(二)异常/包及访问权限/多线程/泛型

    成鹏致远 | lcw.cnblog.com |2014-01-28 异常 1.异常的基本概念 异常是导致程序中断运行的一种指令流 基本的异常处理格式:try...catch,try中捕获异常,出现异常 ...

  2. haproxy配置详解

    先看一个ha的配置文件: # # Global settings # global # to have these messages end up in /var/log/haproxy.log yo ...

  3. const读书笔记

    Const 的使用读书笔记 Const的主要主要用法有: 常变量: const 类型说明符 变量名 常引用: const 类型说明符 &引用名  常对象: 类名 const 对象名 常成员函数 ...

  4. Solr学习笔记——导入JSON数据

    1.导入JSON数据的方式有两种,一种是在web管理界面中导入,另一种是使用curl命令来导入 curl http://localhost:8983/solr/baikeperson/update/j ...

  5. struts建立工程helloworld

    Java web环境:Tomcat + Jdk +eclipse java EE 创建一个能运行的java web工程,记得勾选上web.xml 下载struts库,目前最新2.5-2.16 all. ...

  6. 怎样利用Heartbeat与Floating IP在Ubuntu 14.04上创建高可用性设置

    提供 ZStack社区 内容简单介绍 Heartbeat是一款开源程序,负责将集群基础设施容量--包括集群成员与消息收发--交付至客户server. Hearbeat在高可用性server基础设施其中 ...

  7. 【NLP】分词 新词

    基于大规模语料的新词发现算法 https://blog.csdn.net/xgjianstart/article/details/52193258 互联网时代的社会语言学:基于SNS的文本数据挖掘 h ...

  8. 斯坦福2011秋季 iPad and iPhone Application Development 资源

    1. MVC and Introduction to Objective-C (September 27, 2011) - HD 2. My First iOS App (September 29, ...

  9. Linux设备驱动剖析之Input(二)

    分别是总线类型.厂商号.产品号和版本号. 1156行,evbit,设备支持的事件类型的位图,每一位代表一种事件,比如EV_KEY.EV_REL事件等等.BITS_TO_LONGS(nr)是一个宏,假设 ...

  10. C#设计模式--观察者模式(发布-订阅模式)

    0.C#设计模式--简单工厂模式 1.C#设计模式--工厂方法模式 2.C#设计模式--抽象工厂模式 3.C#设计模式--单例模式 4.C#设计模式--建造者模式 5.C#设计模式--原型模式 6.C ...