Files
orbit/docs/api/priority-and-fairness.md

118 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务优先级与公平派发
状态:P3 已实现。优先级使用持久化任务字段,节点公平派发控制领取机会;任务执行权、应用配额与两类并发许可仍由 MySQL 事务决定。平台不增加缓存或消息队列服务。
## 1. 任务优先级
在 [POST /v1/tasks](tasks.md) 或 [计划版本的 definition.task](schedules.md) 中指定:
| 字段 | 类型 / 默认 | 含义 |
| --- | --- | --- |
| priority | 可选 JSON 整数,默认 0,范围 0–9 | 受理时固定的任务优先级;每级最多取得 30 秒排序优势 |
负数、超过 9、浮点表示、字符串、bool 和 null 均返回 HTTP 400 / INVALID_ARGUMENT。Go SDK 对应 `Priority int16`。零值省略以保留旧请求摘要;任务详情、摘要和 Worker 派发在非零时返回 priority。查询到零或省略都表示普通优先级。
权限、请求头、提交结果、受理幂等、取消和截止规则沿用任务接口,没有单独的优先级管理权限或运行中改级接口。相同受理键改变 priority 返回 409 IDEMPOTENCY_CONFLICT。安全重试保持冻结优先级,下一次 available_at 改变时重新计算排序时间;计划触发继承对应不可变版本里的 priority。
```json
{
"queue_id":"11111111111111111111111111111111",
"handler":"object.prepare",
"handler_version":"v1",
"payload_version":"v1",
"payload":{"object_ref":"objects/example","operation_ref":"prepare-001"},
"priority":9
}
```
成功响应仍是任务接口的完整信封,首次 201、重复受理 200;任务对象包含 `"priority":9`。提交确认不代表已执行。
## 2. 排序与等待补偿
同一队列、与当前 Worker 版本和能力匹配且满足门控/租约/许可条件的到期任务,按下面三个键升序排列:
1. `dispatch_at = available_at - priority × 30 秒`。
2. available_at。
3. task_id(只用于确定性打破同时间相等,不表示业务顺序)。
dispatch_at 为数据库生成的只读字段,并有相应派发索引;它不对外提供执行授权。真正的最早执行时间始终是 available_at,派发仍检查截止、次数、门控、能力和资源键占用。
| 两条已就绪任务 | 先选择的任务 |
| --- | --- |
| A:0 级、5 秒前到期;B:9 级、刚到期 | B,获得 270 秒排序优势 |
| A:0 级、271 秒前到期;B:9 级、刚到期 | A,旧任务的等待已超过最大优势 |
| A:0 级、已到期;B:9 级、1 小时后才到期 | A,B 不具备领取资格 |
| 同一优先级、不同 available_at | 更早到期者 |
由此限制新高优先级工作插队的时间优势,避免纯粹按优先级排序造成持续饥饿。**270 秒不是最大排队时长**:旧积压、队列/应用额度、兼容 Worker、暂停、共享资源和每个任务执行时长仍影响等待。已被预占或正在运行的任务不被抢占;没有全局 FIFO 或跨队列优先级比较。
## 3. 节点内应用与队列轮转
Worker 连接不再各自直接并发发起一批数据库领取。节点采用有界领取池:
| 约束 | 当前开发基线 |
| --- | --- |
| 同时执行的领取 SQL 操作 | 每节点最多 4 个 |
| 一个应用同时在途的领取 | 每节点最多 1 个,其他应用可以继续领取 |
| 待处理和在途领取请求合计 | 每节点最多 128 个 |
| 每个连接未完成领取请求 | 最多 1 个 |
| 选择顺序 | 活跃应用轮转 → 该应用的队列轮转 → 队列内连接请求顺序 |
| 单个连接派发节奏 | 每个扫描周期最多 4 次领取,当前周期 1 秒 |
应用身份来自服务端验证过的命名空间归属,客户端不能通过增加命名空间或伪造应用字段分散公平组。一个应用增加队列或 Worker 数量,也不会在所有应用都持续有待处理请求时得到额外轮次。某个应用没有请求或已有在途领取时,其他应用继续使用空闲池容量。
同命名空间、队列、精确处理器能力集合及资源键支持声明的空领取结果,在本节点合并一个扫描周期,缓存最多 128 项。不同能力不共用空结果。缓存过期重新查询;任务新到达或容量释放可能多等待一个周期。这只抑制重复探测,不能据此授权任务或更改持久化状态。
领取在后台有界执行,WS 控制循环继续接收心跳、Start、续租、Finish 和排空。关闭节点时取消待领取操作并等待在途收尾;已提交但尚未发送的预占仍由数据库租约恢复。任务处理器运行在业务 Worker,完全不占这个 SQL 领取池。
这是**节点内领取机会的公平性**。各节点分别轮转,没有宣称跨节点完全相等吞吐、全局权重或严格最大延迟。跨节点执行并发和开始额度仍由已实现的 [应用配额](application-resources.md) 与 [队列控制](queues.md) 原子保证。连接接入总上限仍为每节点 128;该机制不提供每应用连接预留,也不代替请求层限流或生产容量规划。
## 4. SDK 完整场景
源码:[priority/main.go](../../examples/priority/main.go)。创建专用队列,先按 0、9、4 的顺序提交三条任务,再启动容量为 1 的自动 Worker;三条任务在近同时到期的条件下按 9、4、0 执行,每条只有一次尝试。
从仓库根目录准备专用开发数据库,设置 ORBIT_MYSQL_DSN 和 `ORBIT_HTTP_ADDR=127.0.0.1:8080` 后:
```sh
go run ./cmd/orbit-migrate up
go run ./cmd/orbit-admin bootstrap --application priority-example --namespace default \
--environment test --queue initial --worker-access --output /tmp/orbit-priority.credentials.json
go run ./cmd/orbit
```
凭据需要全部队列范围的 queues:manage、tasks:submit/read、workers:connect/consume。另一个终端执行:
```sh
cd examples
ORBIT_URL=http://127.0.0.1:8080 \
ORBIT_CREDENTIALS_FILE=/tmp/orbit-priority.credentials.json go run ./priority
```
预期输出:
```text
submitted_priorities=0,9,4 executed_priorities=9,4,0
completed=3 attempts_per_task=1 worker_drained=true
```
示例返回输入对象及精确大整数,不访问外部业务资源。总任务截止为 20 秒,整个场景 25 秒;每次创建独立队列/处理器与任务,正常结束排空,历史保留。异常非零退出并有界收尾。这个短场景演示优先级加速,等待补偿的 271 秒边界由真实数据库夹具验证,不让读者实际等待几分钟。
调用片段(上下文、客户端和队列已初始化):
```go
accepted, err := api.SubmitTask(ctx, "prepare-operation-001", orbit.SubmitTaskRequest{
QueueID: queueID, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1",
Payload: json.RawMessage(`{"object_ref":"objects/example"}`), Priority: 9,
})
```
在处理器中通过 `e.Task.Priority` 读取冻结值;调度顺序由平台确定,业务 Worker 不应绕过 Start 或自行重排已授权任务。
## 5. 升级与验收边界
停止旧写入节点,应用 000012,再启新版服务。历史任务优先级为 0,生成排序时间等于原 available_at;既有任务、索引和历史保留,新增索引单独建立。优先级限制附 CHECK,generated 字段与 Go 模型均有中文说明,数据库最终仍无外键。
新版 SDK 开始提交非零 priority 前,先升级服务端;旧服务严格拒绝未知输入字段。公开 Worker Schema 已扩展可选 priority,其他语言的严格验证器需同步升级;默认 0 的派发报文保持原形状。
单节点公平领取及真实 MySQL/WSS 故障隔离已纳入回归。多节点不均匀连接分布、混合负载下各应用延迟、SQL 锁等待、海量未来任务扫描及峰值容量在 P6 验收,当前参数不能作为百万租户容量承诺。完整记录见 [本阶段报告](../reports/phase-3-priority-fairness.md)。