New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[IOTDB-2880]Add procedure framework #5477
Conversation
final KeepAliveWorkerThread worker = new KeepAliveWorkerThread(threadGroup); | ||
workerThreads.add(worker); | ||
worker.start(); | ||
System.out.println("-------------size:" + workerThreads.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete print
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
"ThreadGroup {} contains running threads; {}: See STDOUT", | ||
this.threadGroup, | ||
e.getMessage()); | ||
this.threadGroup.list(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why execute list ?
@Override | ||
public long getDelay(TimeUnit unit) { | ||
long delay = procedure.getTimeoutTimestamp() - System.currentTimeMillis(); | ||
System.out.println("---------delay------(" + delay); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete print log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
I support the procedure and confignode can be deployed on the same process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
FAILED = 7 | ||
} | ||
|
||
struct SubmitProcedureReq{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a big feature, so I will split the comment to serveral parts. Part one: 1. mvn spotless:apply ? seems that the format it not unified
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
procedure/src/main/java/org/apache/iotdb/procedure/scheduler/ProcedureScheduler.java
Outdated
Show resolved
Hide resolved
byteBuffer.flip(); | ||
channel.write(byteBuffer); | ||
} | ||
Files.deleteIfExists(walTmpPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why delete tmp path before move?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed, intends to delete the real file path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add some ut for procedureStore to proof the simple recovery scene after restart.
FileChannel channel = fis.getChannel()) { | ||
while (channel.read(byteBuffer) > 0) { | ||
byteBuffer.flip(); | ||
procedure = Procedure.newInstance(byteBuffer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should you check the byteBuffer's size?
- If a procedure's size is less than bytebuffer's size, after first time deserialization, we clear the bytebuffer, can we deserialize the following procedure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, add a loop for bytebuffer remaining check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the new loop is not enough. If the bytebuffer could contains two or more procedures, the loop could not deserialize them corretly. And please add a test for deserialize and serialize for the procedurewal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now one procedure maps to one ProcedureWAL, which contains a buffer and a filename. It won't contain two or more procs in one bytebuffer. In order to control memory overhead, a constraint configuration will be introduced to control concurrent procedures. Is this design acceptable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a procedure is a file? If we delete a batch of timeseries, every timeseries will have a file? Does it cause too many files? How do you think? @wangchao316
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a procedure is a file. delete timeseries does not use procedure framework. there is used for procedure framework, as so delete storage group, add node or remove node.
why a procedure is a file?
Because The procedure records need to be deleted frequently. Therefore, the procedure records can be deleted easily by storing them in different files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
try { | ||
clazz = Class.forName(className); | ||
} catch (ClassNotFoundException e) { | ||
throw new RuntimeException("Invalid procedure class", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two blank char, haha
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
/** Called by the ProcedureExecutor to assign the ID to the newly created procedure. */ | ||
protected void setProcId(long procId) { | ||
this.procId = procId; | ||
this.submittedTime = System.currentTimeMillis(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe set submmitedTime and set state should in different method? SetProcId do more than the name can express?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introduce a new method setProcRunnable()
try { | ||
for (int i = 0; isAlive(); i++) { | ||
sendStopSignal(); | ||
join(250); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
create contant variable in the class?
|
||
private ProcedureDelayContainer<Env> takeQuietly() { | ||
try { | ||
return queue.poll(20, TimeUnit.SECONDS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please create a constant variable for 20
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
private enum State { | ||
RUNNING, // The Procedure is running or ready to run | ||
FAILED, // The Procedure failed, waiting for the rollback executing | ||
ROLLINGBACK, // The Procedure failed and the execution was rolledback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rolling or rolled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a intermedia state for a rollback stack, means that there is at least one child proc is failed/rolledback and the whole stack is rolling back.
this.timeoutExecutor = | ||
new TimeoutExecutorThread<>(this, threadGroup, "ProcedureTimeoutExecutor"); | ||
this.workerMonitorExecutor = | ||
new TimeoutExecutorThread<>(this, threadGroup, "WorkerThreadMonitor"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WorkerThreadMonitor -> ProcedureWorkerThreadMonitor is better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
* | ||
* @param procedureList procedure list | ||
*/ | ||
public void load(List<Procedure> procedureList) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
procedureList is not used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
* remotes/upstream/master: Serialize measurement schema of insert node to wal entry (apache#5638) filter non schemaRegionDir (apache#5640) [IOTDB-2976] Add English and Chinese docs for count devices and count storage groups (apache#5635) change jenkins timeout from 2 hours to 3 hours [IOTDB-2740] Equal size bucket sampling UDFs: EQUAL_SIZE_BUCKET_RANDOM_SAMPLE, EQUAL_SIZE_BUCKET_AGG_SAMPLE, EQUAL_SIZE_BUCKET_M4_SAMPLE (apache#5518) Fix the issue that EndTime in FragmentInstanceContext is not set (apache#5636) fix concurrent bug of CachedMNodeContainer.putIfAbsent (apache#5632) [IOTDB-2880] Fix NPE occured in ci test (apache#5634) Fix CI (apache#5639) Add ColumnMerger to merge multipul input columns of same sensor into one column (apache#5630) Add block cancel when GetBlockTask throws exception (apache#5628) fix the bug when matching multi-wildcard in pattern tree (apache#5631) [IOTDB-2835]Fix empty page in selfcheck method of TsFileSequenceReader (apache#5552) Add FragmentInstanceStateMachine for FragmentInstance State change (apache#5615) [IOTDB-2880] Fix import check style (apache#5629) [IOTDB-2971] Fix sink handle memory leak (apache#5626) [rocksdb] updated the interface support (apache#5625) [IOTDB-2970] Code style: Avoid wildcard imports (apache#5622) [IOTDB-2880]Add procedure framework (apache#5477) [rocksdb] add rocksdb properties (apache#5588) # Conflicts: # server/src/main/java/org/apache/iotdb/db/mpp/sql/planner/LocalExecutionPlanner.java
Procedure模块功能设计
1.概要描述
主要功能类包括四个部分:
ProcedureExecutor (executor):
用于处理客户端请求,提供任务提交、查询、中止功能
向scheduler提交与执行Procedure
通过store持久化Procedure
主要包含以下数据结构:
Environment:Procedure执行的环境,用于和其他进程通信
WorkerThreadPool:worker线程池,worker异步执行scheduler中的任务
RootProcedureStack:hashMap+Stack的形式,用于维护任务之间的父子关系,保证执行与回滚的顺序一致
CompletedProcedureRetainer:存储已完成的任务,提供查询与定时清理功能
ProcedureScheduler (scheduler)
队列,主要用于实现任务的调度,并且结合
ProcedureStore ():
用于持久化Procedure对象,实现故障恢复
整体流程如图所示:
g)
2.详细设计
2.1 类与接口设计
2.1.1 Procedure
Procedure抽象类,用于描述一个具体的任务。
必须实现的抽象方法包括execute, rollback,abort
此外,提供了其他方法,用户可以根据需要进行重写实现。
2.1.2 ProcedureExecutor
ProcedureExecutor是任务调度框架的核心,主要用于实现具体的任务调度功能。下面详细讲解其内部结构。
1.Map<Long,ReentrantLock> idLockMap
用于维护id的对象锁,防止同一个任务被多次提交。
2.Map<Long,Procedure> procedures
任务对象的索引
3.WorkerThread,WorkerMonitorExecutor
二者共同实现了一个轻量的线程池,初始化时需要指定其核心线程数与最大线程数。
WorkerThread用于异步执行如下:
WorkerMonitorExecutor定时轮询检查WorkerThread是否存在stuck的情况,如果stuck线程数超过阈值,则创建带KeepAlive的临时线程,用于执行队列中其他任务。
4.Map<Long,CompletedProcedureRetainer> completed, CompletedProcedureCleaner
任务执行完毕后,会从procedures转移至completed,CompletedProcedureCleaner会周期执行检查completed清理过期的任务。
5.Map<Long,RootProcedureStack> rollbackStack,用于实现回滚机制
对于每个通过客户端提交上来的Procedure,称为RootProcedure,而在执行过程中产生的Procedure,成为SubProcedure。RootProcedureStack用于维护每个RootProcedure下所有子Procedure。
2.1.3 ProcedureScheduler
ProcedureScheduler提供Procedure的调度功能,目前的实现是一个简单的阻塞队列。后续可以对任务(如按照业务类型分为DataRegion任务和SchemaRegion任务)进行分类,实现一个支持优先级策略的调度。
2.1.4 ProcedureStore
ProcedureStore用于持久化Procedure内容到磁盘,用于重启恢复。当前为最简单的实现:每一个procid对应一个id.proc.wal文件,当需要更新状态时,使用buffer写入临时文件,再整个进行替换。
2.1.5 StateMachineProcedure
StateMachineProcedure用于实现自定义任务状态。除了7种固定的Procedure状态,用户可以自定义任务状态,例如STEP1,STEP2,然后实现抽象方法executeFromState(),rollbackState(),实现状态直接的转换。
重要的属性与方法包括:
subProcList与addChildProcedure(),用于添加子任务
int cycles和previousState,当执行状态不变时(“原地踏步”),cycles会自增,可以用于设置break,防止stuck。
executeFromState(),抽象方法,用于实现自定义状态流转,多为一个Switch case结构
rollbackState(),抽象方法,用于实现自定义状态的回滚
isRollbackSupported(),定义是否支持回滚,如果是,则失败后会走到ROLLEDBACK状态;否则则会进行重试,直至另一个中止状态(SUCCESS/ROLLEDBACK)
2.1.6 完整类图
2.2 Procedure执行流程
2.2.1 Procedure生命周期与状态流转
Procedure包含以下7中状态,状态之间的流转关系,如下图所示。
客户端提交Procedure,服务端反序列化后得到Procedure对象proc,初始化状态为INITIALIZING
服务端ProcedureExecutor执行submitProcedure(proc),proc加入ProcedureSchedure,状态为RUNNABLE
WorkerThread将schedure中状态为RUNNABLE的任务proc取出,执行proc.doExecute()中用户自定义的业务逻辑。
用户根据doExecute()的执行情况去设置下一步的状态
2.2.2 Procedure:submitProcedure()执行过程
任务提交后,ProcedureExecutor会分别在procedures,rollbackStack中新增一个entry。然后将procedure加入scheduler队尾。
2.2.3 ProcedureExecutor:executeProcedure()执行过程
WorkerThread从调度中拿到proc后,调用ProcedureExecutor:executeProcedure(proc)执行
2.2.4 ProcedureExecutor:executeRootStackRollback()执行过程
在3.2.3中,rootRollbackStack中任意一个proc失败,都会使stack状态置为FAILED。下图为RootProcedureStack的类图。
包含:
State state,用于标记栈的状态,当栈中的任一子任务执行失败,则栈的状态会置为Failed,即开始回滚该RootProc下所有的SubProc。
ArrayList subprocStack,用于实现栈的功能。执行时,从头部添加,回滚时,从尾部读取。
int running,用于实现一个轻量级的CountdownLatch,通过acquire和release控制,保证栈中所有的subproc都执行后,state才能进入下一个状态。
当stack状态为FAILED,且stack下running为0,即所有子任务都执行完毕时,则开始回滚整个rollbackStack,其执行逻辑如下:
2.2.5 ProcedureExecutor:RootProcedureCleanup()过程
当rootProc处于完成状态(ROLLEDBACK/SUCCESS),会进行清理工作
2.3 ProcedureExecutor重启恢复过程
ProcedureExecutor初始化时,会进行恢复,加载磁盘上的Procedure文件信息。恢复的过程如下:
4 功能部署
主要用于节点管理的流程,生命周期与ConfigNode一致。部署在ConfigNode上,方便获取DataNode状态。
适配ConfigNode共识层
只有Leader节点上的Executor可以接收客户端提交请求
Procedure状态更新,通过共识层实现。
Leader发生切换的时候,执行回调,通过磁盘上的WAL,恢复Executor和Scheduler运行时的状态,期间阻塞请求。恢复过程异步执行,不阻塞ConfigNode功能。
5 优缺点总结
优点:
为包含多部操作的业务,提供统一的状态管理接口。通过状态调度与回滚,实现业务的顺序性与最终状态一致。
异步任务形式,配合客户端轮询与重试,可以用于应对故障场景(Leader切换/连接超时)。
缺点: