您的位置:首页 > 其它

Twemproxy源码分析(一)启动过程

2015-08-05 09:59 495 查看


概述:

Twemproxy是Twitter开发并开源的适用于Redis和Memcached的轻量级的缓存中间件,可以代理对Redis和Memcached的访问。因为项目需要,我们打算在此基础上进行二次开发,加入一些自己需要的功能(例如cluster之间的数据同步),所以需要深入了解一下Twemproxy源码,我将我阅读Twemproxy源码过程的笔记记录在此。


代码概况:

clone源码:

1songqi@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()函数非常短:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546intmain(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()回收资源

12345678910111213static voidnc_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

1234567#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结构体,后面有多处会应用到
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: