最近工作中需要写一个一致性哈希的代理,在网上找到了twemproxy,结合网上资料先学习一下源码。
一、Twemproxy简介
Twemproxy是memcache与redis的代理,由twitter公司开发并且目前已经开源。研究这个对于理解网络通信有很大的帮助。
亮点有以下:
1、twemproxy自己创建并维护和后端server(即reids实例)的长连接,保证长连接对于来自不同client但去向同一server的复用。
2、自动识别异常状态的server,保证之后的请求不会被转发到该异常server上。但如果此server恢复正常,twemproxy又能识别此server,并恢复正常访问。
3、提供专门的状态监控端口供外部工具获取状态监控信息。
4、真正实现了多阶段处理多请求,其IO 模型值得学习,采用单线程收发包,基于epoll事件驱动模型。
5、支持Zero Copy技术,通过将消息指针在3个队列之间流转。
二、Twemproxy 入口main函数分析
Twemproxy是以c语言编写的,其整体入口在main函数中。
grep “main”,我们可以看到在文件nc.c中的main函数。开篇我们可以看到定义了一个变量,struct instance nci;
下边我们看下这个instance的定义:
1 struct instance { 2 struct context *ctx; /* active context */ 3 int log_level; /* log level */ 4 char *log_filename; /* log filename */ 5 char *conf_filename; /* configuration filename */ 6 uint16_t stats_port; /* stats monitoring port */ 7 int stats_interval; /* stats aggregation interval */ 8 char *stats_addr; /* stats monitoring addr */ 9 char hostname[NC_MAXHOSTNAMELEN]; /* hostname */ 10 size_t mbuf_chunk_size; /* mbuf chunk size */ 11 pid_t pid; /* process id */ 12 char *pid_filename; /* pid filename */ 13 unsigned pidfile:1; /* pid file created? */ 14 };
再来看一下context的定义:
1 struct context { 2 uint32_t id; /* unique context id */ 3 struct conf *cf; /* configuration */ 4 struct stats *stats; /* stats */ 5 6 struct array pool; /* server_pool[] */ 7 struct event_base *evb; /* event base */ 8 int max_timeout; /* max timeout in msec */ 9 int timeout; /* timeout in msec */ 10 11 uint32_t max_nfd; /* max # files */ 12 uint32_t max_ncconn; /* max # client connections */ 13 uint32_t max_nsconn; /* max # server connections */ 14 };
这个instance就相当于是一个twemproxy实例,一个twemproxy进程对应一个instance,后边整个程序的初始化很多都会用到。这里面 一个instance对应于一个context,而一个context维护一个server pool列表(也就是说一个instance可以包含多个server pool)。
下面先把整个main函数的过程贴出来,加上了一些简要的注释
1 int 2 main(int argc, char **argv) 3 { 4 rstatus_t status; 5 struct instance nci; 6 7 /* 8 * 赋初始值 9 */ 10 nc_set_default_options(&nci); 11 12 /* 13 * 从命令行读入相应参数 14 */ 15 status = nc_get_options(argc, argv, &nci); 16 if (status != NC_OK) { 17 nc_show_usage(); 18 exit(1); 19 } 20 21 if (show_version) { 22 log_stderr("This is nutcracker-%s" CRLF, NC_VERSION_STRING); 23 if (show_help) { 24 nc_show_usage(); 25 } 26 27 if (describe_stats) { 28 stats_describe(); 29 } 30 31 exit(0); 32 } 33 34 /* 35 * 测试配置文件合法性,不必关心 36 */ 37 if (test_conf) { 38 if (!nc_test_conf(&nci)) { 39 exit(1); 40 } 41 exit(0); 42 } 43 44 /* 45 * 初始化log、守护进程、信号、pidfile 46 */ 47 status = nc_pre_run(&nci); 48 if (status != NC_OK) { 49 /* 50 * 失败就对上面的操作进行析构清理,主要就是删除pidfile、关闭log文件描述符 51 */ 52 nc_post_run(&nci); 53 exit(1); 54 } 55 56 nc_run(&nci); 57 58 nc_post_run(&nci); 59 60 exit(1); 61 }
下面我们进到各个函数里面看一下吧。
1、函数nc_set_default_options
1 static void 2 nc_set_default_options(struct instance *nci) 3 { 4 int status; 5 6 nci->ctx = NULL; 7 8 nci->log_level = NC_LOG_DEFAULT; 9 nci->log_filename = NC_LOG_PATH; 10 11 nci->conf_filename = NC_CONF_PATH; 12 13 nci->stats_port = NC_STATS_PORT; 14 nci->stats_addr = NC_STATS_ADDR; 15 nci->stats_interval = NC_STATS_INTERVAL; 16 17 status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN); 18 if (status < 0) { 19 log_warn("gethostname failed, ignored: %s", strerror(errno)); 20 nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, "unknown"); 21 } 22 nci->hostname[NC_MAXHOSTNAMELEN - 1] = '