vivo 霍金实验平台设计与实践 - 平台产品系列 02
vivo 互联网平台产品研发团队 - Bao Dawei
本篇介绍了 vivo 霍金实验平台的系统架构以及业务发展过程中遇到的问题以及对应的解决方案。
《平台产品》系列文章:
一、前言
互联网企业经历过野蛮生长的开拓红利期之后,逐渐越发重视产品发展的科学化、精细化,从粗放型向集约型转换。在美国,增长黑客等数据驱动增长的方法论,正在帮助如 Google、Microsoft、Facebook 等全球科技巨头实现持续的业务增长;在国内,数据精细运营、AB 实验分析来驱动业务有效增长也逐渐成为共识,成为核心手段。其中,A/B 测试平台作为典型代表,自然成为了国内主流公司中必不可少的核心工具,有效的提升流量的转化效率和产研的迭代效率。
在过去几年,vivo 互联网持续重视科学的实验决策,这意味着所有对用户的改动的发布,都要决策者以相应的实验结论作为依据。比如,修改顶部广告的背景色、测试一个新的广告点击率 (CTR) 预测算法,都需要通过实验的方式进行,那么一个强大的 A/B 实验平台就非常重要了。vivo 霍金实验平台(以下简称霍金)已经从一个单一系统成长为了解决 A/B 实验相关问题的公司级一站式平台,助力互联网核心业务的快速、准确实验,高效推动业务增长。
二、项目介绍
2.1 A/B 实验
在互联网领域,A/B 实验通常指一种迭代方法,这种方法可以指导如何改进现有产品或者服务。以提升某个产品下单转化率为例,AB 实验过程中,我们设计了新的下单页面,和原页面相比,页面布局和文案做了调整。我们将用户流量随机分成 A/B 两组(分别对应新旧页面),50%用户看到 A 版本页面,50%用户看到 B 版本页面,经过一段时间的观察和统计,发现 A 版本用户下单转化率为 70%,高于 B 版本的 50%。那么我们得出 A 版本效果好,进而将新页面推送并展示给所有用户。

以上就是一个利用 AB 测试迭代产品功能的具体应用,我们将 A/B 实验的完整生命周期分为三个阶段:
实验前,明确改进目标,定义实验指标,完成相关功能开发和上线;
实验中,制定每个实验组流量比例,按照分流比例开放线上流量进行测试;
实验后,实验效果评估并决策。
2.2 分层实验模型
霍金的分层实验模型参考谷歌发布的重叠实验框架论文:《Overlapping Experiment Infrastructure: More, Better, Faster Experimentation》完成设计。
2.3 平台发展及在 vivo 业务中的应用和价值
霍金启动于 2019 年,历经三年多的发展,目前日均实验数量达到 900 多个,高峰期 1000+。
支撑 vivo 国内与海外业务,服务公司 20 多个部门。
通过标准化的实验流程降低了实验门槛,提升实验效率。
通过自动化的数据分析工具辅助业务快速决策,提升产品迭代速度,有效助力业务发展。
平台能力复用,避免不同组织重复建设的情况,有效的提升了生产效率。
三、霍金系统架构

