Copycat - StateMachine
看下用户注册StateMachine的过程,
CopycatServer.Builder builder = CopycatServer.builder(address);
builder.withStateMachine(MapStateMachine::new);
MapStateMachine::new这会构造一个supplier
/**
* Sets the Raft state machine factory.
*
* @param factory The Raft state machine factory.
* @return The server builder.
* @throws NullPointerException if the {@code factory} is {@code null}
*/
public Builder withStateMachine(Supplier<StateMachine> factory) {
this.stateMachineFactory = Assert.notNull(factory, "factory");
return this;
}
在build中,传入初始化ServerContext
ServerContext context = new ServerContext(name, type, serverAddress, clientAddress, storage, serializer, stateMachineFactory, connections, threadContext);
ServerContext中,
this.stateMachineFactory = Assert.notNull(stateMachineFactory, "stateMachineFactory");
threadContext.execute(this::reset).join();
reset逻辑中,
// Create a new user state machine.
StateMachine stateMachine = stateMachineFactory.get();
// Create a new internal server state machine.
this.stateMachine = new ServerStateMachine(stateMachine, this, stateContext);
这里看到stateContext的定义,
this.stateContext = new SingleThreadContext(String.format("copycat-server-%s-%s-state", serverAddress, name), threadContext.serializer().clone());
也是一个单线程,所以这里有两个threadContext
这个stateContex是专门用于更新state
ServerStateMachine,用于管理StateMachine
用户定义的StateMachine中定了对于各种command的处理function,而在copycat中需要一个组件来管理用户的StateMachine,就是ServerStateMachine
ServerStateMachine(StateMachine stateMachine, ServerContext state, ThreadContext executor) {
this.stateMachine = Assert.notNull(stateMachine, "stateMachine");
this.state = Assert.notNull(state, "state");
this.log = state.getLog();
this.executor = new ServerStateMachineExecutor(new ServerStateMachineContext(state.getConnections(), new ServerSessionManager(state)), executor);
this.commits = new ServerCommitPool(log, this.executor.context().sessions());
init();
}
ServerStateMachineExecutor
作为StateMachine的执行环境
class ServerStateMachineExecutor implements StateMachineExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerStateMachineExecutor.class);
private final ThreadContext executor;
private final ServerStateMachineContext context;
private final Queue<ServerTask> tasks = new ArrayDeque<>();
private final List<ServerScheduledTask> scheduledTasks = new ArrayList<>();
private final List<ServerScheduledTask> complete = new ArrayList<>();
private final Map<Class, Function> operations = new HashMap<>();
init
/**
* Initializes the state machine.
*/
private void init() {
stateMachine.init(executor);
}
注意这里stateMachine类是用户定义的,
public void init(StateMachineExecutor executor) {
this.executor = Assert.notNull(executor, "executor");
this.context = executor.context();
this.clock = context.clock();
this.sessions = context.sessions();
if (this instanceof SessionListener) {
executor.context().sessions().addListener((SessionListener) this);
}
configure(executor);
}
configure
protected void configure(StateMachineExecutor executor) {
registerOperations();
}
/**
* Registers operations for the class.
*/
private void registerOperations() {
Class<?> type = getClass();
for (Method method : type.getMethods()) {
if (isOperationMethod(method)) {
registerMethod(method);
}
}
} /**
* Returns a boolean value indicating whether the given method is an operation method.
*/
private boolean isOperationMethod(Method method) {
Class<?>[] paramTypes = method.getParameterTypes();
return paramTypes.length == 1 && paramTypes[0] == Commit.class;
}
我们看下,用户是如何定义operations的?
public class MapStateMachine extends StateMachine {
private Map<Object, Object> map = new HashMap<>();
public Object put(Commit<PutCommand> commit) {
try {
map.put(commit.operation().key(), commit.operation().value());
} finally {
commit.close();
}
}
public Object get(Commit<GetQuery> commit) {
try {
return map.get(commit.operation().key());
} finally {
commit.close();
}
}
}
你就理解这里通过reflection来找到Operation,
逻辑就是有一个参数,参数的类型是Commit
如果是Operation,调用registerMethod
private void registerMethod(Method method) {
Type genericType = method.getGenericParameterTypes()[0];
Class<?> argumentType = resolveArgument(genericType);
if (argumentType != null && Operation.class.isAssignableFrom(argumentType)) {
registerMethod(argumentType, method);
}
}
取得泛型的类型,例子里面的Put
private void registerMethod(Class<?> type, Method method) {
Class<?> returnType = method.getReturnType();
if (returnType == void.class || returnType == Void.class) {
registerVoidMethod(type, method);
} else {
registerValueMethod(type, method);
}
}
private void registerValueMethod(Class type, Method method) {
executor.register(type, wrapValueMethod(method));
}
/**
* Wraps a value method.
*/
private Function wrapValueMethod(Method method) {
return c -> {
try {
return method.invoke(this, c);
} catch (InvocationTargetException e) {
throw new CommandException(e);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
};
}
ServerStateMachineExecutor.register
@Override
public <T extends Operation<U>, U> StateMachineExecutor register(Class<T> type, Function<Commit<T>, U> callback) {
operations.put(type, callback);
return this;
}
这里,会把operations注册到ServerStateMachineExecutor里面,便于后面调用
继续ServerStateMachine,
ServerStateMachine最主要的逻辑,就是apply,即把command apply到state machine上,
可以apply到某index为止的所有commit
/**
* Applies all commits up to the given index.
* <p>
* Calls to this method are assumed not to expect a result. This allows some optimizations to be
* made internally since linearizable events don't have to be waited to complete the command.
*
* @param index The index up to which to apply commits.
*/
public void applyAll(long index) {// If the effective commit index is greater than the last index applied to the state machine then apply remaining entries.
long lastIndex = Math.min(index, log.lastIndex());
if (lastIndex > lastApplied) {
for (long i = lastApplied + 1; i <= lastIndex; i++) { // 接着上次最后apply的index,继续
Entry entry = log.get(i);
if (entry != null) {
apply(entry).whenComplete((result, error) -> entry.release());
}
setLastApplied(i);
}
}
}
也可以单独apply一条index对应的entry
public <T> CompletableFuture<T> apply(long index) {
// If entries remain to be applied prior to this entry then synchronously apply them.
if (index > lastApplied + 1) {
applyAll(index - 1); //按顺序apply,所以之前的先要apply掉
}
// Read the entry from the log. If the entry is non-null them apply the entry, otherwise
// simply update the last applied index and return a null result.
try (Entry entry = log.get(index)) {
if (entry != null) {
return apply(entry);
} else {
return CompletableFuture.completedFuture(null);
}
} finally {
setLastApplied(index);
}
}
apply(entry)
/**
* Applies an entry to the state machine.
* <p>
* Calls to this method are assumed to expect a result. This means linearizable session events
* triggered by the application of the given entry will be awaited before completing the returned future.
*
* @param entry The entry to apply.
* @return A completable future to be completed with the result.
*/
@SuppressWarnings("unchecked")
public <T> CompletableFuture<T> apply(Entry entry) {
if (entry instanceof QueryEntry) {
return (CompletableFuture<T>) apply((QueryEntry) entry);
} else if (entry instanceof CommandEntry) {
return (CompletableFuture<T>) apply((CommandEntry) entry);
} else if (entry instanceof RegisterEntry) {
return (CompletableFuture<T>) apply((RegisterEntry) entry);
} else if (entry instanceof KeepAliveEntry) {
return (CompletableFuture<T>) apply((KeepAliveEntry) entry);
} else if (entry instanceof UnregisterEntry) {
return (CompletableFuture<T>) apply((UnregisterEntry) entry);
} else if (entry instanceof InitializeEntry) {
return (CompletableFuture<T>) apply((InitializeEntry) entry);
} else if (entry instanceof ConfigurationEntry) {
return (CompletableFuture<T>) apply((ConfigurationEntry) entry);
}
return Futures.exceptionalFuture(new InternalException("unknown state machine operation"));
}
看到不同的entry类型有不同的apply逻辑,
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");
}
}
}
Copycat - StateMachine的更多相关文章
- Copycat - Overview
Copycat’s primary role is as a framework for building highly consistent, fault-tolerant replicated s ...
- Quick StateMachine状态机
状态机quick中是一个亮点,假设我们做一款RPG游戏,一个角色通常会拥有idle,attack,walk.run,death这些状态,假设游戏角色的状态採用分支条件推断的话.会造成很庞大而难以维护. ...
- mina statemachine解读(二)
这里主要讲下对外接口暴露的处理. // 创建对外接口对象 TaskWork taskWork = new StateMachineProxyBuilder().setStateContextLooku ...
- mina statemachine解读(一)
statemachine(状态机)在维护多状态数据时有非常好的作用,现在github上star排名最前的是squirrel-foundation以及spring-statemachine,而min ...
- loj#3 -Copycat
原题链接:https://loj.ac/problem/3 题目描述: --- Copycat 内存限制:256 MiB 时间限制:1000 ms 输入文件: copycat.in 输出文件: cop ...
- Copycat - configure
Copycat server之间的configure是如何,何时被同步的? 大家可以看到,只有leader可以同步配置 1. 显式的调用LeaderState.configure Leader ...
- Copycat - command
client.submit(new PutCommand("foo", "Hello world!")); ServerContext connection.h ...
- Copycat - CopycatServer
Server被拉起有两种方式, Address address = new Address("123.456.789.0", 5000); CopycatServer.Builde ...
- Copycat - MemberShip
https://github.com/atomix/copycat http://atomix.io/copycat/docs/membership/ 为了便于实现,Copycat把membe ...
随机推荐
- iOS开发:一个无限滚动自动播放图片的Demo(Swift语言编码)
很久以前就想写这么一个无限滚动的Demo了,最近学习了下Swift,手中没有可以用来练手的Demo,所以才将它实现了. Github地址(由于使用了UIView+AutoLayout第三方进行布局,所 ...
- SILK 预测模块分析
SILK是一种新结构的基于噪声整形量化算法的编解码框架.不同于类CELP的AMR,EVRC,G729,Speex等标准. 类CELP的结构都是以码本激励为量化框架的编码器.但是这里并不讨论NSQ结构和 ...
- 【iCore4 双核心板】DEMO V1.0 测试程序发布
iCore4 Demo V1.0程序说明 一.概要 本资料包含5个文件夹: 1.“arm”里是iCore4上arm的程序包,开发环境为KEIL5.17: 2.“fpga”里是iCore4上FPGA的程 ...
- Java知多少(94)键盘事件
键盘事件的事件源一般丐组件相关,当一个组件处于激活状态时,按下.释放或敲击键盘上的某个键时就会发生键盘事件.键盘事件的接口是KeyListener,注册键盘事件监视器的方法是addKeyListene ...
- Docker入门学习总结
1. 什么是Docker Docker是一种虚拟化技术,其在容器的基础上进一步封装了文件系统.网络互联.进程隔离等等,从而极大地简化了容器的创建和维护.Docker使用 Google 公司推出的 Go ...
- Javascript Base64加密解密代码
<script language="javascript" runat="server"> var keyStr = "ABCDEFGHI ...
- libgl1-mesa-glx:i386 : 依赖: libglapi-mesa:i386
libgl1-mesa-glx:i386 : 依赖: libglapi-mesa:i386 1.获取Android源代码 Android官网给出了从网上下载源代码的方法,具体流程如下网址所示:http ...
- [原]Jenkins(七)---jenkins项目编译测试发布由maven构建的web项目
/** * lihaibo * 文章内容都是根据自己工作情况实践得出. * 版权声明:本博客欢迎转发,但请保留原作者信息! http://www.cnblogs.com/horizonli/p/533 ...
- Unity3D Shader 马赛克后期效果
//效果图 //Shader代码 Shader "Hidden/Mosaic" { Properties { _MainTex ("Texture", 2D) ...
- Fiddler如何捕捉DefaultHttpClient的HTTP请求
实际开发中为了解决Ajax跨域请求问题,会通过一个同域的控制器在服务端代理请求目标的Web API并将结果返回.为了便于调试这类请求,我们希望通过Fiddler可以监控到这些请求.Fiddler官方给 ...