互联网AbTest系统设计思考

1字数 2450阅读 17383

ABTest系统背景

互联网与传统软件行业的开发最大区别就是快速迭代,新增一个业务或者新增一个基于老业务的算法更新也许只是某个工程师一天的结果。在这种代码高速发布过程中, 必然存在结果和期望符合的情况,也存在不怎么符合的情况, 当然更多的是存在结果与期望不相符的情况。正是由于这个原因, 小流量上线测试是比较合理的上线方法, 上线之后必须对这份测试流量进行效果追踪,根据追踪效果做出后续决定,是继续加大流量呢,还是保持观察,或者是回滚代码。所有上述操作需要有一个系统辅助高效地解决,我理解的AbTest系统就是基于上述需求产生的。

我做ABTest的背景介绍

我所在的团队是搜索业务团队,简而言之就是把搜索引擎的匹配结果和算法的排序结果通过产品经理的意愿呈现给用户,当然除了这个基本业务还有一些和搜索相关的小业务,比如搜索提示,导航,相关词推荐等。
我在接上述业务需求的时候,可以归纳成以下两个维度的需求:

  • 产品经理提出的需求一般会从业务层面上对比效果(比如搜索提示新功能上线后对搜索的影响 )
  • 算法层面上的效果对比(还是上面这个例子, 搜索提示功能分流量上线之后,后面有其他人需要优化搜索提示算法,那么算法工程师就需要对比这两种算法的效果)

为了满足产品经理和算法工程师快速切换功能,快速看到效果,ABTest系统就开始逐步规划了。

设计故事

我以搜索提示ABTest举例,解释整个ABTest系统工作流程。我一开始接触搜索业务时,搜索的功能就是接受关键词,结果是匹配关键词的商品。基本业务如下图表示,方框表示功能模块走的流量范围,此时表示没有分流量测试全部走搜索基本业务。

全流量走搜商品业务

按照上一节的场景,搜索提示功能出现了,搜索提示功能即对用户输入做智能补全。用户可以点击提示的候选词列表,进行搜索流程最终展现结果。这个时候产品经理的需求是50%流量走搜索提示测试,其余保持原状。下面先简单介绍下分流量的问题。

分流量简单介绍

分流量原则上需要保证均匀性和一致性。均匀性指的是流量唯一标识符取摸后均匀地落在每个区间。举个例子,使用cookid通过标识符生成算法模块生成一个唯一标识符(uuid),我对全站流量分成100份,那么uuid%100的值0~99,必须做到每个值分配的流量几乎相同。一致性指的是某个流量生成uuid后再取摸的值是一定的,比如某个cookid经过算法模块后取摸的值为1,那么下次再次经过uuid生成算法取摸后值还是为1。大家可以百度一下保证这两个特性的算法。

根据50%的流量分法,我给出如下示意图。


50%流量上线

50%使用搜索的用户会有搜索提示这个功能,其余只能直接搜商品。这个层次结构正好解决了搜索提示分流量上线的需求。效果追踪稳定后,搜索提示改造算法出现了,我会在原先结构上新增一份对比测试流量。

两种搜索提示算法各占原先测试流量的一半

当然故事会继续,产品经理认为搜索提示这个功能可以全量上线,那么此时划分结构会按原来的比例扩展并覆盖原先非测试的流量。

搜索提示功能全量上线并且保持搜索提示算法AB测试.png

如果我的业务只有一个功能做AB测试,以上流程就可以解决了。但是业务工程师必须要多想一下,万一在原来的场景下又多一种分流量测试怎么办?我不可能停止搜索提示测试吧。支持多种类型测试的ABTest系统,最重要的是各个类型实验之间互不干扰。 对于上面提到的搜索商品基本业务,算法工程师提出优化原来的排序算法,需要接入1/3的搜索流量, ABTest系统流量划分示意图如下所示。按照这个流量划分思路会引来一个问题,搜索排序A的流量全部来自搜索提示A,搜索排序B的流量大部分来自搜索提示小部分来自搜索提示A。搜索提示算法对搜商品排序算法影响是不均匀的。我的解决方法是对实验类型分层编号,uuid生成方法为根据(cookid,layerid)生成。这样每一层的实验类型不管有多少个实验对其他层实验的影响都是均匀的。此时搜商品排序A和搜商品排序B都有来自搜索提示AB的流量。

1/3流量走搜商品排序A,其余走搜商品排序B

到这里为止,ABTest系统通过分层结构已经可以满足任意种类的AB测试。我把这个想法和算法组老大沟通后,他基本也是满意的,只是提出了还有一种独占性流量测试需求,比如我划一个区域搜商品排序A只能来自搜索提示A,其他的还是按原先流程进行。那么现在就需要引入域的概念,我对域的理解是一个流量划分,域中会有不同类型的实验。我给出一些包含域的分层实验示意图,读者看完后应该会有所理解。左上角图中有三个域,最外层的域中包含两个子域,每个子域中有实验层。右上角图中也包含三个域,其中左边域中有三个实验。域和域的流量是互不干扰的,比如左上角图中的搜索提示A的实验流量不会作用于搜商品排序B。同个域中的流量分配规则和没有引入域的分层实验规则一致。

划分域的分层实验示意图

实现方法

我的实现方法是把上述示意图抽象成一棵多叉树,节点是域和实验层的列表。为了更加通用化解释,我给出一个较为复杂的流量划分示意图。父节点的流量范围是0-100,这个域中有两个实验层和两个实验域,层下面是实验,域下面还有子域。业务接入方可以根据cookid和层id(这个有ABTest系统给出)通过遍历这棵树,获取正在测试的实验配置。

比较通用的ABTest流量划分图.png
搜索提示ABTest数据结构.png

总结

以上设计流程参考google 的ABTest分层实验模型,那篇文章讲述过于抽象,我按照自己碰到的问题结合模型叙述了一遍。读者在理解的时候肯定会遇到些问题,可以评论留言,我在这里只是做简单的分享。

后续系统规划

本文所说的流量划分方法是按uuid均匀划分,未来的划分方式会有更多样化,比如按兴趣爱好分,按地域分等等。这些划分方法也是可以集成到ABTest系统中,业务方可以调用类似DivideBy...这样的API调用获取ABTest实验数据。当然还有效果实时统计,最终当然是ABTest上线之后立马能看出结果。

我的思考

工作2快两年了,渐渐开始领悟做业务开发的含义。刚毕业那会(应该大部分同学刚毕业的时候),多多少少都怀揣着干高端技术,实现伟大码农梦的理想。比如一个引擎优化提升多少性能,一个算法gmv翻倍的各种yy想法,然而做业务技术总让人觉得很low,就是脏活累活。现在来看,做业务确实充斥着各种脏活累活,但是需要思考的是为什么会有这么多脏活累活?这些脏活累活是不是可以用一些专业的系统去解决,像上面讲的ABTest系统就是对分流量测试的一个通用抽象系统。那么其他活我们是不是也能创造一些系统去解决呢?因此我定义业务开发工程师从技术的角度应该称作系统开发工程师,但是系统的形成必须解决现实中的存在的各种相似的业务需求,否则不能解决问题的系统就是个装逼系统吧。我现阶段的结论,一名优秀的系统开发工程师,从业务层面上必须要了解需求,抽象需求,并且又得满足可能扩展的需求;从技术层面上必须要会用各种解决问题的技术,不需要要了解得多深入,至少要知道怎么用。

推荐阅读更多精彩内容