最近工作中需要写一个一致性哈希的代理,在网上找到了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. Scut快速开发

    1     开发环境 Scut Lib版本:5.2.3.2 需要安装的软件 a)        IIS和消息队列(MSMQ) b)        数据库,Sql2005以上版本 c)        V ...

  2. ylbtech-LanguageSamples-Indexers(索引器)

    ylbtech-Microsoft-CSharpSamples:ylbtech-LanguageSamples-Indexers(索引器) 1.A,示例(Sample) 返回顶部 “索引器”示例 本示 ...

  3. iOS:图片上传时两种图片压缩方式的比较

    上传图片不全面的想法:把图片保存到本地,然后把图片的路径上传到服务器,最后又由服务器把路径返回,这种方式不具有扩展性,如果用户换了手机,那么新手机的沙盒中就没有服务器返回的图片路径了,此时就无法获取之 ...

  4. linux下软件的更新命令

    linux下软件的更新命令   在ubuntu服务器下安装包的时候,经常会用到sudo apt-get install 包名 或 sudo pip install 包名,那么两者有什么区别呢? 1.区 ...

  5. Android 常见内存泄漏的解决方式

    在Android程序开发中.当一个对象已经不须要再使用了,本该被回收时.而另外一个正在使用的对象持有它的引用从而导致它不能被回收.这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了. ...

  6. 在笛卡尔坐标系上描绘函数 y=4x^2-2/4x-3

    代码: <!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type ...

  7. Json 简易教程

    一.什么是Json JSON的全称是"JavaScript Object Notation",意思是JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式 ...

  8. 用filter:grayscale将图片过滤成灰色

    设置成百分之百直接过滤成灰色: img{filter:gray; filter:grayscale(100%); -0-filter:grayscale(100%); -moz-filter:gray ...

  9. css position: relative | absolute | static | fixed详解

    static(静态):没有特别的设定,遵循基本的定位规定,不能通过z-index进行层次分级. fixed(固定定位):这里所固定的参照对象是可视窗口而并非是body或是父级元素.可通过z-index ...

  10. Odoo,OpenERP widget标签

    Odoo,OpenERP widget标签 widget="statusbar" 头部状态条标签 widget="email"  电子邮件地址标签 widget ...