导言
任务模型的抽象具有广泛通用性的,例如饿了么骑手每笔骑单是任务,小法庭中交易纠纷的评审也可以被抽象为任务。
下面是简易的任务系统模块图,图示中去除了上下游的模块,保留了核心与小法庭业务特色部分。
在任务分发中核心会遇到以下的挑战和难点:
• 分配制下任务消费堆积问题以及解法
• 申领制下任务异常消费问题以及解法
• 并发下任务过度消费问题以及解法
任务堆积
任务堆积根本原因是任务的流入速度大于任务的消费速度,导致任务失去时效性。
一个稳定的系统,产生任务堆积的原因可能是流入速度的瞬时增大,例如业务准入规则的变化。也可能是任务消费速度的下降,例如业务完结条件的改变、操作人处理效率下降等。
小法庭任务系统在重构之前,任务的分发采用了分配制(见下图),任务投票系统处理效率不光低,更致命的是不稳定。造成系统在一个月内流判(未在指定时间内完成)的任务达到了近上万件。
分配制会设定一个每小时级别的定时任务,该定时任务的逻辑如下:
1.捞取全量在库任务,对于每个任务,重复以下2-4操作
2.从用户底池中随机捞取,进行规则校验(如同人模型去重、买卖家去重等业务规则),通过校验后,对选定的用户进行赋权操作-即加上任务-人关系锁。
3.重复步骤2,直到一个任务已经分配给N位用户
4.发送系统消息给N位用户,邀请用户来小法庭进行评审
对单个任务进行分配,程序平均处理需要花费1-2s。纠纷场景在库任务量日均为3000件,每次对3000件任务进行程序处理花费0.8-1.6小时。实际中我们经常能发现如下图所示的定时任务超时的情况,在9-12点时间段,理论上进行了4次定时任务,但是由于执行超时,实际只执行了2次定时任务。此时业务实际系统消息量就会大大低于预期。下图是一个定时任务理论执行与实际超时执行的示意图,其中定时任务的宽度代表所耗时间。
分配制的问题总结
• 分配制的技术定时任务执行时间长,影响了任务投票系统的处理效率,导致任务消费堆积,带来业务上流判问题。
• 评审员随机抽取,部分用户并不愿意参与小法庭评审,此时系统消息就会打扰用户。
主动申领制
针对任务分发模式进行了技术改造,将任务发放由系统通过业务处理计算分配改成用户主动来获取当前可评审任务(如下图)。在申领制下,我们为符合小法庭评审员资质的用户,开放了小法庭入口,人群的计算通过每天的离线任务产出获得。
申领制的链路进行了业务逻辑解耦,例如用户资质校验的业务逻辑前置在用户模块与后置投票模块。而不是像分配制中在任务分发环节进行校验。
模块之间的解耦提高了技术系统的性能与稳定性。且用户不再被邀约消息打扰。
任务异常消费
任务异常消费,定义为任务的完成不符合预期的业务规范。各个业务系统表现不一,此处依旧以小法庭系统举例。
申领制上线后,业务同学发现有评审员向买卖家提供“买票”服务。具体的表现为买家或者卖家一方支付了“服务费”后,评审员选择该方进行投票。申领制允许用户换评审任务,因此灰产可以利用反复刷任务,最终找到指定任务进行评审,更有甚者利用多端设备同时对指定的任务的一方投票,达到“包赢”操作。此种行为不仅扰乱了评审公正性并且还对正常用户进行了打扰。
针对这种异常消费现象,技术同学在任务分发时引入用户锁队列。其思想是在分配任务时,不允许用户无限制换任务,对于单个账号,在指定时间内的只能看到m件任务,使得灰黑产无法通过不断刷新的方式,找到他们期望的任务,从而打击评审员帮投、包赢的非法手段。
我们在技术上的实现是基于用户锁队列。每次分发任务时,对用户锁队列进行业务规则校验与维护,再随机从维护后的m件任务选择一件任务分发给来申领任务的用户。
任务过消
在本文中,任务过消是一种并发下的业务系统现象。随着入口流量逐步增大,用户评审热情的持续提升,申领任务争抢高并发的现象也逐渐增多,造成了任务过快消费。带来的业务影响是评审员资源的浪费与用户舆情(用户看完陈词进行投票时,任务已经完结)。
通过完善了监控项,业务开发同学统计出某半天并发冲突的量达1.5w,单月估算资源浪费数量可达100万!
可调整参数分析-用户疲劳度
小法庭系统核心的任务处理是投票操作,以一秒的时间滑动窗口去计算。投票接口的qps为y,假定申领任务时均匀,当前剩余可投票数目为r,在当前时刻资源冲突的条件为r < y。此时冲突资源计算可得为:c = y - r
举一个例子,假设当前只有1个任务,买家支持7票,卖家支持6票,9票为单方胜利条件,此时剩余可投票数为Min(9 - 6, 9 - 7) = 2。如果此时y = 3,那么会有产生一个人的投票资源浪费。
我们的目标是,如何通过技术侧能力建设,使得系统任务处理时间相对精确可控,同时降低人力资源浪费的情况。对于小法庭这个系统,想要减小并发冲突造成资源浪费的情况,我们要尽量减小单位时间内的系统投票数目为Na。此处有关系:Na∝(在线用户量Np、用户疲劳度F、用户参与意愿度M......)。
Na正相关于若干因素,其中调节用户的疲劳度是一个可控方案,能使得Na变化。通过推理以及实际观察,当减小用户疲劳度F时,Na减小,同时资源浪费的情况减少了。调控用户疲劳度只能进行宏观的调整,无法做到更精粒度的调整,同时降低了用户体验(即用户每天可参与投票次数减少),不是一个合理的调控方案。
申领时分配-任务锁队列
我们上文还提到分配制的缺点,转头又有分配的概念了!这并不是乌龙,此处分配的本质是设计任务锁队列,来降低资源冲突问题,具体逻辑如下,在每次进行任务发放时
1.从数据库中捞取n件任务到内存中
2.判断当前是否还有可剩余任务,若有否则进入下一步,否则进入步骤6。
3.任务锁队列作校验
4.如果队列 size > m ,重试步骤2。如果队列 size < m ,将用户加入该队列,并设置相应缓存失效时间
5.返回该任务
6.不返回任务
到这里,笔者想提问一下读者,这种方案能不能彻底解决资源浪费的现象?
答案是不能。对于小法庭的业务场景,假设单边胜利投票剩余投票数为m,任务锁队列大小为n,只要 n > m,理论上就有机会产生并发冲突。不过合理的设置任务锁队列大小n与任务锁队列的生命周期T,可以最大程度减少资源浪费。
总结
本文介绍了任务(投票)系统中的三个挑战,并以小法庭系统为例展开具体的解法。在实际落地中,获得了不错的业务效果。
1.任务分配制优化为主动申领制后,任务处理效率有了极大提升,任务完结率达到100%,任务的处理时长从平均20小时下降至2小时以内,有效解决了任务堆积的问题。同时再无舆情反馈系统消息打扰。
2.通过用户锁队列,有效解决了任务异常消费的现象。
3.针对申领制带来的任务过消,利用分配制中任务锁的思想,将两种任务发放方式结合,有效降低了并发冲突产生的资源浪费现象。
思考
横向来看,除了任务分发(调度)模块,任务流入模块在贴合业务做的定制开发时,也要保障系统程度的通用抽象性。例如,为保证新场景快速稳定的接入,可以采用工厂+策略的设计模式组织代码,留给技术系统一定的可扩展性。