概述:
Twemproxy是Twitter开发并开源的适用于Redis和Memcached的轻量级的缓存中间件,可以代理对Redis和Memcached的访问。因为项目需要,我们打算在此基础上进行二次开发,加入一些自己需要的功能(例如cluster之间的数据同步),所以需要深入了解一下Twemproxy源码,我将我阅读Twemproxy源码过程的笔记记录在此。
代码概况:
clone源码:
1
|
songqi
@
ubuntu
:
~
/
software
$
git
clone
https
:
//github.com/twitter/twemproxy.git
|
首先看一下代码规模:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
songqi
@
ubuntu
:
~
/
software
/
twemproxy
/
src
$
cloc
.
100
text
files
.
100
unique
files
.
39
files
ignored
.
http
:
//cloc.sourceforge.net v 1.56 T=0.5 s (122.0 files/s, 41636.0 lines/s)
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
-
Language
files
blank
comment
code
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
-
C
34
2894
1684
10985
make
8
281
99
2044
C
/
C
++
Header
19
458
535
1838
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
-
SUM
:
61
3633
2318
14867
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
--
-
|
代码规模不大只有1w多行,而且功能划分的比较清晰,包括:
- 事件处理:event/nc_epoll.c、event/nc_event.h、event/nc_evport.c、event/nc_kqueue.c
- 各种Hash函数:hashkit/nc_crc16.c、hashkit/nc_crc32.c、hashkit/nc_fnv.c、hashkit/nc_hashkit.h、hashkit/nc_hsieh.c、hashkit/nc_jenkins.c、hashkit/nc_ketama.c、hashkit/nc_md5.c、hashkit/nc_modula.c、hashkit/nc_murmur.c、hashkit/nc_one_at_a_time.c、hashkit/nc_random.c
- 协议:proto/nc_memcache.c、proto/nc_proto.h、proto/nc_redis.c
- 自定义的数据类型:nc_array.c、nc_array.h、nc_string.c、nc_string.h
- 网络通信相关:nc_connection.c、nc_connection.h、nc_client.c、nc_client.h、nc_proxy.c、nc_proxy.h
- 信号处理:nc_signal.c、nc_signal.h
- 关键数据结构和算法:nc_rbtree.h、nc_rbtree.c、nc_queue.h、nc_request.c、nc_response.c、nc_mbuf.c、nc_mbuf.h、nc_message.c、nc_message.h、nc_server.c、nc_server.h
- 统计、日志和工具:nc_stats.c、nc_stats.h、nc_log.c、nc_log.h、nc_util.c、nc_util.h
- 配置文件:nc_conf.c、nc_conf.h
- 主程序:nc.c、nc_core.c、nc_core.h
以上就是Twemproxy代码的概况。
启动主程序:
我们从main()函数开始看,main()函数在nc.c中,也就是Twemproxy的主程序。Twemproxy的main()函数非常短:
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
|
int
main
(
int
argc
,
char
*
*
argv
)
{
rstatus_t
status
;
struct
instance
nci
;
nc_set_default_options
(
&
nci
)
;
status
=
nc_get_options
(
argc
,
argv
,
&
nci
)
;
if
(
status
!=
NC_OK
)
{
nc_show_usage
(
)
;
exit
(
1
)
;
}
if
(
show_version
)
{
log_stderr
(
"This is nutcracker-%s"
CRLF
,
NC_VERSION_STRING
)
;
if
(
show_help
)
{
nc_show_usage
(
)
;
}
if
(
describe_stats
)
{
stats_describe
(
)
;
}
exit
(
0
)
;
}
if
(
test_conf
)
{
if
(
!
nc_test_conf
(
&
nci
)
)
{
exit
(
1
)
;
}
exit
(
0
)
;
}
status
=
nc_pre_run
(
&
nci
)
;
if
(
status
!=
NC_OK
)
{
nc_post_run
(
&
nci
)
;
exit
(
1
)
;
}
nc_run
(
&
nci
)
;
nc_post_run
(
&
nci
)
;
exit
(
1
)
;
}
|
main函数的整理流程非常清晰,包含以下几步:
- 初始化option,设置默认值
- 从argv中获取option,若argv有错误则调用nc_show_usage()打印使用帮助
- 根据argv,判断是否是:show_version、show_help、describe_stats或者test_conf,是则执行相应逻辑后退出
- 依次调用nc_pre_run()、nc_run()和nc_post_run()
前面的步骤目前无关紧要,可以看出主要逻辑在nc_pre_run()、nc_run()和nc_post_run()函数中,也就是上面代码中高亮的部分。这三个函数的定义也在nc.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
|
static
rstatus_t
nc_pre_run
(
struct
instance
*
nci
)
{
rstatus_t
status
;
status
=
log_init
(
nci
->
log_level
,
nci
->
log_filename
)
;
if
(
status
!=
NC_OK
)
{
return
status
;
}
if
(
daemonize
)
{
status
=
nc_daemonize
(
1
)
;
if
(
status
!=
NC_OK
)
{
return
status
;
}
}
nci
->
pid
=
getpid
(
)
;
status
=
signal_init
(
)
;
if
(
status
!=
NC_OK
)
{
return
status
;
}
if
(
nci
->
pid_filename
)
{
status
=
nc_create_pidfile
(
nci
)
;
if
(
status
!=
NC_OK
)
{
return
status
;
}
}
nc_print_run
(
nci
)
;
return
NC_OK
;
}
|
nc_pre_run()的函数逻辑是:
- 初始化日志
- 启动守护进程
- 初始化信号处理函数
- 创建pidfile
- 输出启动信息
如果nc_pre_run()运行失败,main()函数的逻辑中会调用nc_post_run()回收资源
1
2
3
4
5
6
7
8
9
10
11
12
13
|
static
void
nc_post_run
(
struct
instance
*
nci
)
{
if
(
nci
->
pidfile
)
{
nc_remove_pidfile
(
nci
)
;
}
signal_deinit
(
)
;
nc_print_done
(
)
;
log_deinit
(
)
;
}
|
nc_post_run()函数的主要逻辑是:
- 删除pidfile
- 信号处理函数deinit(这里其实什么也不做)
- 打印关闭信息
- 日志deinit(关闭日志文件描述符)
接下来就是比较重要的nc_run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
static
void
nc_run
(
struct
instance
*
nci
)
{
rstatus_t
status
;
struct
context
*
ctx
;
ctx
=
core_start
(
nci
)
;
if
(
ctx
==
NULL
)
{
return
;
}
/* run rabbit run */
for
(
;
;
)
{
status
=
core_loop
(
ctx
)
;
if
(
status
!=
NC_OK
)
{
break
;
}
}
core_stop
(
ctx
)
;
}
|
nc_run()函数封装的也不错,它的主要逻辑是:
- 调用core_start(),初始化上下文ctx
- 进入死循环,调用core_loop()进入事件循环
- 检查退出条件status != NC_OK,退出循环
- 调用core_stop()结束
可以看出细节都封装到了core_start()、core_loop()和core_stop()当中,但是nc.c主程序的逻辑到这里就结束了。值得一提的是,nc_daemonize()函数封装了创建守护进程的一个标准逻辑。
关键数据结构和数据类型:
程序到这里,涉及到了两个关键的数据结构和数据类型,一个是rstatus_t,一个是instance。
rstatus_t的定义在nc_core.h中,与它同时定义的还有err_t
1
2
3
4
5
6
7
|
#define NC_OK 0
#define NC_ERROR -1
#define NC_EAGAIN -2
#define NC_ENOMEM -3
typedef
int
rstatus_t
;
/* return type */
typedef
int
err_t
;
/* error type */
|
如注释所写,rstatus_t定义返回状态,err_t定义错误类型。其中rstatus_t的可选值如上面宏定义描述,而err_t对应系统的errno。
然后我们看一下instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct
instance
{
struct
context
*
ctx
;
/* active context */
int
log_level
;
/* log level */
char
*
log_filename
;
/* log filename */
char
*
conf_filename
;
/* configuration filename */
uint16_t
stats_port
;
/* stats monitoring port */
int
stats_interval
;
/* stats aggregation interval */
char
*
stats_addr
;
/* stats monitoring addr */
char
hostname
[
NC_MAXHOSTNAMELEN
]
;
/* hostname */
size_t
mbuf_chunk_size
;
/* mbuf chunk size */
pid_t
pid
;
/* process id */
char
*
pid_filename
;
/* pid filename */
unsigned
pidfile
:
1
;
/* pid file created? */
}
;
|
注释很明确,不再一一解释了。启动一个Twemproxy,就是生成一个实例(struct instance nci),这个实例保存了运行所需的一切信息,instance当中的字段通过nc_set_default_options()和nc_get_option()来初始化。
小结:
可以看到,Twemproxy的代码量很小,逻辑封装的也比较清晰,读起来是很舒服的。这一段中我们只看了主程序的启动过程,还未深入到细节,细节我们后面再看。这里需要着重记忆的是instance结构体,后面有多处会应用到