# job队列的设计

在原一号进程中，通过任务（job）引擎来解除单元（unit）间关联联动和单个unit处理的耦合。在新的一号进程自研替代项目中，我们仍然需要这么一个模块将unit间联动逻辑同单个unit处理逻辑隔离开。

## 1.1 目的

本文档对系统中job模块的功能进行涉及，明确主要思路和主要处理过程，作为今后的编码阶段的输入和编码人员、测试人员的指导。

## 1.2 范围

job模块涉及如下场景：job的执行、通告、终结、取消、查询，所涉及的交互对象包括：unit、线程运行环境。

# 2 特性需求概述

| 需求编号 | 需求名称    | 特性描述                                                                                         | 优先级 |
| -------- | ----------- | ------------------------------------------------------------------------------------------------ | ------ |
| 1        | 支持job执行 | 管理侧只能通过job引擎来执行unit动作，job引擎来保证unit及其关联unit状态的一致性。                 | 高     |
| 2        | 支持job通告 | 对于app侧自触发unit动作，通过向job引擎通告扩散到关联unit上，以保证unit及其关联unit状态的一致性。 | 高     |
| 3        | 支持job终结 | unit在自身状态发生变化时通过向job引擎通告状态变化，以尝试终结对应job。                           | 高     |
| 4        | 支持job取消 | 支持管理侧取消指定job。                                                                          | 中     |
| 5        | 支持job查询 | 支持查询符合指定要求的job信息。                                                                  | 中     |

# 3 需求场景分析

## 3.1 需求来源

在分析过原一号进程实现后，我们认为job引擎的存在是合理的。对于一号进程而言，需要有一个模块来隔离unit间联动逻辑和单个unit处理逻辑，无论是原一号进程还是新的一号进程。

## 3.2 价值分析

这样unit间联动逻辑和单个unit处理逻辑即可以独立开发，也可以灵活组合，同时也降低了整体的代码复杂度。

## 3.3 场景分析

场景1：unit（记录）执行动作
交互对象：unit-manager
外部输入：动作的对象(unit)+类型(kind)+选项(mode)
外部输出：动作记录结果（非执行结果）【记录成功？失败？】

场景2：（不可模拟）unit自生成动作通告
交互对象：unit子类
外部输入：动作的对象(unit)+类型(kind)+选项(mode)
外部输出：通告入参检测结果（非记录结果）【入参合法？非法？】

场景3：unit（实际）执行动作
交互对象：线程运行环境
外部输入：无
外部输出：无

场景4：unit终结执行动作
交互对象：unit子类--unit状态（变迁）订阅-->unit-manager
外部输入：动作的对象(unit)+历史状态(os)+当前状态(ns)+当前附加状态(flags)[reload结果、自启动状态]
外部输出：终结入参检测结果（非终结结果）【入参合法？非法？】

场景5：取消动作
交互对象：unit-manager
外部输入：动作的标识(job-id)
外部输出：取消结果【取消成功？取消对象不存在？取消对象不支持？】

场景6：读取动作信息
交互对象：unit-manager
外部输入：动作的标识(job-id)
外部输出：动作信息【可能存在，可能不存在】

## 3.4 影响分析

从依赖关系上看，job处于单个unit处理逻辑（unit_entry）之上、整体unit管理逻辑（unit_manager）之下。从业务逻辑上看，job作为管理侧unit（写类）动作的唯一入口，对其依赖关系之上的模块屏蔽unit间的依赖逻辑。

## 3.5 已有实现方案分析

原一号进程以其经验从业务场景中抽象处了一套机制，在较长的时间段中不断演进并经受住考验，历史证明此机制足以应对绝大部分当前启动场景。当然，其长期演进，当前的架构耦合较为严重，造成后续演进性不足、可靠性问题较大等问题。所以我们对其有两方面的考虑：第一、紧盯隔离unit间联动逻辑和单个unit处理逻辑这个总目标，不局限其内部具体实现；第二、在对业务把握不大的情况下，不轻易改动原有业务场景相关设置，典型例子为：对外枚举设定。

# 4 特性/功能实现方案

## 4.1 总体方案

第一，我们需要确立job同unit直接的定位和依赖关系。其分析过程如下：
业务特征：unit行为同job之间并非包含关系，存在如下关联类型：（a）非对应unit行为触发job变更；（b）job触发unit行为；（c）非job触发unit行为；
选择思路：unit+job组成一个模块？unit->job？job->unit？unit-manager|子类unit->job->unit<-子类unit？
考虑因素：（1）功能，可覆盖的业务场景；（2）架构，整体代码复杂度；
采用策略：unit-manager|子类unit->job->unit<-子类unit
约束：拆分unit和job的重要问题是unit和job间的映射性能下降、数据传递性能下降，对应的改善手段是job支持按unit组织时采用高速映射（选择特定key进行hash计算）、减少在unit整体业务逻辑中job的嵌入次数和输入输出信息量。

第二、job作为管理侧unit（写类）动作的唯一入口，我们需要明确unit动作如何管理。其分析过程如下：
业务特征：（1）业务动作中包括同步、异步；（2）业务动作间存在连带关联；
选择思路：rtc？pipeline？
考虑因素：（1）可靠性，动作执行引擎具有的最小调度颗粒度；（2）可维护性，以统一的模式管理所有动作；
采用策略：pipeline + trigger-finish
约束：pipeline的典型问题是分割的动作之间存在时序控制力弱、场景易扩大、数据传递性能差、业务逻辑呈现不直观，对应的改善手段是动作间采用固定顺序（类似锁间）并进行封装、不同场景间如无必要采用不同参量承载、数据采用统一管理且采用指针或告诉key值传递、关联业务逻辑代码位置集中放置。

第三、job引擎一个非常显著的性能加速点在于job的合并管理，而job合并管理策略会影响到job的绝大部分方面。其分析过程如下：
业务特征：（1）unit部分动作耗时远超job编排耗时、部分动作耗时小于job编排耗时；（2）unit部分动作由一系列子异步动作组合完成，且此类动作为主功能场景。
选择思路：已触发未完成job允许合并？已触发未完成job不允许合并？已触发未完成job限定在固定有限场景中才允许合并？
考虑因素：（1）功能，合并与否不能改变unit的最终状态；（2）性能，主功能场景的性能要有竞争力；（3）架构，整体代码复杂度；
采用策略：已触发未完成job限定在固定有限场景[* + {stop | restart}]中才允许合并
约束：已触发未完成job限定在固定有限场景中才允许合并的前提是不改变unit的最终状态，其典型问题是job部分的复杂度上升、job编排耗时上升，对应的改善手段是job合理划分子模块分割场景、引入合理约束+保持内部状态减少主功能场景编排耗时。

第四、job引擎对外呈现的另一个重要关联特性是时序控制，我们接着要解决的是job的时序控制策略。其分析过程如下：
业务特征：（不同unit所属）job间存在时序诉求：（1）来自于执行顺序配置且受当前执行状态影响；（2）来自于unit自身动作执行逻辑；（3）不鼓励过多使用时序，应用数量不应该占大多数。
选择思路：宽入严出？严入宽出？
考虑因素：（1）功能，可覆盖的业务场景；（2）性能，主功能场景的性能要有竞争力；（3）架构，整体代码复杂度；
采用策略：宽入严出，即在添加到执行队列中时条件宽松，但在实际执行时采用严格判断条件。如果条件不满足会从执行队列中摘出，等待关联job执行完成后再次关联入队。
约束：宽入严出的典型问题是在不满足条件的样本较多时整体性能较差，对应的改善手段是在检测到过多样本不满足条件时切换到严入宽出的策略（考虑到这种场景是不鼓励的行为，目前尚未支持）。

## 4.2 特性功能设计

在做出整体策略选择后，我们对job模块内各子模块的定位和依赖关系制定如下：
子模块定位
job_entry: 作为job和unit的连接点和单个job的类型差异屏蔽曾，提供unit动作实际触发、unit状态关联job结果、复合类型分解等能力。
job_unit_entry: 作为单个unit内job间关系的维护者，提供单个unit内job间的合并、排序、触发、终止能力，是整个job模块的机制核心。
job_alloc: 作为job-id的管理者，提供job-id的分配能力。
job_table: 作为job的实际拥有者，提供按id、unit、已就绪状态组织（添加、删除、合并、运行、终结、查找）job的能力，是整个job模块的机制整合节点。
job_transaction: 承载（不同unit间）job的关联展开数据，将外部输入执行动作诉求映射为job（群）。
job_notify: 承载（不同unit间）job的通告展开数据，将外部输入执行动作结果、事件映射为执行动作诉求（群）。
job_stat: 作为job统计信息的管理者，提供job统计信息的更新、查看能力。
job_manager: 作为job的总体管理者，负责各子模块拼装和对外界面封装，提供job的对外操作（执行动作生成、执行事件通告、运行、终结、删除、查询、调测）界面。

