转:A/B测试:实现方法
概念:http://www.aliued.cn/2010/09/13/ab-testing-basic-concept.html
我们先来看一个图:

(注:感谢Algo提供本图。)
上图展示了 A/B 测试的实现原理。从左到右,四条较粗的竖线代表了 A/B 测试中的四个关键角色:客户端(Client)、服务器(Server)、数据层(Data)、数据仓库(Data Warehouse)。从上到下代表了三种访问形式:无 A/B 测试的普通访问流程(Non AB test)、基于后端的 A/B 测试访问流程(Back-end AB test)、基于前端的 A/B 测试访问流程(Front-end AB test)。
一般情况下,用户在一次浏览中,会从客户端(Client)发起一个请求,这个请求被传到了服务器(Server),服务器的后台程序根据计算,得出要给用户返回什么内容(Data),同时向数据仓库(Data Warehouse)添加一条打点信息,记录本次访问的相关信息。这个过程也就是图上横向的流程。数据仓库收集到足够的数据之后,就可以开始进行分析(Analytics)了,这也即是图中右上角的部分。
A/B 测试需要将多个不同的版本展现给不同的用户,即需要一个“分流”的环节。从上图中我们可以看到,分流可以在客户端做,也可以在服务器端做。传统的 A/B 测试一般是在服务端分流的,即基于后端的 A/B 测试(Back-end AB test),当用户的请求到达服务器时,服务器根据一定的规则,给不同的用户返回不同的版本,同时记录数据的工作也在服务端完成。
基于后端的 A/B 测试技术实现上稍微简单一些,不过缺点是需要技术部工程资源介入,另外收集到的数据通常是比较宏观的PV(Page View)信息,虽然可以进行比较复杂的宏观行为分析,但要想知道用户在某个版本的页面上的具体行为往往就无能为力了。
基于前端的 A/B 测试则可以解决上面的问题。它的特点是,利用前端 JavaScript 方法,在客户端进行分流,同时,可以用 JavaScript 记录下用户的鼠标行为(甚至键盘行为,如果需要的话),直接发送到对应的打点服务器记录。这样的好处是不需要技术部(如果你们和我们一样,前端工程师与后端工程师分属不同部门的话)参与,并且可以比较精确地记录下用户在页面上的每一个行为,甚至包括后端方法难以记录到的无效点击!
下面,我将重点介绍一下我们在基于前端的 A/B 测试上的一些实践。
一、分流
首先遇到的问题是如何分流的问题。对于大部分需求来说,我们希望各个版本的访问人数平均分配。解决办法有很多种,比较简单的一种即是前面提到过的,根据某一个 Cookie ID 来划分用户,前提是你的网站上每一位访客在第一次访问时就要有一个不重复的 Cookie ID,比如“123.180.140.*.1267882109577.3”。然后,可以根据这个 Cookie ID 的最后一位(在本例中是“3”)来划分人群,比如单数的显示 A 版本,偶数的显示 B 版本。
因为 Cookie ID 一般设定后不会轻易改变,基于 Cookie ID 的好处是我们能很好地对访客保持一致性,某个用户如果第一次看到的是 A 版本,那他刷新后看到的还是 A 版本,不会一会儿看到 A 版本一会儿看到 B 版本。但不足之处就是如果用户浏览器不支持 Cookie 的话,分流就不能正常进行了。不过,现代浏览器默认情况下都是支持 Cookie 的,如果真有用户的浏览器不支持 Cookie ,那也应该是极少数特殊情况,对结果的影响非常微小,对于这些特殊情况,我们一般可以安全地忽略掉。
还有一点需要注意的是,A/B 测试的页面必须有较高的 UV (Unique Visitor,独立访客数),因为分流带有一定的随机性,如果页面 UV 太小,分到每一个版本的人数就更少,结果很有可能被一些偶然因素影响。而 UV 较大时,根据大数定理,我们得到的结果会接近于真实数据。就像想知道一个地方的成年人的平均身高,当然是取的样本越大结论越可信。
二、展示
决定向当前访问者显示哪个版本后,怎么用前端的方法加载对应的版本呢?这需要分情况处理。
一般情况下,如果两个版本只有一个较小的区域不一样,我们可以同时将两个区域的 HTML 都加载到当前页面中,先用 CSS 把它们隐藏起来(也可以默认显示一个版本),等 JS 判断出该显示哪个版本后,再控制对应版本的 CSS 显示。
有时候,测试区域比较大,代码比较多,或者需要后台较多的计算资源,如果一开始就把两个版本的 HTML 全加载到当前页面中,就会需要比较大的开销(比如带宽、后台计算量)。这种情况下,我们可以先把测试区留空,之后再用 Ajax 的方式延迟加载。
还有的时候,测试区域非常大,几乎占了整个页面,或者完全就是不同的页面,这时,用 Ajax 方式加载也不适合了,可以将不同的版本做成不同的页面,然后再用 JS 跳转。不过这样的方式并不是很好,因为前端 JS 跳转需要一定的时间,这个过程很有可能被用户感受到,并且留下不好的体验。对这个问题,似乎没有很好的解决办法,至少在前端层面很难完美解决,所以并不是非常推荐这种跳转方式,如果真的需要跳转,最好是在服务器端由后端代码来操作。
三、数据采集
正确展示对应的版本后,就要开始采集需要的数据了。有一个可选的数据,是当前版本有多少 PV (Page Views,访问量),如果需要记录这个数据的话,在正确版本加载完成之时就要发送一个打点信息。不过很多需求中,具体版本的 PV 的精确数值可能不是很重要,而且要收集这个信息需要多一次打点操作,所以一般情况下这个数据是可选的。
必须的数据是测试区域内用户的点击信息。当用户在测试区域点击了鼠标左键(无论这个点击是点击在链接、文字、图片还是空白处),我们就需要发送一条对应的打点信息到打点服务器。一般来说,这个打点信息至少需要包含以下数据:
当前 A/B 测试以及版本标识
点击事件的位置
点击时间戳(客户端时间)
当前点中的URL(如果点在非超链接区域,此项为空)
用户标识(比如 Cookie ID)
用户浏览器信息
为了尽可能精确地还原用户的点击位置,我们的页面对前端有比较高的要求,要求页面在不同的浏览器下有基本一致的表现,至少在IE6、7、8以及 Fiefox 下,页面横向的元素要精确一致,纵向上很难做到完全一致,但也要尽可能保持统一。另外,这样的测试也不太适合自适应宽度的页面,比较适合定宽的页面,为了避免不同分辨率下页面左右空白不同导致鼠标点击位置的不同,点击位置取的应该是相对于测试区域左上角的位置。除此之外,最好再记录一下测试区域相对于页面内容左上角的位置,在后面还原点击分布图以及绘制热区图时会用到这个数据。
这一阶段的流程大致如下图所示:

