用C语言撸了个DBProxy
前言
笔者在阅读了一大堆源码后,就会情不自禁产生造轮子的想法。于是花了数个周末的时间用C语言撸了一个DBProxy(MySQL协议)。在笔者的github中给这个DBProxy起名为Hero。
为什么采用C语言
笔者一直有C情节,求学时候一直玩C。工作之后,一直使用Java,就把C渐渐放下了。在笔者最近一年阅读了一堆关于linux Kernel(C)和MySQL(C++)的源码后,就萌生了重拾C的想法。同时用纯C的话,势必要从基础开始造一大堆轮子,这也符合笔者当时造轮子的心境。
造了哪些轮子
习惯了Java的各种好用的类库框架之后,用纯C无疑就是找虐。不过,既然做了这个决定,跪着也得搞完。在写Hero的过程中。很大一部分时间就是在搭建基础工具,例如:
Reactor模型
内存池
packet_buffer
协议分包处理
连接池
......
下面在这篇博客里面一一道来
DBProxy的整体原理
Hero(DBProxy)其实就是自己伪装成MySQL,接收到应用发过来的SQL命令后,再转发到后端。如下图所示:
由于Hero在解析SQL的时候,可以获取各种信息,例如事务这个信息就可以通过set auto_commit和begin等命令存在连接状态里面,再根据解析出来的SQL判断其是否需要走主库。这样就可以对应用透明的进行主从分离以至于分库分表等操作。
当然了,笔者现在的Hero刚把基础的功能搭建好(协议、连接池等),对连接状态还没有做进一步的处理。
Reactor模式
Hero的网络模型采用了Reactor模式,而且是多线程模型,同时采用epoll的水平触发。
采用多线程模型
为什么采用多线程,纯粹是为了编写代码简单。多进程的话,还得考虑worker进程间负载均衡问题,例如nginx就在某个worker进程达到7/8最大连接数的时候拒绝获取连接从而转给其它worker。多线程的话,在accept线程里面通过取模选择一个worker线程就可以轻松的达到简单的负载均衡结果。
采用epoll水平触发
为什么采用epoll的水平触发,纯粹也是为了编写代码简单。如果采用边缘触发的话,需要循环读取直到read返回字节数为0为止。然而如果某个连接特别活跃,socket的数据一直读不完,会造成其它连接饥饿,所以必须还得自己写个均衡算法,在读到一定程度后,去选择其它连接。
Reactor
整体Reactor模型如下图所示:
其实代码是很简单的,如下面代码所示的就是reactor中的accept处理:
// 中间省略了大量的错误处理
int init_reactor(int listen_fd,int worker_count){
// 注意,这边需要是unsigned 防止出现负数
unsigned int current_worker = 0;
for(;;){
int numevents = 0;
int retval = epoll_wait(reactor->master_fd,reactor->events,EPOLL_MAX_EVENTS,500);
......
for(j=0; j < numevents; j++){
client_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len))
poll_add_event(reactor->wo