客戶標簽系統的設計


 


客戶標簽系統架構設計

一、項目背景

    為了幫助某個公司精准營銷,通過大數據分析,對客戶畫像,對產品畫像,輸出客戶標簽,產品標簽。當某位客戶進入系統時,根據客戶的標簽及產品的標簽實時匹配規則,向客戶推薦合適的產品。這里要解決的是:如何設計一個合適的應用系統及存儲方案,可以滿足上述標簽的存儲與更新、規則的制定與運算,從而實現實時的向客戶推薦產品。

    營銷團隊的營銷經理期望可以自由的制定營銷規則。而這些規則涉及到客戶的信息,客戶的標簽,產品的標簽。營銷經理已經習慣了導入導出白名單(由於某些信息系統不完備而導致的手工操作)。公司現在的存量客戶是1000萬,業務團隊規划三年后客戶數量能達到5000萬。在售的產品數量不會超過100款,更新頻率低。

    某位客戶的標簽可能是這樣的:性別:男;年齡:青年; 職業:IT; 收入:白領。而某個理財產品的標簽可能是這樣的:產品風險:中; 准入門檻:低; 起購金額:1000元。這種形式的標簽種類(即key的數量)現在生效的數量是150個,業務團隊規划三年后標簽的有效種類可能達到800個。

    業務團隊所說的標簽,並沒有一個來自於業務團隊的明確定義,有時候標簽是像上面所說的,產品風險:中; 准入門檻:低。有時候則是指類似於客戶的月收入21300元這樣的一些連續的數值,在公司的某個系統M里甚至是將住址、登陸次數這樣的信息也在數據庫表里設計成標簽。這個系統M的標簽表是傳統的設計,每一個標簽就是一列,所以系統M的客戶標簽表已經成了一張超大的表,目前已有120個字段左右,成一個稀疏矩陣,行數與列數都非常多,性能已嚴重跟不上需要,功能擴展也無法滿足業務團隊的需要。每當定義一個新的標簽時,就需要變更數據庫,在這張表上增加一個字段。所以需要一些新的思路重新設計客戶標簽系統。

二、初步分析

    對業務場景與標簽數據進行初步分析后,一個很關鍵的問題是,應當如何理解業務團隊所說的“標簽”呢?

1、一個新的思路是只將離散型的值才認為是標簽,標簽的值是離散型的可以窮舉的。例如“年收入”可以打成標簽值30-50萬,但不能是具體的“31萬”。客戶的標簽應當區別於客戶的屬性,是經過分析后定性的不是定量的。即每一種類的標簽有一個標簽名+若干個可窮舉的值,值的類型是不確定的,但都可以認為是離散型數據。 而客戶的屬性可以是連續型的數值或字符串,例如住址,月收入。

2、當前存在大量的標簽是布爾類型值的;例如某個客戶標簽:“是否黃金類理財產品的老客戶:是”。 多數標簽的枚舉值在15個以內。 

3、負責營銷的業務團隊制定一些營銷規則時,有些規則不僅僅涉及客戶的標簽,還會涉及客戶一些連續數值的屬性,例如:資產金額,年齡,登陸次數。甚至可能需要對如家庭地址、電話號碼這樣的信息進行字符串處理。

4、營銷過程中有時會導入白名單,一個白名單可能最多會有10萬個客戶,不同的白名單有不同的用途。系統需要保存這些白名單以供規則運算。例如某款產品允許/不允許這個白名單內的人購買。

5、一個客戶可能會有某些標簽,但不一定會有。每個客戶有哪些標簽是不確定的,並且某個客戶的標簽數量可能很多,可能很少。通常為30+個至100+個。

三、分析用例場景

(一)主要用例場景:

1、客戶在訪問產品列表時,系統需要根據客戶的標簽及產品的標簽進行實時規則運算。

2、營銷過程中有時需要導入或者導出白名單,或者導出符合一定規則的客戶名單。

3、需要有一個后台管理界面,提供給運營人員配置或者修改規則。

4、標簽的種類過多,為了運營人員可以有效的利用標簽設置規則,需要對標簽進行分類管理。

5、標簽是根據歷史數據(T-1天),或當前事件(T+0天)進行計算的。大數據平台可以綜合分析歷史數據,計算客戶的標簽。但由於客戶的數量比較龐大,計算出來的標簽數量更是龐大。大數據平台不能很好的實時計算,而有些精准營銷的規則要求比較實時,即客戶最近的事件能及時參與到規則運算。當前事件(T+0天)要求在分鍾級內計算與存儲,以供實時訪問。

(二)轉化為初步需求(User Story)

1、根據客戶ID,查找客戶的所有標簽。實現在標簽系統,涉及Redis、MySQL。

2、根據客戶ID、標簽名,確認是否有某個標簽或得到其標簽值。實現在標簽系統,涉及Redis、MySQL。

