開發與維運

數據驅動UI迭代,如何設計簡單高效的前端AB實驗方案?

作者 | 黃昱

image.png

以前,我們沒有一條簡潔而高效的AB實驗接入方案,過去大多數前端在接到AB需求,唯一的選擇就是調用客戶端提供的庫函數來實現,我們從中發現了一些痛點:

  • 首先前端開發者需要花大量的心思去處理AB的邏輯;
  • 此外客戶端實驗的作用範圍僅侷限於端內,並不能覆蓋前端所有的場景,比如外投,小程序或PC等等;
  • 數據的精細度不足,不管是服務端實驗,還是客戶端實驗都是頁面維度的,而業務方常常需要看更細粒度的指標,比如頁面上某個模塊的點擊率,前端在數據採集上具有天然的優勢;

因此,打造一套標準化的簡單高效的AB實驗鏈路,是我們做這件事的初衷。

設計思路

一條完整的AB實驗鏈路到底需要什麼?創建實驗,工程接入,分流,實驗數據迴流...恩,是的,我還可以列一大堆,但是它們沒有組織不成體系,就毫無意義,更談不上架構設計。所以,我們來把這些分散的元素嘗試歸類。

一條完整的AB實驗鏈路其實可以拆分成兩部分,第一部分是實驗配置鏈路,顧名思義它包含了所有與實驗配置相關的操作,配置創建,保存,分組,配置的推送或下發,以及使用配置進行實時分流。第二部分是實驗數據鏈路,它包含實驗數據的染色,過濾,計算,展示(實驗報表)。把這兩部分拼成一個閉環,也就是我們設計前端AB實驗的核心。

image.png

下面給大家分別介紹這兩個數據模型。

實驗配置模型

我們將實驗配置以應用為維度進行組合,然後推送到CDN。前端運行時則通過JSSDK讀取配置,完成分流並渲染相應的業務組件。

image.png

應用我們可以理解為一個前端工程,一個小程序可以是一個應用,或手淘內某個活動頁面(比如淘金幣)也可以是一個應用。應用裡面包含了場景,一個獨立的流量入口我們稱之為場景,比如某應用的首頁就是一個場景,導購鏈路也是一個場景。場景同時也是底層的分流模型,這部分我們放在下一個章節再詳細介紹場景內部的結構,此處我們只需要知道我們新建的實驗是與場景直接關聯的。

實驗數據模型

從上圖我們可以得出,業務是數據指標的集合,並且同一業務下的實驗可以創建並關聯這個業務域下的所有指標。這些被關聯的指標將最終通過數據採集和計算,反映在實驗的報表裡。

image.png

舉個實際的例子,手淘內的淘金幣業務,在淘金幣首頁(場景)我們想要跑一個實驗,AB營銷模塊哪個更高效?那麼營銷模塊的曝光UV和點擊UV就是我們要去創建並關聯的指標。該指標也會存在於這個業務域下的某個數據集裡。說到這裡也許有人會疑惑,如果淘金幣既是應用又是業務,為啥還要區分這兩個概念?為什麼不是應用既下發配置又組合指標呢?原因很簡單,因為業務是可以跨應用的,比如手淘某個業務由於業績出色,決定擴張,在淘寶特價版App裡也要上線。那麼這個業務就有兩個應用了,且一個在手淘一個在手淘特價版,這裡配置下發要區分,但是業務指標很多卻是一樣的,這種情況下,一個業務兩個應用是一個相對優化的解決方案。

設計方案

前端AB實驗鏈路的設計,就像是對上述兩個模型做的一道“完形填空”。我們從平臺側和運行時兩個大的方向入手,將上述模型的各個元素及其關係一一佈局,並串聯形成閉環。

AB 平臺

image.png

對於第一次入駐AB平臺的業務,我們需要進行相關工作空間的註冊

  • 應用,用於管理實驗配置,即配置的整合及推送
  • 業務,用於管理實驗相關的數據指標
  • 場景,分流模型(後面章節詳細描述)

創建完工作空間以後,我們開始核心的實驗創建鏈路,這裡包含了實驗的基本信息的讀取,分桶設置(流量分配)。為了進一步降低實驗接入的門檻,平臺側在流量分配步驟之後會自動生成前端接入代碼,方便前端開發者能快速接入實驗。

image.png

接下來是創建指標並將指標與實驗關聯的過程(可以回顧下實驗數據模型章節)。然後是實驗發佈,正式發佈前,有一個beta發佈環節,beta發佈可以理解為實驗上線前的一次“非正式”下發,開發者和業務方可以在beta發佈後對實驗分流、數據及相關業務邏輯進行充分驗證後,再正式發佈。當然,實驗是支持多次發佈的,即實驗中期業務方可以回到流量分配這一步,重新調控流量比例,並重新發布實驗。

