Chinaunix首页 | 论坛 | 博客
  • 博客访问: 58914
  • 博文数量: 40
  • 博客积分: 1607
  • 博客等级: 上尉
  • 技术积分: 382
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-09 16:35
文章分类

全部博文(40)

文章存档

2011年(1)

2010年(30)

2009年(9)

我的朋友

分类: LINUX

2010-07-08 17:49:40

大二上学期就立了一个项目,要Varnish-2.0.4加入一个Hash的负载均衡算法(或者叫做负载均衡器)。这样可以在大规模服务器机群中为http请求快速找到空闲的后端服务器,提高http请求响应速度。

因为各种琐碎的事情以及自己和项目小组的拖拖拉拉,终于在第二学期末才把这个项目完成。不过遗憾的是,在我们完成之前,Varnish开发团队已经推出了Varnish版本2.1.0,在这个版本里已经实现了。在此将我们的实现方式与专业的Varnish团队的实现方法作以对比,当做总结和学习。

要使用Varnish负载均衡器,首先用户需要在配置文件中加入了相应的“director”关键字(2.0.4版中只有randomround-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_ParseIpHashDirectordirector ip-hash的配置进行解析,将ip-hash的配置转化成C语言代码,加入临时文件中。

Varnish-2.1.0的实现中,通过在director的解析列表中加入“hash”关键字,并用vcc_ParseRandomDirector函数进行解析,然后将hash的配置转成C语言代码加入临时C文件。vcc_ParseRandomDirectorVarnish-2.0.4中已经存在,2.1.0中对它的实现进行了修改。通过加入标志字来区别randomhash的均衡器。

解析完成,之后就是算法的具体实现部分了。

我们的均衡器,是提取将每次会话所得的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 charip地址。2.1.0直接利用此iphash值,并利用此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) |
给主人留下些什么吧!~~

chinaunix网友2010-07-23 16:08:14

不错,有前途,继续努力 话说怎么无法在留言板里面打字,我在记事本里面打好贴过来才行