3.1【实验人员】
实验人员包含多个角色,供业务方在霍金管理后台进行实验、指标的管理和实验效果的分析。
3.2【实验门户】
包括两部分功能:实验管理和实验效果分析。
3.2.1 实验管理
平台提供可视化页面供业务方进行实验配置、分流策略的选择、流量的分配以及白名单的管理。
3.2.2 实验效果分析
包括如下 4 个核心能力:
1. 指标管理
不同实验关注指标不同,为了实现效果评估自动化,平台提供了指标配置和集成能力。
【必看指标】:通常为业务核心指标,每个实验都需要保障不能有明显负向的指标,平台通过集成大数据指标管理系统,这部分指标结果直接复用指标管理系统的数据服务。
【个性化指标】:通常为实验中临时分析的指标,如某 banner 样式实验,观察的指定 banner 的曝光点击率。平台提供了自定义指标配置的能力,并通过大数据计算平台自动生成计算任务,实现自定义指标自动化产出数据的能力。
2. 对比分析和显著性结论
效果评估的可视化展示,平台沉淀了对比分析和显著性结论等可视化组件。非常直观的告知实验者,每个实验方案相比对照方案整体提升幅度以及每日的涨跌幅。同时给出指标的置信区间和显著性结论。
3. AA 分析
平台提供的 AA 分析,目的是帮助实验者验证实际进入实验的不同方案的人群,实验前在业务核心指标上是否存在显著差异,辅助实验者判断实验结论是否可靠。
4.分流实时监控
可以直观的看到实时分流的效果,针对流量异常可以及时人工干预和解决。
3.3 【实验分流服务】
1. 多端接入服务
平台根据业务的不同诉求提供丰富的接入能力,如针对安卓客户端的 Android SDK、服务端的 JAVA SDK、基于 NGINX 进行分流的 H5 实验服务、dubbo/http 服务,C++ SDK 待建设。
2.实验分流的方式
平台提供了稳定高效的在线实时分流服务。
【随机分流】:根据用户标识符基于哈希算法对人群进行随机分组并分流
【指定人群分流】:实验前圈定一拨人群并打上标签进行分流
【协变量均匀分流】:在基于哈希算法对人群随机分组的时候,虽然分组的人群数量等比例划分,但是分组的人群分布的指标存在不均匀的情况,导致实验效果达不到预期。为了解决这个痛点,平台推出了协变量平衡算法,
该算法能够保证人群在进行分组样本量均匀的同时,人群上指标分布也是均匀的。
详见本文:4. vivo 霍金实验平台实践→4.1 协变量平衡算法 的详细介绍。
【基于 openresty web 服务分流】:针对服务端非 JAVA 语言,对性能有着严苛要求的业务方,我们在 NGINX 基于 OpenResty 采用 lua 脚本实现了一套实验分流功能,以 http 接口提供服务,平均响应时间小于 1ms,p9999<20ms。
3.4 【分流数据处理服务】
采用公司统一的数据采集组件进行分流数据的采集、加工,并最终存储至 HDFS。
3.5【指标计算服务】
独立的服务用于高效计算指标结果,同时配备了指标计算失败重试和监控告警机制,有效的保证了指标计算成功率。
3.6【数据存储】
主要是利用 MySQL 来进行业务数据的存储,同时采用 Ehcahe 进行实验配置的主要缓存,Redis 作为辅助缓存,最后实验分流的数据经过加工处理后保存到 HDSF 中,供后续的实验数据分析使用。
四、霍金实践
上面介绍了霍金的发展情况以及整体的系统架构,接下来介绍下在平台发展过程中遇到的问题以及对应的解决方案。
4.1 协变量平衡算法
4.1.1 遇到的问题
业务方在进行实验对象分组的时候,最常用的做法是根据实验对象的某个属性进行哈希后对 100 取模,根据结果分到不同组。虽然 hash 算法分流可以做到尾号号段分布均匀,但是分完组后,可能存在不同组的实验对象在某些指标特征上分布不匀均,导致实验效果评估不准确。如下图所示(图中四个不同颜色代表不同的人群以及对应的指标类型):

有没有一种方式能够实现对人群进行均匀分组的同时,保证人群对应的指标分布也是均匀的呢,如下图所示:

4.1.2 解决方案
1. 协变量平衡算法
该算法能够保证对人群进行均匀分组的同时,人群对应的指标分布也是均匀的。整体由三部分组成,示意图如下:

(1)离线分层抽样
需要经历如下 3 个步骤:
和业务方确定核心指标
采用等比例分层+Kmeans 聚类模型完成指标对应的用户的分层抽样
将分层抽样好的数据写入 hive 库相关表中
这里介绍下等比例分层抽样
等比例分层抽样:

