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



拉模式的架构清晰 , 基于 Redis 等流行软件可以快速搭建任务分发系统 , 在简单任务场景下表现良好 。 但如果要支持任务去重 , 任务优先级 , 批量暂停或删除 , 弹性的资源扩缩容等复杂业务场景需要的功能 , 拉模式的实现复杂度会迅速增加 。 实践中 , 拉模式面临以下一些主要的挑战:
资源自动伸缩和负载均衡复杂 。 任务执行实例和任务队列建立连接 , 拉取任务 。 当任务执行实例规模较大时 , 对任务队列的连接资源会造成很大的压力 。 因此需要一层映射和分配 , 任务实例只和对应的任务队列连接 。 下图是 Slack 公司的异步任务处理系统架构 。 Worker 节点只和部分 Redis 实例相连 。 这解决了 worker 节点大规模扩展的能力 , 但是增加了调度和负载均衡的复杂度 。
从支持任务优先级 , 隔离和流控等需求的角度考虑 , 最好能使用不同的队列 。 但队列过多 , 又增加了管理和连接资源消耗 , 如何平衡很有挑战 。任务去重 , 任务批量暂停或者删除等功能依赖消息队列功能 , 但很少有消息类产品能满足所有需求 , 常常需要自行开发 。 例如从可扩展性的角度 , 通常做不到每一类任务都对应单独的任务队列 。 当任务队列中包含多种类型的任务时 , 要批量暂停或者删除其中某一类的任务 , 是比较复杂的 。任务队列的任务类型和任务处理逻辑耦合 。 如果任务队列中包含多种类型的任务 , 要求任务处理逻辑也要实现相应的处理逻辑 , 对用户不友好 。 在实践中 , A 用户的任务处理逻辑不会预期接收到别的用户任务 , 因此任务队列通常由用户自行管理 , 进一步增加了用户的负担 。推模式的核心思想是将任务队列和任务执行实例解耦 , 平台侧和用户的边界更加清晰 。 用户只需要专注于任务处理逻辑的实现 , 而任务队列 , 任务执行节点资源池的管理都由平台负责 。 推模式的解耦也让任务执行节点的扩容不再受任务队列的连接资源等方面的限制 , 能够实现更高的弹性 。 但推模式也引入了很多的复杂度 , 任务的优先级管理 , 负载均衡 , 调度分发 , 流控等都由分配器负责 , 分配器需要和上下游系统联动 。
总的来说 , 当任务场景变得复杂后 , 无论拉还是推模式 , 系统复杂度都不低 。 但推模式让平台和用户的边界更清晰 , 简化了用户的使用复杂度 , 因此有较强技术实力的团队 , 实现平台级的任务处理系统时 , 通常会选择推模式 。
任务执行 任务执行子系统管理一批执行任务的 worker 节点 , 以弹性、可靠的方式执行任务 。 典型的任务执行子系统需具备如下功能:
任务的可靠执行 。 任务一旦提交成功 , 无论任何情况 , 系统应当保证任务被执行 。 例如执行任务的节点宕机 , 任务应当调度到其他的节点执行 。 任务的可靠执行通常是任务分发和任务执行子系统共同配合实现 。共享资源池 。 不同类型的任务处理资源共享统一的资源池 , 这样才能削峰填谷 , 提高资源利用效率 , 降低成本 。 例如把计算密集 , io密集等不同类型的任务调度到同一台 worker 节点上 , 就能更充分的利用节点上的CPU , 内存 , 网络等多个维度的资源 。 共享资源池对容量管理 , 任务资源配额管理 , 任务优先级管理 , 资源隔离提出了更高的要求 。资源弹性伸缩 。 系统能根据负载的执行情况伸缩执行节点资源 , 降低成本 。 伸缩的时机和数量非常关键 。 常见的根据任务执行节点的 CPU , 内存等资源水位情况伸缩 , 时间较长 , 不能满足实时性要求高的场景 。 很多系统也使用排队任务数等指标进行伸缩 。 另一个值得关注的点是执行节点的扩容需要匹配上下游系统的能力 。 例如当任务分发子系统使用队列来分发任务时 , worker 节点的扩容要匹配队列的连接能力 。任务资源隔离 。 在 worker 节点上执行多个不同的任务时 , 资源是相互隔离的 。 通常使用容器的隔离机制实现 。任务资源配额 。 用户的使用场景多样 , 常常包含多种任务类型和优先级 。 系统要支持用户为不同优先级的任务或者处理函数设置资源配额 , 为高优先级任务预留资源 , 或者限制低优先级任务能使用的资源 。简化任务处理逻辑的编码 。 好的任务处理系统 , 能够让用户专注于实现单个任务处理逻辑 , 系统自动并行、弹性、可靠的执行任务 。平滑升级 。 底层系统的升级不要中断长时任务的执行 。执行结果通知 。 实时通知任务执行状态和结果 。 对于执行失败的任务 , 任务的输入被保存到死信队列中 , 方便用户随时手动重试 。任务执行子系统通常使用 K8s 管理的容器集群作为资源池 。 K8s 能够管理节点 , 将执行任务的容器实例调度到合适的节点上 。 K8s 也内置了作业(Jobs)和定时作业(Cron Jobs)的支持 , 简化了用户使用 Job 负载的难度 。 K8s 有助于实现共享资源池管理 , 任务资源隔离等功能 。 但 K8s 主要能力还是在POD/实例管理上 , 很多时候需要开发更多的功能来满足异步任务场景的需求 。 例如: