概述:
Twemproxy使用yaml作为配置文件的格式,本节我们看一下Twemproxy如何读取配置文件的,如果要扩展Twemproxy的功能,修改配置是必不可少的。
配置加载:
前面我们提到,instance结构中,有个conf_filename字段,这个字段存放的就是配置文件名,于是我们通过这个字段查找一下Twemproxy是在什么地方加载的配置文件:
1
2
3
4
5
6
7
8
|
songqi
@
ubuntu
:
~
/
sf_Work
/
twemproxy
/
src
$
grep
conf_filename
*
-
r
nc
.c
:
nci
->
conf_filename
=
NC_CONF_PATH
;
nc
.c
:
nci
->
conf_filename
=
optarg
;
nc
.c
:
cf
=
conf_create
(
nci
->
conf_filename
)
;
nc
.c
:
nci
->
conf_filename
)
;
nc
.c
:
nci
->
conf_filename
)
;
nc_core
.c
:
ctx
->
cf
=
conf_create
(
nci
->
conf_filename
)
;
nc_core
.h
:
char
*
conf_filename
;
/* configuration filename */
|
可见,加载conf_filename使用的是conf_create函数,这个函数在nc_conf.c中定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
struct
conf
*
conf_create
(
char
*
filename
)
{
rstatus_t
status
;
struct
conf
*
cf
;
cf
=
conf_open
(
filename
)
;
if
(
cf
==
NULL
)
{
return
NULL
;
}
/* validate configuration file before parsing */
status
=
conf_pre_validate
(
cf
)
;
if
(
status
!=
NC_OK
)
{
goto
error
;
}
/* parse the configuration file */
status
=
conf_parse
(
cf
)
;
if
(
status
!=
NC_OK
)
{
goto
error
;
}
/* validate parsed configuration */
status
=
conf_post_validate
(
cf
)
;
if
(
status
!=
NC_OK
)
{
goto
error
;
}
conf_dump
(
cf
)
;
fclose
(
cf
->
fh
)
;
cf
->
fh
=
NULL
;
return
cf
;
error
:
fclose
(
cf
->
fh
)
;
cf
->
fh
=
NULL
;
conf_destroy
(
cf
)
;
return
NULL
;
}
|
载入的步骤很简单:
- open
- pre_validate
- parse
- post_validate
- dump
我整理了一个图,来稍微看一下conf_create的细节:
其中从左指向右的箭头是调用关系,从上指向下的箭头是调用先后顺序,圆圈代表循环。可以看到conf_parse包括了conf_begin_parse、conf_parse_core和conf_end_parse三个步骤,begin和end是相互对应的,conf_parse_core还没有展开细节,我们后面再细看。
conf_create函数返回一个conf结构体,这个结构体的定义在nc_conf.h中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
struct
conf
{
char
*
fname
;
/* file name (ref in argv[]) */
FILE
*
fh
;
/* file handle */
struct
array
arg
;
/* string[] (parsed {key, value} pairs) */
struct
array
pool
;
/* conf_pool[] (parsed pools) */
uint32_t
depth
;
/* parsed tree depth */
yaml_parser_t
parser
;
/* yaml parser */
yaml_event_t
event
;
/* yaml event */
yaml_token_t
token
;
/* yaml token */
unsigned
seq
:
1
;
/* sequence? */
unsigned
valid_parser
:
1
;
/* valid parser? */
unsigned
valid_event
:
1
;
/* valid event? */
unsigned
valid_token
:
1
;
/* valid token? */
unsigned
sound
:
1
;
/* sound? */
unsigned
parsed
:
1
;
/* parsed? */
unsigned
valid
:
1
;
/* valid? */
}
;
|
每个字段含义见注释,其中arg和pool都是前面介绍的array类型。接下来我们具体看一下上面几个比较重要函数:conf_open、conf_begin_parse、conf_parse_core、conf_end_parse。
conf_open:
conf_create通过conf_open函数创建conf,conf_open函数定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
static
struct
conf
*
conf_open
(
char
*
filename
)
{
rstatus_t
status
;
struct
conf
*
cf
;
FILE
*
fh
;
fh
=
fopen
(
filename
,
"r"
)
;
if
(
fh
==
NULL
)
{
log_error
(
"conf: failed to open configuration '%s': %s"
,
filename
,
strerror
(
errno
)
)
;
return
NULL
;
}
cf
=
nc_alloc
(
sizeof
(
*
cf
)
)
;
if
(
cf
==
NULL
)
{
fclose
(
fh
)
;
return
NULL
;
}
status
=
array_init
(
&
cf
->
arg
,
CONF_DEFAULT_ARGS
,
sizeof
(
struct
string
)
)
;
if
(
status
!=
NC_OK
)
{
nc_free
(
cf
)
;
fclose
(
fh
)
;
return
NULL
;
}
status
=
array_init
(
&
cf
->
pool
,
CONF_DEFAULT_POOL
,
sizeof
(
struct
conf_pool
)
)
;
if
(
status
!=
NC_OK
)
{
array_deinit
(
&
cf
->
arg
)
;
nc_free
(
cf
)
;
fclose
(
fh
)
;
return
NULL
;
}
cf
->
fname
=
filename
;
cf
->
fh
=
fh
;
cf
->
depth
=
0
;
/* parser, event, and token are initialized later */
cf
->
seq
=
0
;
cf
->
valid_parser
=
0
;
cf
->
valid_event
=
0
;
cf
->
valid_token
=
0
;
cf
->
sound
=
0
;
cf
->
parsed
=
0
;
cf
->
valid
=
0
;
log_debug
(
LOG_VVERB
,
"opened conf '%s'"
,
filename
)
;
return
cf
;
}
|
可以看出conf_open做了以下几件事:
- 打开配置文件
- 创建conf
- 初始化cf->arg和cf_pool这两个数组,分别存放string和conf_pool
- 初始化cf其他各字段
创建了conf之后,便是进行conf_pre_validate、conf_parse然后是conf_post_validate。pre_validate和post_validate都是对配置进行校验,具体怎么校验的我们暂不细看了,我们主要看parse的部分,在深入了解parse的逻辑之前,我们要先来了解一下twemproxy使用的libyaml。
libyaml:
Twemproxy用到了libyaml来解析yaml格式的配置文件,并将libyaml-0.1.4的代码放在contrib目录下。libyaml包括了两个主要功能:yaml文件的解析和生成(称为parser和emitter)。这两个功能是可以完全互斥的,也就是说parser解析出来的event序列,可以交给emitter生成相同格式的yaml文件,反过来也是一样。
刚才提到了event序列,parser解析yaml文件时,产生event序列,event包含以下类型:
- STREAM-START
- STREAM-END
- DOCUMENT-START
- DOCUMENT-END
- ALIAS
- SCALAR
- SEQUENCE-START
- SEQUENCE-END
- MAPPING-START
- MAPPING-END
每个event产生时,根据不同的类型,还会有不同的attribute,具体这里不做赘述,感兴趣的可以看一下libyaml的文档。
event产生的序列,应该符合这样的语法描述:
1
2
3
4
5
|
stream
::
=
STREAM
-
START
document*
STREAM
-
END
document
::
=
DOCUMENT
-
START
node
DOCUMENT
-
END
node
::
=
ALIAS
|
SCALAR
|
sequence
|
mapping
sequence
::
=
SEQUENCE
-
START
node*
SEQUENCE
-
END
mapping
::
=
MAPPING
-
START
(
node
node
)
*
MAPPING
-
END
|
那么解析一个yaml文档的步骤大概是这样的:
- 调用yaml_parser_initialize,生成一个parser
- 调用yaml_parser_set_input_file,为parser制定yaml文件的handle。或者调用yaml_parser_set_input_string指定文件内容也行。
- 调用yaml_parser_parse,产生一个event,根据event的type和attribute进行处理
- 调用yaml_event_delete销毁刚刚产生的event,回到第3步获得下一个event,直到所有event处理完
- 调用yaml_parser_delete销毁parser
通过这个图可以很容易理解:
为了更好的理解yaml解析的过程,我写了个简单的程序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
#include <yaml.h>
yaml_parser_t
parser
;
yaml_event_t
event
;
void
print_event
(
yaml_event_t *
event
)
{
switch
(
event
->
type
)
{
case
YAML_NO_EVENT
:
printf
(
"YAML_NO_EVENT\n"
)
;
break
;
case
YAML_STREAM_START_EVENT
:
printf
(
"YAML_STREAM_START_EVENT\n"
)
;
break
;
case
YAML_STREAM_END_EVENT
:
printf
(
"YAML_STREAM_END_EVENT\n"
)
;
break
;
case
YAML_DOCUMENT_START_EVENT
:
printf
(
"YAML_DOCUMENT_START_EVENT\n"
)
;
break
;
case
YAML_DOCUMENT_END_EVENT
:
printf
(
"YAML_DOCUMENT_END_EVENT\n"
)
;
break
;
case
YAML_ALIAS_EVENT
:
printf
(
"YAML_ALIAS_EVENT\n"
)
;
break
;
case
YAML_SCALAR_EVENT
:
printf
(
"YAML_SCALAR_EVENT:%s\n"
,
event
->
data
.
scalar
.
value
)
;
break
;
case
YAML_SEQUENCE_START_EVENT
:
printf
(
"YAML_SEQUENCE_START_EVENT\n"
)
;
break
;
case
YAML_SEQUENCE_END_EVENT
:
printf
(
"YAML_SEQUENCE_END_EVENT\n"
)
;
break
;
case
YAML_MAPPING_START_EVENT
:
printf
(
"YAML_MAPPING_START_EVENT\n"
)
;
break
;
case
YAML_MAPPING_END_EVENT
:
printf
(
"YAML_MAPPING_END_EVENT\n"
)
;
break
;
}
return
;
}
void
main
(
)
{
int
done
=
0
;
FILE *
fp
=
fopen
(
"conf.yml"
,
"r"
)
;
yaml_parser_initialize
(
&
parser
)
;
yaml_parser_set_input_file
(
&
parser
,
fp
)
;
do
{
if
(
yaml_parser_parse
(
&
parser
,
&
event
)
)
{
done
=
(
event
.
type
==
YAML_STREAM_END_EVENT
)
;
print_event
(
&
event
)
;
yaml_event_delete
(
&
event
)
;
}
}
while
(
!
done
)
;
yaml_parser_delete
(
&
parser
)
;
fclose
(
fp
)
;
return
;
}
|
程序的作用就是调用libyaml解析一个yaml文件,并将产生的event序列输出,使用的yaml文件是Twemproxy默认的配置文件,内容如下:
1
2
3
4
5
6
7
8
9
10
11
|
alpha
:
listen
: 127.0.0.1
:22121
hash
: fnv1a_64
distribution
: ketama
auto_eject_hosts
: true
redis
: false
server_retry_timeout
: 2000
server_failure_limit
: 1
servers
:
-
127
.0
.0
.
1
:11211
:1
-
127
.0
.0
.
1
:11212
:1
|
程序执行产生的输出如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
YAML_STREAM_START_EVENT
YAML_DOCUMENT_START_EVENT
YAML_MAPPING_START_EVENT
YAML_SCALAR_EVENT
:
alpha
YAML_MAPPING_START_EVENT
YAML_SCALAR_EVENT
:
listen
YAML_SCALAR_EVENT
:
127.0.0.1
:
22121
YAML_SCALAR_EVENT
:
hash
YAML_SCALAR_EVENT
:
fnv1a_64
YAML_SCALAR_EVENT
:
distribution
YAML_SCALAR_EVENT
:
ketama
YAML_SCALAR_EVENT
:
auto_eject_hosts
YAML_SCALAR_EVENT
:
true
YAML_SCALAR_EVENT
:
redis
YAML_SCALAR_EVENT
:
false
YAML_SCALAR_EVENT
:
server_retry_timeout
YAML_SCALAR_EVENT
:
2000
YAML_SCALAR_EVENT
:
server_failure_limit
YAML_SCALAR_EVENT
:
1
YAML_SCALAR_EVENT
:
servers
YAML_SEQUENCE_START_EVENT
YAML_SCALAR_EVENT
:
127.0.0.1
:
11211
:
1
YAML_SCALAR_EVENT
:
127.0.0.1
:
11212
:
1
YAML_SEQUENCE_END_EVENT
YAML_MAPPING_END_EVENT
YAML_MAPPING_END_EVENT
YAML_DOCUMENT_END_EVENT
YAML_STREAM_END_EVENT
|
具体是怎么个逻辑就不解释了,大家自己理解就好了。
大概了解了libyaml之后,我们可以继续了解conf_parse了。
conf_begin_parse、conf_end_parse:
前面我们提到,begin_parse和end_parse是一对相互对应函数,我们看下他们具体做了什么:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
static
rstatus_t
conf_begin_parse
(
struct
conf
*
cf
)
{
rstatus_t
status
;
bool
done
;
ASSERT
(
cf
->
sound
&&
!
cf
->
parsed
)
;
ASSERT
(
cf
->
depth
==
0
)
;
status
=
conf_yaml_init
(
cf
)
;
//调用yaml_parser_initialize创建parser
if
(
status
!=
NC_OK
)
{
return
status
;
}
done
=
false
;
do
{
status
=
conf_event_next
(
cf
)
;
//调用yaml_parser_parse获得event
if
(
status
!=
NC_OK
)
{
return
status
;
}
log_debug
(
LOG_VVERB
,
"next begin event %d"
,
cf
->
event
.
type
)
;
switch
(
cf
->
event
.
type
)
{
case
YAML_STREAM_START_EVENT
:
case
YAML_DOCUMENT_START_EVENT
:
break
;
case
YAML_MAPPING_START_EVENT
:
ASSERT
(
cf
->
depth
<
CONF_MAX_DEPTH
)
;
cf
->
depth
++
;
done
=
true
;
//done = true跳出循环,也就是说begin_parse忽略掉了MAPPING_START之前的所有event
break
;
default
:
NOT_REACHED
(
)
;
}
conf_event_done
(
cf
)
;
//调用yaml_event_delete销毁产生的event
}
while
(
!
done
)
;
return
NC_OK
;
}
|
begin_parse创建了parser,然后循环处理并且忽略掉了parser产生的MAPPING_START之前的event(还包括MAPPING_START本身)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
static
rstatus_t
conf_end_parse
(
struct
conf *
cf
)
{
rstatus_t
status
;
bool
done
;
ASSERT
(
cf
->
sound
&&
!
cf
->
parsed
)
;
ASSERT
(
cf
->
depth
==
0
)
;
done
=
false
;
do
{
status
=
conf_event_next
(
cf
)
;
//调用yaml_parser_parse获得下一个event
if
(
status
!=
NC_OK
)
{
return
status
;
}
log_debug
(
LOG_VVERB
,
"next end event %d"
,
cf
->
event
.
type
)
;
switch
(
cf
->
event
.
type
)
{
case
YAML_STREAM_END_EVENT
:
done
=
true
;
//done = true跳出循环,忽略掉STREAM_END之前的所有event
break
;
case
YAML_DOCUMENT_END_EVENT
:
break
;
default
:
NOT_REACHED
(
)
;
}
conf_event_done
(
cf
)
;
}
while
(
!
done
)
;
conf_yaml_deinit
(
cf
)
;
//调用yaml_parser_delete销毁parser
return
NC_OK
;
}
|
end_parse是和begin_parse对应的,忽略掉STREAM_END之前的所有event(包括STREAM_END)。可见,begin_parse和end_parse的作用是忽略掉除mapping以外的部分。
conf_parse_core:
conf_parse_core是解析配置文件的主要逻辑,它是一个递归函数,在了解conf_parse_core之前,我们要先看一下conf_push_scalar和conf_pop_scalar函数的作用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
tatic
rstatus_t
conf_push_scalar
(
struct
conf *
cf
)
{
rstatus_t
status
;
struct
string
*
value
;
uint8_t *
scalar
;
uint32_t
scalar_len
;
scalar
=
cf
->
event
.
data
.
scalar
.
value
;
scalar_len
=
(
uint32_t
)
cf
->
event
.
data
.
scalar
.
length
;
log_debug
(
LOG_VVERB
,
"push '%.*s'"
,
scalar_len
,
scalar
)
;
value
=
array_push
(
&
cf
->
arg
)
;
if
(
value
==
NULL
)
{
return
NC_ENOMEM
;
}
string_init
(
value
)
;
status
=
string_copy
(
value
,
scalar
,
scalar_len
)
;
if
(
status
!=
NC_OK
)
{
array_pop
(
&
cf
->
arg
)
;
return
status
;
}
return
NC_OK
;
}
|
可以看到, conf_push_scalar的作用,是将scalar event的值(一个字符串),压入cf->arg数组中。
1
2
3
4
5
6
7
8
9
|
static
void
conf_pop_scalar
(
struct
conf *
cf
)
{
struct
string
*
value
;
value
=
array_pop
(
&
cf
->
arg
)
;
log_debug
(
LOG_VVERB
,
"pop '%.*s'"
,
value
->
len
,
value
->
data
)
;
string_deinit
(
value
)
;
}
|
conf_pop_scalar的作用是从cf->arg数组中pop一个元素出来,然后丢弃这个元素。好了,接下来我们就看conf_parse_core
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
static
rstatus_t
conf_parse_core
(
struct
conf *
cf
,
void
*
data
)
{
rstatus_t
status
;
bool
done
,
leaf
,
new_pool
;
ASSERT
(
cf
->
sound
)
;
status
=
conf_event_next
(
cf
)
;
//调用yaml_parser_parse获得下一个event
if
(
status
!=
NC_OK
)
{
return
status
;
}
log_debug
(
LOG_VVERB
,
"next event %d depth %"
PRIu32
" seq %d"
,
cf
->
event
.
type
,
cf
->
depth
,
cf
->
seq
)
;
done
=
false
;
leaf
=
false
;
new_pool
=
false
;
switch
(
cf
->
event
.
type
)
{
case
YAML_MAPPING_END_EVENT
:
cf
->
depth
--
;
//mapping层级 + 1,与下面cf_depth--对应
if
(
cf
->
depth
==
1
)
{
conf_pop_scalar
(
cf
)
;
//depth == 1表示第二层级处理结束,也就是pool下的具体配置,从cf->arg弹出一个元素
}
else
if
(
cf
->
depth
==
0
)
{
done
=
true
;
//depth == 0的话,表示pool层级结束,整个配置文件处理完毕
}
break
;
case
YAML_MAPPING_START_EVENT
:
cf
->
depth
++
;
//mapping的层级 + 1
break
;
case
YAML_SEQUENCE_START_EVENT
:
cf
->
seq
=
1
;
//如果遇到sequence,也就是对应到配置文件里的servers列表,标记cf->seq = 1
break
;
case
YAML_SEQUENCE_END_EVENT
:
conf_pop_scalar
(
cf
)
;
//然后在整个sequence处理结束后,从cf->arg弹出一个元素,标记cf->seq为0
cf
->
seq
=
0
;
break
;
case
YAML_SCALAR_EVENT
:
status
=
conf_push_scalar
(
cf
)
;
//每遇到一个scalar,将scalar value压入cf->arg
if
(
status
!=
NC_OK
)
{
break
;
}
/* take appropriate action */
if
(
cf
->
seq
)
{
//如果是在sequence中,则leaf = true
/* for a sequence, leaf is at CONF_MAX_DEPTH */
ASSERT
(
cf
->
depth
==
CONF_MAX_DEPTH
)
;
leaf
=
true
;
}
else
if
(
cf
->
depth
==
CONF_ROOT_DEPTH
)
{
//如果是在root,也就是对应pool层级,则需要创建一个pool
/* create new conf_pool */
data
=
array_push
(
&
cf
->
pool
)
;
if
(
data
==
NULL
)
{
status
=
NC_ENOMEM
;
break
;
}
new_pool
=
true
;
}
else
if
(
array_n
(
&
cf
->
arg
)
==
cf
->
depth
+
1
)
{
//如果cf->arg的元素数量 == depth + 1则leaf = true
/* for {key: value}, leaf is at CONF_MAX_DEPTH */
ASSERT
(
cf
->
depth
==
CONF_MAX_DEPTH
)
;
leaf
=
true
;
}
break
;
default
:
NOT_REACHED
(
)
;
break
;
}
conf_event_done
(
cf
)
;
if
(
status
!=
NC_OK
)
{
return
status
;
}
if
(
done
)
{
/* terminating condition */
return
NC_OK
;
}
if
(
leaf
||
new_pool
)
{
status
=
conf_handler
(
cf
,
data
)
;
//对于leaf或者new_pool的情况,需要
if
(
leaf
)
{
conf_pop_scalar
(
cf
)
;
//如果是leaf,则从cf->arg弹出一个元素
if
(
!
cf
->
seq
)
{
conf_pop_scalar
(
cf
)
;
//如果不是seq,则多弹出一个元素
}
}
if
(
status
!=
NC_OK
)
{
return
status
;
}
}
return
conf_parse_core
(
cf
,
data
)
;
//递归调用conf_parse_core
}
|
单独看代码,和注释,不是很容易理解,我先解释一下这里面的几个概念:
- depth:一层mapping对应一个depth,前面我们看到了,ROOT的层级是1,这个层级上的每一个node,都代表一个pool。在第一次调用core_parse_core的时候,depth == 1,这个时候会向cf->pool压入一个pool,并将标记new_pool置为1。
- leaf:有两个条件情况会导致leaf == true,一个是在sequence中(Twemproxy配置文件sequence下不再有map了),另一个是cf->arg中元素数量 == depth + 1,这个如何理解呢,看下面这个图,灰色部分是leaf:
- done:只有depth==1层级处理结束,done才为1,这也是递归的退出条件。
基本上这个函数逻辑是这样的:
- 最外层depth == 1,得到的scalar的value是这个pool的name,于是创建一个新的pool、压入cf->pool、置new_pool为1,然后递归调用conf_parse_core。
- 每遇到一个mapping,则depth++,离开mapping的时候depth–
- 每遇到sequence的时候置cf->seq = 1,离开时置cf->seq = 0
- 每遇到一个scalar,将它压入cf->arg
- 如果是leaf,或者new_pool,则调用conf_handler,创建/修改pool的配置
- 最后将处理完的scalar弹出,seq弹出1个(value),否则弹出2个(key和value)
- 递归调用conf_parse_core
OK,那么可以看到,在遇到scalar的时候,都是先压入cf->arg,这个认为是临时存放配置项的地方。那么每次遇到leaf的时候(上图灰色部分),则需要调用conf_handler来处理,那么这个函数做了什么呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
static
rstatus_t
conf_handler
(
struct
conf *
cf
,
void
*
data
)
{
struct
command *
cmd
;
struct
string
*
key
,
*
value
;
uint32_t
narg
;
if
(
array_n
(
&
cf
->
arg
)
==
1
)
{
//cf->arg元素数为1,表示一个新的pool,value就是pool名,调用conf_pool_init
value
=
array_top
(
&
cf
->
arg
)
;
log_debug
(
LOG_VVERB
,
"conf handler on '%.*s'"
,
value
->
len
,
value
->
data
)
;
return
conf_pool_init
(
data
,
value
)
;
}
/** 获取key和value **/
narg
=
array_n
(
&
cf
->
arg
)
;
value
=
array_get
(
&
cf
->
arg
,
narg
-
1
)
;
key
=
array_get
(
&
cf
->
arg
,
narg
-
2
)
;
log_debug
(
LOG_VVERB
,
"conf handler on %.*s: %.*s"
,
key
->
len
,
key
->
data
,
value
->
len
,
value
->
data
)
;
for
(
cmd
=
conf_commands
;
cmd
->
name
.
len
!=
0
;
cmd
++
)
{
char
*
rv
;
if
(
string_compare
(
key
,
&
cmd
->
name
)
!=
0
)
{
//判断key是否是允许的command,不是则忽略掉
continue
;
}
rv
=
cmd
->
set
(
cf
,
cmd
,
data
)
;
//data是一个conf_pool实例,cmd->set就是调用不同command对应的set函数,来设置conf_pool对应字段的值,如果要扩展配置文件,需要注意这个地方。
if
(
rv
!=
CONF_OK
)
{
log_error
(
"conf: directive \"%.*s\" %s"
,
key
->
len
,
key
->
data
,
rv
)
;
return
NC_ERROR
;
}
return
NC_OK
;
}
|
看到这里就比较清楚了,当new_pool的情况下调用conf_pool_init初始化这个conf_pool,其他情况则设置conf_pool实例中的具体字段值。conf_commands中定义了twemproxy配置所接受的所有command,以及他们的set函数指针,这个通过command的定义可以看出:
1
2
3
4
5
|
struct
command
{
struct
string
name
;
char
*
(
*
set
)
(
struct
conf *
cf
,
struct
command *
cmd
,
void
*
data
)
;
int
offset
;
}
;
|
不同的配置项,可能有专属的set函数,这个通过定义conf_commands数组来指定。
至此Twemproxy加载和解析配置文件的过程就分析完了。
小结:
本节内容较多,不过不用担心,nc_conf是Twemproxy里面代码最多的模块了,后面的都会简单些。本节主要分析了配置文件加载和解析的过程,其中涉及到了libyaml,以及一些重要的数据结构如conf_pool、conf等,配置的验证validate则没有分析,目前不太涉及到,就偷个懒吧。