A/B測(cè)試系統(tǒng)設(shè)計(jì)
1.什么是A/B測(cè)試
A/B 測(cè)試,簡(jiǎn)單來說,就是為同一個(gè)目標(biāo)制定兩個(gè)方案,讓一部分用戶使用 A 方案,另一部分用戶使用 B 方案,記錄下用戶的使用情況,看哪個(gè)方案的效果更好,以便全面推廣。
A/B 測(cè)試在有的公司又稱為小流量測(cè)試或者灰度發(fā)布,原因:
一 是為了統(tǒng)計(jì)新功能的效果;
二 是為了在全流量上線前修復(fù)可能出現(xiàn)的BUG。
雖然在業(yè)務(wù)上的含義有所差異,但是在系統(tǒng)設(shè)計(jì)上卻是完全相通的。
2.A/B測(cè)試系統(tǒng)的模塊
A/B測(cè)試有三個(gè)主要的功能模塊:
一是用戶分類
二是效果統(tǒng)計(jì)
三是流量分配
3.用戶分類模塊設(shè)計(jì)
用戶分類是A/B測(cè)試的核心。
為了滿足不同的業(yè)務(wù)場(chǎng)景,這里簡(jiǎn)述兩種分類方案:
全用戶抽樣
這種分類方案的使用場(chǎng)景是新功能是針對(duì)全站的。而操作方法也很簡(jiǎn)單:(userId % X) < n。其中X是抽樣的比例,例如按照1%抽樣,那么X就等于100。而n是抽樣的份數(shù),這里有一個(gè)流量從小到大的過程,也就是n從1到X的過程。
按用戶特質(zhì)分類
這種分類的使用場(chǎng)景是新功能是針對(duì)某種特定用戶的。比如我們針對(duì)大學(xué)生人群設(shè)計(jì)了一個(gè)新功能,如果采用全用戶抽樣,那么不是我們的特定用戶的統(tǒng)計(jì)效果就會(huì)沖淡新功能的真實(shí)效果,導(dǎo)致統(tǒng)計(jì)結(jié)果趨于平淡,無法體現(xiàn)真實(shí)效果。這時(shí)候我們就需要針對(duì)特質(zhì)人群來做抽樣。
不同的分類方法模塊設(shè)計(jì)也會(huì)不同,這里提出一種比較通用的用戶分類模塊設(shè)計(jì)方案:用戶標(biāo)簽(tag)系統(tǒng)。
我們把用戶的分類用tag來表示,屬于某個(gè)分類的用戶,就給他打上一個(gè)對(duì)應(yīng)的tag。怎么找到某個(gè)分類的用戶需要具體情況具體分析,這里就不深入了。我們需要建立一個(gè)用戶tag存儲(chǔ)模塊,這個(gè)模塊的功能非常簡(jiǎn)單,就是存取一個(gè)用戶的tag。這個(gè)模塊可以僅僅只是一個(gè)Redis服務(wù)器,也可以是一個(gè)數(shù)據(jù)庫,還可以是一個(gè)RESTful Service。根據(jù)業(yè)務(wù)需要進(jìn)行選擇。
有了用戶tag存儲(chǔ)模塊后,我們把我們需要抽樣的用戶打上一個(gè)tag,比如大學(xué)生。有時(shí)候?yàn)榱烁_的統(tǒng)計(jì)效果,我們?cè)诮y(tǒng)計(jì)的時(shí)候不是小流量和全流量對(duì)比,而是兩個(gè)對(duì)照組對(duì)比,那么兩個(gè)對(duì)照組都需要打上tag,比如大學(xué)生A,大學(xué)生B。有時(shí)候我們需要一個(gè)流量從小到大的過程,那么我們就把目標(biāo)人群分成n份,打上不同的tag,比如大學(xué)生1…大學(xué)生n。這樣在測(cè)試過程中,我們可以不斷增加測(cè)試流量。
4.效果統(tǒng)計(jì)簡(jiǎn)述
有了tag系統(tǒng)后,效果統(tǒng)計(jì)模塊就很好設(shè)計(jì)了。我們?cè)诔鰣?bào)表的時(shí)候,根據(jù)tag的不同,把用戶對(duì)應(yīng)的數(shù)據(jù)統(tǒng)計(jì)到不同的報(bào)表里,自然就可以出兩組對(duì)比報(bào)表。至于具體需要統(tǒng)計(jì)哪些維度的哪些指標(biāo),這個(gè)就根據(jù)業(yè)務(wù)的需要來選擇,此處暫且不表。
5.流量分配模塊設(shè)計(jì)
流量分配模塊針對(duì)不同的業(yè)務(wù)需要也有很多種設(shè)計(jì)方案,這里簡(jiǎn)單提幾種:
從nginx分流
這種設(shè)計(jì)方案最簡(jiǎn)單。首先把不同的代碼部署到兩組服務(wù)器上面,各自獨(dú)立,然后在nginx上面加載一個(gè)模塊,根據(jù)用戶的tag和流量分配規(guī)則,把流量轉(zhuǎn)發(fā)到不同的服務(wù)器。這種方案的缺點(diǎn)很明顯,對(duì)于小流量,如果上一臺(tái)服務(wù)器就存在單點(diǎn)問題,上兩臺(tái)服務(wù)器又存在資源浪費(fèi)問題,在兩組服務(wù)器上面的壓力會(huì)不均勻。以服務(wù)器作為最小分配單元粒度顯得太大了。
通過配置文件或者硬編碼的形式分配流量
這種方案由于對(duì)代碼不透明,所以開發(fā)人員需要完全了解流量分配規(guī)則、完全跟進(jìn)流量從小大到的變化等過程,而且要開發(fā)很多相關(guān)的代碼,在流量完全上線后又要?jiǎng)h除這些代碼,造成人力資源浪費(fèi)。
通過獨(dú)立的模塊進(jìn)行流量分配控制,在業(yè)務(wù)代碼里面通過注解的方式進(jìn)行流量分配。
這種方案是我們推薦的設(shè)計(jì)方案,接下來會(huì)詳細(xì)介紹這種設(shè)計(jì)。
在現(xiàn)代軟件開發(fā)中,為了避免系統(tǒng)存在單點(diǎn)故障,各種軟件都是作為分布式多機(jī)部署的。在多機(jī)部署的情況下,流量分配模塊的數(shù)據(jù)同步問題就必須考慮。我們推薦采用zookeeper進(jìn)行基本數(shù)據(jù)存儲(chǔ),而像用戶標(biāo)簽則存儲(chǔ)在另外的獨(dú)立系統(tǒng)中。利用zookeeper的通知機(jī)制,我們可以動(dòng)態(tài)的改變流量分配策略。
SDK依賴這兩個(gè)存儲(chǔ)系統(tǒng)來進(jìn)行流量分配。Zookeeper里面存儲(chǔ)采用何種策略來分配流量,而用戶標(biāo)簽系統(tǒng)只存儲(chǔ)用戶的標(biāo)簽。業(yè)務(wù)系統(tǒng)集成SDK只需要在配置里面初始化SDK,然后在需要流量分配的方法上面寫上注解就可以:
1)初始化SDK
2)添加注解
我們目前支持兩種注解級(jí)別:
一是類級(jí)別,也就是整個(gè)類里面的所有方法的調(diào)用都涉及到流量分配;
二是方法級(jí)別,就是只有這個(gè)方法的調(diào)用涉及到流量分配。
這里詳細(xì)說一下方法級(jí)別的注解如何使用。
首先創(chuàng)建一個(gè)interface,當(dāng)然也可以使用已有的interface:
然后針對(duì)兩種實(shí)現(xiàn)創(chuàng)建這個(gè)interface的兩個(gè)實(shí)現(xiàn)類,***個(gè)是默認(rèn)的實(shí)現(xiàn)類:
默認(rèn)的實(shí)現(xiàn)類上面需要添加@Primary注解,表示這個(gè)是默認(rèn)實(shí)現(xiàn)類。
在需要流量控制的方法上,添加@Switch注解,表示一個(gè)動(dòng)態(tài)開關(guān)。featureUID表示這個(gè)開關(guān)的具體策略,alterBean表示開關(guān)打開之后需要調(diào)用這個(gè)bean上面的相應(yīng)的方法。
接下來是我們第二個(gè)實(shí)現(xiàn)類:
***是調(diào)用這個(gè)方法的時(shí)候怎么傳入環(huán)境參數(shù):
到這里SDK的集成就全部完成了。根據(jù)具體的策略和用戶的tag,我們會(huì)選擇正確的方法去執(zhí)行。