从零搭建精准运营系统

2018刚过去,趁着春节放假对过去一年主导开发的项目做个梳理和总结

项目背景

平台运营到一定阶段,一定会累积大批量的用户数据,这些用户数据是运营人员的黄金财产。而如何利用用户的数据来做运营(消息推送、触达消息、优惠券发送、广告位等),正是精准运营系统需要解决的问题。本文是基于信贷业务实践后写出来的,其它行业如保险、电商、航旅、游戏等也可以参考。

业务场景

先看几个具有代表性的需求

用户可用额度在20000~50000元,而且有借款记录,未还本金为0,性别为“男”
用户发生了A行为且未还本金大于5000
用户在1天内发生A行为次数大于等于3次
用户在A行为前24小时内未发生B行为
用户在A行为后一个月内未发生B行为

业务上有两种消息类型

  • 日常消息:由业务人员通过条件筛选锁定用户群,定时或即时给批量用户发送消息或者优惠券
  • 触达消息:主要由用户自身的行为触发,比如登陆、进件申请、还款等,满足一定筛选条件实时给用户发送消息或优惠券

对于用户筛选条件,也主要有两种类型

  • 用户状态:包括用户自身属性如性别、年龄、学历、收入等,还有用户相关联实体如进件订单、账户信息、还款计划、优惠券等的属性,以及用户画像数据如行为偏好、进件概率等
  • 用户行为:即用户的动作,包括登陆、进件申请、还款,甚至前端点击某个按钮、在某个文本框输入都算

早期方案

早期方案.png

早期方案存在以下痛点

  1. 至少两次跨部门沟通配合成本,周期被拉长
  2. 非实时消息推送,无法实现基于用户行为的实时推送场景
  3. 非实时效果验证,无法及时调整运营策略

系统搭建的目标

  • 需要定义规则,提供可视化界面给业务人员动态配置,无需重启系统即使生效,减少沟通成本和避免重复开发,总之就是要更加 自动化易配置
  • 采集实时数据,根据实时事件做实时推送,总之就是要 实时

技术选型

数据采集、转换、存储

  • 采集:状态类的数据主要放在各个业务系统的关系型数据库中,由于历史原因有postgres和mysql,需要实时采集表的数据变更,这里使用kafka connector读取mysql的binlog或postgres的xlog,另外还有标签系统计算出来的标签,在kafka中;而事件类数据主要来源于前端上报事件(有专门的服务接收再丢到kafka),关系型数据库里面也可以提取一些事件。
  • 转换:采集出来的数据需要做一些格式统一等操作,用kafka connector。
  • 存储:采用Elasticsearch存储用户数据,ES查询不像mysql或mongoDB用B-tree 或B+tree实现索引,而是使用bitset和skip list来处理联合索引,特别适合多字段的复杂查询条件。

下面重点看下kafka connector和Elasticsearch如何使用

kafka connector

kafka connector有Source和Sink两种组件,Source的作用是读取数据到kafka,这里用开源实现debezium来采集mysql的binlog和postgres的xlog。Sink的作用是从kafka读数据写到目标系统,这里自己研发一套组件,根据配置的规则将数据格式化再同步到ES。
kafka connector有以下优点:

  • 提供大量开箱即用的插件,比如我们直接用debezium就能解决读取mysql和pg数据变更的问题
  • 伸缩性强,对于不同的connector可以配置不同数量的task,分配给不同的worker,,我们可以根据不同topic的流量大小来调节配置。
  • 容错性强,worker失败会把task迁移到其它worker上面
  • 使用rest接口进行配置,我们可以对其进行包装很方便地实现一套管理界面

Elasticsearch

对于状态数据,由于状态的写操作相对较少,我们采取嵌套文档的方式,将同个用户的相关实体数据都同步写入到同个文档,具体实现用painless脚本做局部更新操作。效果类似这样:

{
   "id":123,
   "age":30,
   "credit_line":20000,
   "education":"bachelor",
   ...
   "last_loan_applications":{
         "loan_id":1234,
         "status":"reject",
          ...
    }
  ...
}

事件数据写入比较频繁,数据量比较多,我们使用父子文档的方式做关联,效果类似这样:

{
  "e_uid":123,
  "e_name":"loan_application",
  "e_timestamp":"2019-01-01 10:10:00"
  ...
}

(e_前缀是为了防止同个index下同名字段冲突)
ES这样存储一方面是方便做统计报表,另一方面跟用户筛选和触达有关。

规则引擎

在设计规则引擎前,我们对业界已有的规则引擎,主要包括Esper, Drools, Flink CEP,进行了初步调研。

Esper

Esper设计目标为CEP的轻量级解决方案,可以方便的嵌入服务中,提供CEP功能。
优势:

  • 轻量级可嵌入开发,常用的CEP功能简单好用。
  • EPL语法与SQL类似,学习成本较低。

劣势:

  • 单机全内存方案,需要整合其他分布式和存储。
  • 以内存实现时间窗功能,无法支持较长跨度的时间窗。
  • 无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。

Drools Fusion

Drools开始于规则引擎,后引入Drools Fusion模块提供CEP的功能。
优势:

  • 功能较为完善,具有如系统监控、操作平台等功能。
  • 规则支持动态更新

