varnish的负载分配模块的解析分析

Jun 16, 2010

要使用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请求时选取相应策略。

下面先从配置文件的解析开始,对实现的方法进行比较。

在我们的实现方式中,在vccbackend.c文件中director的解析列表里加入“ip-hash”关键字。调用加入的函数vccParseIpHashDirector对director ip-hash的配置进行解析,将ip-hash的配置转化成C语言代码,加入临时文件中。

varnish的解析函数主要存放在lib/libvcl目录下,其中vcc_parse.c文件中的结构体:

C语言
01 static struct toplev {
02     const char  *name;
03     parse_f     *func;
04 } toplev[] = {
05     { "acl",        vcc_Acl },
06     { "sub",        vcc_Function },
07     { "backend",        vcc_ParseDirector },
08     { "director",       vcc_ParseDirector },
09     { NULL, NULL }
10 };

记录了解析acl, sub, backend, director用到的函数。 项目涉及的url hash和ip hash属于director解析范畴之内,我们进入vcc_ParseDirector(/lib/libvcl/backend.c文件内)

C语言
01 /*现在着重对vcc_ParseDirector函数进行下分析*/
02 static const struct dirlist {
03     const char  *name;
04     parsedirector_f *func;
05 } dirlist[] = {
06     { "random",     vcc_ParseRandomDirector },          /*不同的负载均衡方式对应不同的解析方式,因为不同的负载均衡方式对应的配置方式有所不同*/
07     { "round-robin",    vcc_ParseRoundRobinDirector },
08     { NULL,     NULL }
09 };
10
11 void
12 vcc_ParseDirector(struct tokenlist *tl)
13 {
14     struct token *t_dir, *t_first, *t_policy;
15     struct dirlist const *dl;
16
17     t_first = tl->t;
18     vcc_NextToken(tl);      /* ID: director | backend */
19
20     vcc_ExpectCid(tl);      /* ID: name */
21     ERRCHK(tl);
22     t_dir = tl->t;
23     vcc_NextToken(tl);
24
25     Fh(tl, 1, "\n#define VGC_backend_%.*s (VCL_conf.director[%d])\n",
26             PF(t_dir), tl->ndirector);
27     vcc_AddDef(tl, t_dir, R_BACKEND);
28     tl->ndirector++;
29
30     if (vcc_IdIs(t_first, "backend")) {     /*vcc_IdIs函数查找配置文件中有"backend关键字", 下同*/
31         vcc_ParseSimpleDirector(tl, t_first, t_dir);
32     } else {
33         ExpectErr(tl, ID);      /* ID: policy */
34         t_policy = tl->t;
35         vcc_NextToken(tl);
36
37         for (dl = dirlist; dl->name != NULL; dl++)      /*依次查找确定配置文件中规定的director的解析方式*/
38             if (vcc_IdIs(t_policy, dl->name))
39                 break;
40         if (dl->name == NULL) {
41             vsb_printf(tl->sb, "Unknown director policy: ");
42             vcc_ErrToken(tl, t_policy);
43             vsb_printf(tl->sb, " at\n");
44             vcc_ErrWhere(tl, t_policy);
45             return;
46         }
47         ExpectErr(tl, '{');
48         vcc_NextToken(tl);
49         dl->func(tl, t_policy, t_dir);
50         if (!tl->err) {
51             ExpectErr(tl, '}');
52             vcc_NextToken(tl);
53         }
54     }
55     if (tl->err) {
56         vsb_printf(tl->sb,
57                 "\nIn %.*s specification starting at:\n", PF(t_first));
58         vcc_ErrWhere(tl, t_first);
59         return;
60     }
61 }

在确立了解析方式之后开始分析roundrobin的源文件(cachedirroundrobin.c):

C语言
001 #include "config.h"
002
003 #include
004 #include
005
006 #include
007 #include
008 #include
009 #include
010
011 #include "shmlog.h"
012 #include "cache.h"
013 #include "cachebackend.h"
014 #include "vrt.h"
015
016 /--------------------------------------------------------------------/
017
018 struct vdi
roundrobinhost {
019     struct backend          backend;       /保存vcl配置文件中的backend的所有信息信息,包括timeout,以及ipv4,ipv6等等信息/
020 };
021
022 struct vdiroundrobin {
023     unsigned            magic;
024 #define VDIROUNDROBIN_MAGIC       0x2114a178      /
魔法数,一般用来检验结构体是否有效/
025     struct director         dir;               
026         /
这个结构体可以说是本项目的核心结构体,之所以这么说是因为它定义了backend的选择机制(它同样是用的函数指针的方式,
027         这点跟nginx很想像,该结构体的初始化我们在下面的模块初始化部分介绍)
028         ,结构体定义是:
029         struct director {
030         unsigned        magic;
031             #define DIRECTORMAGIC      0x3336351d
032         const char      name;      /当前负载分配机制的名称/
033         char           
vcl
name;     
034         vdigetfdf     getfd;     /负载分配机制的核心函数指针/
035         vdifinif     
fini;      /退出当前负载分配的函数指针/
036         vdihealthy     healthy;   /健康检查函数指针/
037         void           
priv;
038         };
039         */
040     struct vdi
roundrobinhost hosts;
041     unsigned            nhosts;
042     unsigned            nexthost;
043 };
044
045 static struct vbe
conn