第 i 层应抽取的样本数量;第 i 层的总体样本数量;N 全体可用的流量总数;n 本次实验设定的流量样本数量
假设 N 为 3kw,n 为 50w。按照以下维度进行分类:

共有 9 种组合,确定每种组合别在总量中的占比(总数 N=3kw,通过在全体可用流量中筛取特定人群):

通过公式计算得到每层的样本数量;对应分类的样本数量(总样本量 60w):

至此完成了整个离线分层抽样的工作,接下来介绍下实时均匀分组。
(2)实时均匀分组
需要经历如下 4 个步骤:
数据同步
通过配置的定时任务将准备好的分层抽样数据由 hive 库相关表同步至 redis,数据包括每天的用户标识符(uid,下同)到层的映射,以及每天每层所拥有的用户占比。
实验创建
通过实验编号,实验组编号和每个实验组的样本量创建实验。创建的实验会与当前最新一天的用户数据相关联,通过 样本量 * 层用户占比可以确定该层每个实验组的样本量。
实验分流
通过实验编号和用户标识符(uid) 先找到用户所在的层,之后将用户均匀的分配到该层下的实验组中,保证实验组之间在不同层上分流的用户均匀。如下图所示:

用户数据删除
因为我们采取的方案需要每天同步大量数据,所以对于无用的用户数据需要及时删除,增加资源利用率。
实时均匀分组整体流程图如下:

在做实时均匀分组的时候,面临性能和存储的压力,为此我们分别设计了高性能分流方案和高内存利用率用户信息存储方案设计。
高性能分流方案
我们通过不同的 redis 数据结构和 lua 脚本完成层下桶的均匀分配
方案 1
预先分配每一个桶的样本量,每次选出当前样本量最多的桶 Redis 结构:HASH,field 为对应的桶编号,value 为桶对应的当前样本量
方案 2
预先分配每一个桶的样本量,每次选出当前样本量最多的桶 Redis 结构:SORTED SET,key 为对应的桶编号,score 为桶对应的当前样本量
方案 3
通过当前层样本量与桶大小取模选出命中的桶 Redis 结构:HASH

方案 1:在只有两个桶时拥有最高的性能,是方案 3 的 1.05 倍,但其性能随着桶的增多而线性减少。方案 2:拥有稳定的性能。方案 3:拥有稳定的性能,但性能是方案 2 的 1.12 倍,是单 GET 请求性能的 58%。
综合考虑选择方案 3。
高内存利用率用户信息存储方案设计
uid-层的内存消耗对比

方案 1:使用 redis string 存储。方案 2:分为 10000 个 hash 存储。方案 3:分为 10000 个一级桶,每个一级桶下有 125 个二级桶。
假设 uid 为 15 位数字,层 id 为 2 位数字,考虑过期时间,不考虑 cluser 模式下的额外消耗,不考虑 malloc 内存碎片和占用。
综合考虑选择方案 3。
(3)离线分析验证
因为协变量算法实验流程比较复杂,所以我们还是采用人工取数的方式进行实验效果的分析。
4.2 Java SDK
4.2.1 遇到的问题
早期 Java SDK 的能力较弱,只提供了分流,需要接入方上报分流结果数据,对接入方而言改造成本较大;故当时主要以 Dubbo 接口对外分流服务,在服务内由平台服务端统一进行分流结果数据的上报。
随着接入方越来越多,频繁发生 Dubbo 线程池耗尽、或者因为网络原因导致分流失败的情况发生,导致分流体验很差,影响实验效果分析;面对上述问题,平台除了不断地做性能优化外,还需要不停的对应用服务器等资源做扩容,造成一定的资源浪费。
4.2.2 解决方案
针对上述情况,霍金开发团队经过充分的技术方案研究,对 Java SDK 进行了数次升级,成功解决上述问题。目前具备了实验分流、分流结果的上报、实验配置实时 &增量更新、SDK 自监控等核心功能,极大的提升了分流的稳定性和成功率。
4.2.3 SDK6 大核心能力
1. 分流结果上报
在 SDK 内部依托公司的数据采集组件进行分流结果的上报。
2. 分流结果上报失败的兜底方案
在进行分流上报的时候,因为数据链路无法保证数据 100%的完整性,如果遇到机器宕机,业务服务异常,网络异常等情况,实验分流数据上报失败,直接影响实验效果分析。
如何保证在任何情况下实验分流数据 100%不丢失,为此霍金实验平台设计了一套分流数据落磁盘的方案,作为异常场景的兜底措施,从而 100%保证了数据的完整性,设计图如下:

3. 实验配置实时 &增量更新
在通过定时任务拉取实验配置至业务方本地缓存的方式外,还提供了实时和增量更新,适用于对实验配置变更时效性要求高的业务,可以通过开关控制,动态生效,默认采用实时增量更新 + 定期全量更新,方便业务方灵活使用。下面是配置实时与增量更新的流程图:

在实时更新发生失败的情况下,我们设计了失败退避策略:采用指数级失败退避策略, 默认长轮询的间隔为 1s,每次失败间隔增加 2 倍, 最大为 60, 所以增长序列为 1, 2, 4, 8, 16, 32, 60;每次成功将间隔置为 1。
此外我们做了数据最终一致性的保证,保证 SDK 拉取配置时最终可以拉取到最新的配置,且不会出现配置回退:
实验信息和模块信息缓存的刷新是线性的。
同一次变更的实验信息缓存的刷新在模块信息缓存刷新之前(发送缓存刷新消息时保证实验缓存刷新消息在模块缓存刷新消息之前)。
模块信息缓存的刷新时不会出现版本号跳跃问题(缓存方法入参加上版本号,刷新缓存时将数据库的版本号与传入的版本号对比,如果版本号不一致则打印日志并使用传入的版本号作为此次缓存刷新的版本号)。
SDK 拉取配置并更新本地配置时,只更新拉取配置版本号大于等于本地配置版本号的配置
4. 多级配置管理
SDK 支持多级配置管理,优先级依次为:方法入参的配置(原有) > 业务方配置中心灰度配置 > 业务方配置中心配置 > 远程默认配置 > 本地默认配置;业务方配置中心灰度配置是指在配置中心通过配置指定机器 ip 进行功能灰度。
5. 分流策略兜底
采用 SDK 痛点之一是新增功能后需要业务方随之升级,否则新功能无法使用,进而影响业务。为此霍金设计了一套兜底方案,在 SDK 中探测到新增的策略不存在时,通过 dubbo 泛化调用的方式访问霍金服务器,保证分流功能正常;有效的保证业务方有充足的时间升级到最新版本,提升用户体验。
6. SDK 监控告警
采用 SDK 的另一个痛点是 SDK 集成在业务方服务端的进程中,所打印的错误信息自己看不到,依赖业务方的反馈,显得很被动,不能第一时间跟进处理和解决问题;针对这种情况,霍金实验平台设计一套 SDK 自监控方案。
自监控数据按照时间精度预聚合后通过埋点域名上报到通用监控,自监控支持内销,新加坡和印度环境。通过监控我们可以直观的看到每个业务、实验、SDK 版本等维度是否存在错误信息,并根据相应维度配置告警,方便开发人员第一时间跟进处理和解决问题。
4.3 H5 实验
4.3.1 遇到的问题
业界在做 H5 实验时,通常做法是开发 H5 SDK,让业务方前端引入。
存在如下几个问题:
需要业务方前端做代码改动进行适配
另外需要对实验的页面或者元素做遮罩,因存在页面跳转对用户体验有一定影响
实验功能发生变化时,需要业务方升级 H5 SDK
整个 H5 实验接入周期比较长,存在一定的接入门槛
4.3.2 解决方案
那么有没有一种简单快捷的方式,只需要接入方在后台配置完实验就完成整个 H5 实验的接入呢?为此霍金开发团队设计了一套方案顺利解决了该问题,整个 H5 实验架构基于开源 apisix 搭建,业务方在霍金管理后台创建实验时,所有基于 nginx 的路由配置全部自动化通过接口下发完成(配合工单审核),无需接入方在代码层面做任何修改,无侵入性,大大提升业务方做 H5 实验的效率。
这里解释下几个名词:
【公共 VUA】:vivo unified access。vivo 统一接入层,可以理解是后续替换 nginx 的产品,基于开源 apisix 搭建。
【霍金 VUA】:为霍金单独搭建的 VUA 平台,做 H5 实验时,公共 VUA 将需要做实验的页面代理到霍金 VUA,霍金 VUA 通过开发的霍金分流 APISIX 插件完成实验分流。
【VUA 变更平台】:基于 NGINX 的配置变更通过在该平台上可视化操作后下发至 VUA 平台(公共 VUA/霍金 VUA)。
(1)H5 实验的整体时序图

(2)NGINX → VUA 分流方案切换
公共 VUA 将需要做实验的页面代理到霍金 VUA,霍金 VUA 通过开发的霍金实验平台分流 APISIX 插件完成实验分流。
多版本实验分流
1)H5 多版本实验介绍
同一个 url 做实验,通过霍金分流,不同用户访问到的 url 都是相同的,但是页面访问内容不同(因为多版本实验是将页面版本资源发布到不同的机器上),然后通过霍金实验平台分流访问不同的资源。
2)H5 多版本实验分流原理
公共 VUA 将多版本实验对应的静态资源请求代理到霍金 VUA。
霍金 VUA 通过 APISIX 插件 按照实验配置选择 upstream 并代理到对应的静态资源服务器。
3)流程示意图

多版本实验分流
多页面实验分流
霍金实验平台分流 APISIX 插件
H5 实验的分流数据采集
多页面实验分流
1)H5 多页面实验介绍
多个不同 url 做实验,通过霍金分流,不同用户访问到不同的 url 页面。
2)H5 多页面实验原理
公共 VUA 将多页面实验对应的入口业务路径的静态资源请求代理到霍金 VUA。
霍金 VUA 通过 APISIX 插件 按照实验配置重写路径到不同的页面。
3)流程示意图

霍金实验平台分流 APISIX 插件
流程示意图如下:

插件开发规范参考:https://apisix.apache.org/zh/docs/apisix/plugin-develop
H5 实验的分流数据采集
H5 实验的分流数据保存在霍金 VUA 平台的 access_log 中,经过如下几个步骤最终存入 HIVE 库的 DW 表中,供后续的数据分析使用。

五、实验效果分析
该模块包括指标服务、数据分析与效果展示、准实时指标计算、AA 分析等功能,因篇幅有限,不在此展开。
六、总结与展望
本文主要通过介绍 A/B 实验在 vivo 的平台化、产品化的建设和实践,实现了以下的价值和能力:
用户可在平台上完成创建实验-数据分析-决策-调整实验的闭环,操作简单,灵活性高;
提供科学可靠的多层分流算法,流量可复用,无需发版即可快速验证产品方案、运营策略、优化算法;
提供实时实验分流监控、小时级的指标监控以及离线数据分析功能;
支持自定义指标,无需等待分析师开发报表,即配即查。
但还存在用户体验等问题,后续我们会重点在实验流程,数据服务功能诸如指标配置(常用指标固化、指标配置简化)和数据展示(交互优化、多维分析、归因分析)等进行优化和完善,持续提升用户体验。
参考资料:
版权声明: 本文为 InfoQ 作者【vivo互联网技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/271120ed39217cb81a2cc38c9】。文章转载请联系作者。
评论