產品的改變不是由咱們隨便「拍腦殼」得出,而是須要由實際的數據驅動,讓用戶的反饋來指導咱們如何更好地改善服務。正如馬蜂窩 CEO 陳罡在接受專訪時所說:「有些東西是須要 Sense,但大部分東西是能夠用 Science 來作判斷的。」web
說到 ABTest 相信不少讀者都不陌生。簡單來講,ABTest 就是將用戶分紅不一樣的組,同時在線試驗產品的不一樣版本,經過用戶反饋的真實數據來找出採用哪個版本方案更好的過程。redis
咱們將原始版本做爲對照組,以每一個版本進行儘可能是小的流量迭代做爲原則去使用 ABTest。一旦指標分析完成,用戶反饋數據表現最佳的版本再去全量上線。算法
不少時候,一個按鈕、一張圖片或者一句文案的調整,可能都會帶來很是明顯的增加。這裏分享一個ABTest 在馬蜂窩的應用案例:緩存
如圖所示,以前咱們交易中心的電商業務團隊但願優化一個關於「滑雪」的搜索列表。能夠看到優化以前的頁面顯示從感受上是比較單薄的。可是你們又不肯定複雜一些的展示形式會不會讓用戶以爲不夠簡潔,產生反感。所以,咱們將改版先後的頁面放在線上進行了 ABTest。最終的數據反饋代表,優化以後的樣式 UV 提升了 15.21%,轉化率提升了 11.83%。使用 ABTest 幫助咱們下降了迭代的風險。安全
經過這個例子,咱們能夠更加直觀地理解 ABTest 的幾個特性:併發
先驗性:採用流量分割與小流量測試的方式,先讓線上部分小流量用戶使用,來驗證咱們的想法,再根據數據反饋來推廣到全流量,減小產品損失。微服務
並行性:咱們能夠同時運行兩個或兩個以上版本的試驗同時去對比,並且保證每一個版本所處的環境一致的,這樣之前整個季度才能肯定要不要發版的狀況,如今可能只須要一週的時間,避免流程複雜和週期長的問題,節省驗證時間。工具
科學性:統計試驗結果的時候,ABTest 要求用統計的指標來判斷這個結果是否可行,避免咱們依靠經驗主義去作決策。性能
爲了讓咱們的驗證結論更加準確、合理而且高效,咱們參照 Google 的作法實現了一套算法保障機制,來嚴格實現流量的科學分配。測試
大部分公司的 ABTest 都是經過提供接口,由業務方獲取用戶數據而後調用接口的方式進行,這樣會將原有的流量放大一倍,而且對業務侵入比較明顯,支持場景較爲單一,致使多業務方需求須要開發出不少分流系統,針對不一樣的場景也難以複用。
爲了解決以上問題,咱們的分流系統選擇基於 Openresty 實現,經過 HTTP 或者 GRPC 協議來傳遞分流信息。這樣一來,分流系統就工做在業務的上游,而且因爲 Openresty 自帶流量分發的特性不會產生二次流量。對於業務方而言,只須要提供差別化的服務便可,不會侵入到業務當中。
選型 Openresty 來作 ABTest 的緣由主要有如下幾個:
在設計 ABTest 系統的時候咱們拆分出來分流三要素,第一是肯定的終端,終端上包含了設備和用戶信息;第二是肯定的 URI ;第三是與之匹配的分配策略,也就是流量如何分配。
首先設備發起請求,AB 網關從請求中提取設備 ID 、URI 等信息,這時終端信息和 URI 信息已經肯定了。而後經過 URI 信息遍歷匹配到對應的策略,請求通過分流算法找到當前匹配的 AB 實驗和版本後,AB 網關會經過兩種方式來通知下游。針對運行在物理 web 機的應用會在 header 中添加一個名爲 abtest 的 key,裏面包含命中的 AB 實驗和版本信息。針對微服務應用,會將命中微服務的信息添加到 Cookie 中交由微服務網關去處理。
分流算法咱們採用的 MurmurHash 算法,參與算法的 Hash 因子有設備 id、策略 id、流量層 id。
MurmurHash 是業內 ABTest 經常使用的一個算法,它能夠應用到不少開源項目上,好比說 Redis、Memcached、Cassandra、HBase 等。MurmurHash 有兩個明顯的特色:
快,比安全散列算法快幾十倍
變化足夠激烈,對於類似字符串,好比說「abc」和「 abd 」可以均勻散佈在哈希環上,主要是用來實現正交和互斥實驗的分流
下面簡單解釋下正交和互斥:
互斥。指兩個實驗流量獨立,用戶只能進入其中一個實驗。通常是針對於同一流量層上的實驗而言,好比圖文混排列表實驗和純圖列表實驗,同一個用戶在同一時刻只能看到一個實驗,因此他們互斥。
正交。正交是指用戶進入全部的實驗之間沒有必然關係。好比進入實驗 1 中 a 版本的用戶再進行其它實驗時也是均勻分佈的,而不是集中在某一塊區間內。
流量層內實驗分流
流量層內實驗的 hash 因子有設備 id、流量層 id。當請求流經一個流量層時,只會命中層內一個實驗,即同一個用戶同一個請求每層最多隻會命中一個實驗。首先對 hash 因子進行 hash 操做,採用 murmurhash2 算法,能夠保證 hash 因子微小變化可是結果的值變化激烈,而後對 100 求餘以後+1,最終獲得 1 到 100 之間的數值。
示意圖以下:
實驗內版本分流
實驗的 hash 因子有設備 id、策略 id、流量層 id。採用相同的策略進行版本匹配。匹配規則以下:
剛纔說到,每個請求來臨以後,系統都會嘗試去獲取與之匹配的實驗策略。實驗策略是在從後臺配置的,咱們經過消息隊列的形式,將通過配置以後的策略,同步到咱們的策略池當中。
咱們最初的方案是每個請求來臨以後,都會從 Redis 當中去讀取數據,這樣的話對 Redis 的穩定性要求較高,大量的請求也會對 Redis 形成比較高的壓力。所以,咱們引入了多級緩存機制來組成策略池。策略池總共分爲三層:
第一層 lrucache,是一個簡單高效的緩存策略。它的特色是伴隨着 Nginx worker 進程的生命週期存在,worker 獨佔,十分高效。因爲獨佔的特性,每一份緩存都會在每一個 worker 進程中存在,因此它會佔用較多的內存。
第二層 lua_shared_dict,顧名思義,這個緩存能夠跨 worker 共享。當 Nginx reload 時它的數據也會不丟失,只有當 restart 的時候纔會丟失。但有個特色,爲了安全讀寫,實現了讀寫鎖。因此再某些極端狀況下可能會存在性能問題。
第三層 Redis。
從整套策略上來看,雖然採用了多級緩存,但仍然存在着必定的風險,就是當 L一、L2 緩存都失效的時候(好比 Nginx restart),可能會面臨由於流量太大讓 Redis 「裸奔」的風險,這裏咱們用到 lua-resty-lock 來解決這個問題,在緩存失效時只有拿到鎖的這部分請求才能夠進行回源,保證了 Redis 的壓力不會那麼大。
咱們在緩存 30s 的狀況下對線上數據的進行統計顯示,第一級緩存命中率在 99% 以上,第二級緩存命中率在 0.5 %,回源到 Redis 的請求只有 0.03 %。
吞吐量:當前承擔全站 5% 流量
低延遲:線上平均延時低於 2ms
全平臺:支持 App、H五、WxApp、PC,跨語言
容災:
自動降級:當從 redis 中讀取策略失敗後,ab 會自動進入到不分流模式,之後每 30s 嘗試 (每臺機器) 讀取 redis,直到讀取到數據,避免頻繁發送
請求手動降級:當出現 server_event 日誌過多或系統負載太高時,經過後臺「一鍵關閉」來關閉全部實驗或關閉 AB 分流
測試工具採用 JMeter,併發數 100,持續 300s。
從響應時間來看,除了剛開始的時候請求偏離值比較大,以後平均起來都在 1ms 之內。分析剛開始的時候差距比較大的緣由在於當時的多級緩存裏面沒有數據。
TPS的壓測表現有一些輕微的降低,由於畢竟存在 hash 算法,但整體來講在能夠接受的範圍內。
常規 A/B 發佈主要由 API 網關來作,當面臨的業務需求比較複雜時, A/B 發佈會經過與與微服務交互的方式,來開放更復雜維度的 A/B 發佈能力。
須要注意的是,ABTest 並不徹底適用於全部的產品,由於 ABTest 的結果須要大量數據支撐,日流量越大的網站得出結果越準確。一般來講,咱們建議在進行 A/B 測試時,可以保證每一個版本的日流量在 1000 個 UV 以上,不然試驗週期將會很長,或很難得到準確(結果收斂)的數據結果推論。
要設計好一套完整的 ABTest 平臺,須要進行不少細緻的工做,因爲篇幅所限,本文只圍繞分流算法進行了重點分享。總結看來,馬蜂窩 ABTest 分流系統重點在如下幾個方面取得了一些效果:
採用流量攔截分發的方式,摒棄了原有接口的形式,對業務代碼沒有侵入,性能沒有明顯影響,且不會產生二次流量。
採用流量分層並綁定實驗的策略,能夠更精細直觀的去定義分流實驗。經過和客戶端上報已命中實驗版本的機制,減小了服務數據的存儲並能夠實現串行實驗分流的功能。
在數據傳輸方面,經過在 HTTP 頭部增長分流信息,業務方無需關心具體的實現語言。
近期規劃改善:
監控體系。
用戶畫像等精細化定製AB。
統計功效對於置信區間、特徵值等產品化功能支持。
經過 AARRR 模型評估實驗對北極星指標的影響。
這套系統將來須要改進的地方還有不少,咱們也將持續探索,期待和你們一塊兒交流。
本文做者:李培,馬蜂窩基礎平臺信息化研發技術專家;張立虎,馬蜂窩酒店研發靜態數據團隊工程師。
(馬蜂窩技術原創內容,轉載務必註明出處保存文末二維碼圖片,謝謝配合。)