澳门金沙vip 2

澳门金沙viptwemproxy接收流程探索——剖析twemproxy代码正编

本文旨在帮助大家探索出twemproxy接收流程的代码逻辑框架,有些具体的实现需要我们在未来抽空去探索或者大家自行探索。在这篇文章开始前,大家要做好一个小小的心理准备,由于twemproxy代码是一份优秀的c语言代码,为此,在twemproxy的代码中会大篇幅使用c指针。但是不论是普通类型的指针还是函数指针,都可以让我们这些c语言使用者大饱眼福,生出一种“原来还可以这样写!!!”的快感。

本篇将去探索twemproxy源码的主干流程,想来对于想要开始啃这份优秀源码生肉的童鞋会有不小的帮助。这里我们首先要找到 twemproxy正确的打开方式——twemproxy的文件结构,接着介绍twemproxy程序代码框架,最后介绍twemproxy程序的主干流程。主干流程是本章节的重中之重。这次主要是为了能将这份代码较为复杂的流程进行一些简单的模块分解和流程分解,以方便我们后面的阅读。

 

twemproxy的文件结构

数据结构

当然接下来首先要上一幅图,就是twemproxy的文件结构图

在探索twemproxy接收流程之前,我们必须对一些我们会用到的数据结构进行说明,以便我们更好地去探索,这边在讲解结构时,仅仅讲解与twemproxy接收流程相关的代码,其他代码暂时不进行剖析。

澳门金沙vip 1

 

图1 文件结构图

mbuf

如图1所示,src下的是我们最重要的业务代码,这里有几个重要的文件夹,1文件夹里的是网络编程模型,里面包含epoll,kqueue以及evport几个常用的模型。2文件夹里的是哈希算法,其主要来哈希redis协议或者memcache协议的key,包括crc16,crc32,MD5等哈希算法。3是比较重要的文件,它是在这里不仅完成了解析redis协议或者memcache协议,还完成了这两个协议的分片工作。

在nc_mbuf.h里

这里还有几个重要的文件组,4是程序的入口,如图所示,main函数就在其中,5是管理与客户端的连接,6是用来解析配置文件的,7是实现了透明连接池的,8是可以看到主要的程序流程,9是实现了内存管理的内存池,10是数据收发的主干流程,11是数据请求和响应,是数据收发流程10的具体实现,12是用于管理与服务器的连接。

1 struct mbuf {
2     uint32_t           magic;   /* mbuf magic (const) 这个值不是很理解是什么意思,一般是0xdeadbeef*/
3     STAILQ_ENTRY(mbuf) next;    /* next mbuf 下一块mbuf,代码里所有的mbuf几乎都是以单向链表的形式存储的*/
4     uint8_t            *pos;    /* read marker 表示这块mbuf已经读到那个字节了*/
5     uint8_t            *last;   /* write marker 表示这块mbuf已经写到哪个字节*/
6     uint8_t            *start;  /* start of buffer (const) 表示这块mbuf的起始位置*/
7     uint8_t            *end;    /* end of buffer (const) 表示这块mbuf的结束位置*/
8 };
9 STAILQ_HEAD(mhdr, mbuf);    /*mhdr是mbuf单向队列的队列头部*/

这里可以初步窥视到twemproxy精致的代码结构和高可读性,通过查看文件的名字,我们竟然可以大概YY出这个文件下的代码的作用,而且是八九不离十。

这里要对mbuf解释几句,这里涉及到nc_mbuf.c里的代码:

 

1.mbuf的每一块可以通过配置规定其大小
,可以说每一块mbuf的大小都是一个固定值,为此在生成时mbuf会去申请一个固定大小的内存,如果这个大小是mbuf_chunk_size,那么end
= start + mbuf_chunk_size – sizeof(struct
mbuf),为此start,end,以及magic都是定值。

通过这个我们得到了阅读代码的整体思路(我们本次阅读代码的重点是redis服务器集群下的twemproxy)

2.mbuf在申请后一般不会被释放,在使用完后会被放入static struct mhdr
free_mbufq这个队列中,一旦要使用mbuf时首先从free_mbufq中取出未使用的mbuf,如果这个队列为空时,它才会去向系统申请新的mbuf。

1.首先以10,11为主线来观察整个程序的收发流程,因为我们知道作为一个缓存中间件代理,最重要的无非是与客户端以及服务器的交互流程。

 

2.再在主干流程上慢慢拓展,通过探索3文件夹里的解析分片功能,来谈下redis协议的细节和它能进行分片的原因。

msg

3.接着通过上述标红的各功能点6,7,9去观察整个程序,这样对于程序的实现细节有一定的了解。

在nc_message.h里