運行時

JSSDK

我們不妨回顧下上文實驗配置模型圖中JSSDK的位置,它運行在前端項目中,讀取實驗配置,實時分流,並根據分流結果返回要渲染的組件。我們再來回顧下實驗數據模型,JSSDK在數據這部分要做的工作是什麼,答案是實驗數據上報。第一,它需要上報分流結果,第二,它需要上報實驗所關聯的指標數據,即業務要看的實驗數據。這麼一想我們對於JSSDK要做的事情就比較清晰了,下面我們來聊一聊我們具體的設計方案,及擴展方案。

下圖說明了運行時,JSSDK與前端工程及AB平臺的關係。先從前端工程說起,我們在工程裡引入了一個業務AB實驗組件,該組件是AB平臺根據用戶的配置動態生成的,作為(AB實驗相關的)業務組件與頁面(或父容器)的銜接器,其核心工作就是讀取AB實驗所需的參數並傳入SDK,以此觸發SDK的整個AB實驗邏輯

image.png

再來看JSSDK部分(藍色),首先從全局來看,我們把JSSDK拆成了兩個包:

  • 一個是核心(Core)包,封裝了通用的核心邏輯如實驗配置讀取及緩存策略,實驗週期控制及分流算法;
  • 另一個是接入具體DSL工程的銜接器包(Coupler),如圖所示,它就像是一個AB實驗流程的中轉站,它實現了一套接口函數,即在特定DSL環境下(如React)的請求、緩存及cookie解析(分流因子),並將這些接口函數和實驗參數一起透傳給Core,我們這麼設計的目的是實現Core與前端DSL的徹底解耦,這樣極大的增加了JSSDK的可擴展性

在拿到實驗參數,及所需的接口函數以後,Core要做的工作就是先獲取實驗配置,此時Core不會直接去請求實驗配置,而是觸發版本控制策略,該策略主要是檢查遠程實驗配置的版本號是否更新,若有更新才會去請求配置,否則會讀取本地緩存的配置。這份本地緩存的配置是用戶第一次觸發實驗時從CDN請求並緩存下來的,之後每次版本更新,才會重新請求並更新緩存。拿到實驗配置後,Core會確認當前事前是否處於實驗週期(AB平臺側配置)內,校驗通過後才會正式觸發分流算法(見下一章節),然後將分流結果返回給Coupler。

Coupler接著會根據分流結果來判斷應該展示哪個對應的業務組件,同時它會將分流結果上報給平臺,用戶此時已經可以在實驗的實時數據報表看到分流數據了,技術同學可以通過實時數據來確認實驗是否正常觸發。另外,埋點組件會對命中的業務組件做一層封裝,這裡會傳入可供業務組件調用的埋點上報方法,具體的調用我們在AB平臺創建實驗時,就已經生成好了,前端同學是需要將這些上報實驗指標的代碼部署到相應的業務即可。這一部分埋點數據是T+1的,業務方可以在平臺側看到相應的實驗報表,並分析實驗結果。

擴展方案

前端DSL可謂是百家爭鳴,為了覆蓋所有的前端場景,我們在設計上慎重的考慮了JSSDK的易擴展性。這也是為什麼我們在上一節的Rax 1.0方案中,將JSSDK拆成了Core和Coupler兩個包,我們的思路是Core封裝AB實驗的核心邏輯,不依賴任何前端DSL,在Core與前端工程之間引入一個"銜接器"包,來串聯起整條鏈路。這樣如下圖所示,我們可以通過這樣的架構,非常低成本的擴展到React,小程序,Node FaaS等等。同時也具備良好的可維護性。

image.png

其實在上一節中也有提到我們是如何做到將核心包(Core)與這些DSL徹底解耦的,我們定義了一套接口規範,然後在"銜接器(Coupler)"包中根據這個規範實現一系列的接口函數,當Core在執行某段邏輯調用這些函數時,根本無需關心其底層用的是哪一個DSL的API。比如對localStorage的操作,React和小程序的API是完全不一樣的,所以我們在React和小程序的"銜接器"中按照接口規範各自實現了這樣一套對localStorage的處理函數,並透傳給Core。

分流模型

正交和互斥