子模块间依赖关系---不含外部模块依赖关系
job_entry: NA
job_unit_entry: job_entry
job_alloc: job_entry
job_table: job_entry, job_unit_entry, job_alloc
job_transaction: job_entry, job_alloc, job_table
job_notify: job_entry
job_stat: job_entry
job_manager: job_entry, job_alloc, job_table, job_transaction, job_notify, job_stat

从整体处理流程上来看，job包括6个场景，在前述章节描述的对外呈现基础上，我们这里再补充其内部信息如下：

场景1：unit（记录）执行动作
交互对象：unit-manager
外部输入：动作的对象(unit)+类型(kind)+选项(mode)
内部输出：输入动作及其关联动作被事务性记录
外部输出：动作记录结果（非执行结果）【记录成功？失败？】
涉及模块：job_entry + job_unit_entry + job_alloc + job_table + job_transaction + job_manager

场景2：（不可模拟）unit自生成动作通告
交互对象：unit子类
外部输入：动作的对象(unit)+类型(kind)+选项(mode)
内部输出：输入动作的关联动作被事务性记录
外部输出：通告入参检测结果（非记录结果）【入参合法？非法？】
涉及模块：job_entry + job_unit_entry + job_alloc + job_table + job_transaction + job_notify + job_manager

场景3：unit（实际）执行动作
交互对象：线程运行环境
外部输入：无
内部输出：实际动作触发结果（非执行结果）【触发成功？失败？】
外部输出：无
涉及模块：job_entry + job_unit_entry + job_table + job_manager

场景4：unit终结执行动作
交互对象：unit子类--unit状态（变迁）订阅-->unit-manager
外部输入：动作的对象(unit)+历史状态(os)+当前状态(ns)+当前附加状态(flags)[reload结果、自启动状态]
内部输出：尝试删除相关job + 失败回退关联动作 + （可模拟）unit自生成动作通告 + 根据状态变化触发关联动作
外部输出：终结入参检测结果（非终结结果）【入参合法？非法？】
涉及模块：job_entry + job_unit_entry + job_table + job_transaction + job_notify + job_manager

场景5：取消动作
交互对象：unit-manager
外部输入：动作的标识(job-id)
内部输出：删除相关job + 回退关联动作 + job自生成unit状态变迁事件通告
外部输出：取消结果【取消成功？取消对象不存在？取消对象不支持？】
涉及模块：job_entry + job_unit_entry + job_table + job_transaction + job_manager

场景6：读取动作信息
交互对象：unit-manager
外部输入：动作的标识(job-id)
内部输出：无
外部输出：动作信息【可能存在，可能不存在】
涉及模块：job_entry + job_unit_entry + job_table + job_manager

从内部动作中我们可以合并一些相似项，合并后use case为：
use case 1：job创建
对应原场景1和原场景2。
在这两个场景中，均根据输入动作进行关联扩展，其区别在于是否将输入动作记录到内部中。

use case 2：job执行
对应原场景3。

use case 3：job终结
对应原场景4和原场景5.
在这两个场景中，均根据输入删除相关job并进一步做相关关联动作，其区别在于相关关联动作不尽相同。

use case 4：job查找
对应原场景6。

