目的

       MySQL网络通信数据结构NET,是基于Vio底层封装,用于实现Client/Server网络通信的基本处理。而核心处理主要是网络通信的读写策略,设计良好的读写策略,可以有效提高网络通信的性能。本文主要通过分析MySQL网络通信数据结构NET,进一步深入理解读写策略。

数据结构

       MySQL数据结构NET,定义在源码文件/include/mysql_com.h,主要函数的实现在源码文件/sql/net_serv.cc中,主要用于实现mysql客户端与mysqld服务端进行通信。具体定义如下所示:

 

typedef struct st_net {

#if !defined(CHECK_EMBEDDED_DIFFERENCES) || !defined(EMBEDDED_LIBRARY)

  Vio *vio;

  unsigned char *buff,*buff_end,*write_pos,*read_pos;

  my_socket fd;    /* For Perl DBI/dbd */

  /* The following variable is set if we are doing several queries in one command ( as in LOAD TABLE ... FROM MASTER ), and do not want to confuse the client with OK at the wrong time */

  unsigned long remain_in_buf,length, buf_length, where_b;

  unsigned long max_packet,max_packet_size;

  unsigned int pkt_nr,compress_pkt_nr;

  unsigned int write_timeout, read_timeout, retry_count;

  int fcntl;

  unsigned int *return_status;

  unsigned char reading_or_writing;

  char save_char;

  my_bool unused1; /* Please remove with the next incompatible ABI change. */

  my_bool unused2; /* Please remove with the next incompatible ABI change */

  my_bool compress;

  my_bool unused3; /* Please remove with the next incompatible ABI change. */

  /* Pointer to query object in query cache, do not equal NULL (0) for queries in cache that have not stored its results yet */

#endif

  /* Unused, please remove with the next incompatible ABI change. */

  unsigned char *unused;

  unsigned int last_errno;

  unsigned char error;

  my_bool unused4; /* Please remove with the next incompatible ABI change. */

  my_bool unused5; /* Please remove with the next incompatible ABI change. */

  /** Client library error message buffer. Actually belongs to struct MYSQL. */

  char last_error[MYSQL_ERRMSG_SIZE];

  /** Client library sqlstate buffer. Set along with the error message. */

  char sqlstate[SQLSTATE_LENGTH+1];

  void *extension;

#if defined(MYSQL_SERVER) && !defined(EMBEDDED_LIBRARY)

  /* Controls whether a big packet should be skipped. Initially set to FALSE by default. Unauthenticated sessions must have this set to FALSE so that the server can't be tricked to read packets indefinitely. */

  my_bool skip_big_packet;

#endif

} NET;

 

       从以上定义中可知,NET数据结构主要包含:网络底层封装结构Vio对象,提供底层的读写函数,具体的分析参考《MySQL数据结构分析--Vio》;用于数据操作的指针地址*buff、*buff_end、*write_pos、*read_pos,分别指向当前数据存储的首地址、结束地址、当前写位置及读位置;参数remain_in_buf、buf_length、where_b分别表示buff中剩余的数据长度、buff的总长度以及buff中数据的偏移值,length参数未使用;max_packet和max_packet_size分别表示通信包中数据的最大长度和通信包的最大长度;pkt_nr、compress_pkt_nr分别表示下一个分包以及压缩包的下一个分包;write_timeout、read_timeout、retry_count分别表示写超时时间、读超时时间以及超时重试次数;compress参数用于检测当前数据包是否为压缩方式传输,如果需要压缩,则通过zlib压缩后传输。

源码分析

       对NET数据结构的处理方法进行分析,主要是网络读写策略进行分析,进一步深入了解MySQL网络通信。

基本常量

       在分析具体的读写策略之前,先给出常用的一些常量:

       NET_HEADER_SIZE=4:用四个字节表示NET的包头长度。

       COMP_HEADER_SIZE=3:压缩包头长度。

net_data_is_ready函数

       net_data_is_ready()函数用于检查socket是否读取数据已经准备好。该函数是内部函数,关键点主要是IO多路复用机制。如果有poll机制,则使用poll函数(Linux下默认都支持这种策略),poll使用pollfd数据结构,没有最大连接数限制;如果在windows环境下,使用select函数。poll和select方式随着连接数的增加,性能会影响较大,而基于事件机制的epoll,则不会存在此问题。具体的poll、select以及epoll的详细资料可参考巨著--《unix网络编程》。

