日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

mysql协议重传,MySQL · 源码分析 · 网络通信模块浅析

發(fā)布時(shí)間:2023/12/2 数据库 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mysql协议重传,MySQL · 源码分析 · 网络通信模块浅析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

MySQL 網(wǎng)絡(luò)通信淺析

MySQL的網(wǎng)絡(luò)通信協(xié)議主要包含以下幾個(gè)層次,從最上層的MySQL數(shù)據(jù)包協(xié)議層到最底層的socket傳輸:

| THD

| Protocol

| NET

| VIO

| SOCKET

本文主要掃一下相關(guān)的代碼,以下分析基于MySQL5.7。

創(chuàng)建會(huì)話

在MySQL5.7中對(duì)會(huì)話協(xié)議層的代碼進(jìn)行了大量的重構(gòu)以優(yōu)化性能,并使得代碼更加可讀。以下這幅圖大概展示了幾個(gè)相關(guān)的類關(guān)系(未包含諸如windows平臺(tái)的相關(guān)類)

創(chuàng)建用戶線程堆棧是從主線程開始的,監(jiān)聽客戶端請(qǐng)求并創(chuàng)建處理線程

mysqld_main

|-->connection_event_loop

|-->listen_for_connection_event

//根據(jù)不同的監(jiān)聽模式,去監(jiān)聽新請(qǐng)求, 當(dāng)獲取到一個(gè)新的監(jiān)聽請(qǐng)求時(shí),會(huì)創(chuàng)建一個(gè)Channel_info類,用來存儲(chǔ)用戶的socket信息

|-->Connection_handler_manager::process_new_connection

|-->Per_thread_connection_handler::add_connection

//我們通常用的one thread one connection對(duì)應(yīng)的類為Per_thread_connection_handler

|-->創(chuàng)建用戶線程,線程函數(shù)為handle_connection

在MySQL5.7里一個(gè)重大的優(yōu)化,如上所述,就是關(guān)于用戶會(huì)話的thd, net, vio等信息的初始化都不是在主線程進(jìn)行的,而是創(chuàng)建用戶線程后,由用戶線程自己來完成。通過這種方式,主線程可以更高效的接受新的連接請(qǐng)求,從而優(yōu)化了在短連接場(chǎng)景下的性能。見官方博客 及相應(yīng)的worklog

下面這幅圖摘自官方博客,大家感受下5.7相比之前版本的短連接性能優(yōu)化:

創(chuàng)建用戶會(huì)話的主要函數(shù)棧包括:

handle_connection //線程入口函數(shù)

|-->init_new_thd

|-->Channel_info_local_socket::create_thd

|-->Channel_info::create_thd

|-->create_and_init_vio

|-->Protocol_classic::init_net

|-->my_net_init

|-->vio_fastsend //設(shè)置socket選項(xiàng)

* 設(shè)置IP_TOS為IPTOS_THROUGHPUT

* 設(shè)置TCP_NODELAY

|-->Global_THD_manager::add_thd

// 加入到thd鏈表上

|-->thd_prepare_connection

|-->login_connection

|--> check_connection

//檢查鏈接,設(shè)置thd的鏈接信息,

|--> vio_keepalive // 設(shè)置SO_KEEPALIVE選項(xiàng)

|--> acl_authenticate // 權(quán)限認(rèn)證

|-->prepare_new_connection_state

//如果連接打開了CLIENT_COMPRESS,設(shè)置NET::compress為true。

//如果設(shè)置了init_connect,則在這里執(zhí)行對(duì)應(yīng)的SQL語句

/* 循環(huán)接受請(qǐng)求并處理(do_command) */

|-->Protocol_classic::get_command

|-->Protocol_classic::read_packet

|-->my_net_read// 讀取command包,這里的讀超時(shí)時(shí)間由wait_timeout決定

|-->close_connection

|-->THD::disconnect

|-->THD::shutdown_active_vio

|-->vio_shutdown/* 關(guān)閉socket */

NET/VIO

my_net_write

該函數(shù)用于將數(shù)據(jù)拷貝到NET緩沖區(qū),當(dāng)長度大于MAX_PACKET_LENGTH(即4MB-1字節(jié))會(huì)對(duì)Packet進(jìn)行拆分成多個(gè)packet。每個(gè)Packet的頭部都會(huì)留4個(gè)字節(jié),其中:1~3字節(jié),存儲(chǔ)該packet的長度,第4個(gè)字節(jié)存儲(chǔ)當(dāng)前的packet的序號(hào),每存儲(chǔ)一次后遞增net->pkt_nr。

