ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从 0 构建 AI Workload Platform(六):多 Worker、租约、心跳与故障恢复

从 0 构建 AI Workload Platform(六):多 Worker、租约、心跳与故障恢复 摘要我正在从零开发 AI Workload Platform一个面向 Agent 与确定性程序任务的可靠运行时和调度平台。它接收包含多个步骤的工作流管理任务依赖、状态、重试、取消、恢复和执行节点。模块 3 已经能把自然语言目标转换为经过校验和人工确认的结构化工作流但确认后的任务仍由控制面进程自己执行。控制面Control Plane是接收请求、保存运行状态并决定任务何时可以执行的服务部分。一个进程既负责 HTTPHypertext Transfer Protocol超文本传输协议请求、数据库协调又负责实际任务会留下三个直接问题执行能力不能通过增加节点扩展任务可能被控制面故障一起中断旧执行者恢复后可能与新执行者同时上报结果。模块 4 把任务执行拆到独立 Worker主动领取并执行任务的工作进程并使用 PostgreSQL 关系数据库保存 Dispatch任务分发记录。Worker 通过 HTTP 主动领取任务以租约记录有期限的执行权以心跳报告存活并续期再由隔离令牌识别和拒绝过期执行者。本文从这些对象的关系开始解释事务与状态变化再结合真实多进程测试说明 Worker 崩溃、网络响应丢失、迟到结果、优雅退出和控制面重启如何处理。文章最后给出方案取舍、性能证据和当前限制。目录为什么单进程执行不够Worker、Dispatch、Attempt 各自是什么租约为什么是临时所有权心跳同时维护会话和租约一次任务怎样从 Ready 走到 Succeeded背压与并发限制在哪里生效Worker 崩溃后怎样恢复迟到结果、重复请求与取消PostgreSQL 事务为什么重要为什么选择 PostgreSQL 加 HTTP 拉取真实验证与性能结果当前限制与下一步总结1. 为什么单进程执行不够模块 2 建立了 HTTP 控制面、PostgreSQL 持久化和进程重启恢复模块 3 增加 Agent Runtime、工具权限与自然语言草稿。Run运行实例是某个不可变 Workflow 版本的一次实际运行。模块 2 和模块 3 证明了“怎样可靠保存和控制一个 Run”但还没有建立多执行节点之间的所有权协议。如果继续在控制面进程中执行任务增加控制面副本会产生多个协调者不能直接等于增加执行能力一个长任务会占用控制面进程中的 CPU、内存和 goroutineGo 的轻量级并发执行单元控制面升级或故障会同时影响 API 与全部执行无法描述某次真实执行尝试Attempt当前属于哪个执行节点任务重新执行后旧执行者的迟到结果可能覆盖新结果。模块 4 的目标不是让所有工作流自动变快而是先建立一个可恢复、可验证的多进程执行边界控制面决定哪些任务可以执行Worker 决定自己何时有空领取PostgreSQL 保存双方共同认可的所有权事实。2. Worker、Dispatch、Attempt 各自是什么模块 4 没有重新定义前面模块的工作流对象而是在既有对象外增加执行分发层。先统一四个基础对象对象表示什么在模块 4 中是否改变Workflow包含任务、依赖、重试和超时规则的静态定义不改变仍使用不可变版本Run某个 Workflow 版本的一次实际运行增加分布式执行产生的状态和归属记录TaskWorkflow 中一个可调度的工作单元增加queued状态和执行器类型Action交给指定执行器解释的动作标识仍是标识不是 Shell 命令或文件路径Input传给 Action 的结构化 JSON 参数由控制面从不可变定义中读取并交给 Worker模块 4 新增的 Worker、Dispatch 和租约回答“任务由谁执行、执行权何时失效”Attempt 继续回答“这个任务实际执行了第几次”。这些对象不是同一个 ID 的不同叫法。2.1 WorkerWorker执行节点是独立运行、主动从控制面领取任务并执行的进程。每个 Worker 注册时声明显示名称协议版本支持的 ExecutorKind最大并发槽位。ExecutorKind执行器类型表示 Worker 能处理哪类任务。模块 4 只有mock它返回确定的模拟结果不会把 Action 解释为命令、文件、URL 或动态程序。类型匹配由服务端完成Worker 不能靠修改请求领取不支持的任务。2.2 DispatchDispatch分发记录是“一个 Task 已经可以交给兼容 Worker”的持久化数据库事实。它有pending、leased、completed、expired、canceled等状态。Task 从ready进入queued时创建 pending Dispatch。queued只表示已经进入分发队列此时不创建 Attempt不消耗重试次数不开始任务超时不代表任何 Worker 已经获得执行权。如果没有兼容 WorkerTask 保持或返回ready。这样不会因为系统暂时没有执行能力而提前消耗一次业务尝试。2.3 AttemptAttempt执行尝试是 Task 的一次真实执行记录。Worker 成功领取 Dispatch 后事务才创建 Attempt并把 Task 从queued改为running。一个 Task 可能有多个 Attempt。例如 Worker A 领取 Attempt 1 后失联租约回收把 Attempt 1 记为interrupted如果仍有重试次数Worker B 随后领取 Attempt 2。Attempt 历史回答的是“实际执行过几次”Dispatch 回答的是“每次执行权怎样分配”。3. 租约为什么是临时所有权租约Lease是带过期时间的任务执行权。领取成功后控制面返回DispatchID只返回给当前 Worker 的租约令牌RunID、TaskKey、Action 和 InputAttempt 编号Attempt 截止时间当前租约过期时间。数据库只保存租约令牌的 SHA-256 摘要不保存明文。SHA-256是把任意输入转换成固定长度摘要的哈希算法服务端可以比较摘要但不能靠摘要直接还原明文令牌。Worker 提交心跳或结果时必须同时提供 WorkerID、DispatchID 和明文租约令牌服务端计算摘要并使用常量时间比较。常量时间比较尽量让比较耗时不随第一个不同字节的位置变化降低通过时间差推测令牌内容的风险。租约有效必须同时满足Worker 会话有效 AND Dispatch 仍为 leased AND WorkerID 匹配 AND 租约令牌匹配 AND 数据库当前时间早于 lease_expires_at AND 数据库当前时间早于 attempt_deadline任一条件不满足服务端都不能接受这个执行者代表当前 Attempt 更新结果。租约不能阻止 Worker 在外部系统产生副作用。Worker A 的租约过期后它已经发送的邮件、扣款或模型调用不会自动撤销。因此模块 4 仍是at-least-once至少执行一次平台保证任务不会因节点失联而静默丢失但故障窗口内可能重复执行。真实任务必须使用幂等业务键、唯一请求 ID、结果去重或补偿机制幂等Idempotency表示同一个操作重复执行多次最终业务效果仍与执行一次相同。4. 心跳同时维护会话和租约心跳Heartbeat是 Worker 周期发送的存活请求。请求可以携带当前活动租约列表服务端会更新 Worker 的last_heartbeat_at检查每个 Dispatch 和租约令牌为仍合法的租约返回renewed并延长过期时间为已经取消或过期的租约返回revoked为未知或不匹配的租约返回unknown。空闲 Worker 也必须发送心跳。否则一个长时间没有任务的健康 Worker 会因为last_heartbeat_at不更新而被标记为 offline真正出现任务时又无法领取。模块 4 的多进程恢复测试实际发现并修复了这个问题。租约续期和 Worker 离线判定都使用 PostgreSQL 的当前时间而不是 Worker 本机时间。原因是不同机器的时钟可能有偏差Worker 也不能自行声明“我的租约仍有效”。Worker 本地只设置一个比服务端截止时间更早的安全期限初始期限取租约过期时间与 Attempt 截止时间中较早者再减去安全余量。合法心跳续租后可以延长本地计时器但不能越过 Attempt 截止时间。如果网络或 PostgreSQL 长时间不可用本地 Context上下文用于在 Go 调用链中传递取消和截止时间会在安全期限到达时取消 Executor降低无所有权执行时间。5. 一次任务怎样从 Ready 走到 Succeeded正常数据流如下Task ready - Dispatch Coordinator 检查兼容 Worker 容量和全局上限 - 在一个 PostgreSQL 事务中创建 pending Dispatch - Task ready - queued - Worker 按空闲槽位发送 claim - 事务锁定 Run 和 Dispatch - 创建 AttemptTask queued - running - 返回租约明文和执行输入 - Worker 执行并周期心跳 - Worker 提交结构化结果 - 事务校验当前租约并推进状态机 - Dispatch completedTask succeeded - 解锁下游任务或结束 RunDispatch Coordinator分发协调器是控制面中周期扫描可运行任务、创建 Dispatch、回收过期租约并监督 Advisory Lock 的组件。Advisory Lock建议锁是 PostgreSQL 提供、由应用自行约定含义的锁这里用它保证同一数据库只有一个活动 Coordinator。Wake只是降低新 Run 的等待时间即使内存唤醒丢失周期扫描仍会从 PostgreSQL 重新发现任务。每个扫描轮次对每个 Run 最多推进一个 Task避免一个大 Run 一次占满全部 Dispatch。只要上一轮仍创建了 DispatchCoordinator 会立即开始下一轮公平扫描返回 0 才等待下次唤醒或周期扫描时刻。这样既保留跨 Run 轮换也不会让单个大 Run 每个任务都额外等待一个完整扫描周期。只限制“单轮一个 Task”仍不够。假设全局 Dispatch 上限为 1每次容量释放后都从最早创建的 Run 重新排序同一个旧大 Run 仍可能连续赢得每一轮。当前实现把公平依据保存在 PostgreSQL从未创建过 Dispatch 的 Run 优先其余按最近一次 Dispatch 创建时间从早到晚选择。这样进程重启后也不依赖内存游标代价是候选查询需要读取 Dispatch 历史因此数据库增加了(run_id, created_at)索引。它仍是基础公平性不包含租户权重、任务优先级或严格的等待时间保证。6. 背压与并发限制在哪里生效背压Backpressure是下游处理能力不足时上游停止继续积累工作的机制。本模块有三层限制Workflow 的concurrency限制同一 Run 中queued running的任务数Worker 的max_concurrency限制该会话同时持有的租约数控制面的 Dispatch limit 限制全局pending leased数。Coordinator 只有在存在兼容活动 Worker 且还有全局容量时才创建 Dispatch。Worker 领取时PostgreSQL 再检查max_concurrency - active_leases不能信任客户端自己上报的 slots。Worker 空闲领取返回空leases是正常结果不是服务错误。运行时使用指数退避从较短等待开始连续空领取时逐步增加到上限领取成功后重置。它还加入随机抖动让多个 Worker 的下一次请求不集中在同一时刻。随机抖动不能消除所有同时请求但能减少固定周期造成的同步峰值。Worker 的并发槽位释放也需要唤醒本地领取循环。否则任务已经完成领取循环却仍在等待上一次退避计时器顺序任务会在每个步骤之间增加无意义延迟。模块 4 在活动租约结束时发送一个合并的容量通知控制面接受成功结果后也唤醒 Coordinator使新解锁的下游任务尽快创建 Dispatch。通知只用于降低延迟真正状态仍以 PostgreSQL 为准。7. Worker 崩溃后怎样恢复假设 Worker A 已经领取 Attempt 1Worker A 持有租约并执行 - Worker A 进程被强制终止 - 心跳停止 - PostgreSQL 当前时间超过 lease_expires_at - Reaper 锁定 Run 和 Dispatch - Attempt 1 - interrupted - 仍有重试次数时 Task 回到 waiting_retry/ready - 创建新的 pending Dispatch - Worker B 领取 Attempt 2 - Worker B 提交成功Reaper过期回收器是 Coordinator 周期执行的数据库回收逻辑。租约先到期时Attempt 记为interrupted任务自己的 Attempt deadline 先到期时Attempt 记为timed_out。两者都会消耗一次 Attempt并复用工作流内核原有的重试或最终失败规则。超过三个心跳周期没有更新的 Worker 会被标记为offline。旧进程重新联网后不能恢复该会话或过期租约控制面返回永久的会话错误后Worker 进程会取消本地执行并退出由人工或进程管理器重新启动再注册新的 WorkerID 和会话令牌。临时网络错误和服务端 5xx 不会触发该退出仍按原轮询策略重试。8. 迟到结果、重复请求与取消8.1 迟到结果Worker A 可能在租约过期后恢复网络并提交自己之前算出的成功结果。此时即使结果内容正确也不能覆盖 Worker B 的新 Attempt。控制面检查 Dispatch 状态、WorkerID、租约摘要、数据库时间和当前 Attempt 归属任一不匹配都返回lease_lostRun 的 revision 不变化。revision修订号是 Run 每次成功提交状态后递增的版本号用于发现基于旧状态产生的并发写入。当前方案实现了fencing隔离旧持有者的效果存储层只接受当前租约对应的随机令牌旧持有者的迟到写入被隔离。严格意义上的fencing token隔离令牌通常还带有单调递增的 generation代次编号使下游系统能够直接比较新旧。模块 4 的随机租约令牌必须和 PostgreSQL 当前状态一起校验不能单独传播到任意下游作为顺序编号。8.2 重复完成请求Worker 可能已经提交成功但 HTTP 响应在返回途中丢失。它会使用同一个租约令牌和相同结果重试。服务端对受限结果字段生成规范化 SHA-256同一租约、相同结果返回首次成功同一租约、不同结果返回result_conflict不会重复递增 revision 或重复解锁下游。8.3 运行取消取消请求先持久化cancel_requested_at再由 Coordinator 在事务中撤销 pending/leased Dispatch、取消未开始 Task 和运行中 Attempt。两步不能假设总在同一进程生命周期内完成服务可能在取消意图提交后、状态收敛前崩溃。因此 Coordinator 的启动扫描和周期扫描都会重新处理“已经请求取消但仍未终止”的 Run。后续心跳返回revoked迟到完成返回lease_lost。“进程关闭”和“用户取消”不是同一件事。Worker 优雅退出时先进入draining停止领取新任务并在关闭期限内继续心跳和完成已有租约活动租约清空后再次调用 drain最终进入stopped并记录停止时间。没有活动租约的 Worker 可以直接进入stopped。这不会把用户 Run 改成 canceled。9. PostgreSQL 事务为什么重要数据库事务Transaction把一组数据库读写作为一个整体提交全部成功才生效任一步失败就回滚。模块 4 必须让以下变化处于同一个事务签发租约与创建 AttemptTask 状态与 Run revisionStateEvent 序号StateEvent 是按顺序记录每次状态变化的持久化事件Dispatch 状态结果哈希。如果先返回租约再创建 Attempt进程可能在两步之间崩溃Worker 已经开始执行但数据库不知道如果先把 Task 改成 succeeded 再更新 Dispatch失败重试可能让两份事实矛盾。并发事务还必须使用一致的加锁顺序。假设完成事务先锁 Dispatch再准备锁 Run同时取消事务已经锁住 Run又准备锁同一个 Dispatch。两个事务各自等待对方释放锁就会形成数据库死锁。模块 4 因此统一采用“先锁 Run再锁 Dispatch”的顺序。完成、回收、取消和孤儿清理都遵守这个顺序。领取路径需要同时选择可运行的 Run 和可领取的 Dispatch因此使用一条FOR UPDATE OF r, d SKIP LOCKED查询同时锁定两类记录。FOR UPDATE表示其他事务不能同时修改选中的行SKIP LOCKED表示遇到已经被其他事务锁住的候选时直接跳过继续查找其他任务而不是让所有 Worker 排队等待同一行。Reaper 也必须在筛选过期候选时锁定 Dispatch并在结算前重新读取最新租约期限和数据库实时时钟。否则 Heartbeat 可能已经返回renewedReaper 却仍按更早的查询结果把同一租约回收。SKIP LOCKED让 Reaper 跳过正在续租的 Dispatch下一轮再依据最新事实判断。首轮多 Worker 基准确实发现并修复了相反锁顺序导致的循环等待风险。修复后的并发测试和正式基准没有再次出现已知超时但这只覆盖当前测试输入数据库锁等待仍需要在后续指标中持续观测。revision 和连续事件序号可以拒绝基于旧快照的写入但也意味着同一个 Run 的状态事务最终要串行提交。这是当前正确性边界也是性能基准中多 Worker 无法让单个 Run 线性加速的主要原因。10. 为什么选择 PostgreSQL 加 HTTP 拉取10.1 PostgreSQL 同时保存状态和分发模块 2 已经使用 PostgreSQL 保存 Workflow、Run、Task、Attempt 和事件。模块 4 继续用它保存 Worker、Dispatch 和租约可以在一个事务中校验执行所有权并提交状态不需要先解决数据库与消息队列之间的双写一致性。代价是领取、心跳、完成和回收都会增加数据库负载单 Run revision 还是串行提交点。模块 5 后续基准显示多 Run 可以通过独立 revision 提高并行度而单 Run 仍受串行提交限制当前证据还不足以证明必须引入第二套队列事实源。因此只有后续负载证明 PostgreSQL 分发成为不可接受的瓶颈时才评审消息队列、Outbox 或状态分片。Outbox事务发件箱是把待发布消息与业务状态写入同一个数据库事务再由后台过程异步转发消息的模式用于避免数据库与消息系统直接双写不一致。状态分片State Sharding是按 Run 或其他稳定键把状态分散到多个独立存储分区使不同分区可以并行处理它会增加路由、跨分片查询和一致性复杂度。10.2 HTTP 主动拉取Worker 通过 HTTP 主动请求任务控制面不需要知道 Worker 的可访问地址也不需要穿透防火墙主动连接执行节点。已有 HTTP、认证、错误结构和日志边界可以继续使用。OpenAPI是用机器可读文件描述 HTTP 路径、参数、认证和响应结构的接口规范模块 4 在模块 2 的 OpenAPI 契约上继续增加 Worker 路由。没有优先选择控制面推送因为推送需要管理 Worker 地址、连接状态、失败重投和控制面侧容量视图Worker 本地空闲槽位反而最直接。没有优先选择 gRPC因为当前消息较小、不需要双向流和跨语言高吞吐证据引入 Protocol Buffers用于定义并生成结构化消息代码的序列化协议、代码生成和流生命周期会增加学习与运维成本。若后续需要大量流式日志、长连接心跳或多语言 Worker再基于测量结果评审 gRPC。10.3 为什么没有引入 Kafka、RabbitMQ、NATS 或 Redis成熟消息系统能提供高吞吐队列、消费组和投递机制但不能自动替代 Run 状态机、Attempt 归属、租约截止时间和迟到结果检查。当前规模没有证据要求额外系统而且引入后必须处理消息与 PostgreSQL 状态的原子发布、重复消费和运维。Redis 可以实现短期队列和租约但当前项目仍需要 PostgreSQL 保存不可变版本、关系查询、revision 和事件。增加 Redis 会形成缓存或第二事实来源。只有实际负载证明 PostgreSQL 分发查询不能满足目标并且能明确缓存失效与恢复策略时才值得增加。10.4 为什么没有直接使用 Kubernetes JobKubernetes 能创建和重启容器但它不知道一个工作流 Task 的重试策略、Run revision、用户取消和旧结果是否仍可接受。模块 4 先验证平台自己的执行所有权协议模块 6 再把 Worker 或受限任务运行到 Kubernetes。Pod是 Kubernetes 调度和运行一个或多个紧密关联容器的最小部署单元Pod 故障仍必须遵守同一套租约和状态规则。11. 真实验证与性能结果自动化验证使用真实 PostgreSQL并覆盖一个控制面、两个 Worker 执行并行 DAGDirected Acyclic Graph有向无环图强制终止持有租约的 Worker由第二个进程创建 Attempt 2新 Attempt 创建后通过 HTTP 重放旧租约返回lease_lost完整重启控制面后原 Worker 会话继续心跳和完成PostgreSQL 不可用时本地安全期限取消执行运行中取消、Worker 优雅退出和空闲心跳没有 Worker 时保持 ReadyWorker 后注册后恢复全局背压、跨轮 Run 公平性和并发领取唯一性取消意图提交后由周期扫描收敛、Heartbeat 与 Reaper 的受控锁竞争永久 Worker 会话错误退出以及临时网络错误继续重试默认mock执行器的历史幂等哈希兼容和 OpenAPI 状态枚举契约。验证实际发现并修复了十三类问题空闲 Worker 不发送心跳导致健康会话被误判离线优雅退出过早取消已有任务没有留出 draining 完成窗口Worker 配置时长与控制面数据库中的时间事实不一致不同事务的锁顺序相反存在循环等待风险任务完成后控制面和 Worker 都没有及时触发下一轮分发与领取本地安全期限没有同时受租约过期时间和 Attempt 截止时间限制Worker 会话进入draining后没有在租约清空时收敛到stopped取消意图已经提交但即时收敛失败时周期扫描没有继续处理新增默认mock执行器后相同历史请求的幂等哈希发生变化Heartbeat 续租与 Reaper 的旧候选结果竞争可能出现先返回续租、后回收租约每轮最多推进一个 Task 不能保证跨轮公平旧大 Run 仍可能长期占用唯一容量OpenAPI 漏写queued同时错误允许 Worker 提交控制面不接受的canceled结果Worker 忽略永久会话错误后不会重新工作也不会退出。这些问题都不会被只覆盖单 Worker 成功结果的测试发现。1、4、16 Worker 处理单个 1,000 任务 Run 的五轮正式基准均值如下Worker平均耗时平均吞吐平均空领取次数154.94 秒18.34 tasks/s1015.0436.46 秒27.59 tasks/s384.01637.61 秒30.02 tasks/s1558.6tasks/s表示每秒完成的任务数。基准为缩短 15 个样本的总耗时使用 25 毫秒至 500 毫秒的压力轮询配置和 64 个数据库连接上限生产 Worker 默认轮询范围仍是 250 毫秒至 5 秒。4 Worker 比 1 Worker 的平均吞吐提高约 50%16 Worker 比 4 Worker 只提高约 9%而且 16 Worker 有一轮降到 14.33 tasks/s。这说明增加 Worker 不会让单 Run 线性加速每次任务变化都要串行更新同一个 Run revisionWorker 越多还会增加领取竞争和空轮询。当前数据用于暴露正确性设计的性能代价不是生产容量承诺。多进程故障测试使用 2 秒租约、50 毫秒心跳和 5 毫秒扫描周期。这个测试值必须明显大于 Worker 的 1 秒本地安全余量否则 Worker 会在领取后立即取消执行测到的就不再是真实的崩溃接管。修正参数后连续五轮均通过其中一次带详细日志的运行从强杀 Worker 到 Run 成功约为 2.04 秒。默认配置是 15 秒租约和 1 秒扫描因此默认人工演示会明显更慢。恢复速度、失联误判概率和心跳数据库压力相互制约不能只为了得到更小数字而任意缩短租约。模块 4 当时的完整环境、命令、原始结果和限制见模块 4 验证报告。模块 5 后续已经补充连接池指标、队列聚合、多 Run 吞吐、进程资源与四种观测配置对照这些新证据见模块 5 验证报告不倒写成模块 4 当时已经掌握的数据。12. 当前限制与下一步模块 4 已经证明执行进程可以独立扩展和注册任务领取、续租、完成与回收有持久化事实Worker 崩溃后任务可以重新分配旧租约、重复结果和取消后的完成不会覆盖当前状态控制面重启不会丢失 Worker 会话和未过期租约Worker 不能通过 Action 执行任意代码。模块 4 完成时仍然没有解决控制面只有一个协调者没有高可用切换高可用表示部分实例故障后其他实例能够自动接管并继续提供服务单 Run revision 限制并发提交扩展性指标、Trace、告警和故障注入平台当时尚未建立Worker 只支持 Mock Executor没有真实资源限制、容器隔离和 Kubernetes 部署Bearer Token 没有企业身份、证书和租户边界至少执行一次仍要求真实业务处理重复副作用。因此模块 4 完成后的下一步进入模块 5不是继续堆叠 Worker 数量而是建立可观测性和持续故障实验测量队列深度、领取空转、数据库事务延迟、租约回收时间、单 Run 与多 Run 吞吐定位 revision 串行点并让日志、指标和 Trace 能从 Run 关联到 Worker 和 Attempt。Trace分布式调用链记录一次请求或任务跨组件经过的步骤和耗时用于定位延迟或失败发生在哪一段。模块 5 已经完成这些观测与实验并确认单 Run revision 是明显串行点这为模块 6 的受限执行环境保留了可比较基线。13. 总结多 Worker 的核心不是启动多个进程而是定义哪一个执行者在什么时间内有权代表某个 Attempt 写入结果。模块 4 使用持久化 Dispatch 表达待分发事实领取时才创建 Attempt用租约和数据库时间限制临时所有权用心跳续租和发现失联用隔离令牌拒绝迟到写入再通过 PostgreSQL 事务同时提交 Dispatch、Task、Run revision 和事件。Worker 主动 HTTP 拉取让容量决策靠近执行节点指数退避和背压控制空轮询与待处理数量。这套设计保证任务不会因 Worker 崩溃静默丢失也不会让旧租约覆盖新结果但不承诺只执行一次。模块 5 已经补充可观测性并量化单 Run 串行瓶颈真实副作用的幂等和受限执行环境仍需要后续模块继续解决。参考资料Gocontext官方文档https://pkg.go.dev/contextGonet/http官方文档https://pkg.go.dev/net/httpPostgreSQL 显式锁官方文档https://www.postgresql.org/docs/current/explicit-locking.htmlPostgreSQLSELECT与SKIP LOCKED官方文档https://www.postgresql.org/docs/current/sql-select.htmlPostgreSQL 日期与时间函数官方文档https://www.postgresql.org/docs/current/functions-datetime.htmlRFC 6750 Bearer Token 规范https://www.rfc-editor.org/rfc/rfc6750项目源码本文对应模块 4。完整源码、多 Worker 故障实验和后续模块见 AI Workload Platform GitHub 仓库。
返回列表