net_real_write函数

       net_real_write()函数是NET真正的写操作处理函数。该函数的具体处理逻辑是:首先判断是否数据是否压缩,如果需要压缩,分配内存空间并调用zlib进行压缩。然后,通过Vio通过具体的网络通信方式,进行写处理。写处理根据具体的通信方式,进行不同的写处理策略。如果写失败,在超时时间内重传。如果写成功,继续写packet中剩余的数据。具体的流程图如下所示:

       以下流程图中,为了简洁直观,将压缩处理过程和写处理作为一个整体过程。而实际这两个处理过程的详细处理逻辑可参考源码,由于逻辑较简单,不再赘述。

 


MySQL数据结构分析--NET-LMLPHP

图1net_real_write()流程图

      

net_write_buff函数

       net_write_buff()函数用于缓冲写处理过程。该函数分别对压缩和非压缩的数据进行不同的处理:首先,对于非压缩的数据,直接将传输的数据信息拷贝到net->buff缓冲中,然后调用net_real_write()函数进行网络写处理;如果数据是压缩传输,那么循环将传输的数据信息交由net_real_write()函数处理,由于压缩数据包的大小不超过MAX_PACKET_LENGTH(即压缩数据包的大小不超过16M),所以传输的数据可以在一个压缩数据包内传输结束。因为net_write_buff()处理逻辑主要是往net->buff中缓冲写处理的过程,处理逻辑较简单,不在赘述。

my_net_write函数

       my_net_write()函数是对外提供网络写处理的函数。该函数主要用于封装传输的数据信息,调用底层处理逻辑进行数据包的传输。主要处理逻辑是:如果传输的数据长度大于MAX_PACKET_LENGTH(即16M),进行分包写处理;否则正常写处理。

       当传输的数据长度大于16M时,每个满包的数据封装设计如下图所示。包长度为MAX_PACKET_LENGTH(16M-1),正好占用3个字节;一个字节存储下一个包的序列号,这四个字节构成了包头长度;之后空间存储包的数据信息,长度为MAX_PACKET_LENGTH。

 


MySQL数据结构分析--NET-LMLPHP

图2 数据包封装设计

 

       从以上设计可以看出,在读取包后,解析包头时,通过解析net->buff[3]包的序列号和包长度是否为MAX_PACKET_LENGTH,即可判断数据包是否乱序。

net_write_command函数

       net_write_command()函数是用于传输command指令及数据信息的写处理操作。与my_net_write()函数的不同之处在于:需要多一个字节存储command指令,并且command指令放在数据net->buff[4]位置;然后在command指令后,存放数据头信息和数据信息。具体的实现细节,不再赘述。包的数据封装设计如下:



MySQL数据结构分析--NET-LMLPHP

图3command数据包封装设计

 

my_real_read函数

       my_real_read()函数是NET真正读取数据的处理逻辑。读取数据分为两个处理过程:首先读取包头信息,通过解析表头内容,处理接下来的数据读取;然后,根据包头信息,读取数据信息。在解析表头中,首先读取表头;然后检查传输是否乱序;如果是压缩传输的数据,需要读取3个字节长度数据,得到压缩数据的长度。读取数据时,根据解析的数据长度,读取数据信息。

       由于读取过程共用,因此在代码设计中,使用两次循环执行。其中在第一次处理包头时,需要判断包乱序和压缩等情况。特别的,检查包乱序的方法与my_net_write()处理过程中包的封装有关,详细来说,如果当前包序列不是最后一个包,那么net->buff[0]的值为FF(即255),否则表示包发送乱序。具体处理流程如下所示:

 


MySQL数据结构分析--NET-LMLPHP

图4my_real_read()流程图

 

my_net_read函数

       my_net_read()函数是对外提供网络读处理的函数。该函数分别对压缩包和非压缩包进行不同处理。并且对于多个分包的情况下,将多个包的数据合并。该处理过程不对net_write_command()函数的数据格式进行读处理,仅对my_net_write()函数的正常数据封装格式进行处理。该过程中主要根据读取的数据,对net中的buff及read_pos等参数进行修改,而对压缩数据,需要调用zlib库进行解压数据处理。

结论

       通过以上分析,对MySQL的网络传输结构NET及其读写处理策略有了深入的理解。在分析过程中得到以下结论:网络读写处理通过net_write_buffer()可以减少频繁的IO,提高网络吞吐率;调用底层的Vio数据结构的封装实现,进一步提高了读写的性能,具体参考《MySQL数据结构分析--Vio》;使用select/poll方式进行多路IO复用,随着连接数的增加,性能影响较大。

参考资料

1、《UNIX网络编程(卷1):套接字联网API》
2、《MySQL数据结构分析--Vio》

 

09-21 03:09