4.最后我们再去观察一些次要的功能点,如监控,日志记录以及哈希算法等功能的实现。

 1 struct msg {
 2    /*
 3     ...
 4    */
 5     struct conn          *owner;          /* message owner - client | server 服务端或客户端连接*/
 6    /*
 7     ...
 8    */
 9     struct mhdr          mhdr;            /* message mbuf header mbuf单向队列的队列头部*/
10     uint32_t             mlen;            /* message length mbuf字节长度*/
11    /*
12     ...
13    */
14     uint8_t              *pos;            /* parser position marker 现在解析到哪个个字节*/
15     msg_parse_t          parser;          /* message parser 消息解析函数指针*/
16     msg_parse_result_t   result;          /* message parsing result 消息解析结果*/
17    /*
18     ...
19    */
20 
21 };

 

msg是用来存储每一条发送过来的redis包的内容,一般一个msg对应一个redis包,所有收发网络数据都存储在mhdr中。

twemproxy的主干流程概述

 

接下来我们要探索twemproxy的主干流程,首先我们要知道twemproxy与外界如何交互的,在上文《twemproxy架构分析》中,我们知道了twemproxy架构的架构,为此,我们可以抽象出一幅图形

conn

澳门金沙vip 2

在connection.h中

图2.twemproxy零层数据流图

 1 struct conn {
 2    /*
 3     ...
 4    */
 5    int                 sd;              /* socket descriptor 套接字描述符*/    
 6    /*
 7     ...
 8    */
 9     conn_recv_t         recv;            /* recv (read) handler 接收msg函数指针*/
10     conn_recv_next_t    recv_next;       /* recv next message handler 接收下一个msg的函数指针*/
11     conn_recv_done_t    recv_done;       /* read done handler 接收完成的函数指针*/
12    /*
13     ...
14    */
15     size_t              recv_bytes;      /* received (read) bytes 接收数据的字节数*/
16     size_t              send_bytes;      /* sent (written) bytes 发送数据的字节数*/
17    /*
18     ...
19    */
20     err_t               err;             /* connection errno 接受数据错误*/
21     unsigned            recv_active:1;   /* recv active? 是否在接收数据*/
22     unsigned            recv_ready:1;    /* recv ready? 是否准备接收数据*/
23    /*
24     ...
25    */
26     unsigned            eof:1;           /* eof? aka passive close? 数据读到尾部*/
27     unsigned            done:1;          /* done? aka close? 完成数据接收*/
28     unsigned            redis:1;         /* redis?           网络协议是不是redis*/
29    /*
30     ...
31    */
32 };

如图2,这里的conn是表示由客户端发起的与twemproxy的连接,而s_conn是由twemproxy发起的与服务端的连接

 conn是与服务端或客户端的连接,用于管理连接上的所有事件和网络数据

这样我们可以将数据收发过程分成以下四步:

 

1.twemproxy首先接受客户端发起的conn请求。

接收流程

2.再通过哈希conn请求的key值将其切分成具有相同key哈希值的请求——s_conn请求发给服务端——redis服务集群

首先看下主要流程,很简单的代码在nc_message.c中的msg_recv

3.接着等待接受来自服务端——redis服务集群的s_conn响应,通过这之前的映射关系将其和conn请求关联起来。

 1 rstatus_t
 2 msg_recv(struct context *ctx, struct conn *conn)
 3 {
 4     rstatus_t status;
 5     struct msg *msg;
 6 
 7     ASSERT(conn->recv_active);
 8 
 9     conn->recv_ready = 1;//表示准备接收网络数据
10     do {
11         msg = conn->recv_next(ctx, conn, true);
12         if (msg == NULL) {
13             return NC_OK;
14         }
15 
16         status = msg_recv_chain(ctx, conn, msg);//接收函数链,在这个流程中会改变conn->recv_ready的值,表示本次接收流程终止
17         if (status != NC_OK) {
18             return status;
19         }
20     } while (conn->recv_ready);//一旦不准备接收网络数据,就停止
21 
22     return NC_OK;
23 }

4.最后将每一个conn请求对应的每一个conn响应发给客户端。

 在这个代码中我们会发现一个conn->recv_next,目前我们只要知道它是准备接收下一个msg的函数,不需要知道他的具体实现,因为他在《twemproxy代码框架概述——剖析twemproxy代码前编》提到的客户层服务层扮演的角色是不同的,为此,实现也是不同的,这里主要指的是《twemproxy代码框架概述——剖析twemproxy代码前编》提到的模块1模块3,在这里我们居然看到了c语言的代码里出现了一个在面向对象语言中才有的特性——多态,在下面几篇文章的探索中会讲到,不小心做了广告,请无视上面的部分内容。

 

 

这样我们可以将其分成两个部分,与客户端交互的1,4是客户层,与服务端交互的2,3是服务层。这样twemproxy就被分成了下面一幅图。