046 vdiroundrobingetfd(struct sess *sp)
047 {
048     int i;
049     struct vdi
roundrobin vs;
050     struct backend
backend;
051     struct vbe
conn vbe;
052
053     CHECKOBJNOTNULL(sp, SESSMAGIC);
054     CHECK
OBJNOTNULL(sp->director, DIRECTORMAGIC);
055     CASTOBJNOTNULL(vs, sp->director->priv, VDIROUNDROBIN_MAGIC);
056
057     for (i = 0; i < vs->nhosts; i++) {          /
roundrobin*/
058         backend = vs->hosts[vs->next
host].backend;
059         vs->nexthost = (vs->nexthost + 1) % vs->nhosts;
060         if (!backend->healthy)
061             continue;
062         vbe = VBEGetVbe(sp, backend);      /和当前选中的backend建立连接/
063         if (vbe != NULL)
064             return (vbe);
065     }
066
067     return (NULL);
068 }
069
070 static unsigned
071 vdi
roundrobinhealthy(const struct sess sp)
072 {
073     struct vdiroundrobin
vs;
074     int i;
075
076     CHECKOBJNOTNULL(sp, SESSMAGIC);
077     CHECK
OBJNOTNULL(sp->director, DIRECTORMAGIC);
078     CASTOBJNOTNULL(vs, sp->director->priv, VDIROUNDROBINMAGIC);
079
080     for (i = 0; i < vs->nhosts; i++) {
081         if (vs->hosts[i].backend->healthy)
082             return 1;
083     }
084     return 0;
085 }
086
087 /*lint -e{818} not const-able */
088     static void
089 vdi
roundrobinfini(struct director d)
090 {
091     int i;
092     struct vdiroundrobin
vs;
093     struct vdiroundrobinhost *vh;
094
095     CHECK
OBJNOTNULL(d, DIRECTORMAGIC);
096     CASTOBJNOTNULL(vs, d->priv, VDIROUNDROBINMAGIC);
097
098     vh = vs->hosts;
099     for (i = 0; i < vs->nhosts; i++, vh++)
100         VBE
DropRef(vh->backend);
101     free(vs->hosts);
102     free(vs->dir.vclname);
103     vs->dir.magic = 0;
104     vs->next
host = 0;
105     FREEOBJ(vs);
106 }
107
108     void
109 VRT
initdirroundrobin(struct cli cli, struct director *bp,
110         const struct vrt
dirroundrobin t)
111 {
112     struct vdiroundrobin
vs;
113     const struct vrtdirroundrobinentry te;
114     struct vdiroundrobin_host
vh;
115     int i;
116
117     (void)cli;
118
119     ALLOCOBJ(vs, VDIROUNDROBINMAGIC);           /申请结构体vdiroundrobin空间指向vs
120                                                     ,且把VDIROUNDROBIN_MAGIC的值赋给vs->magic,
121                                                     表示当前结构体生效
/
122     XXXAN(vs);
123     vs->hosts = calloc(sizeof vh, t->nmember);
124     XXXAN(vs->hosts);
125
126     vs->dir.magic = DIRECTORMAGIC;        
127     vs->dir.priv = vs;
128     vs->dir.name = "round
robin";           /
以下四行为dir的初始化/
129     REPLACE(vs->dir.vclname, t->name);
130     vs->dir.getfd = vdi
roundrobingetfd;      /
函数指针初始化/
131     vs->dir.fini = vdiroundrobin_fini;        /
同上/
132     vs->dir.healthy = vdiroundrobin_healthy;      /
同上/
133
134     vh = vs->hosts;
135     te = t->members;
136     for (i = 0; i < t->nmember; i++, vh++, te++)
137         vh->backend = VBEAddBackend(cli, te->host);
138     vs->nhosts = t->nmember;
139     vs->next
host = 0;
140
141    
bp = &vs->dir;
142 }

到此我们已经对varnish后端负载分配模块实现机制有了一定了解: 查找director方式例如"roundrobin","random"等等,在配置文件中找到上述关键字后,确定其对应的解析方式(因为每种负载 分配机制可能对应有自己独特的一些关键字),解析完毕,把信息添加到相应的结构体中,记录这些信息,然后进入当前负载分配模块对应的初始化函数(例如"VRTinitdirround_robin()"),申请当前模块对应的结构体,使上述的函数指针指向当前模块相应的处理函数。当需要负载分配的时候就直接调用当前模块函数指针所指函数即可。整个实现过程与nginx的模块处理方式基本相同。

对于ip_hash的解析我们选取跟round-robin同样的解析方式,对于结构体做了如下修改:

static const struct dirlist {
    const char  *name;
    parsedirector_f *func;
} dirlist[] = {
    { "random",     vcc_ParseRandomDirector },         
    { "round-robin",    vcc_ParseRoundRobinDirector },
    { "ip_hash",        vcc_ParseRoundRobinDirector },
    { NULL,     NULL }
}

新添加了cachedirip_hash文件.[略]

Comments

  1. 果然内涵。。。

    Avatar

Get In Touch With Us ...