隔离|异步任务处理系统,如何解决业务长耗时、高并发难题?( 二 )



任务 API/Dashboard 该子系统提供一组任务相关的 API , 包括任务创建、查询、删除等等 。 用户通过 GUI , 命令行工具 , 后者直接调用 API 的方式使用系统功能 。 以 Dashboard 等方式呈现的可观测能力也非常重要 。 好的任务处理系统应当包括以下可观测能力:
日志:能够收集和展示任务日志 , 用户能够快速查询指定任务的日志 。指标:系统需要提供排队任务数等关键指标 , 帮助用户快速判断任务的执行情况 。链路追踪:任务从提交到执行过程中 , 各个环节的耗时 。 比如在队列中排队的时间 , 实际执行的时间等等 。 下图展示了 Netflix Cosmos 平台的 tracing 能力 。
任务分发 任务分发负责任务的调度分发 。 一个能应用于生产环境的任务分发系统通常要具备以下功能:
任务的可靠分发:任务一旦提交成功后 , 无论遇到任何情况 , 系统都应当保证该任务被调度执行 。任务的定时/延时分发:很多类型的任务 , 希望在指定的时间执行 , 例如定时发送邮件/消息 , 或者定时生成数据报表 。 另一种情况是任务可以延时较长一段时间执行也没问题 , 例如下班前提交的数据分析任务在第二天上班前完成即可 , 这类任务可以放到凌晨资源消耗低峰的时候执行 , 通过错峰执行降低成本 。任务去重:我们总是不希望任务被重复执行 。 除了造成资源浪费 , 任务重复执行可能造成更严重的后果 。 比如一个计量任务因为重复执行算错了账单 。 要做到任务只执行一次(exactly-once) , 需要在任务提交 , 分发 , 执行全链路上的每个环节都做到 , 包括用户在实现任务处理代码时也要在执行成功 , 执行失败等各种情况下 , 做到 exactly-once 。 如何实现完整的 exactly-once 比较复杂 , 超出了本文的讨论范围 。 很多时候 , 系统提供一个简化的语义也很有价值 , 即任务只成功执行一次 。 任务去重需要用户在提交任务时指定任务 ID , 系统通过 ID来判断该任务是否已经被提交和成功执行过 。任务错误重试:合理的任务重试策略对高效、可靠的完成任务非常关键 。 任务的重试要考虑几个因素:1)要匹配下游任务执行系统的处理能力 。 比如收到下游任务执行系统的流控错误 , 或者感知到任务执行成为瓶颈 , 需要指数退避重试 。 不能因为重试反而加大了下游系统的压力 , 压垮下游;2)重试的策略要简单清晰 , 易于用户理解和配置 。 首先要对错误进行分类 , 区分不可重试错误 , 可重试错误 , 流控错误 。 不可重试错误是指确定性失败的错误 , 重试没有意义 , 比如参数错误 , 权限问题等等 。 可重试错误是指导致任务失败的因素具有偶然性 , 通过重试任务最终会成功 , 比如网络超时等系统内部错误 。 流控错误是一种比较特殊的可重试错误 , 通常意味着下游已经满负荷 , 重试需要采用退避模式 , 控制发送给下游的请求量 。任务的负载均衡:任务的执行时间变化很大 , 短的几百毫秒 , 长的数十小时 。 简单的 round-robin 方式分发任务 , 会导致执行节点负载不均 。 实践中常见的模式是将任务放置到队列中 , 执行节点根据自身任务执行情况主动拉取任务 。 使用队列保存任务 , 让根据节点的负载把任务分发到合适的节点上 , 让节点的负载均衡 。 任务负载均衡通常需要分发系统和执行子系统配合实现 。任务按优先级分发:任务处理系统通常对接很多的业务场景 , 他们的任务类型和优先级各不相同 。 位于业务核心体验相关的任务执行优先级要高于边缘任务 。 即使同样是消息通知 , 淘宝上买家收到一个商品评论通知的重要性肯定低于新冠疫情中的核酸检测通知 。 但另一方面 , 系统也要保持一定程度的公平 , 不要让高优先级任务总是抢占资源 , 而饿死低优先级任务 。任务流控:任务流控典型的使用场景是削峰填谷 , 比如用户一次性提交数十万的任务 , 期望在几个小时内慢慢处理 。 因此系统需要限制任务的分发速率 , 匹配下游任务执行的能力 。 任务流控也是保证系统可靠性的重要手段 , 某类任务提交量突然爆发式增长 , 系统要通过流控限制其对系统的冲击 , 减小对其他任务的影响 。批量暂停和删除任务:在实际生产环境 , 提供任务批量暂停和删除非常重要 。 用户总是会出现各种状况 , 比如任务的执行出现了某些问题 , 最好能暂停后续任务的执行 , 人工检查没有问题后 , 再恢复执行;或者临时暂停低优先级任务 , 释放计算资源用于执行更高优先级的任务 。 另一种情况是提交的任务有问题 , 执行没有意义 。 因此系统要能让用户非常方便的删除正在执行和排队中的任务 。 任务的暂停和删除需要分发和执行子系统配合实现 。任务分发的架构可分为拉模式和推模式 。 拉模式通过任务队列分发任务 。 执行任务的实例主动从任务队列中拉取任务 , 处理完毕后再拉取新任务 。 相对于拉模式 , 推模式增加了一个分配器的角色 。 分配器从任务队列中读取任务 , 进行调度 , 推送给合适的任务执行实例 。