## 4.3 job创建实现
### 4.3.1 设计思路
job创建涵盖unit（记录）执行动作和（不可模拟）unit自生成动作通告，二者存在一定的相似，也存在一定的差异。如何确定共用范围？其分析过程如下：
业务特征：在多种场景中会触发业务动作的连带关联，不同业务场景中所关联动作的范围、模式均有差异：（a）触发执行unit动作[图单点探索{1->N(1)}、图多点探索{1->M(1)->N(M(1))}]；（b）通告非job触发unit行为[图单点探索{nop(1)->n(1)->N(n(1))}]；（c）unit动作执行结果确定[图单点探索{nop(1)->n'(1)->N(n'(1))}]；
选择思路：基于业务场景定制不同处理逻辑？合并内部相同处理逻辑+基于业务场景定制数据？
考虑因素：（1）功能，可覆盖的业务场景；（2）架构，整体代码复杂度；
采用策略：合并内部相同处理逻辑{N(M)} + 基于业务场景定制数据{n or n'}
约束：分离机制和数据的前提是覆盖业务场景，其典型问题是机制需要应对的场景过多导致逻辑复杂，对应的改善手段是合理划定机制应用范围，比如：不强行追求一套框架，将框架拆分为几个互不关联的子框架再一次为基础进行不同的组合。

### 4.3.2 实体关系分析
UnitManager-------------------------------+
      |                                   |
      |                                   |
      +--------->JobManager---------------+------------>UnitDb--------------+
                    |                                                       |
                    +->JobTable(jobs)---+--->JobUnitTable--->JobUnit--->Job-+->unit
                    |                   ^
                    |                   |
                    +->JobTable(stage)--+

相关内部实体概念解释如下：
1、JobManager
（1）暂存区（stage）：
由于unit之间存在配置依赖关系，外部触发的单个动作会联动多个其他动作，为了保障整体动作群的完整性，需要一个暂存区用于临时保存处理新增动作群。待整体动作群校验处理完毕后会合并到正式仓库中，暂存区内容会被清除。

2、JobUnit
（1）已触发job：
动作已经被触发，正在执行中，还未完成整体动作。
一个unit只能有一个已触发job。
只在指定情况下才可以被打断（例外描述见下），其他情况中不能被删除、不能被打断，只能由unit业务逻辑自身（通过通告unit状态变迁）终结。
删除例外场景：unit删除。
打断例外场景：后续首个未触发动作拥有停止属性(stop or restart)。
组合类型由多个标量类型动作组合而成，其中每个组合的标量类型动作同常规标量类型动作一样，均需要由unit业务逻辑终结。每单个组合标量类型动作完成后，会重新触发下一个组合标量类型动作，直至所有组合标量类型动作完成。
（2）未触发job：
动作还未被触发，正在等待引擎调度触发执行。
一个unit可能存在多个未触发job，这些未触发job以某种顺序排列，等待引擎依序触发。
可以被删除、被合并。
不同类型动作间根据规则存在冲突，当冲突发生时根据动作模式进行失败退出或者替换。
暂停+继续：job可以根据配置的时序或者unit业务逻辑要求被暂停，以等待其他指定事务触发或者完成，等待条件满足后可以继续触发执行。已触发job和未触发job均可能被暂停。

3、Job
（1）类型：
根据是否依赖unit状态可以分为：基础类型、复合类型。
基础类型根据是否由其他类型动作组合可以分为：标量类型、组合类型。
基础类型根据是否改变unit状态可以分为：可变类型、不可变类型。

### 4.3.3 实现分析
参见伪码
exec: build changes in stage->commit stage to jobs->clear stage
notify: build inputs -> exec inputs

## 4.4 job执行实现
### 4.4.1 设计思路
从之前的总体分析章节可知，我们通过异步机制来触发执行job，job触发unit执行动作后等待，最终由unit手动终结。那么这个过程中，job内部需要推进每个job的可触发状态、触发状态和完成状态。此外，由于在控制时序中采用宽进严出的策略，我们需要反复改变job的状态。在job的执行流程中，除了正确触发unit动作外，其主要任务就是正确的维持job的各种状态。

### 4.4.2 实体关系分析
JobManager--------------------->UnitDb-----------------------+
  |  |                            ^                          |
  |  |                            |                          |
  |  +->JobTable(jobs)------->JobUnitTable--->JobUnit--->Job-+->unit
  |
  |
  +---->Events

相关内部实体概念解释如下：
1、JobUnitTable
就绪队列：
保存系统内所有已就绪的unit及对应待触发job消息，其以unit及相关属性为key进行优先级排序。
单个unit内的job变化均会改变unit的就绪状态，从而影响就绪队列。出于性能考虑，在批量操作job时会通过标记位来记录中间状态，等待所有操作完成后统一同步。

2、JobUnit
就绪：
当前某个unit中存在可以满足就绪条件且可以立即触发执行的job时，此unit的状态即为就绪。
被暂停的unit处于非就绪状态。
一个unit如果存在一个已触发job，在此已触发job全部完成或者被打断之前，此unit处于非就绪状态，其他非触发job只能等待。

3、Job
运行类型：实际触发unit动作时生效的类型，用于分解组合类型。

### 4.4.3 实现分析
参见伪码
loop -> try to trigger something to run -> try to finish it in two case: case 1. the job has been finished synchronously in context; case 2. the trigger is finished(failed or over); -...-> nothing is triggered -> break loop

## 4.4 job终结实现
### 4.4.1 设计思路
job终结涵盖unit终结执行动作和取消动作，二者存在一定的相似，也存在一定的差异。如何确定共用范围？其分析过程如下：
业务特征：在多种场景中会终结业务动作，不同业务场景中终结业务动作时所完成的事务均有差异：（a）主动终结unit动作[cancel/timeout/dependency/collect]；（b）触发unit动作失败[again + done/skip/invalid/assert/un-support/dependency/once/fail]；（c）unit动作中终结对应unit动作[done/fail]；（d）unit动作中终结不对应unit动作[done/fail]；
完成事务同终结业务场景对应关系为：
1、正常+异常：记录结果+释放自身资源；---a,b,c,d
2、正常：restart类型job重激活；---b,c,d
3、异常：关联job回退；---a,b,c,d
4、异常：非对应unit行为触发job变更场景中unit状态变迁事件模拟+通告；---a,b
5、正常+异常：执行顺序关联job重激活；---a,b,c,d
6、非job触发unit行为场景中job模拟+通告；---d
7、unit状态变迁事件通告；---c,d
选择思路：基于业务场景定制不同处理逻辑？合并内部相同处理逻辑+基于业务场景定制数据？
考虑因素：（1）功能，可覆盖的业务场景；（2）架构，整体代码复杂度；
采用策略：基于业务场景定制不同处理逻辑{公共机制[动作1,2,3,5] + 场景定制[动作4 | 动作6,7]}
约束：基于业务场景定制不同处理逻辑的典型问题是定制入口过多导致机制复杂或者重复代码过多，对应的改善手段是划定合理的公共机制范围、合理的组合各业务子场景群。

### 4.4.2 实体关系分析
UnitManager-------------------------------+
  |   |                                   |
  |   |                                   |
  |   +--------->JobManager---------------+------------>UnitDb--------------+
  |                 |                                                       |
  |                 +->JobTable(jobs)------->JobUnitTable--->JobUnit--->Job-+->unit---+-->DataManager--->Table
  |                                                                                   ^
  |                                                                                   |
  +-----------------------------------------------------------------------------------+

相关内部实体概念解释如下：
1、JobManager
上下文：由于job可能会被unit业务逻辑在触发流程中同步终结，为了避免job的运行流程和终结流程存在交集，需要建立一个同步终结上下文用于临时保存终结信息。这样unit业务逻辑同步终结流程中实际上不做终结处理，待job运行流程结束后会重新处理终结上下文中记录的终结信息，处理完成后上下文信息会被清除。

2、Table
提供数据存储和数据变更通告订阅能力，用来打断业务逻辑无关但存在数据关联的循环。

### 4.4.3 实现分析
参见伪码
try_finish:
(synchronous)finish in context -> record and wait -> (asynchronous)finish not in context -> judgement -> delete itself -> remove relational jobs on failure -> simulate job events -> start on previous result
remove:
delete itself -> remove relational jobs on failure -> simulate and notify unit events

## 4.4 job查找实现
### 4.4.1 设计思路
job在job引擎内部有两种保存方式：第一，基于唯一的id保存；第二，基于unit保存。在基于unit保存的场景中，单个unit可以存在1个已触发job和多个未触发job，其中多个未触发job的unit-type唯一。我们基于这些组织方式向外提供查询能力，包括基于id的查询和基于unit+触发状态+unit-type的查询。
### 4.4.2 实体关系分析
JobManager
    |
    |
    +->JobTable(jobs)------->JobUnitTable--->JobUnit--->Job--->unit

### 4.4.3 实现分析
参见伪码
get_xxx: just lookup it

# 5 可靠性/可用性/Function Safety设计

NA

# 6 安全/隐私/韧性设计

NA

# 7 数据结构设计(可选)

NA

# 8 其他

NA

# 9 参考资料

NA
