最近工作中需要写一个一致性哈希的代理,在网上找到了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的定义:

 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:; /* pid file created? */
};

再来看一下context的定义:

 struct context {
uint32_t id; /* unique context id */
struct conf *cf; /* configuration */
struct stats *stats; /* stats */ struct array pool; /* server_pool[] */
struct event_base *evb; /* event base */
int max_timeout; /* max timeout in msec */
int timeout; /* timeout in msec */ uint32_t max_nfd; /* max # files */
uint32_t max_ncconn; /* max # client connections */
uint32_t max_nsconn; /* max # server connections */
};

这个instance就相当于是一个twemproxy实例,一个twemproxy进程对应一个instance,后边整个程序的初始化很多都会用到。这里面 一个instance对应于一个context,而一个context维护一个server pool列表(也就是说一个instance可以包含多个server pool)。

下面先把整个main函数的过程贴出来,加上了一些简要的注释

 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();
} 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();
} /*
* 测试配置文件合法性,不必关心
*/
if (test_conf) {
if (!nc_test_conf(&nci)) {
exit();
}
exit();
} /*
* 初始化log、守护进程、信号、pidfile
*/
status = nc_pre_run(&nci);
if (status != NC_OK) {
/*
* 失败就对上面的操作进行析构清理,主要就是删除pidfile、关闭log文件描述符
*/
nc_post_run(&nci);
exit();
} nc_run(&nci); nc_post_run(&nci); exit();
}

下面我们进到各个函数里面看一下吧。

1、函数nc_set_default_options

 static void
nc_set_default_options(struct instance *nci)
{
int status; nci->ctx = NULL; nci->log_level = NC_LOG_DEFAULT;
nci->log_filename = NC_LOG_PATH; nci->conf_filename = NC_CONF_PATH; nci->stats_port = NC_STATS_PORT;
nci->stats_addr = NC_STATS_ADDR;
nci->stats_interval = NC_STATS_INTERVAL; status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN);
if (status < ) {
log_warn("gethostname failed, ignored: %s", strerror(errno));
nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, "unknown");
}
nci->hostname[NC_MAXHOSTNAMELEN - ] = '\0'; nci->mbuf_chunk_size = NC_MBUF_SIZE; nci->pid = (pid_t)-;
nci->pid_filename = NULL;
nci->pidfile = ;
}

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

2、函数nc_get_options

 static rstatus_t
nc_get_options(int argc, char **argv, struct instance *nci)
{
int c, value; opterr = ; for (;;) {
c = getopt_long(argc, argv, short_options, long_options, NULL);
if (c == -) {
/* no more options */
break;
} switch (c) {
case 'h':
show_version = ;
show_help = ;
break; case 'V':
show_version = ;
break; case 't':
test_conf = ;
break; case 'd':
daemonize = ;
break; case 'D':
describe_stats = ;
show_version = ;
break; case 'v':
value = nc_atoi(optarg, strlen(optarg));
if (value < ) {
log_stderr("nutcracker: option -v requires a number");
return NC_ERROR;
}
nci->log_level = value;
break; case 'o':
nci->log_filename = optarg;
break; case 'c':
nci->conf_filename = optarg;
break; case 's':
value = nc_atoi(optarg, strlen(optarg));
if (value < ) {
log_stderr("nutcracker: option -s requires a number");
return NC_ERROR;
}
if (!nc_valid_port(value)) {
log_stderr("nutcracker: option -s value %d is not a valid "
"port", value);
return NC_ERROR;
} nci->stats_port = (uint16_t)value;
break; case 'i':
value = nc_atoi(optarg, strlen(optarg));
if (value < ) {
log_stderr("nutcracker: option -i requires a number");
return NC_ERROR;
} nci->stats_interval = value;
break; case 'a':
nci->stats_addr = optarg;
break; case 'p':
nci->pid_filename = optarg;
break; case 'm':
value = nc_atoi(optarg, strlen(optarg));
if (value <= ) {
log_stderr("nutcracker: option -m requires a non-zero number");
return NC_ERROR;
} if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) {
log_stderr("nutcracker: mbuf chunk size must be between %zu and"
" %zu bytes", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE);
return NC_ERROR;
} nci->mbuf_chunk_size = (size_t)value;
break; case '?':
switch (optopt) {
case 'o':
case 'c':
case 'p':
log_stderr("nutcracker: option -%c requires a file name",
optopt);
break; case 'm':
case 'v':
case 's':
case 'i':
log_stderr("nutcracker: option -%c requires a number", optopt);
break; case 'a':
log_stderr("nutcracker: option -%c requires a string", optopt);
break; default:
log_stderr("nutcracker: invalid option -- '%c'", optopt);
break;
}
return NC_ERROR; default:
log_stderr("nutcracker: invalid option -- '%c'", optopt);
return NC_ERROR; }
} return NC_OK;
}

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

