從零搭建精準運營系統

2018剛過去,趁着春節放假對過去一年主導開發的項目作個梳理和總結html

項目背景

平臺運營到必定階段,必定會累積大批量的用戶數據,這些用戶數據是運營人員的黃金財產。而如何利用用戶的數據來作運營(消息推送、觸達消息、優惠券發送、廣告位等),正是精準運營系統須要解決的問題。本文是基於信貸業務實踐後寫出來的,其它行業如保險、電商、航旅、遊戲等也能夠參考。前端

業務場景

先看幾個具備表明性的需求mysql

用戶可用額度在20000~50000元,並且有借款記錄,未還本金爲0,性別爲「男」
用戶發生了A行爲且未還本金大於5000
用戶在1天內發生A行爲次數大於等於3次
用戶在A行爲前24小時內未發生B行爲
用戶在A行爲後一個月內未發生B行爲

業務上有兩種消息類型sql

  • 平常消息:由業務人員經過條件篩選鎖定用戶羣,定時或即時給批量用戶發送消息或者優惠券
  • 觸達消息:主要由用戶自身的行爲觸發,好比登錄、進件申請、還款等,知足必定篩選條件實時給用戶發送消息或優惠券

對於用戶篩選條件,也主要有兩種類型數據庫

  • 用戶狀態:包括用戶自身屬性如性別、年齡、學歷、收入等,還有用戶相關聯實體如進件訂單、帳戶信息、還款計劃、優惠券等的屬性,以及用戶畫像數據如行爲偏好、進件機率等
  • 用戶行爲:即用戶的動做,包括登錄、進件申請、還款,甚至前端點擊某個按鈕、在某個文本框輸入都算

早期方案

早期方案.png
早期方案存在如下痛點apache

  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如何使用json

kafka connector

kafka connector有Source和Sink兩種組件,Source的做用是讀取數據到kafka,這裏用開源實現debezium來採集mysql的binlog和postgres的xlog。Sink的做用是從kafka讀數據寫到目標系統,這裏本身研發一套組件,根據配置的規則將數據格式化再同步到ES。
kafka connector有如下優勢:api

  • 提供大量開箱即用的插件,好比咱們直接用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

Drools開始於規則引擎,後引入Drools Fusion模塊提供CEP的功能。
優點:

  • 功能較爲完善,具備如系統監控、操做平臺等功能。
  • 規則支持動態更新

劣勢:

  • 之內存實現時間窗功能,沒法支持較長跨度的時間窗。
  • 沒法有效支持定時觸達(如用戶在瀏覽發生一段時間後觸達條件判斷)。

Flink

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查詢語句,實現輕量級的業務規則配置功能。

總體技術方案

總體技術方案.png

系統組成模塊及功能以下:
mysql binlog:mysql的數據變動,由kafka connector插件讀取到kafka,數據源之一
postgres xlog:pg的數據變動,由kafka connector插件讀取到kafka,數據源之一
report server:事件上報服務,數據源之一
tags:用戶畫像系統計算出來的標籤,數據源之一
觸發場景路由:分實時觸發和延遲觸發,實時觸發直接到下一步,延遲觸發基於 rabbitmq的延遲隊列實現
用戶篩選模塊:將篩選規則翻譯爲ES查詢語句到ES查詢用戶數據,能夠是批量的和單個用戶的
變量渲染模塊:對推送內容作處理
推送適配器:兼容不一樣的推送方式
定時任務調度器:基於elastic-job,處理定時推送任務
規則配置控制檯:提供可視化配置界面(運營規則配置、數據採集規則配置、字段元數據配置等)
報表服務:提供報表查詢功能
運營位服務:提供外部接口,根據條件匹配運營位(如啓動圖、首頁banner圖片等)

總結與展望

  • 系統基本知足了目前的業務需求,對轉化率等運營指標提高顯著
  • 能夠擴展其它業務,如推薦、風控、業務監控等
  • 規則定時拉取,實時性差,能夠用zk作發佈訂閱實現即時更新
  • 目前事件的聚合函數只支持count,能知足業務需求可是將來可能還須要支持其它函數
  • 系統只通過千萬級用戶的生產驗證,再高數量級的話可能還有不少性能優化的工做,如ES並行查詢(目前用scroll api批量拉取用戶數據是串行的)
  • 事件類數據愈來愈多,目前採起定時刪除半年前數據的方式,防止持續增加過快不可控,因此事件類條件不可超過半年的時間窗口
  • 雖然系統對業務無入侵,可是反過來看本系統依賴於上游數據,上游數據發生變化時如何作到影響最小?

將來會繼續從技術及業務兩方面入手,將系統建設的更加易用、高效。

歡迎您掃一掃上面的二維碼關注我的微信公衆號

相關文章
相關標籤/搜索