数据打点该如何发送以及如何存储呢?这要取决于你的打点服务器如何存储信息。
四、数据存储
我们使用了一台专用的服务器收集打点信息,为了能支持尽可多尽可能密集的打点请求,这台服务器的 apache 服务网站目录下只有两个静态文件,分别是 abtest.html 和 abtest.gif ,两者都是非常小的空白文件(空白图片)。访客端进行打点时,只需要以 GET 的方式带上相关的参数请求两个文件中的任意一个即可。比如:
http://abtest.xxx.com/abtest.gif?abid=1-a&clickBlockX=244&clickBlockY=372&clickBlockW=392&clickBlockH=76&clickTime=1263264082137&clickRX=233&clickRY=47&clickURL=&clickBeaconID=123.180.140.*.1267882109577.3&browserType=FireFox
这个请求可以通过 Ajax 的方式发送,也可以通过 JS 在页面上创建 new Image() 对象的方式完成。
对打点服务器来说,这只是一条普通的 HTTP 请求,它会在日志里留下一条普通的日志记录,形如:
123.180.140.* – – [13/Jan/2010:15:21:15 +0800] “GET /abtest.gif?a=123&b=456&c=789 HTTP/1.1″ 304 – “-” “Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.6 (KHTML, like Gecko) Chrome/4.0.266.0 Safari/532.6″
可以看到了,除了 JS 发送给我们的信息外,Apache 还帮我们记录了一些信息,比如访客 IP 、服务器时间、用户浏览器信息。
对于数据记录和存储来说,到这一步就足够了。Apache 静态文件 + 日志的方式足够高效,基本不用担心性能的问题。剩下的,就是另外一个问题,如何从 Apache 日志中读取打点信息并加以分析,这已经和前端无关了,并且是一个比较复杂的问题,将在后续日志中介绍。
转:A/B测试:实现方法的更多相关文章
- Xcode 真机测试破解方法(转加修改)xcode 4.3 通过
Xcode 真机测试破解方法(转加修改)xcode 4.3 通过 生成本机证书 应用程序->实用工具->钥匙串访问 菜单:钥匙串访问->证书助理->创建证书, 然后按以下图片顺 ...
- junit测试延伸--方法的重复测试
在实际编码测试中,我们有的时候需要对一个方法进行多次测试,那么怎么办呢?这个问题和测试套件解决的方案一样,我们总不能不停的去右键run as,那怎么办呢?还好伟大的junit帮我们想到了. OK,现在 ...
- 使用电脑ODBC测试数据库连接方法
使用电脑ODBC测试数据库连接方法 一.打开电脑的控制面板——管理工具——数据源(ODBC),在用户dsn页面中点击添加按钮,选择IBM DB2 ODBC DRIVER,点击完成. 二.在弹出的配置页 ...
- Go单元测试注意事项及测试单个方法和整个文件的命令
Go程序开发过程中免不了要对所写的单个业务方法进行单元测试,Go提供了 "testing" 包可以实现单元测试用例的编写,不过想要正确编写单元测试需要注意以下三点: Go文件名必须 ...
- centos LB负载均衡集群 三种模式区别 LVS/NAT 配置 LVS/DR 配置 LVS/DR + keepalived配置 nginx ip_hash 实现长连接 LVS是四层LB 注意down掉网卡的方法 nginx效率没有LVS高 ipvsadm命令集 测试LVS方法 第三十三节课
centos LB负载均衡集群 三种模式区别 LVS/NAT 配置 LVS/DR 配置 LVS/DR + keepalived配置 nginx ip_hash 实现长连接 LVS是四层LB ...
- 在MS Test中如何测试private方法
前言: 在博客开始之前,我们先讨论一下是否应该对private方法做测试,通常有两种观点: private方法应该被测试: private方法不应该被测试: 我们以下面的代码为例子来进行说明: pub ...
- 实验二 输出“Hello Word!”,测试主方法的输入参数和心得体会
实验二 一.输出“Hello World!” 1.首先打开eclipse,如下图所示. 2.选择一个工作空间,如下图所示. 3.接下来,点击[Java项目]创建一个Java项目,如下图所示. 4.然后 ...
- Go 测试单个方法
1.目录 gotest.go package mytest import ( "errors" ) func Division(a, b float64) (float64, er ...
- JUnit 3.8 通过反射测试私有方法
测试私有(private)的方法有两种: 1)把目标类的私有方法(修饰符:private)修改为(public),不推荐,因为修改了源程序不佳 2)通过反射 (推荐) 代码演示: 目标程序 Priva ...
- unittest对单个测试类的多种测试执行方法总结
基于unittest测试框架编写的测试脚本,一般单个测试类下会有多个测试方法,unittest也提供多种测试执行方式,下面就不同方式或者需求一一实操并说明: 一.使用unittest下main()方法 ...
随机推荐
- Python3_实例汇总
1.Python数字求和 # -*- codingLuft-8 -*- #Filename: test.py #author by:Leq #用户输入数字 num1 = input("输入第 ...
- 我的Android进阶之旅------>解决错误:You need to use a Theme.AppCompat theme (or descendant) with this activity.
#1.错误描述 今天,想实现Activity不显示标题栏的效果,在项目的AndroidManifest.xml文件,对相应的Activity添加属性 android:theme="@andr ...
- linux执行run文件显示cannot execute binary file
感谢朋友支持本博客,欢迎共同探讨交流,因为能力和时间有限.错误之处在所难免,欢迎指正! 假设转载,请保留作者信息. 博客地址:http://blog.csdn.net/qq_21398167 原博文地 ...
- hibernate自动建表之engine设置
1.MYSQL的数据库引擎中,只有InnoDB和BDB(Berkley DB )包括了对事务处理和外键的支持.如果数据引擎建为MyISAM则rollback无效. 2.而hibernate自动建表的时 ...
- oracle 禁用索引
同步数据的时候 有索引会比较慢 可以暂时禁用索引 --禁用索引 ALTER INDEX PK_T_AUTH_USERROLE_ID UNUSABLE; --恢复索引ALTER INDEX UK_T_A ...
- Angular学习笔记—路由(转载)
创建路由 1.首先安装 Angular Router.你可以通过运行以下任一操作来执行此操作: yarn add @angular/router # OR npm i --save @angular/ ...
- gdb各种调试命令和技巧
陈皓:用GDB调试程序 GDB概述———— GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像VC.BCB等IDE的调试,但如果你是在UNIX平台 ...
- c#的yield return
4.1 迭代器块 一个迭代器块(iterator block)是一个能够产生有序的值序列的块.迭代器块和普通语句块的区别就是其中出现的一个或多个yield语句. yield return语句产生迭代的 ...
- return false 和 return true
常规用法 在普通函数中:return 语句终止函数的执行,并返回一个指定的值给函数调用者,一般会用一个变量接收这个返回值再进行其它处理.如果未指定返回值,则返回 undefined 其中,返回一个函数 ...
- 关于git bash的问题,pull不下来(登录之后,git帮你记住了,想切换其他用户)
参考博客: https://www.jianshu.com/p/8a7f257e07b8 从某个项目地址pull代码下来,老是报错 fatal: Authentication failed for ' ...