3、函数nc_test_conf

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

4、函数nc_pre_run

 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();
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;
}

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

5、函数nc_run

 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开始启动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

twemproxy源码分析1——入口函数及启动过程的更多相关文章

  1. [源码分析] 消息队列 Kombu 之 启动过程

    [源码分析] 消息队列 Kombu 之 启动过程 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 是 ...

  2. Spark源码分析(一)-Standalone启动过程

    原创文章,转载请注明: 转载自http://www.cnblogs.com/tovin/p/3858065.html 为了更深入的了解spark,现开始对spark源码进行分析,本系列文章以spark ...

  3. linux源码分析(二)-启动过程

    前置:这里使用的linux版本是4.8,x86体系. 这篇是 http://home.ustc.edu.cn/~boj/courses/linux_kernel/1_boot.html 的学习笔记. ...

  4. Tomcat源码分析 (七)----- Tomcat 启动过程(二)

    在上一篇文章中,我们分析了tomcat的初始化过程,是由Bootstrap反射调用Catalina的load方法完成tomcat的初始化,包括server.xml的解析.实例化各大组件.初始化组件等逻 ...

  5. Tomcat源码分析 (六)----- Tomcat 启动过程(一)

    说到Tomcat的启动,我们都知道,我们每次需要运行tomcat/bin/startup.sh这个脚本,而这个脚本的内容到底是什么呢?我们来看看. 启动脚本 startup.sh 脚本 #!/bin/ ...

  6. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  7. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  8. external-provisioner源码分析(3)-组件启动参数分析

    更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 external-provisioner源码分析(3)-组件启动参数分析 本文将对extern ...

  9. ceph-csi源码分析(2)-组件启动参数分析

    更多ceph-csi其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 ceph-csi源码分析(2)-组件启动参数分析 ceph-csi组件的源码分析分为五部分: ...

随机推荐

  1. ECSHOP中transport.js和jquery冲突的解决方法

    jQuery 和global.js 冲突 百度和google多次,根据网上的大多数建议和自己测试,解决办法如下:删除global.js 或者global.js 文件的10-13行屏蔽//Object. ...

  2. Visual Studio中 sln 和 suo 文件

    我们通过双击.sln加载出我们的工程,Visual Studio采用两种文件类型(.sln和.suo)来存储解决方案的设置,它们总称为解决方案文件. .sln文件 solution,即解决方案.它引用 ...

  3. Spring @Autowired 注解不生效

    @Autowired默认不生效.为了生效,需要在xml配置:<context:annotation-config> 注解一<context:component-scan base-p ...

  4. http://my.oschina.net/China2012/blog/178655

    http://my.oschina.net/China2012/blog/178655 http://git.oschina.net/huangyong/smart-framework

  5. POJ 2664 Prerequisites?(简单题)

    [题意简述]:k:已经选择的科目数:m:选择的科目类别:c:能够选择的科目数.r:要求最少选择的科目数量 在输入的k和m以下的一行是选择的科目号. 比如: 3 2 //3是他选择了3科.2表示选择了两 ...

  6. php fopen函数返回false

    使用yum安装的apache2.4.6   php 7.2.2  正确安装后使用fopen()函数打开文件时返回false 百度一下又三种原因 1.目录或者文件夹权限原因 (可以用   chmod - ...

  7. db2 v9.5迁移至v10.5,及遇重名节点数据库无法创建db的解决办法

    同系统同版本可以使用备份恢复,本文前提是不同系统不同版本,使用db2move命令. 1.db2move db db_name export 此处注意,先建个目录放文件,因为文件比较多,如果上来直接ex ...

  8. Struts基本概念

    内容源自: Struts2基本概念 一.struts2体系结构: 1.Web浏览器请求一个资源.2.过滤器Dispatcher查找方法,确定适当的Action.3.拦截器自动对请求应用通用功能,如验证 ...

  9. wireshark在windows下无法抓取localhost数据包

    在调试SSL时要抓包,通过tcpview和minisniffer等工具明明看到tcp连接已经建立并开始收发数据了,但wireshark却总是无法抓到相应的数据包. 今天早上,HQ的高工告诉我“wire ...

  10. [Functional Programming ADT] Initialize Redux Application State Using The State ADT

    Not only will we need to give our initial state to a Redux store, we will also need to be able to re ...