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