阿里巴巴DBA,原去哪儿网DBA。专注于MySQL源码研究、DBA运维、CGroup虚拟化及Linux Kernel源码研究等。 github:https://github.com/HengWang/ Email:king_wangheng@163.com 微博 :@王恒-Henry QQ :506437736
分类: Mysql/postgreSQL
2013-01-15 11:11:36
目的
MySQL数据结构Vio是MySQL对网络通信底层的进行封装结构,是MySQL网络通信数据结构NET重要的成员变量。Vio数据结构的封装,屏蔽了跨平台的差异性、统一了不同读写策略的接口等。使得网络通信过程可以不考虑具体实现的细节,而仅考虑算法和处理逻辑,从而处理MySQL的通信。
数据结构
MySQL数据结构Vio的定义在源码中/include/violate.h和/vio/vio_priv.h,具体定义如下所示:
enum enum_vio_type { VIO_CLOSED, VIO_TYPE_TCPIP, VIO_TYPE_SOCKET, VIO_TYPE_NAMEDPIPE, VIO_TYPE_SSL, VIO_TYPE_SHARED_MEMORY }; /* This structure is for every connection on both sides */ struct st_vio { my_socket sd; /* my_socket - real or imaginary */ HANDLE hPipe; my_bool localhost; /* Are we from localhost? */ int fcntl_mode; /* Buffered fcntl(sd,F_GETFL) */ struct sockaddr_storage local; /* Local internet address */ struct sockaddr_storage remote; /* Remote internet address */ int addrLen; /* Length of remote address */ enum enum_vio_type type; /* Type of connection */ char desc[30]; /* String description */ char *read_buffer; /* buffer for vio_read_buff */ char *read_pos; /* start of unfetched data in the read buffer */ char *read_end; /* end of unfetched data */ /* function pointers. They are similar for socket/SSL/whatever */ void (*viodelete)(Vio*); int (*vioerrno)(Vio*); size_t (*read)(Vio*, uchar *, size_t); size_t (*write)(Vio*, const uchar *, size_t); int (*vioblocking)(Vio*, my_bool, my_bool *); my_bool (*is_blocking)(Vio*); int (*viokeepalive)(Vio*, my_bool); int (*fastsend)(Vio*); my_bool (*peer_addr)(Vio*, char *, uint16*, size_t); void (*in_addr)(Vio*, struct sockaddr_storage*); my_bool (*should_retry)(Vio*); my_bool (*was_interrupted)(Vio*); int (*vioclose)(Vio*); void (*timeout)(Vio*, unsigned int which, unsigned int timeout); my_bool (*poll_read)(Vio *vio, uint timeout); my_bool (*is_connected)(Vio*); my_bool (*has_data) (Vio*); #ifdef HAVE_OPENSSL void *ssl_arg; #endif #ifdef HAVE_SMEM HANDLE handle_file_map; char *handle_map; HANDLE event_server_wrote; HANDLE event_server_read; HANDLE event_client_wrote; HANDLE event_client_read; HANDLE event_conn_closed; size_t shared_memory_remain; char *shared_memory_pos; #endif /* HAVE_SMEM */ #ifdef _WIN32 OVERLAPPED pipe_overlapped; DWORD read_timeout_ms; DWORD write_timeout_ms; #endif }; typedef struct st_vio Vio; |
枚举类型enum_vio_type定义了Vio的几种不同的网络连接类型,根据不同的类型,在网络通信时有相应不同的处理逻辑。
Vio数据结构的定义分为几个部分:成员变量部分、接口部分、SSL部分、共享内存部分和windows特有成员变量。其中成员变量包括:socket描述符sd;管道描述符hPipe;localhost表示是否为本机;fcntl_mode表示socket文件描述符的模式,通过fcntl()函数(在windows下是fcntlsocket()函数)设置sd描述符的一些特性;local和remote分别表示本地和远端的网络地址,其中sockaddr_storage是通用网络地址数据结构;addrLen远端的网络地址长度;type表示网络连接类型;数组desc是Vio的描述信息;read_buffer、read_pos、read_end分别表示读buffer缓冲的指针地址、读取的当前位置以及结束地址,socket数据读取采用缓冲机制,提高读的性能。接口部分是socket的基本操作的函数指针,根据不同的平台和不同的读写策略,分别指向不同的处理函数。SSL部分是指当定义了HAVE_OPENSSL宏时,定义SSL相关的成员变量ssl_arg。共享内存部分是指当定义了HAVE_SMEM宏时,定义共享内存相关的成员变量。如果定义了_WIN32宏时,那么需要定义windows操作的成员变量。SSL部分、共享内存部分和windows特有成员变量都是根据编译时定义的宏及平台决定的,是不同读写策略时,使用的成员变量。
源码实现
MySQL数据结构Vio对网络通信的封转,在源码的/vio/vio.c、/vio/viosocket.c、/vio/viossl.c、/vio/viosslfactories.c中实现。由于大多数的实现是根据不同的连接类型,对底层函数的调用,基本不涉及复杂的算法和处理逻辑。因此,以下内容中,仅对几个比较核心的处理过程进行简要的分析。
vio_init函数
vio_init()函数是Vio的内部初始化函数,对外接口vio_new*()初始化函数,都调用vio_init()函数进行初始化。该函数根据不同的连接类型,初始化相应类型的处理函数指针。参考源码/vio/vio.c。
vio_read_buffer函数
vio_read_buffer()函数是网络缓冲写方法,该方法同IO_CACHE的io读有相似之处。读取策略分为三种:如果read_buffer中有数据,直接从read_buffer中读取数据;如果读取的数据长度小于16K,那么调用vio_read()函数读取16K数据到read_buffer中,然后再从read_buffer中读取数据;如果数据长度大于16K,直接通过vio_read()读取制定长度的数据,不需要写入缓冲read_buffer。这种读写策略,可以有效的提高小数据量读取的性能。例如readline这种方式,需要通过逐个字符判断是否为换行符,如果直接调用vio_read()进行读取,会每次产生一次寻址、读取等物理操作。而采用缓冲读策略,所有的操作从read_buffer中获取数据,避免频繁的物理操作。
vio_close函数
vio_close()函数是关闭网络连接的操作,在该过程中,有一个问题需要特别说明。在调用close()(windows下使用closesocket()函数)进行关闭socket连接之前,调用shutdown()函数将读写关闭。这是因为,在多个进程共享一个socket套接字,调用close()只是引用数减1,其他进程仍然可以通信,直到计数为0,才将socket套接字释放。而调用shutdown(2)则使得其他进程也无法进行通信。具体close()[1]和shutdown()[2]的区别,可以参考Linux manual中相应的解释。
socket_poll_read函数
socket_poll_read()函数是实现IO复用的封装函数,从该封装函数中可以看出,MySQL在IO复用中,如果操作系统是windows,那么采用select()函数(在linux下select()的最大文件描述数目为1024,windows下无限制),在Linux系统中,使用poll()函数。select()和poll()类似,性能比select()略高。然而,这两种方式都存在一个问题,因为他们都需要遍历所有的文件描述符,当监听描述符个数增加时,监听效率降低,并且select和poll每次都要在用户态和内核态拷贝监听的描述符参数。更为高效的解决方案是epoll()方法,可以解决select()和poll()存在的不足。具体详细的分析和差异,参考相应的文档。
SSL相关的处理函数在源码的/vio/viossl.c和/vio/viosslfactories.c,具体的实现主要是对SSL相关函数的封装,不再赘述。
结论
通过以上分析可知,MySQL数据结构Vio主要封装了不同连接方式的网络通信接口,从而使得网络通信可以忽略平台差异和连接方式的不同,并且可以支持共享内存、SSL安全连接方式等通信方式。
然而在IO复用方面,MySQL数据结构Vio存在一定的不足,使用了poll()方式。对于数据库这种高并发系统来说,会随着连接数的增加,使得poll()的系统调用次数严重下降,处理能力也随之降低。因此,对MySQL来说,一般的优化方案是限制连接数,而在应用层使用连接池的策略,来解决这个问题。然而当多个不同系统同时使用一个数据库实例时,仍然会导致连接数增加,如果该值设置较小的话,会导致连接失败。最佳的方式是使用epoll()方式代替poll()方式,从本质上提高处理能力。
参考
1、
2、
3、
4、http://www.cnblogs.com/xuxm2007/archive/2011/08/15/2139809.html