`
凉粉仔
  • 浏览: 40759 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

nginx做A/B测试

 
阅读更多

事先声明,这里说的A/B测试跟工具ApacheBench没有半毛钱关系。这里说的是关于web页面转化率统计方面的测试,点击这里看其解释。A/B测试是目前很多大公司采用的一种科学的统计方法。使用了这种方法后,就再也不需要争吵到底是A图片好还是B图片好了。一切看统计数据。

 

发布新版本web网站前,先做下A/B测试是最好的做法。那么,在服务器、代码结构方面应该如何实现呢?这个就是本文打算探讨的问题。

 

前提说明:本文只考虑静态页面的A/B测试。动态请求的A/B测试很容易实现,因此不作考虑。

 

 

最初的考虑:利用页面跳转

假设目标页面的URL是http://www.example.com/index.html,可以在index.html加一段随机跳转的代码,例如:

 

<script>
if(Math.random()>0.5){
	location.href='a.html';
}else{
	location.href='b.html';
}
</script>

 

这种实现的最大有点是实现简单,并且可以做很多自定义的判断。

但最大的缺点就是页面的URL会改变,而我们的目标是不让用户觉察出他正在被A/B测试,因此这种方式不列入考虑。

 

 

一:使用random_index命令

蔽司的服务器使用的是nginx服务器,所以我们先来看下nginx是否有自带的实现。

从google搜索了一下,找到了一个命令random_index文章链接。具体使用方法如下:

 

server {
   listen 80;
   server_name www.example1.com;
   location / {
      root /var/www/www.example1.com/test_index;
      random_index on;
   }
}

 

这个命令的作用是在当前目录随机挑选一个文件进行输出。不过它有一个缺点,就是同一个用户每次刷出来的页面可能是不一样的。

 

 

二:使用nginx user模块

介绍的文章请看这里,作者是大虾。由于我没实验过,因此我将文章内容直接抄过来给大家看下:

 

userid on;
userid_name uid;
userid_domain milanoo.com;
userid_path /;
userid_expires 365d;
if ( $uid_set ~ “^uid=(.{9})(.)(.+)$” ) {
set $serp $2;
set $uid $1$2$3;
}
if ( $uid_got ~ “^uid=(.{9})(.)(.+)$” ) {
set $serp $2;
set $uid $1$2$3;
}
set $fa A;
if ( $serp ~ “(A|B|C|D)” ) {
set $fa B;
}
##这个也可以出C方案D方案,就和 $serp分吧,但是必须是1/16的。。
log_format main ‘$uid_got – $serp – $uid_set’; //debug Log
access_log logs/php.log main; //debug//
#####要想做a/b test对$serp进行正则即可。。###
fastcgi_pass 127.0.0.1:9000;
fastcgi_param FA $fa #将方案号传递给php $_SERVER['FA']
fastcgi_param UID $uid; #传递给php $_SERVER['UID']

 

感觉有点复杂了,因此我不打算使用这种方法。

 

 

三:使用ip_hash

ip_hash也是nginx自带的命令,通常用于负载均衡。配置方法:

 

upstream backend {
ip_hash;
server 211.100.26.100:80;
server 211.100.26.101:80;
}

 

这种方法实现简单,但是不能通过weight来分配权重。同一个IP访问,每次得到的结果是一致的。

这种实现方式有缺点。假如你的server上面有数千个静态页面,而你需要做A/B测试的或许只有一个页面,你仍然得将所有代码都COPY到每个机器上面去。

 

 

四:第二种ip匹配方法

这种方法是使用nginx的正则匹配上打主意,请看代码:

 

	location / {
            if ($remote_addr ~ "[02468]$") { 
                rewrite ^(.+)$ /experiment$1 last; 
            }
            rewrite ^(.+)$ /main$1 last;
        }

        location /main {
            internal;
            proxy_pass http://www.reddit.com/r/lisp;
        }

        location /experiment {
            internal;
            proxy_pass http://www.reddit.com/r/haskell;
        }

简单解释一下代码的意思,$remote_addr表示用户的IP。第一个判断语句的意思是,如果IP第四段最后一个数字,如果是0、2、4、6、8,就转向到test中去。这种方式也可以调整概率。假如我希望experiment页面显示的概率是20%,那么我可以这样写: $remote_addr ~ "[0]$" 这样就实现了概率的控制。同一个IP访问服务器时,每次得到的结果都是一样的。

这种方式也是我目前最主要使用的一种方法。

 

 

 

五:自己写一个fastcgi程序

这种实现方式是我自己想的。我编写了一个abtest.cgi的fastcgi程序。

访问方式:/abtest.cgi?templist=a.html;b.html;c.html;d.html。参数templist是需要显示的不同页面。

我只需要以SSI的方式将这段代码嵌入到页面中,就神不知鬼不觉了。

下面贡献出我写的代码:

 

#include "fcgi_server.h"
#include "fcgi_processor.h"
#include "fcgi_stdio.h"
#include <string>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <vector>
#include <map>
using namespace std;

/*
[A/B测试通用类]说明:
本CGI的访问形式为:
/abtest?ip=$REMOTE_ADDR&templist=page_a.html;page_b.html;page_c.html
ip参数表示用户的IP地址,templist表示模板列表。

思路:
将用户的IP地址切成4个整数相加,然后%模板数量,得到模板下标。
然后根据得到的模板下标,读取相应的模板,输出。
*/

#define BLOCK_SIZE 40960

//切割字符串的函数,用于后面处理IP hash
vector<string> explode(const char *sep , const char* str){
	int i,last_i;
	string ss(str),block;
	vector<string> ips;

	size_t len = strlen(str);
	for(i=0,last_i=0;i<len;i++){
		if(str[i] == sep[0]){
			block = ss.substr(last_i , i - last_i).c_str();
			ips.push_back(block);
			//cout<<block<<endl;
			last_i = i + 1;
		}
	}
	block = ss.substr(last_i , len - last_i).c_str();
	ips.push_back(block);
	//cout<<block<<endl;

	return ips;
}
//IP转化为INT以便hash
int ip2int(const char* str){
	const char *sep = ".";
	vector<string> ips;
	ips = explode(sep , str);
	return atoi(ips[0].c_str())+atoi(ips[1].c_str())+atoi(ips[2].c_str())+atoi(ips[3].c_str());
}
string file_get_contents(string filename){//读取静态文件内容以输出
	ifstream input;
	char c;
	int n=0;
	char html[BLOCK_SIZE] = "";

	filename = "/path/to/your/document/abtest/" + filename;
	input.open(filename.c_str());
	if(input.fail())
	{
		cerr<<" File not found"<<endl;
		return "404 File not found";
	}

	while(!input.eof())
	{
		input.get(c);
		html[n++] = c;
	}

	input.close();

	return html;
}
string ip2html(std::string ip_addr , std::string templist){
		int value = ip2int(ip_addr.c_str());
		map<string,string> mytemplates;
		string mytemp;
		const char *sep = ";";
		vector<string> temps;

		temps = explode(sep , templist.c_str());
		mytemp = temps[value % temps.size()];
		return file_get_contents(mytemp);
}

class MyProcessor : public FcgiProcessor
{
public:
	void HandleRequest(FcgiRequest* pRequest, FcgiResponse* pResponse)
	{
		pResponse->SetContentType("text/html");
		pResponse->PrintHeader();

		std::string myip = pRequest->GetClientIp();
		std::string templist = pRequest->GetParam("templist");

		printf(ip2html(myip , templist).c_str());
		printf("<div style='display:none;'>");
		printf("IP:[%s]\n" , myip.c_str());
		printf("</div>");
	}
};

int main()
{
	MyProcessor processor;
	FcgiServer svr(&processor);
	svr.Run();
	return 0; 
}

(这个代码写得烂别见怪。我只是个PHPer,C++代码只在大学时学过,丢光了。)

 

使用这种方式实现有一个好处,就是可以实现很多个性化的功能,而你只需要稍微修改一下上面的代码。当然,前提是你必须懂C++和fastcgi。

 

看到我这里使用C++写了一个fastcgi接口,可能你会说既然这样何不使用PHP?主要的原因是PHP太笨重了,对于每秒钟千次的请求,PHP必死无疑。

 

 

六:使用SSI判断

既然可以在nginx.conf中使用条件判断,那么是不是也可以使用SSI中的条件判断来实现呢?这样的话,就更具有通用性了。

笔者我昨天花了一个下午做实验,在apache下实验成功了,可是在nginx中却不行。在谷歌中搜索了一下,说是nginx对SSI的支持还不够好。

希望往后新版本的nginx能将这一块继续优化一下啦!

 

 

分享到:
评论

相关推荐

    基于C的Nginx Virtual Host A/B Testing设计源码

    本源码是基于C开发的Nginx Virtual Host A/B Testing设计,包含91个文件,其中包括29个.json文件,19个.h文件,以及19个.c文件。此外,还包括4个.md文件,3个.yml文件,...该项目是一个Nginx虚拟主机A/B测试的设计。

    在linux系统下安装两个nginx的简单方法

    在实际工作中,有时我们需要在同一台Linux服务器上安装并运行多个Nginx实例,以满足不同项目的需求或进行A/B测试等操作。本文将详细介绍如何在Linux系统下安装两个Nginx,并确保它们能够正常运行而不互相干扰的方法...

    nginx.1.10

    Nginx 提供 A/B 测试模块,允许在不同用户间测试多种版本的页面或应用。 通过以上特性,Nginx 1.10 成为了构建高效、稳定、安全的 Web 系统的理想选择。其简洁的设计和强大的功能使其在 IT 领域中广受欢迎。对于...

    nginx 端口映射

    b) 安装 nginx-1.3.13.tar.gz 1. cd /home/rick -- 进入软件目录 2. tar -zxvf nginx-1.3.13.tar.gz -- 解压 3. cd nginx-1.3.13 -- 进入目录 4. ./configure --prefix=/usr/local/nginx -- 设置安装路径 5. make 6...

    Nginx正向代理http和https.md

    - **目标**:配置Nginx正向代理服务,使得服务器B可以通过服务器A访问http和https资源。 #### 2. 安装依赖环境 在服务器A上(IP地址为192.168.252.247)安装必要的依赖库: ```shell yum install gcc gcc-c++ ...

    nginx 域名跳转nginx多域名向主域名跳转

    - **测试 DNS 解析**:可以使用 `nslookup` 或 `dig` 命令来测试域名是否成功解析到对应的 IP 地址。例如: ```bash nslookup mgcrazy.com nslookup www.mgcrazy.com ``` ##### 2. Nginx 配置 - **编辑 Nginx ...

    详解Nginx服务器中配置超时时间的方法

    在Nginx服务器的配置中,超时时间的设置至关重要,因为它关系到服务器对客户端请求的响应速度和系统的稳定性。本文将深入讲解如何在Nginx中配置超时时间,并介绍相关的参数设置。 首先,我们需要了解何时需要设置...

    nginx-rtmp-module

    ffmpeg -re -i input.mp4 -c:v libx264 -preset veryfast -c:a aac -ar 44100 -ac 2 -b:v 500k -maxrate 500k -bufsize 1000k -vf "scale=w=1280:h=720,format=yuv420p" -b:a 128k -f flv rtmp://your-server-...

    Nginx 应用技术指南

    **15.5 Nginx的内存池管理分析(b)** 继续深入探讨Nginx内存池管理的相关技术细节。 **15.6 Nginx数据结构数组,列表** 探讨Nginx内部使用的数据结构,如数组和链表等。 **15.7 Nginx源代码分析** 对Nginx的源代码...

    高性能Web服务器Nginx的配置与部署.pdf

    Nginx是一款高性能的Web服务器和反向代理服务器,以其轻量级、高效能和高并发处理能力在互联网行业中广泛应用。本文件主要介绍了Nginx的配置与部署,特别是Rewrite规则的设置以及Nginx的基本操作命令。 1. **Nginx ...

    Nginx安装配置、Resin安装配置说明文档

    a)对c:\nginx\conf\nginx.conf文件进行配置: - 1 - b)常用的 Nginx 参数 - 3 - c)静态文件处理 - 4 - d)动态页面请求处理 - 4 - e)下面为nginx.conf配置实例: - 5 - f)Nginx 启动,停止等命令 - 8 - (2) Resin安装...

    nginx外网访问内网站点配置操作

    配置完成后,需要执行sudonginx -t来测试nginx配置文件是否有误,如果没有问题,再执行sudonginx -s reload来重载配置文件,使修改生效。 3. 处理可能出现的错误。如果在重启nginx时遇到错误nginx:[error] invalid ...

    跟我学Nginx+Lua开发

    4. **A/B测试**:通过Lua脚本来实现不同版本的服务或界面之间的切换,以进行A/B测试。 5. **流量复制**:通过Lua脚本复制一部分请求到另一个目的地,用于日志记录或数据分析。 6. **协程管理**:利用Lua的协程特性来...

    Nginx配置多个HTTPS域名的方法

    开发测试过程中,因为某些原因,想要让手头的A、B域名同时指向云服务器的443端口,支持HTTPS。 Nginx支持TLS协议的SNI扩展(同一个IP上可以支持多个不同证书的域名),只需要重新安装Nginx,使其支持TLS即可。 安装...

    nginx入门指南

    Nginx配置文件测试** 在修改Nginx配置文件后,可以通过命令`nginx -t`来检查配置是否正确无误。如果配置有误,Nginx会显示错误信息,帮助开发者快速定位问题所在。 **4. Nginx启动** 启动Nginx服务通常使用命令`...

Global site tag (gtag.js) - Google Analytics