3、找到某個白名單的所有客戶,並且可支持導出白名單。實現在BOSS(Back Office Support System)系統,涉及MySQL。

4、根據標簽名、標簽值,找到所有符合的客戶。 實現在BOSS系統,涉及MySQL。

5、找出某個標簽的所有枚舉值。實現在BOSS系統,涉及MySQL。

6、歷史數據清理,應該有物理刪除及邏輯刪除。標簽肯定需要刪除標志。實現在BOSS系統,涉及MySQL,Redis同步。客戶的過期無效標簽可以物理刪除。停用白名單時可使用標簽名的validity,customer_tag表不變,但相應的查詢語句得考慮。移除白名單,物理刪除關聯標簽名的客戶標簽。

7、設置白名單失效。實現在BOSS系統,涉及MySQL,Redis同步。

8、導出符合一定規則的客戶名單。實現在BOSS系統,涉及MySQL,HBase;實際上是集合運算。

9、結合客戶各種數據計算客戶標簽。實現在大數據平台,HBase+MapReduce。

10、由Kafka某個事件觸發的客戶標簽計算及更新。 實現在標簽系統,涉及MySQL,Redis。

 

三、基於MySQL數據庫的設計

1、標簽表 tag_key

create  table  tag_key (

tag_key_id        int unsigned not null, // tag_key_id設計成short int,2個字節。

tag_key             varchar(35) not null,

data_type  TINYINT  NOT NULL//布爾型,字符串,數字區間;數字區間通過[   ,   )表示;

category  TINYINT NOT NULL,    //管理大類,//白名單,關於管理大類還需要另外設計一張小表。

validity           TINYINT NOT NULL,    //0表示無效,1表示有效;

description     varchar(50) ,    //描述

display_name    varchar(50),//顯示在界面上的名字

created_date TIMESTAMP NOT NULL ,

creator VARCHAR(30),

updated_date TIMESTAMP NOT NULL,

updator VARCHAR(30),

primary_key(tag_name_id)

)

 

    估算此表的最大行數為120行-500行,更新頻度較低。可以直接緩存於應用系統。將白名單也設計成一類特殊的標簽,可能只有一個特殊的標簽值:“是”,這可以統一模型,簡化設計。data_type數據類型的說明,有助於Java應用系統構建對象並且檢查正確性,但實際在tag_value表都是以字符串形式存儲,有利於擴展。相當於將數據的完整性約束從數據庫轉移到Java實現。

2、標簽枚舉值表

 1 create  table  tag_value (
 2 
 3     tag_value_id  int  unsigned not null,
 4 
 5     tag_value varchar(100) not null,
 6 
 7     tag_key_id  int unsigned not null,
 8 
 9     primary_key(tag_value_id)
10 
11 )

    估算最大行數:200key x 每個key對應25個value = 5000行。增加tag_key_id索引。

 

3、客戶標簽表

 1 create table customer_tag(
 2 
 3 customer_id int  not null ,
 4 
 5 tag_key_id int not null ,
 6 
 7 tag_value_id int not null ,
 8 
 9 tag_time TIMESTAMP NOT NULL,
10 
11 created_date TIMESTAMP NOT NULL ,
12 
13 creator VARCHAR(30),
14 
15 updated_date TIMESTAMP NOT NULL,
16 
17 updator VARCHAR(30),//不同的處理程序必須使用不同的身份update記錄,通過不同的creator或者updator表示批處理作業程序、流處理作業程序、運營人員; 
18 
19 primary_key(customer_id,tag_key_id)
20 
21

 

 

    估算最大行數:客戶數*(標簽數/客戶)=1000萬*100=10億; 

4、客戶的屬性信息

客戶的屬性是比較確定的,確定的屬性有確定的值類型,不同的屬性有不同的值類型,屬性不會經常增加或減少,屬性可以通過系統間同步獲得,也可以能過暴露出來的服務獲得,但通常不能由運營人員直接導入。標簽系統不是會員系統。必要時可以進行表關聯操作。

5、找出某個標簽的所有枚舉值

select tag_value.tag_value_id tag_value_id, tag_value.value tag_value_string  

from tag_value

INNER JOIN tag_key on tag_value.tag_key_id = tag_key.tag_key_id

WHERE tag_name.name=%name

 

四、基於Redis的緩存設計

(實際值得緩存的表只有兩張)

1、緩存客戶標簽表

1.1 數據結構:使用Redis的散列

1.2 示例:conn.hmset(customer_id,{k1:v1,k2:v2})

customer_id k1 v1 k2 v2,全部是整數

1.3 解釋:其實在進行規則匹配時,用不着標簽的字符串。但可能用得着客戶屬性表的字符串或金額。所以Redis散列結構中不需要存放過多的字符串及其它的附屬信息,可以直接使用,與數據庫表結構保持一致,也可以在必要時更具效率。

1.4 占用空間評估:customer_id 4字節,tag_name_id 2字節,tag_value_id 4字節,一條MySQL記錄大概10B,

假設全量緩存:1000萬*100 *10字節 =  K 萬 *KB =萬 MB = 10GB ,加上額外的存儲空間,即20GB以內。需要3個節點集群*8GB/節點。

假設只用Redis服務器簡單的主從部署結構,單個Redis節點最多可緩存一半的客戶標簽數據。足以應對活躍客戶的查詢請求。

1.5 如何保持同步更新:

    確保MySQL數據庫只有一個應用入口,即插入與更新操作,只有同一個Application可以操作。在這個Application進行刪改操作時檢查Redis同步刪改。而在insert時不需理會Redis,在查時沒有命中Redis緩存則從MySQL緩存至Redis,設置Reids緩存有效期為一個月(視Redis大小而定)。

2、緩存tag_name表

    表的數據量很少,可以直接緩存在應用系統內存(評估最多為50KB),不需要緩存在Redis; Java應用多個實例無法共享這個緩存,需要有一個監聽變化的機制。可以通過注冊中心或配置中心實現。

3、緩存tag_value表

    采用Redis數據結構:string,以tag.<tag_value_id> 為key ,以標簽值對象序列化后的json字符串或字節數組為值。這種方式可以輕松獲得一個完整的標簽值對象。雖然在規則匹配時用不着,但在配置管理等后台界面中特別適用。

    如何更新緩存:保持一個Application入口,改刪同步,啟動時全量緩存,定期輪詢糾錯。

4、白名單是否適合存放於Redis?

    不適合,假設一個白名單有10萬客戶,一個白名單一個key,那對應的集合有10萬*1KB,即100MB。這個對象太大了,傳輸時網絡IO容易成為性能瓶頸,而且使用頻次很低。

5、如何保持java緩存、Redis數據、MySQL數據的及時更新一致呢?

    先訪問緩存,如果不命中,則訪問數據庫,再更新緩存。先更新數據庫,再移除緩存。

五、基於Hbase數據庫的設計

1、表設計customer_tag

【三個列族】tag,p,ext;

tag:表示客戶的標簽;

p:客戶的基本屬性;客戶的基本屬性包括:姓名、性別、手機號、郵箱、年齡、信用評分 等。

ext:客戶的擴展信息; 客戶的擴展信息包括:總資產、登陸次數等; 

行鍵:customer_id

對於tag列族,有列限定符:tag_key_id,對應的列值則為:tag_value_id。

六、基於標簽的匹配規則

規則組: 表達式1 AND 表達式2  OR  表達式3 AND 表達式4

默認AND的運算優先級更高,相當於 (表達式1 AND 表達式2 ) OR ( 表達式3 AND 表達式4)

NOT ?

表達式1:%tag_name = %tag_value  ,例如:age = 20-30 

表達式2:線下渠道客戶 !=某某公司

表達式3:新手客戶 = 是; 表達式4:資產級別 = 白領級別。

支持EQ 或 NE,等值比較運算符;AND、 OR ,邏輯運算符。用負數表示,-1,-2,-3,-4。

    一個規則在UI上顯示為(表達式1 AND 表達式2 ) OR ( 表達式3 AND 表達式4)形式,存儲於數據庫及應用系統執行規則時,則是語法樹形式,某個規則可能是:-4 -3  -1 873 945 -2 303 934 -3 -1 833 974 -1 893 865  。即:運算符在前,兩個值在后。由於標簽的值都是離散型可窮舉的值,所以只能進行等值比較,不能進行大於或者小於這樣的范圍比較。

1、設計rule表

create table rule{

rule_id  int    primary key,

rule_name varchar(30), // 用於BOSS系統被運營人員識別的規則名字。

expression     varbinary(100),  //序列化字節數組表達的規則模板。

valid_from  datetime, //規則的有效起始日期

valid_to datetime, //規則的失效結束日期

created_date TIMESTAMP NOT NULL ,

creator VARCHAR(30),

updated_date TIMESTAMP NOT NULL,

updator VARCHAR(30),

}

 

    提供給營銷人員自由配置的規則是基於UI簡單拖拽而成的,不能支持非常復雜的規則。對於特別復雜的規則通過內置SPI實現,不僅僅可以利用標簽庫內的標簽 ,還可以利用會員系統的客戶表信息,甚至其它更復雜的數據計算、配置管理。

七、Java設計:

    在應用系統中通過設計一個解釋器(設計模式)專門用於匹配規則。這些規則可以構成一個解釋器鏈。從數據庫中一次SQL取出某個客戶的所有標簽及其值,然后ORM為一組標簽對象,放到解釋器鏈中進行匹配。

class Expression( Operator,  Expression1,Expression2)

Enum Operator{EQ,NE,LE,GE,LT,GT,AND,OR,NOT}  //運算符的枚舉值

class TagOperator(Operator,tagNameId,tagValueId) extends Expression

    客戶屬性的表,例如年齡、資產、登陸次數。客戶屬性不等於客戶標簽。客戶標簽有可能是客戶屬性。客戶的屬性可以參與規則運算嗎?可以通過API接口與SPI算法實現。當涉及客戶屬性時,不能簡單的通過BOSS UI配置規則,而是需要開發人員開發相應的SPI實現規則。BOSS UI可以指定某些營銷場景使用特定的SPI規則而不僅僅是標簽規則。

    如何進行API與SPI的設計呢?

    整個標簽(客戶畫像、產品畫像)的應用過程(規則運算),會涉及到3個實體:客戶、產品、營銷活動。上面的表達式、規則也都是通過BOSS UI設置綁定到這3個實體之一的,當客戶訪問系統某些功能或頁面時觸發計算。舉個例子,公司推出了某個營銷活動,主要是對某款理財產品補貼1%的收益用於拉新獲客,要求滿足下面要求的客戶才可以參與這個活動:(1)來源於某個互聯網平台(假設公司K)的客戶,(2)不曾購買過同類產品的客戶,(3)根據公司K的客戶授權信息評估月收入達到白領標准。這個例子只是用於方便大家理解,不討論其運營規則設置的合理性。客戶授權信息是不能預先存儲與分析打標簽的,每個合作公司所能給出的評估信息卻又各有不同,又要求實時規則運算,這就要求SPI的特殊定制的存在。

    場景分析:客戶訪問系統時,可知客戶的基本信息。要向客戶推薦產品就要將所有在售產品過濾,找到合適的產品再排個優先級,將top1/2/3推薦出去。一個營銷活動可能涉及多款產品,而一款產品也可能參與到多個營銷活動。所以API可以簡單設計成

Rule.execute(Customer cust, Prodcut ... prods);

    SPI(Service Provider Interface)是JDK提供的一個機制,以Jar的形式存在,當Application在classpath中找到這個Jar,能加載相應的算法。SPI可以做得非常靈活,可以復用已有的標簽,可以運算各種非標簽信息,可以做成這個標簽系統的強大的擴展機制。這些SPI規則雖然不同於BOSS UI的標簽規則,卻可以在BOSS UI中同等的被營銷人員綁定到產品與營銷活動中。

    將這樣的SPI規則設計成微服務(RPC或Restful API)是否可以呢?單從技術上來說是可以的。但這樣的SPI不一定是完整的服務,更重要的是性能。產品可能有100個,運算一個SPI規則可能只需要3ms,而一次最簡單的RPC網絡IO耗時至少10ms,SPI本地Jar服務一個客戶只需300ms,而設計成RPC則需1.5秒。這還沒有計算可能的數據傳輸與數據訪問的開銷,實際差異應該更明顯。

八、小結

    這個設計方案相對於舊系統而言,最關鍵的在於對標簽的重新理解,將標簽的概念區別於客戶的屬性。將標簽的值理解為可窮舉的離散值,這樣就可以用一個整數ID指代一個標簽,參與到整個系統的存儲與運算過程。字符串的存儲及運算消耗的資源要比整數的存儲及運算消耗的資源要多很多。將tag_key與tag_value設計成維度表的方式,所有涉及字符串的存儲及運算在這里作為訪問入口一次完成,使得客戶標簽這樣的大表避開了占據大量存儲資源及運算資源的字符串處理。為什么上面多次強調是可窮舉的離散值呢?例如某客戶的具體月收入,可能是2135.50元,可能是35605元,可能是58000元,它是連續的有兩位小數的精確到金額“分”的值,如果存進tag_value表每個分值一行,就意味着可能需要數千萬行,這是不現實的。另一方面,營銷的規則確實需要考慮月收入,它是一個很重要的維度,這就要求系統設計要給出替代的方案,通過大數據分析的聚類算法或分類算法將連續的值轉變為離散的值,例如月收入,可以經聚類分析確認(假設)3000元-30000元是“白領”,3萬元-10萬元是“金領”,那月收入就可以用標簽“白領”、“金領”替代,而不用出現具體的金額。而這又會引入另外的2個問題。一是如何實時計算更新標簽,這是設計中有Spark流計算的原因。二是可能有些規則還是需要一些更復雜的可能會涉及原始屬性的計算,要說服運營人員放棄這些規則是不可能的,要設計出一個傻瓜式的用於設置這樣規則的BOSS UI 也是非常困難的,這就需要引入SPI的設計。

    關鍵的關鍵,理解業務,分析需求,拋開業務分析談所謂的架構設計都是耍流氓。

更多原創博客:https://www.jianshu.com/u/b659b577b0f6

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM