最近工作中需要写一个一致性哈希的代理,在网上找到了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. CAS4.0.4 之自定义登录页实践

    因最近公司要用到cas登陆而且要使用自定登陆页面,网络上搜索了一下cas自定义登陆,比较好的两篇文章CAS 之自定义登录页实践和CAS 之 跨域 Ajax 登录实践,感觉写的不错,但是发现改动的地方很 ...

  2. iOS:第三方框架MJPhotoBrowser图片浏览器的使用

    介绍:MJPhotoBrowser这个第三方库是MJ老师封装的一套用来浏览图片的浏览器,可是是本地图片.网络图片.gif图片等,其也依赖了SDWebImage.SVProgressHUD.YLGIFI ...

  3. chrome.webRequest

    chrome.webRequest 描述: 使用 chrome.webRequest API 监控与分析流量,还可以实时地拦截.阻止或修改请求.  可用版本: 从 Chrome 17 开始支持.  权 ...

  4. 如何判断两个IP是否在同一网段

    下来举例说明,如何去判断A和B两个IP是否在同一网段. A IP:202.194.128.9 B IP:202.194.128.14 子网掩码:255.255.255.0 1.把A和B的地址转换为二进 ...

  5. weblogic8.1 登陆5 ip 限制

    weblogic8.1  5 ip 限制 报错信息如图所示: 解决办法:此weblogic 未破解,去网上下载破解包,然后放到 copy weblogic_sp.jar to $WL_HOME/ser ...

  6. java 过滤器补充

    多个Filter按照在配置文件中配置的filter顺序执行. 在web.xml文件中配置该Filter,使用init-param元素为该Filter配置参数,init-param可接受如下两个子元素: ...

  7. 接口测试框架开发(一):rest-Assured_接口返回数据验证

    转载:http://www.cnblogs.com/lin-123/p/7111034.html 返回的json数据:{"code":"200","m ...

  8. 算法笔记_071:SPFA算法简单介绍(Java)

    目录 1 问题描述 2 解决方案 2.1 具体编码   1 问题描述 何为spfa(Shortest Path Faster Algorithm)算法? spfa算法功能:给定一个加权连通图,选取一个 ...

  9. ionic 图片加载失败,显示默认图片代替

    1.首先编写自定义指令 angular.module('starter.directives', []) //当图片找不到事显示替代图片 .directive("errSrc", ...

  10. struts2 常用标签

    版权声明:本文为博主原创文章,未经博主允许不得转载. Struts2可以将所有标签分成3类: 准备工作:需要在JSP页面引入标签库 <%@ taglib prefix="s" ...