前言
上篇文章里笔者介绍了Ozone中使用的Apache Ratis的内部一致性实现原理,得益于底层的一致性封装实现,在Ozone层面,它只需要调用此库并实现自定义的StateMachine方法即可。在Ozone Datanode中,就自定义了ContainerStateMachine来实现Container操作在多副本间的一致性控制。本文我们来聊聊Ozone Datanode ContainerStateMachine的内部实现,这样我们能够进一步深入了解Container的请求操作处理过程。
ContainerStateMachine对于StateMachine/RaftLog的语义实现
因为Ozone使用了Apache Ratis的内部实现,里面会多次涉及到以下两个概念:
- RaftLog
- StateMachine
所以我们需要先了解清楚这2个概念在Ozone中是一个什么样的变量定义。
首先是RaftLog,RaftLog在Ozone中简单理解就是用户的每一次请求操作,在这里的表现形式为TransactionContext。这里面的过程如下所示:
首先Ozone层面会将用户请求包装为TransactionContext对象,
ContainerStateMachine的startTransaction操作方法,
public TransactionContext startTransaction(RaftClientRequest request)
throws IOException {
long startTime = Time.monotonicNowNanos();
final ContainerCommandRequestProto proto =
message2ContainerCommandRequestProto(request.getMessage());
Preconditions.checkArgument(request.getRaftGroupId().equals(gid));
try {
dispatcher.validateContainerCommand(proto);
} catch (IOException ioe) {
if (ioe instanceof ContainerNotOpenException) {
metrics.incNumContainerNotOpenVerifyFailures();
} else {
metrics.incNumStartTransactionVerifyFailures();
LOG.error("startTransaction validation failed on leader", ioe);
}
TransactionContext ctxt = TransactionContext.newBuilder()
.setClientRequest(request)
.setStateMachine(this)
.setServerRole(RaftPeerRole.LEADER)
.build();
ctxt.setException(ioe);
return ctxt;
}
...
}
然后Datanode把接收到的TransactionContext写入RaftLog时,会进行log entry的转化,
@Override
public LogEntryProto initLogEntry(long term, long index) {
Preconditions.assertTrue(serverRole == RaftPeerRole.LEADER);
Preconditions.assertNull(logEntry, "logEntry");
Objects.requireNonNull(smLogEntryProto, "smLogEntryProto == null");
return logEntry = ServerProtoUtils.toLogEntryProto(smLogEntryProto, term, index);
}
然后Datanode内部的RaftServer会将这些由TransactionContext转化而来的log entry写入到本地的RaftLog内。
在log entry的写入过程中,还需要再分为下面两种情况:
第一种,请求带有用户数据的情况,在Ozone中意为写数据操作请求,例如writeChunk请求,我们需要把这些数据独立写出到StateMachine中,RaftLog只保留Transaction信息本身。StateMachine在这里可以理解为Datanode的Metadata当前状态。
因为用户数据真正写出是需要时间的,因此Datanode ContainerStateMachine在里面实现了内部cache的方式先保存用户请求数据,然后再异步写出这部分chunk数据,不过是先保存为了tmp临时文件状态。
/*
* writeStateMachineData calls are not synchronized with each other
* and also with applyTransaction.
*/
@Override
public CompletableFuture<Message> writeStateMachineData(LogEntryProto entry) {
try {
metrics.incNumWriteStateMachineOps();
long writeStateMachineStartTime = Time.monotonicNowNanos();
ContainerCommandRequestProto requestProto