每個(gè)Net對(duì)象有一個(gè)Buff(net->buff),即將發(fā)送的數(shù)據(jù)被拷貝到這個(gè)buffer中,當(dāng)Buffer滿時(shí)需要立刻發(fā)出到客戶端。如果Buffer足夠大,則只做memcpy操作。net->write_pos被更新到寫入結(jié)束的位置偏移量 (net_write_buff)

如果一次寫入的數(shù)據(jù)被拆分成多個(gè)Packet,那么net->pkt_nr也對(duì)應(yīng)的遞增多次. pkt_nr的作用是在客戶端解析時(shí),防止包發(fā)送亂序。

net_flush

實(shí)際上在my_net_write函數(shù)中,如果net->buff不夠用,已經(jīng)會(huì)做網(wǎng)絡(luò)寫了,net_flush最終保證所有在buff中的數(shù)據(jù)被寫到網(wǎng)絡(luò)

當(dāng)客戶端啟用壓縮協(xié)議時(shí),這里會(huì)有些不同的,會(huì)給packet頭部再加3個(gè)字節(jié)(COMP_HEADER_SIZE),被壓縮的數(shù)據(jù)不包含頭部的7個(gè)字節(jié):

[3bytes:Packet的長度]

[1bytes: pkt_nr]

[3bytes:壓縮后的長度]

[1bytes: compress_pkt_nr]

同樣的,每個(gè)壓縮包都會(huì)遞增net->compress_pkt_nr

net_write_raw_loop

當(dāng)packet準(zhǔn)備好發(fā)送后,調(diào)用函數(shù)net_write_raw_loop開始進(jìn)行數(shù)據(jù)發(fā)送

發(fā)送模式受vio->write_timeout影響(通過參數(shù)net_write_timeout控制);當(dāng)該參數(shù)被設(shè)置成大于等于0時(shí),使用非阻塞模式send數(shù)據(jù)包(MSG_DONTWAIT)

若網(wǎng)絡(luò)發(fā)送被中斷(EINTR),會(huì)去嘗試重傳

使用非阻塞模式send,每次并不保證數(shù)據(jù)全部發(fā)送完畢,因此需要循環(huán)的調(diào)用直到所有的數(shù)據(jù)都發(fā)送完畢

當(dāng)輸出緩沖區(qū)滿時(shí),獲得錯(cuò)誤碼EWOULDBLOCK/EAGAIN,則阻塞等待(vio_socket_io_wait),最大等待時(shí)間為net_write_timeout,超時(shí)則返回錯(cuò)誤

my_net_read

根據(jù)NET接口先讀取數(shù)據(jù)包(net_read_packet):

先讀取packet header,一個(gè)普通的packet header包含4個(gè)字節(jié),壓縮協(xié)議下則另外再加3個(gè)字節(jié),如上述(net_read_packet_header)。其中的pkt_nr會(huì)提取出來和本地的值相比較。在讀寫兩段維持的pkt_nr自增值保證了服務(wù)器和客戶端的通信以一種有序的方式進(jìn)行,并用于校驗(yàn)包的有序性。如果不一致,則說明網(wǎng)絡(luò)包發(fā)生了亂序。直接報(bào)錯(cuò)。如果一致,本地net->pkt_nr++

從packet header中提取剩下的packet長度,繼續(xù)從socket讀取

Vio

Vio在NET的更下一層,封裝了所有對(duì)socket的操作。根據(jù)不同的連接類型(TCP/IP, Socket, Name Pipe, SSL, SHARED MEMORY),相關(guān)函數(shù)指針在vio_init函數(shù)中定義,這里不展開描述

相關(guān)參數(shù)

connect_timeout: 在連接認(rèn)證階段的網(wǎng)絡(luò)交互超時(shí)時(shí)間(ref login_connection);

wait_timeout: 等待來自客戶端的新的command請(qǐng)求;

net_read_timeout: 一般情況下的SQL通常直接從command發(fā)過來,但拿到command后,在一條語句內(nèi)可能還需要和客戶端交互,這里會(huì)用到該timeout值,例如load data local infile語句;

net_write_timeout: 就是通過網(wǎng)絡(luò)發(fā)送數(shù)據(jù)的最大超時(shí)時(shí)間;

interactive_timeout: 當(dāng)客戶端打開選項(xiàng)CLIENT_INTERACTIVE時(shí),將當(dāng)前會(huì)話的NET的wait_timeout設(shè)置為該值;

結(jié)果集

MySQL有兩種常用的數(shù)據(jù)協(xié)議,一種是用于Prepared Statement,對(duì)應(yīng)類為Protocol_binary,另外一種是普通的協(xié)議,對(duì)應(yīng)類為Protocol_classic