劣势:

  • 以内存实现时间窗功能,无法支持较长跨度的时间窗。
  • 无法有效支持定时触达(如用户在浏览发生一段时间后触达条件判断)。

Flink CEP

Flink 是一个流式系统,具有高吞吐低延迟的特点,Flink CEP是一套极具通用性、易于使用的实时流式事件处理方案。
优势:

  • 继承了Flink高吞吐的特点
  • 事件支持存储到外部,可以支持较长跨度的时间窗。
  • 可以支持定时触达(用followedBy+PartternTimeoutFunction实现)

劣势:

  • 无法动态更新规则(痛点)

自定义规则

综上对比了几大开源规则引擎,发现都无法满足业务特点:

  • 业务方要求支持长时间窗口(n天甚至n个月,比如放款一个月后如果没产生还款事件就要发消息)
  • 动态更新规则,而且要可视化(无论用哪个规则引擎都需要包装,需要考虑二次开发成本)
  • 除了匹配事件,还需要匹配用户状态

最终我们选择自己根据业务需要,开发基于json的自定义规则,规则类似下面例子:

{
  "batchId": "xxxxxxxx", //流水号,创建每条运营规则时生成
  "type": "trigger", //usual
  "triggerEvent": "login",
  "after": "2h", //分钟m,小时h,天d,月M
  "pushRules": [//支持同时推送多条不同类型的消息
    {
      "pushType": "sms", //wx,app,coupon
      "channel": "cl",
      "content": "hello #{userInfo.name}"
    },
    {
      "pushType": "coupon",
      "couponId": 1234
    }
  ],
  "statusConditions": [
    {
      "name": "and", //逻辑条件,支持与(and)或(or)非(not)
      "conditions": [
        {
          "name": "range",
          "field": "credit_line",
          "left": 2000,
          "right": 10000,
          "includeLeft": true,
          "includeRight": false
        },
        {
          "name":"in",
          "filed":"education",
          "values":["bachelor","master"]
        }
      ]
    }
  ],
  "eventConditions": [
    {
      "name": "or",//逻辑条件,支持与(and)或(or)非(not)
      "conditions": [
        {
          "name": "event",
          "function": "count", //聚合函数,目前只支持count
          "eventName": "xxx_button_click",
          "range": { //聚合结果做判断
            "left": 1,
            "includeLeft": true
          },
          "timeWindow": {
            "type": "fixed", //fixed为固定窗口,sliding为滑动窗口
            "start": "2019-01-01 01:01:01",
            "end": "2019-02-01 01:01:01"
          },
          "conditions": [ //event查询条件继承and逻辑条件,所以事件也可以过滤字段
            {
              "name": "equals",
              "field": "f1",
              "value": "v1"
            }
          ]
        }
      ]
    }
  ]
}

使用面向对象思维对过滤条件做抽象后,过滤条件继承关系如下:


过滤条件继承关系.png

然后代码里加一层parser把Condition都转成ES查询语句,实现轻量级的业务规则配置功能。

整体技术方案

整体技术方案

系统组成模块及功能如下:
mysql binlog:mysql的数据变更,由kafka connector插件读取到kafka,数据源之一
postgres xlog:pg的数据变更,由kafka connector插件读取到kafka,数据源之一
report server:事件上报服务,数据源之一
tags:用户画像系统计算出来的标签,数据源之一
触发场景路由:分实时触发和延迟触发,实时触发直接到下一步,延迟触发基于 redis的延迟队列实现
用户筛选处理器:将筛选规则翻译为ES查询语句到ES查询用户数据,可以是批量的和单个用户的
幂等处理器:对数据做幂等处理,防止重复消费
变量渲染处理器:对推送内容做处理
推送适配器:兼容不同的推送方式
BloomFilter记录器:将推送用户和流水号记录到redis,用于幂等处理
推送事件记录器:将推送事件推入kafka
定时任务模块:基于elastic-job,处理定时推送任务
规则配置控制台:提供可视化配置界面(运营规则配置、数据采集规则配置、字段元数据配置等)
报表服务:提供报表查询功能
运营位服务:提供外部接口,根据条件匹配运营位(如启动图、首页banner图片等)

总结与展望

  • 系统基本满足了目前的业务需求,对转化率等运营指标提升显著
  • 可以扩展其它业务,如推荐、风控、业务监控等
  • 规则定时拉取,实时性差,可以用zk做发布订阅实现即时更新
  • 目前事件的聚合函数只支持count,能满足业务需求但是未来可能还需要支持其它函数
  • 系统只经过千万级用户,日千万级事件数据的生产验证,再高数量级的话可能还有很多性能优化的工作,如ES并行查询(目前用scroll api批量拉取用户数据是串行的)
  • 事件类数据越来越多,目前采取定时删除半年前数据的方式,防止持续增长过快不可控,所以事件类条件不可超过半年的时间窗口
  • 虽然系统对业务无入侵,但是反过来看本系统依赖于上游数据,上游数据发生变化时如何做到影响最小?

未来会继续从技术及业务两方面入手,将系统建设的更加易用、高效。

欢迎您扫一扫上面的二维码关注个人微信公众号
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容