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. linux source code search

    https://elixir.bootlin.com/linux/latest/source/fs/eventpoll.c#L1120

  2. visio2013激活软件

    环境是 win7, 64 bit 装了 visio 2013 , 可以却不能用它来画图,在网上找了一些破解工具,大都不能解决问题.网上不靠谱的广告型文章太多了,比较头痛. 所幸,终于找到正确的破解工具 ...

  3. Android Launcher分析和修改8——AllAPP界面拖拽元素(PagedViewWithDraggableItems)

    接着上一篇文章,继续分析AllAPP列表界面.上一篇文章分析了所有应用列表的界面构成以及如何通过配置文件修改属性.今天主要是分析PagedViewWithDraggableItems类,因为在我们分析 ...

  4. 【iCore4 双核心板_FPGA】例程七:状态机实验——状态机使用

    实验现象:按键每按下一次,三色LED改变一次状态. 核心代码: //--------------------module_rst_n---------------------------// modu ...

  5. java InputStream和OutputStream

    InputStream类型 类 功能 构造器参数 如何使用 ByteArrayInputStream 允许将内存的缓冲区当做InputStreams使用 缓冲区,字节将从中取出 作为一种数据源:将其与 ...

  6. c#.net基础

    值类型:值类型的实例一般在线程的栈上分配 引用类型:引用类型的实例在线程的托管堆上分配 引用类型变量的Equals比较的是二者的引用地址而不是内部的值,值类型变量的Equals方法比较的是二者的值. ...

  7. 在VSCode中成功安装Go相关插件问题:tools failed to install.

    一.介绍 目的:本文将主要介绍在windows使用VSCode配置Go语言环境 软件:VSCode 二.安装出现的问题 完整信息如下 Installing tools at D:\GoPath\bin ...

  8. Hbase学习笔记——基本CRUD操作

    进入Hbase的安装目录,启动Hbase bin/start-hbase.sh 打开shell命令行模式 bin/hbase shell 关闭Hbase bin/stop-hbase.sh 一个cel ...

  9. 一个Login页面全面了解session与cookie

    背景 做了四年的前端开发,对外一直说自己是web开发,那么身为一个web开发怎能不知道session与cookie以及其管理方式呢~ Login涉及技术栈:Nodejs,MongoDB,Express ...

  10. Android Demos

    SDK Manager   下载demo后,可以到SDK目录下面找 例如 C:\Program Files (x86)\Java\adt-bundle-windows-x86\sdk\samples\ ...