我們以一個(gè)簡(jiǎn)單的表為例:

mysql> create table t1 (a int, b int);

Query OK, 0 rows affected (0.00 sec)

mysql> insert into t1 values (1,1),(2,2);

Query OK, 2 rows affected (0.00 sec)

當(dāng)執(zhí)行最后一條select操作時(shí),這里使用的類為Protocol_classic

發(fā)送metadata

ref: Protocol_classic::start_result_metadata

將列的個(gè)數(shù)寫入Net緩沖區(qū)

ref: Protocol_classic::send_field_metadata

逐列的準(zhǔn)備元數(shù)據(jù)信息,包含:

| 3bytes 標(biāo)識(shí)符:def

| db_name

| table_name

| org_table_name

| col_name

| org_col_name

| 字符集編碼

| 列長度

| 列類型

| flags

| decimals(這里為0)

| 預(yù)留

| 預(yù)留

可以看到每個(gè)列的元數(shù)據(jù)都包含了非常多的信息,使用字符串存儲(chǔ),這也意味著對(duì)于一條簡(jiǎn)單的SQL,你的網(wǎng)絡(luò)傳輸?shù)膬?nèi)容可能大多數(shù)都是元數(shù)據(jù),即時(shí)你的客戶端可能并不需要引用到。

有多個(gè)列就寫多個(gè)packet到Net buffer (Protocol_classic::end_row)

ref: Protocol_classic::end_result_metadata

write_eof_packet函數(shù)會(huì)被調(diào)用,用于標(biāo)識(shí)元數(shù)據(jù)信息到此結(jié)束。此處共寫5個(gè)字節(jié)(不含packet header)

發(fā)送數(shù)據(jù)

ref: end_send --> Protocol_classic::end_row

如上例,發(fā)送兩行數(shù)據(jù)的packet包括

1

‘1’

1

‘1’

1

‘2’

1

‘2’

結(jié)束發(fā)送

ref: THD::send_statement_status -->net_send_eof --> write_eof_packet

發(fā)送結(jié)果結(jié)束標(biāo)記,其中包含了sql執(zhí)行過程中產(chǎn)生的warning個(gè)數(shù)

元數(shù)據(jù)開銷

從上述可以看到,結(jié)果集中有很大一部分的開銷是給元數(shù)據(jù)的,這意味著類似普通的pk查詢,元數(shù)據(jù)的開銷可能會(huì)非常昂貴。

以下貼下我之前測(cè)試過的一個(gè)例子,增加了幾個(gè)選項(xiàng)來控制發(fā)送的元數(shù)據(jù)量:

0/METADATA_FULL: return all metadata, default value.

1/METADATA_REAL_COLUMN: only column name;

2/METADATA_FAKE_COLUMN: fake column name ,use 1,2...N instead of real column name

3/METADATA_NULL_COLUMN: use NULL to express the metadata information

4/METADATA_IGNORE: ignore metadata information, just for test..

測(cè)試表:

CREATE TABLE `test_meta_impact` (

`abcdefg1` int(11) NOT NULL AUTO_INCREMENT,

`abcdefg2` int(11) DEFAULT NULL,

`abcdefg3` int(11) DEFAULT NULL,

`abcdefg4` int(11) DEFAULT NULL,

……

……

`abcdefg40` int(11) DEFAULT NULL,

PRIMARY KEY (`abcdefg1`)

) ENGINE=InnoDB AUTO_INCREMENT=229361 DEFAULT CHARSET=utf8

使用mysqlslap測(cè)試并發(fā)pk查詢

mysqlslap --no-defaults -uxx --create-schema=test -h$host -P $port --number-of-queries=1000000000 --concurrency=100 --query='SELECT * FROM test.test_meta_impact where abcdefg1 = 2'

測(cè)試結(jié)果

METADATA_FULL : 3.48w TPS, Net send 113M

METADATA_REAL_COLUMN: 7.2W TPS, Net send 111M

METADATA_FAKE_COLUMN: 9.2W TPS , Net send 116M

METADATA_NULL_COLUMN: 9.6w TPS , Net send 115M

METADATA_IGNORE: 13.8w TPS, Net send 30M

很顯然無論網(wǎng)絡(luò)流量還是TPS吞吐量,在這個(gè)人為構(gòu)造的極端場(chǎng)景下,元數(shù)據(jù)的開銷都非常的顯著。

總結(jié)

以上是生活随笔為你收集整理的mysql协议重传,MySQL · 源码分析 · 网络通信模块浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。