任务系统的设计

一、背景

由于业务中引入了小队、队长、导师、服务经理等角色,如何合理地安排和考核几千名司服和上万名队长的工作,就成为了不小的挑战,任务系统应运而生。

任务系统通过接入各类事件,在不同的任务场景下将任务分配给不同的角色——司服、队长、导师等。一旦任务完成,系统通过消息队列(MQ)机制及时通知下游系统。在某些特定场景下,还会推送质检任务,并根据结果发放相应的奖励,以此激励团队成员,提高整体工作效率和质量。

二、现状

2.1 任务分类较多

根据正向牵引数据源(新司机首次在线,完成首单……)、反向异常数据源(行程不安全、申诉…..)将任务分为十几种类别,且分类随着业务的开展还会不断增加。

2.2 任务接入来源较多

不同场景会有其对应的行为事件,进而产生不同的任务,接入任务系统的业务方非常多。

三、系统设计

3.1 梳理业务流程

任务系统业务流程图

针对上面的任务,以及日后要接入的其他类任务,核心业务流程分为4部分:

  1. 事件接入:通过API/ES/MQ系统交互的方式,确定当前任务场景下,司机所对应的行为事件。
  2. 圈定人群:在某一类场景下,司机的行为事件也有多种多样,通过一定的业务规则,来筛选我们的目标人群。
  3. 绑定任务:筛选完目标人群后,通过组织架构以及相应的任务模板来生成任务。
  4. 任务流程:任务接收人在APP上完成相关任务。

以工单任务为例,任务时序图如下所示:

sequenceDiagram participant 任务系统 participant 行为引擎 participant 人群服务 participant 导师 participant 驱动 opt 任务下发 任务系统->>行为引擎: 人群规则 行为引擎->>人群服务: 人群圈定 人群服务->>行为引擎: MQ人群流 行为引擎->>任务系统: 数据填充 end opt 任务绑定 任务系统->>导师: 任务领取 导师->>导师: 任务完成 导师->>任务系统: 领取奖励 任务系统->>驱动: 积分发放 驱动->>任务系统: 同步成功 end opt 任务剔除 任务系统->>行为引擎: 人群准出 行为引擎->>任务系统: 任务剔除 end

3.2 核心模块划分

任务系统核心模块 任务系统按模块逻辑可以分为4部分,任务生成、基础能力、同步模块。

3.2.1 任务生成

1.动态圈定人群:如上图场景1所示的任务。

当服务后台配置好规则后,任务系统会将这些规则给到行为牵引模块。行为牵引模块根据规则来确定目标人群,并利用消息队列(MQ)将人群数据流传递给任务系统。

之所以将行为牵引设计为一个独立的模块,是因为我们需要同时监控目标人群的准入和准出状态。这样做可以防止因不符合某些规则而导致的人群准出问题,行为牵引模块实现了规则的托管功能。

  1. 快照规则人群:如上图场景2所示的任务。

任务系统不需要关注人群的长期状态,只关注当前的状态。它通过ES在线实时拉取人群数据,或者通过消息队列(MQ)或开放接口(OpenAPI)下发人群数据。

  1. 上传文件人群:如上图场景3所示的任务。

通过上传文件并手动选定一批人群的操作,在形式上与第二种方法相似,都可以视为基于快照的逻辑。一旦数据源经过解析和格式化清洗,其数据结构就变得适合在任务系统中进行一系列状态流转。

随后,从ICE服务中获取一个唯一的主键,这是因为当前的任务数据分散在不同的数据库和表中,需要一个全局唯一的标识符来区分每条数据记录。

3.2.2 基础能力

这个模块包含了任务系统的基础能力。例如任务状态的变更,消息触达,任务转发,任务检索等。 系统会在任务创建后,通过binlog将数据同步到 ES 进行索引,从而避免执行复杂的查询操作,减少对多个库扫描的情况。

3.2.3 数据同步

该模块不需要额外开发,基于自研的数据开发平台来配置相关的同步任务。这些任务可以按照离线的小时、天维度来支持业务查询,同时,由于ES中的数据是实时更新的,它可以用作在线系统的检索工具,避免了在查询时需要扫描多个数据库的复杂情况。

3.3 技术实现

3.3.1 人群准入

graph LR 规则配置 -->|规则生效| 任务池 任务池 -->|人群规则| 行为引擎 行为引擎 -->|人群流| 任务池

为了灵活配置任务的完成条件支持,引入了规则引擎。在比较goenginegorulegovaluate后,最终使用 govaluate。相比前两者,govaluate 除了支持in操作、还支持正则表达式,而且表达式也不需要转换成DRL

Govaluate: govaluate 是一个 Go 语言的表达式评估库,允许你在运行时动态评估字符串表达式。虽然它不是一个完整的规则引擎,但可以用于实现一些简单的规则评估功能。

当规则生效时,系统会自动创建一个新的行为牵引项,并将该项对应的 conduct_id 记录下来。这个 conduct_id 将用于在消息队列(MQ)中回推人群信息,以确保与相应的规则相匹配。

人群规则详细内容如下,里面包含规则的类型,已经涉及的城市列表。

{
     "id": 123,
     "name": "导师工单任务规则",
     "create_user": "create_user",
     "rule_type": 1,
     "status": 1,
     "city_ids": "1,2,3",
     "reward_info": "{"reward_type":1,"reward_amount": 80}",
     "expiring_period": 10,
     "bind_period": 2
}

MQ人群流的内容如下:

{
    "driver_id": 580546707680283,
    "operator_time": "2022-01-01 10:00:00",
    "status": 1,
    "type": 1,
    "extra": {
        "conduct_id": 123
    }
}

3.3.2 绑定任务

graph LR B[导师/队长] -- 2. 任务沟通 --> A[新司机] B -- 1. 任务领取 --> C[任务池] D[行为引擎] -- 3. 完单事件 --> C C -- 4. 任务完成/失败 --> B

任务池中的任务被领取后,即视为任务被绑定到了某位导师/队长身上,其他人无法在任务池中查看这个任务。

如果任务在绑定期内未被完成,一旦超过绑定期,任务将重新回到任务池,可被再次领取。如果任务在任务池的有效期间内尚未被领取,该任务将会失效。

由于任务的时间间隔能可能较久,这里采用定时器轮转,加mysql索引来失效过期任务。不考虑延迟队列的原因是可能会导致数据在队列中存储时间过长。

3.3.3 人群准出

针对司机完成订单的情况,我们需要根据不同的任务状态执行相应的逻辑处理。具体来说:

  1. 如果任务还在任务池中,我们直接将其从任务池中移除。

  2. 如果任务已经被领取,我们需要检查领取人和新司机之间在任务的绑定期内是否有过虚拟通话记录。如果有通话记录,我们将任务标识为已完成;如果没有通话记录,则标识任务为未完成。

四、小结

规则引擎结合消息队列MQ和定时器,为任务系统提供了强大的灵活性和自动化能力。通过动态配置规则,系统能够适应新任务需求而无需代码变更,实现任务的自动化处理和状态监控,提高效率并降低维护成本。