在第一章節講實驗流量模型的時候我們就提到了場景,我們將一個獨立的流量入口定義為場景。我們還舉例說,xx應用的首頁可以是一個場景,現在我們不妨來拓展下這個例子,xx的首頁可能同時運行了多個AB實驗:

  • 實驗一,紅包權益彈層有AB兩種樣式,觀測指標是兩個彈層的點擊率;
  • 實驗二,頁面頭部商品模塊有AB兩種樣式,觀測指標是模塊點擊率;
  • 實驗三,運營投放的營銷banner有AB兩種設計,透不同利益點,觀測指標是banner點擊率;

現在的問題是,實驗二和實驗三都是頁面上的模塊,我們希望這兩個實驗同時運行,但不希望它們互相影響,即進入實驗二和實驗三的流量必須互斥。如何做到呢?對我們而言,場景不僅是一個流量入口,更是一個分流模型,實驗在場景裡並不是無序放置的,而是通過層(layer)來進行規範。我們可以這麼理解,場景是一個縱向的容器,而層是一個橫向的容器,實驗則按照一定規則放在不同的層裡。如下圖,實驗1獨佔一層,它與下一層的實驗2和實驗3是正交關係,即進入實驗1的流量,同時也會進入實驗2或實驗3。我們把實驗2和實驗3放在同一層,因為我們希望進入實驗2的流量不要與進入實驗3的流量重疊。這段描述,可以簡單總結為,在一個場景裡,層與層之間的實驗流量正交,層內的實驗流量互斥。

image.png

實驗推全

在上面的模型圖中我們還看到一個特殊的層,Launch Layer,這個層用來放被推全的實驗分組。比如說,經過線上驗證實驗1的A組效果明顯優於B組,用戶在平臺側將A組推至全量,這時候場景內部結構會發生變化,即實驗1的A組會從原來的layer被放到這個特殊的Launch Layer裡,此時該場景內所有觸發實驗1的流量將不會再執行分流算法,而是直接返回組A所對應的前端組件。

JSSDK 分流

JSSDK的分流規則遵循上文提到的分流模型,前端AB實驗用到分流因子是訪問者瀏覽器cookie下的cna字段,即該用戶的web設備id。我們用這個字段來唯一的標識一個用戶,其好處是,可以很好的覆蓋端內,端外,無線和PC等各種場景,缺點是,同一個用戶用不同的web設備來訪問前端應用時,有可能會展示不同的分組結果。此外,web設備id無法對判定用戶的人群。一種分流因子一定有其侷限性,這部分的設計需要是可以擴展的,比如未來可以支持根據用戶id,utdid甚至是各種用戶自定義分流因子的接入。

數據迴流

回到數據鏈路,在AB平臺設計章節我們聊到了創建數據指標並將指標與實驗綁定,在這一步我們會給每一個新創建的數據指標分配一個唯一id(UID)用來標識該指標;在前端工程運行時,分流之後展示相應組件,並觸發相關的埋點,如下圖所示在埋點參數中,我們會帶上日誌key,埋點類型,實驗參數,即實驗發佈id和分組id,實驗發佈id是什麼呢?前文提到在實驗運行中,我們允許業務重新調控流量比例,併發布實驗,也就是說一個實驗是可以多次發佈的,所以實驗發佈id可以標識採集到的數據是當前實驗的某一次發佈;當然我們還會帶上標識指標的UID。

image.png

平臺側,運行定時的數據任務,該任務會從底表中過濾出實驗相關數據,上文提到我們在埋點上報時會上報一個日誌key,這個key是SDK與數據側約定的一個key,用來標記這條記錄是實驗埋點。因為我們的日誌底表每天都有大量的日誌寫入,這個特殊的日誌key可以讓我們從底表的各種日誌中快速過濾出實驗相關的日誌。然後我們根據上報的參數對數據進行歸類,實驗發佈ID用來標識某個實驗的某一次發佈,UID標識指標,然後分桶ID用來標識實驗的分組,這樣我們就可以得到指標對應的UV和PV,AB實驗的報表也就生成了。

結語

前端AB實驗還是一塊未被充分開墾的土地。我們的應用迭代頻繁,但其實我們很少去驗證,或者說用科學的方式去驗證某一次迭代的真正價值。在大數據崛起的時代,如果我們的每一次迭代不能最終沉澱出數據,而僅僅依靠"經驗"恐怕是遠遠不足夠的。前端工程師們,從現在開始我們有了一塊陣地,我們應該發揮科學嚴謹的實驗精神,用實驗數據去驗證迭代,並以此為基礎去決策。最後,歡迎感興趣的朋友進一步交流,可以加我微信MrMarc,期待你的真知灼見。


image.png
關注「Alibaba F2E」
把握阿里巴巴前端新動向

Leave a Reply

Your email address will not be published. Required fields are marked *