概述
本文介绍twemproxy的系统初始化过程。该过程包括以下几个方面的内容:
- 读取配置文件,根据配置文件初始化数据结构
- 和后台服务器建立连接,形成服务器连接池
- 初始化事件处理框架,并设置最开始的事件处理函数
- 创建twemproxy的监听socket,并把该监听socket添加到事件处理框架中,用来监听客户端的连接请求。
加载配置文件
twemproxy0.4是根据YAML文件格式来进行后台参数配置。在进程启动时通过-c或–conf-file选项来指定配置文件。启动的实际命令如下例子:
cd twemproxy-0.4.1
./src/nutcracker -c ./conf/nutcracker.yml
若下载的是twemproxy0.4的源码,配置文件的样例文件在源码根目录下的conf目录下。
样例配置文件example.yml如下:
alpha:
listen: 0.0.0.0:22121
hash: fnv1a_64
distribution: ketama
auto_eject_hosts: true
redis: true
server_retry_timeout: 2000
server_failure_limit: 1
servers:
- 127.0.0.1:6379:1
- 127.0.0.1:6378:1
- 127.0.0.1:6377:1
beta:
listen: 0.0.0.0:22122
hash: fnv1a_64
hash_tag: "{}"
distribution: ketama
auto_eject_hosts: false
timeout: 400
redis: true
servers:
- 127.0.0.1:6380:1
- 127.0.0.1:6381:1
- 127.0.0.1:6382:1
配置文件中各个参数的说明如下:
- listen参数
配置twemproxy监听的地址和端口。注意:每个服务池都需要配置一个监听地址和端口。监听的地址可以相同,但端口需要不同,这样twemproxy才能区分不同的服务器池。
从example.yml配置文件中的listen参数可以看出,有两个redis的服务器池,这两个服务器池分别由监听22122和22121的代理来服务。
- client_connections
允许来自redis客户端的最大连接数。默认情况下无限制,但操作系统施加的限制仍然存在。
- hash
使用的hash函数的名字。可以是以下几种:
函数名 |
---|
md5 |
crc16 |
crc32 |
crc32a |
fnv1_64 |
fnv1a_64 |
fnv1_32 |
fnv1a_32 |
hsieh |
murmur |
jenkins |
- hash_tag参数
由于key的分配非常重要,在使用集群时,最好能让key的分配可控性更强。
该参数可以让key的分配可控性更强。若为该服务器池配置了hash_tag,将会使用hash_tag之内的部分作为key分配的依据,否则会使用整个key的内容作为分配依据。
- distribution
设置key分配算法,可以有三种:
(1) ketama: 通过一致性hash算法来选择后端服务器
(2) modula: 取模方式来选择后端服务器
(3) random: 随机选择一个后端服务器池中的服务器
- timeout
我们等待建立与服务器的连接或从服务器接收响应的超时值(以毫秒为单位)。默认情况下会无限期地等待。
- backlog
TCP listen的backlog参数。默认为512。
- preconnect
一个布尔值,用于控制在进程启动时twemproxy是否应预先连接到此池中的所有服务器。默认为false。
- redis
一个布尔值,用于控制服务器池是否使用redis或memcached协议。默认为false。
- redis_auth
在连接时对Redis服务器进行身份验证。
- redis_db
要在池服务器上使用的库编号(redis中可以通过select命令来选择数据库)。默认为0.注意:Twemproxy将始终以库 0的形式呈现给客户端。
- server_connections
可以打开到每个服务器的最大连接数。默认情况下,我们最多打开1个服务器连接。
- auto_eject_hosts
一个布尔值,用于控制服务器在连续失败server_failure_limit次数时是否应暂时去除。默认值为false
- server_retry_timeout
当auto_eject_host设置为true时,在临时弹出的服务器上重试之前等待的超时值(以毫秒为单位)。默认为30000毫秒。
- servers
此服务器池的服务器列表。格式如下:
ip:port:weight //ip地址:端口:权重
或
name:port:weight //主机名:端口:权重
系统初始化
系统初始化主要完成以下几件事情:
- (1) 初始化三个空闲队列:空闲mbuf队列,空闲msg队列,空闲conn队列
- (2) 初始化服务器池,若需要还要和后端服务建立连接
- (3) 初始化服务器key分配算法,若选用一致性hash算法,还需要构建服务器的一致性hash环。
- (4) 初始化事件处理框架,设置初始事件处理回调函数
- (5) 启动事件处理框架
初始化空闲队列
空闲队列用来保存不再使用的数据结构实体,比如:mbuf,msg,conn,这样就可以复用这些结构体,再次使用时不需要再进行内存分配动作,从而提升了性能。
在twemproxy中会初始化三个空闲队列:mbuf结构体,msg结构体,conn结构体。空闲队列的初始化代码如下:
struct context *
core_start(struct instance *nci)
{
struct context *ctx;
mbuf_init(nci); // 初始化mbuf结构体空闲队列
msg_init(); //初始化msg结构体空闲队列
conn_init(); //初始化conn结构体空闲队列
... ...
}
- 初始化mbuf队列
初始化mbuf空闲队列相对简单,主要是初始化静态变量:
tatic struct mhdr free_mbufq;
并设置每次创建mbuf结构时的内存块的size大小的变量:mbuf_chunk_size,该变量的默认值是:16k。
void
mbuf_init(struct instance *nci)
{
nfree_mbufq = 0; //设置空闲队列的mbuf结构体个数
STAILQ_INIT(&free_mbufq); //初始化队列
mbuf_chunk_size = nci->mbuf_chunk_size; //mbuf的chunk大小
mbuf_offset = mbuf_chunk_size - MBUF_HSIZE; //mbuf的offset大小
... ...
}
free_mbufq初始化后的结构如下:
- 初始化msg空闲队列
初始化msg空闲队列主要是初始化静态变量:free_msgq。
static struct msg_tqh free_msgq;
msg空闲队列初始化要完成以下事项:
(1) 设置msg队列的个数变量nfree_msgq为0
static struct msg_tqh free_msgq;
(2) 初始化静态变量free_msgq,该变量用来管理空闲队列
(3) 初始化一颗红黑树,后面要用该结构来保存msg的引用
初始化的代码如下:
void
msg_init(void)
{
...
msg_id = 0; //初始化msg_id,这是msg的计数器
frag_id = 0; //初始化frag_id,这是fragment计数器
nfree_msgq = 0; //初始化msg的个数为0
TAILQ_INIT(&free_msgq); //初始化空闲msg队列
rbtree_init(&tmo_rbt, &tmo_rbs); //初始化超时红黑树
}
- 初始化conn空闲队列
初始化conn空闲队列很简单,主要是初始化空闲conn队列的个数,并初始化free_connq队列。
static struct conn_tqh free_connq;
初始化代码如下:
void
conn_init(void)
{
... ...
nfree_connq = 0;
TAILQ_INIT(&free_connq); //初始化conn队列
}
初始化服务器池
若设置了preconnect参数为true,则会预先和后端服务器池中的每个服务器建立好连接。若按上面的服务器池配置,会得到如下的连接图:
上图只是一个示意图,实际上在ketama的这个一致性hash环中,还会存在多个虚拟的节点,会根据配置的权重来安排虚拟节点的个数,这些虚拟节点对应着图中的三个实体节点。详细的ketama算法的实现,可以阅读我的这篇文章:twemproxy0.4原理分析-一致性hash算法实现ketama分析。示意图如下:
设置事件处理函数
twemproxy0.4使用的事件驱动框架的入口都是相同的,只是不同的事件会调用不同的函数来处理。事件处理框架的函数调用流程如下:
总结
本文分析了twemproxy0.4的配置文件和系统的初始化过程。