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

    导出的Excel标题.Sheet名称.数据内容都可以使用中文​ 一.pom.xml引入jar包 1 2 3 4 5 <dependency>             <groupId ...

  2. juqery.fn.extend和jquery.extend

    jquery.fn == jquery.prototype //true jquery.extend( obj1,obj2 ) 用一个或多个对象来拓展一个对象,返回拓展之后的对象 var aaa = ...

  3. postgres--wal

    WAL机制 持久性指事务提交后对系统的影响必须是永久的,即使系统意外宕机,也必须确保事务修改的数据已真正永久写入到永久存储中. 最简单的实现方法,是在事务提交后立即将修改的数据写到磁盘.但磁盘和内存之 ...

  4. linux面试题目—2

    linux面试题目—2 二 选择题 1.关闭linux系统(不重新启动)可使用命令 B . A Ctrl+Alt+Del B halt C shutdown -r now D reboot 2.实现从 ...

  5. 关于TagHelper的那些事情——Microsoft.AspNet.Mvc.TagHelpers介绍

    写在开始 在上一篇文章中,简单介绍了什么是TagHelper,怎么使用它.接下来我会简单介绍一下微软随着ASP.NET5一起发布的TagHelpers.它们分别是: AnchorTagHelper C ...

  6. 初入android驱动开发之字符设备(一)

    大学毕业,初入公司,招进去的是android驱动开发工程师的岗位,那时候刚进去,首先学到的就是如何搭建kernel.android的编译环境,然后就是了解如何刷设备以及一些最基本的工具.如adb.fa ...

  7. Oracle imp关于fromuser 和 touser的用法

    fromuser就是把当前的dmp文件中的某一个用户下的数据取出.touser就是把现在dmp文件中的数据导入到目标库的指定user下.具体命令这样.exp userid=system/manager ...

  8. Converter -> public static int ToInt32(double value) 你用对了么?

    Convert.ToInt32()  是我们经常使用的方法,但如果我们写如下的代码,能确定它的输出值么? var x = 7.5; Console.WriteLine(7.5 + ": &q ...

  9. (转)如何在maven环境中设置JVM参数

    有时候我们需要设定maven环境下的JVM参数,以便通过maven执行的命令或启动的系统能得到它们需要的参数设定.比如:当我们使用jetty:run启动jetty服务器时,在进行热部署时会经常发生:J ...

  10. vue1.0 的过滤器

    vue1.0  自带的过滤器: 一 .过滤器写法 {{ message | Filter}} 二. Vue自带的过滤器:capitalize 功能:首字母大写 <!DOCTYPE html> ...