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



接下来 , 让我们调整下用户负载的映射方式 。 如下图所示 , 每个用户的负载均匀的映射到两台服务器上 。 不但负载更加均衡 , 更棒的是 , 即使两台服务器宕机 , 除红色之外的用户负载都不受影响 。 如果我们把分区的大小设为 2 , 那么从3台服务器中选择2台服务器的组合方式有 C_{3^{2=3 种 , 即3种可能的分区方式 。 通过随机算法 , 我们将负载均匀的映射到分区上 , 那么任意一个分区不可服务 , 则最多影响1/3的负载 。 假设我们有100台服务器 , 分区的大小仍然是 2 , 那么分区的方式有 C_{100{2=4950 种 , 单个分区不可用只会影响 1/4950=0.2% 的负载 。 随着服务器的增多 , 随机分区的效果越明显 。 对负载随机分区是一个非常简洁却强大的模式 , 在保障多租系统的可用性中起到了关键的作用 。

自适应下游处理能力的任务分发 函数计算的任务分发采用了推模式 , 这样用户只需要专注于任务处理逻辑的开发 , 平台和用户的边界也很清晰 。 在推模式中 , 有一个任务分配器的角色 , 负责从任务队列拉取任务并分配到下游的任务处理实例上 。 任务分配器要能根据下游处理能力 , 自适应的调整任务分发速度 。 当用户的队列产生积压时 , 我们希望不断增加 dispatch worker pool 的任务分发能力;当达到下游处理能力的上限后 , worker pool 要能感知到该状态 , 保持相对稳定的分发速度;当任务处理完毕后 , work pool 要缩容 , 将分发能力释放给其他任务处理函数 。

在实践中 , 我们借鉴了 tcp 拥塞控制算法的思想 , 对 worker pool 的扩缩容采取 AIMD 算法(Additive Increase Multiplicative Decrease , 和性增长 , 乘性降低) 。 当用户短时间内提交大量任务时 , 分配器不会立即向下游发送大量任务 , 而是按照“和性增长”策略 , 线性增加分发速度 , 避免对下游服务的冲击 。 当收到下游服务的流控错误后 , 采用“乘性减少”的策略来 , 按照一定的比例来缩容 worker pool 。 其中流控错误需要满足错误率和错误数的阈值后才触发缩容 , 避免 worker pool 的频繁扩缩容 。
向上游的任务生产方发送背压(back pressure) 如果任务的处理能力长期落后于任务的生产能力 , 队列中积压的任务会越来越多 , 虽然可以使用多个队列并进行流量路由来减小租户之间的相互影响 。 但任务积压超过一定阈值后 , 应当更积极的向上游的任务生产方反馈这种压力 , 例如开始流控任务提交的请求 。 在多租共享资源的场景下 , 背压的实施会更加有挑战 。 例如A , B应用共享任务分发系统的资源 , 如果A应用的任务积压 , 如何做到:
公平 。 尽可能流控A而不是B应用 。 流控本质是一个概率问题 , 为每一个对象计算流控概率 , 概率越准确 , 流控越公平 。及时 。 背压要传递到系统最外层 , 例如在任务提交时就对A应用流控 , 这样对系统的冲击最小 。如何在多租场景中识别到需要流控的对象很有挑战 , 我们在实践中借鉴了Sample and Hold算法 , 取得了较好的效果 。 感兴趣的读者可以参考相关论文 。
异步任务处理系统的能力分层 根据前述对异步任务处理系统的架构和功能的分析 , 我们将异步任务处理系统的能力分为以下三层:
Level 1:一般需 1-5 人研发团队 , 系统是通过整合 K8s 和消息队列等开源软件/云服务的能力搭建的 。 系统的能力受限于依赖的开源软件/云服务 , 难以根据业务需求进行定制 。 资源的使用偏静态 , 不具备资源伸缩 , 负载均衡的能力 。 能够承载的业务规模有限 , 随着业务规模和复杂度增长 , 系统开发和维护的代价会迅速增加 。Level 2:一般需 5-10人研发团队 , 在开源软件/云服务的基础之上 , 具备一定的自主研发能力 , 满足常见的业务需求 。 不具备完整的任务优先级、隔离、流控的能力 , 通常是为不同的业务方配置不同的队列和计算资源 。 资源的管理比较粗放 , 缺少实时资源伸缩和容量管理能力 。 系统缺乏可扩展性 , 资源精细化管理能力 , 难以支撑大规模复杂业务场景 。Level 3:一般需 10+ 人研发团队 , 能够打造平台级的系统 。 具备支撑大规模 , 复杂业务场景的能力 。 采用共享资源池 , 在任务调度 , 隔离流控 , 负载均衡 , 资源伸缩等方面能力完备 。 平台和用户界限清晰 , 业务方只需要专注于任务处理逻辑的开发 。 具备完整的可观测能力 。