产品的改变不是由咱们随便「拍脑壳」得出,而是须要由实际的数据驱动,让用户的反馈来指导咱们如何更好地改善服务。正如马蜂窝 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 分流
响应时间分布
TPS 分布
测试工具采用 JMeter,并发数 100,持续 300s。
从响应时间来看,除了刚开始的时候请求偏离值比较大,以后平均起来都在 1ms 之内。分析刚开始的时候差距比较大的缘由在于当时的多级缓存里面没有数据。
TPS的压测表现有一些轻微的降低,由于毕竟存在 hash 算法,但整体来讲在能够接受的范围内。
常规 A/B 发布主要由 API 网关来作,当面临的业务需求比较复杂时, A/B 发布会经过与与微服务交互的方式,来开放更复杂维度的 A/B 发布能力。
须要注意的是,ABTest 并不彻底适用于全部的产品,由于 ABTest 的结果须要大量数据支撑,日流量越大的网站得出结果越准确。一般来讲,咱们建议在进行 A/B 测试时,可以保证每一个版本的日流量在 1000 个 UV 以上,不然试验周期将会很长,或很难得到准确(结果收敛)的数据结果推论。
要设计好一套完整的 ABTest 平台,须要进行不少细致的工做,因为篇幅所限,本文只围绕分流算法进行了重点分享。总结看来,马蜂窝 ABTest 分流系统重点在如下几个方面取得了一些效果:
采用流量拦截分发的方式,摒弃了原有接口的形式,对业务代码没有侵入,性能没有明显影响,且不会产生二次流量。
采用流量分层并绑定实验的策略,能够更精细直观的去定义分流实验。经过和客户端上报已命中实验版本的机制,减小了服务数据的存储并能够实现串行实验分流的功能。
在数据传输方面,经过在 HTTP 头部增长分流信息,业务方无需关心具体的实现语言。
近期规划改善:
监控体系。
用户画像等精细化定制AB。
统计功效对于置信区间、特征值等产品化功能支持。
经过 AARRR 模型评估实验对北极星指标的影响。
这套系统将来须要改进的地方还有不少,咱们也将持续探索,期待和你们一块儿交流。
本文做者:李培,马蜂窝基础平台信息化研发技术专家;张立虎,马蜂窝酒店研发静态数据团队工程师。
(马蜂窝技术原创内容,转载务必注明出处保存文末二维码图片,谢谢配合。)
关注马蜂窝技术,找到更多你须要的内容