数据驱动UI迭代,如何设计简单高效的前端AB实验方案?

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 以前,我们没有一条简洁而高效的AB实验接入方案,过去大多数前端在接到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」
把握阿里巴巴前端新动向

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
3月前
|
前端开发 JavaScript 开发者
【前端开发者的福音】彻底改变你编码习惯的神奇数组迭代技巧——从基础到进阶,解锁 JavaScript 数组迭代的N种姿势!
【8月更文挑战第23天】在Web前端开发中,数组是JavaScript中最常用的数据结构之一,掌握高效的数组迭代方法至关重要。本文详细介绍了多种数组迭代技巧:从基础的`for`循环到ES6的`for...of`循环,再到高阶方法如`forEach`、`map`、`filter`、`reduce`及`some`/`every`等。这些方法不仅能提高代码的可读性和维护性,还能有效优化程序性能。通过具体的示例代码,帮助开发者更好地理解和运用这些迭代技术。
41 0
|
20天前
|
前端开发 数据可视化 搜索推荐
深入剖析极态云优雅的前端框架设计方案(上)
最近在体验极态云,这款低代码软件开发产品,发现其前端框架设计方案很优雅很强大! 在接下来的学习过程中,我将持续输出自己对极态云前端框架设计方案的深入理解,包括具体的使用技巧、优势分析以及可能的应用场景等方面的内容,希望能为大家提供有价值的参考。
|
2月前
|
前端开发 开发者 UED
前端只是切图仔?来学学给开发人看的UI设计
该文章针对前端开发者介绍了UI设计的基本原则与实践技巧,覆盖了布局、色彩理论、字体选择等方面的知识,并提供了设计工具和资源推荐,帮助开发者提升产品的视觉与交互体验。
|
1月前
|
缓存 前端开发 UED
前端 8 种图片加载优化方案梳理
本文首发于微信公众号“前端徐徐”,详细探讨了现代网页设计中图片加载速度优化的重要性及方法。内容涵盖图片格式选择(如JPEG、PNG、WebP等)、图片压缩技术、响应式图片、延迟加载、CDN使用、缓存控制、图像裁剪与缩放、Base64编码等前端图片优化策略,旨在帮助开发者提升网页性能和用户体验。
213 0
|
2月前
|
Web App开发 前端开发 JavaScript
Web前端项目的跨平台桌面客户端打包方案之——CEF框架
Chromium Embedded Framework (CEF) 是一个基于 Google Chromium 项目的开源 Web 浏览器控件,旨在为第三方应用提供嵌入式浏览器支持。CEF 隔离了底层 Chromium 和 Blink 的复杂性,提供了稳定的产品级 API。它支持 Windows、Linux 和 Mac 平台,不仅限于 C/C++ 接口,还支持多种语言。CEF 功能强大,性能优异,广泛应用于桌面端开发,如 QQ、微信、网易云音乐等。CEF 开源且采用 BSD 授权,商业友好,装机量已超 1 亿。此外,GitHub 项目 CefDetector 可帮助检测电脑中使用 CEF
347 3
|
3月前
|
缓存 前端开发 JavaScript
前端性能优化方案
【8月更文挑战第15天】前端性能优化方案
37 2
|
2月前
|
编解码 前端开发 JavaScript
前端移动端适配方案
【9月更文挑战第8天】前端移动端适配方案
97 0
|
3月前
|
开发者 C# Android开发
明白吗?Xamarin与Native的终极对决:究竟哪种开发方式更适合您的项目需求,让我们一探究竟!
【8月更文挑战第31天】随着移动应用开发的普及,开发者面临多种技术选择。本文对比了跨平台解决方案Xamarin与原生开发方式的优势与劣势。Xamarin使用C#进行跨平台开发,代码复用率高,可大幅降低开发成本;但因基于抽象层,可能影响性能。原生开发则充分利用平台特性,提供最佳用户体验,但需维护多套代码库,增加工作量。开发者应根据项目需求、团队技能和预算综合考量,选择最适合的开发方式。
115 0
|
3月前
|
JavaScript 前端开发 开发者
决战前端之巅!Element UI与Vuetify谁才是Vue.js组件界的霸主?一场关于颜值与实力的较量!
【8月更文挑战第30天】本文对比了两款热门的Vue.js组件库——Element UI与Vuetify。Element UI由饿了么团队打造,提供多种高质量UI组件,设计简洁大方。Vuetify基于Material Design规范,支持Vue.js 2.0及3.0版本,具备前瞻性。两者均涵盖表单、导航、数据展示等组件,Element UI配置选项丰富,而Vuetify则提供了更深层的样式定制功能。开发者可根据项目需求及个人偏好选择合适的组件库。
269 0
|
3月前
|
机器学习/深度学习 分布式计算 前端开发
构建前端防腐策略问题之前端代码会随着技术引擎的迭代而腐烂的问题如何解决
构建前端防腐策略问题之前端代码会随着技术引擎的迭代而腐烂的问题如何解决