abc_begin

导航

twemproxy源码分析1——入口函数及启动过程

最近工作中需要写一个一致性哈希的代理,在网上找到了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] = '\0';
23 
24     nci->mbuf_chunk_size = NC_MBUF_SIZE;
25 
26     nci->pid = (pid_t)-1;
27     nci->pid_filename = NULL;
28     nci->pidfile = 0;
29 }

这个是用来设置上述instance实例nci的各项参数的默认值。

2、函数nc_get_options

  1 static rstatus_t
  2 nc_get_options(int argc, char **argv, struct instance *nci)
  3 {
  4     int c, value;
  5 
  6     opterr = 0;
  7 
  8     for (;;) {
  9         c = getopt_long(argc, argv, short_options, long_options, NULL);
 10         if (c == -1) {
 11             /* no more options */
 12             break;
 13         }
 14 
 15         switch (c) {
 16         case 'h':
 17             show_version = 1;
 18             show_help = 1;
 19             break;
 20 
 21         case 'V':
 22             show_version = 1;
 23             break;
 24 
 25         case 't':
 26             test_conf = 1;
 27             break;
 28 
 29         case 'd':
 30             daemonize = 1;
 31             break;
 32 
 33         case 'D':
 34             describe_stats = 1;
 35             show_version = 1;
 36             break;
 37 
 38         case 'v':
 39             value = nc_atoi(optarg, strlen(optarg));
 40             if (value < 0) {
 41                 log_stderr("nutcracker: option -v requires a number");
 42                 return NC_ERROR;
 43             }
 44             nci->log_level = value;
 45             break;
 46 
 47         case 'o':
 48             nci->log_filename = optarg;
 49             break;
 50 
 51         case 'c':
 52             nci->conf_filename = optarg;
 53             break;
 54 
 55         case 's':
 56             value = nc_atoi(optarg, strlen(optarg));
 57             if (value < 0) {
 58                 log_stderr("nutcracker: option -s requires a number");
 59                 return NC_ERROR;
 60             }
 61             if (!nc_valid_port(value)) {
 62                 log_stderr("nutcracker: option -s value %d is not a valid "
 63                            "port", value);
 64                 return NC_ERROR;
 65             }
 66 
 67             nci->stats_port = (uint16_t)value;
 68             break;
 69 
 70         case 'i':
 71             value = nc_atoi(optarg, strlen(optarg));
 72             if (value < 0) {
 73                 log_stderr("nutcracker: option -i requires a number");
 74                 return NC_ERROR;
 75             }
 76 
 77             nci->stats_interval = value;
 78             break;
 79 
 80         case 'a':
 81             nci->stats_addr = optarg;
 82             break;
 83 
 84         case 'p':
 85             nci->pid_filename = optarg;
 86             break;
 87 
 88         case 'm':
 89             value = nc_atoi(optarg, strlen(optarg));
 90             if (value <= 0) {
 91                 log_stderr("nutcracker: option -m requires a non-zero number");
 92                 return NC_ERROR;
 93             }
 94 
 95             if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) {
 96                 log_stderr("nutcracker: mbuf chunk size must be between %zu and"
 97                            " %zu bytes", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE);
 98                 return NC_ERROR;
 99             }
100 
101             nci->mbuf_chunk_size = (size_t)value;
102             break;
103 
104         case '?':
105             switch (optopt) {
106             case 'o':
107             case 'c':
108             case 'p':
109                 log_stderr("nutcracker: option -%c requires a file name",
110                            optopt);
111                 break;
112 
113             case 'm':
114             case 'v':
115             case 's':
116             case 'i':
117                 log_stderr("nutcracker: option -%c requires a number", optopt);
118                 break;
119 
120             case 'a':
121                 log_stderr("nutcracker: option -%c requires a string", optopt);
122                 break;
123 
124             default:
125                 log_stderr("nutcracker: invalid option -- '%c'", optopt);
126                 break;
127             }
128             return NC_ERROR;
129 
130         default:
131             log_stderr("nutcracker: invalid option -- '%c'", optopt);
132             return NC_ERROR;
133 
134         }
135     }
136 
137     return NC_OK;
138 }

从命令行读入相应参数,如果有则覆盖上一个函数所设置的默认参数。

3、函数nc_test_conf

如果配置了-t选项 则nc_test_conf : 用于对配置文件做检查,以确保配置文件格式的正确。里面的解析用了一坨yaml的东西,不懂……不过我们也不用关心这个。

4、函数nc_pre_run

 1 static rstatus_t
 2 nc_pre_run(struct instance *nci)
 3 {
 4     rstatus_t status;
 5 
 6     status = log_init(nci->log_level, nci->log_filename);
 7     if (status != NC_OK) {
 8         return status;
 9     }
10 
11     if (daemonize) {
12         status = nc_daemonize(1);
13         if (status != NC_OK) {
14             return status;
15         }
16     }
17 
18     nci->pid = getpid();
19 
20     status = signal_init();
21     if (status != NC_OK) {
22         return status;
23     }
24 
25     if (nci->pid_filename) {
26         status = nc_create_pidfile(nci);
27         if (status != NC_OK) {
28             return status;
29         }
30     }
31 
32     nc_print_run(nci);
33 
34     return NC_OK;
35 }

做了这些事情:初始化log、守护进程、信号、pidfile;其中关于守护进程的实现非常经典,后面会单独写。

5、函数nc_run

 1 static void
 2 nc_run(struct instance *nci)
 3 {
 4     rstatus_t status;
 5     struct context *ctx;
 6 
 7     ctx = core_start(nci);
 8     if (ctx == NULL) {
 9         return;
10     }
11 
12     /* run rabbit run */
13     for (;;) {
14         status = core_loop(ctx);
15         if (status != NC_OK) {
16             break;
17         }
18     }
19 
20     core_stop(ctx);
21 }

nc_run开始启动proxy;这个函数完成的工作就是调用core_start创建context,然后进入死循环调用core_loop开始整个事件循环的处理,接受请求并处理。当然,core_start以及core_loop这两个函数里边还包含了大量的处理工作,包括:配置文件解析以及读取,相关组件(server_pool,conf,context)的初始化等,这个后面再单独写。

总体来说,启动流程就是这些步骤,如下图:

 

 

本文参考自:http://blog.sina.cn/dpool/blog/s/blog_4f8ea2ef0101iill.html?md=gd

 

posted on 2017-12-28 20:13  LastBattle  阅读(469)  评论(0编辑  收藏  举报