大二上学期就立了一个项目,要Varnish-2.0.4加入一个Hash的负载均衡算法(或者叫做负载均衡器)。这样可以在大规模服务器机群中为http请求快速找到空闲的后端服务器,提高http请求响应速度。
因为各种琐碎的事情以及自己和项目小组的拖拖拉拉,终于在第二学期末才把这个项目完成。不过遗憾的是,在我们完成之前,Varnish开发团队已经推出了Varnish版本2.1.0,在这个版本里已经实现了。在此将我们的实现方式与专业的Varnish团队的实现方法作以对比,当做总结和学习。
要使用Varnish负载均衡器,首先用户需要在配置文件中加入了相应的“director”关键字(2.0.4版中只有random和round-robin均衡器),并用VCL语言为此“director”分配好后端服务器。举例如下:
backend breadtalk {
.host = "202.102.58.241";
.port = "80";
}
director footoo round-robin {
{ .backend = { .host = "210.51.**.**"; .port = "80"; } }
{ .backend = { .host = "221.231.**.**"; .port = "80"; } }
{ .backend = { .host = "210.51.**.**"; .port = "80"; } }
}
|
当Varnish启动时配置文件被VCL_complier解析,将包含director的所有配置信息,转化成C语言函数写入一个临时文件中,编译此文件生成动态链接库,供响应Http请求时选取相应策略。
下面先从配置文件的解析开始,对实现的方法进行比较。
在我们的实现方式中,在vcc_backend.c文件中director的解析列表里加入“ip-hash”关键字。调用加入的函数vcc_ParseIpHashDirector对director ip-hash的配置进行解析,将ip-hash的配置转化成C语言代码,加入临时文件中。
在Varnish-2.1.0的实现中,通过在director的解析列表中加入“hash”关键字,并用vcc_ParseRandomDirector函数进行解析,然后将hash的配置转成C语言代码加入临时C文件。vcc_ParseRandomDirector在Varnish-2.0.4中已经存在,2.1.0中对它的实现进行了修改。通过加入标志字来区别random和hash的均衡器。
解析完成,之后就是算法的具体实现部分了。
我们的均衡器,是提取将每次会话所得的sess结构体中所存储的来访ip信息,之后将ip信息的4个段一起求CRC32码,将所得的hash值对后端机群数量求模。所得的值即为响应此ip的后端服务器序号(此序号按照director配置的次序依次为0,1,2,…)。如果对应服务器不能正常响应,均衡器会按照round-robin的方式进行调度,找到能正常响应的后端服务器。Varnish-2.0.4中的sess存储ip的成员是“char * addr”,我们在求CRC32之前,首先对地址进行了转码,将字符串转化成4段的unsigned char 型ip地址。
具体实现代码如下:
static struct vbe_conn *
vdi_ip_hash_getfd(struct sess *sp)
{
unsigned char dst[5] = {0, 0, 0, 0, '\0'};
get_ip(sp->addr, dst);//这里将字符型的ip地址,转化成4个数字
unsigned char *p = dst;
struct vdi_ip_hash *vs;
struct backend *backend;
struct vbe_conn *vbe;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(sp->director, DIRECTOR_MAGIC);
CAST_OBJ_NOTNULL(vs, sp->director->priv, VDI_IP_HASH_MAGIC);
unsigned int regi = get_crc32(p);
backend = vs->hosts[regi % vs->nhosts].backend;
if(backend->healthy){
vbe = VBE_GetVbe(sp, backend);
if(vbe != NULL){
return (vbe);
}
}
if(!backend->healthy || vbe == NULL){
printf("!backend->healthy || vbe == NULL\n");
int i;
for (i = 0; i < vs->nhosts; i++) {
backend = vs->hosts[vs->next_host].backend;
vs->next_host = (vs->next_host + 1) % vs->nhosts;
if (!backend->healthy){
continue;
}
vbe = VBE_GetVbe(sp, backend);
if (vbe != NULL){
return (vbe);
}
}
}
return (NULL);
}
|
2.1.0的实现方式中,它用来保存会话信息的sess结构体中已经增加了“unsigned char digital[4]”成员,此数组中已经存储了4段的unsigned char型ip地址。2.1.0直接利用此ip求hash值,并利用此ip值定位到能够正常响应的后端服务器上。如果所定位的后端服务器无法正常响应,Varnish会在后端其它服务器中进行随机查找,直到找到正常的为止。
具体实现代码如下:
static struct vbe_conn *
vdi_random_getfd(const struct director *d, struct sess *sp)
{
int i, k;
struct vdi_random *vs;
double r, s1;
unsigned u = 0;
struct vbe_conn *vbe;
struct director *d2;
struct SHA256Context ctx;
unsigned char sign[SHA256_LEN], *hp;
CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(d, DIRECTOR_MAGIC);
CAST_OBJ_NOTNULL(vs, d->priv, VDI_RANDOM_MAGIC);
if (vs->criteria == c_client) {
SHA256_Init(&ctx);
AN(sp->addr);
SHA256_Update(&ctx, sp->addr, strlen(sp->addr));
SHA256_Final(sign, &ctx);
hp = sign;
}
if (vs->criteria == c_hash) {
hp = sp->digest;
}
if (vs->criteria != c_random) {
u = hp[3] << 24;
u |= hp[2] << 16;
u |= hp[1] << 8;
u |= hp[0] << 0;
r = u / 4294967296.0;
assert(r >= 0.0 && r < 1.0);
r *= vs->tot_weight;
s1 = 0.0;
for (i = 0; i < vs->nhosts; i++) {
s1 += vs->hosts[i].weight;
if (r >= s1)
continue;
d2 = vs->hosts[i].backend;
if (!VBE_Healthy_sp(sp, d2))
break;
vbe = VBE_GetFd(d2, sp);
if (vbe != NULL)
return (vbe);
break;
}
}
for (k = 0; k < vs->retries; ) {
s1 = 0.0;
for (i = 0; i < vs->nhosts; i++) {
d2 = vs->hosts[i].backend;
if (VBE_Healthy_sp(sp, d2))
s1 += vs->hosts[i].weight;
}
if (s1 == 0.0)
return (NULL);
if (vs->criteria != c_random) {
r = u / 4294967296.0;
} else {
r = random() / 2147483648.0; /* 2^31 */
}
assert(r >= 0.0 && r < 1.0);
r *= s1;
s1 = 0.0;
for (i = 0; i < vs->nhosts; i++) {
d2 = vs->hosts[i].backend;
if (!VBE_Healthy_sp(sp, d2))
continue;
s1 += vs->hosts[i].weight;
if (r >= s1)
continue;
vbe = VBE_GetFd(d2, sp);
if (vbe != NULL)
return (vbe);
break;
}
k++;
}
return (NULL);
}
|
这里的每个服务器用到了weight这个属性,这是Varnish-2.0.4中配置random director时就存在的一个属性,描述某台后端服务器被选取的权重。权重大的,被选取的几率增大。
以上两处为Varnish负载均衡器最核心内容,生成C文件后进行动态编译、调用的方式相同。
两种方式的实现方式差别并不是很大,但是比较可以发现Varnish团队推出的Varnish-2.1.0版本中hash均衡器的实现,整合性比较强,并且直接利用到原有的random均衡器的很多实现方式,这样的做法比较方便。我们的实现方式中,重写了专属于ip-hash的结构体和一些辅助函数,工作量较之2.1.0会有所增多,但是这种方式能够把模块化做的比较好,有利于今后功能的改进和扩展。
通过这个项目的实践,我们读开源代码的能力明显得到了提高,而且对于负载均衡的概念也有了比较深的理解,对于开源web服务器的工作原理以及代码实现有了进一步的认识,真是受益匪浅。
最后,感谢在一起开发Varnish哈希均衡器的兄弟们,感谢我们的指导老师,大二的生活因为你们而变得充实而精彩!
阅读(1239) | 评论(1) | 转发(0) |