詳解連接池參數設置(邊調邊看)

你有同感嗎?

當大家在開發服務端代碼的時候,會不會經常有如下疑問?

  • 納悶 MySQL 連接池到底有多少連接?
  • 每個連接的生命週期持續多久?
  • 連接異常斷開的時候到底是服務端主動斷的,還是客戶端主動斷的?
  • 當長時間沒有請求的時候,底層庫是否有 KeepAlive 請求?

複雜網絡情況的處理從來都是後端開發的重點和難點之一,你是不是也爲各種網絡情況的調試而頭頂發涼呢?

所以我寫了 tproxy

當我在做後端開發和寫 go-zero 的時候,經常會需要監控網絡連接,分析請求內容。比如:

  • 分析 gRPC 連接何時連接、何時重連,並據此調整各種參數,比如:MaxConnectionIdle
  • 分析 MySQL 連接池,當前多少連接,連接的生命週期是什麼策略
  • 也可以用來觀察和分析任何 TCP 連接,看服務端主動斷,還是客戶端主動斷等等

tproxy 的安裝

$ GOPROXY=https://goproxy.cn/,direct go install github.com/kevwan/tproxy@latest

或者使用 docker 鏡像:

$ docker run --rm -it -p <listen-port>:<listen-port> -p <remote-port>:<remote-port> kevinwan/tproxy:v1 tproxy -l 0.0.0.0 -p <listen-port> -r host.docker.internal:<remote-port>

arm64 系統:

$ docker run --rm -it -p <listen-port>:<listen-port> -p <remote-port>:<remote-port> kevinwan/tproxy:v1-arm64 tproxy -l 0.0.0.0 -p <listen-port> -r host.docker.internal:<remote-port>

tproxy 的用法

$ tproxy --help
Usage of tproxy:
  -d duration
            the delay to relay packets
  -l string
            Local address to listen on (default "localhost")
  -p int
            Local port to listen on
  -q        Quiet mode, only prints connection open/close and stats, default false
  -r string
            Remote address (host:port) to connect
  -t string
            The type of protocol, currently support grpc

分析 gRPC 連接

tproxy -p 8088 -r localhost:8081 -t grpc -d 100ms
  • 偵聽在 localhost 和 8088 端口
  • 重定向請求到 localhost:8081
  • 識別數據包格式爲 gRPC
  • 數據包延遲100毫秒

img

其中我們可以看到 gRPC 的一個請求的初始化和來回,可以看到第一個請求其中的 stream id 爲 1。

再比如 gRPC 有個 MaxConnectionIdle 參數,用來設置 idle 多久該連接會被關閉,我們可以直接觀察到時間到了之後服務端會發送一個 http2 的 GoAway 包。

img

比如我把 MaxConnectioinIdle 設爲 5 分鐘,連接成功之後 5 分鐘沒有請求,連接就被自動關閉了,然後重新建了一個連接上來。

分析 MySQL 連接

我們來分析一下 MySQL 連接池設置對連接池的影響,比如我把參數設爲:

maxIdleConns = 3
maxOpenConns = 8
maxLifetime  = time.Minute
...
conn.SetMaxIdleConns(maxIdleConns)
conn.SetMaxOpenConns(maxOpenConns)
conn.SetConnMaxLifetime(maxLifetime)

我們把 MaxIdleConns 和 MaxOpenConns 設爲不同值,然後我們用 hey 來做個壓測:

hey -c 10 -z 10s "http://localhost:8888/lookup?url=go-zero.dev"

我們做了併發爲10QPS且持續10秒鐘的壓測,連接結果如下圖:

img

我們可以看到:

  • 10秒鐘內建立了2000+的連接
  • 過程中在不停的關閉已有連接,重開新的連接
  • 每次連接使用完放回去,可能超過 MaxIdleConns 了,然後這個連接就會被關閉
  • 接着來新請求去拿連接時,發現連接數小於 MaxOpenConns,但是沒有可用請求了,所以就又新建了連接

這也就是我們經常會看到 MySQL 很多 TIME_WAIT 的原因。

然後我們把 MaxIdleConns 和 MaxOpenConns 設爲相同值,然後再來做一次相同的壓測:

img

我們可以看到:

  • 一直維持着8個連接不變
  • 壓測完過了一分鐘(ConnMaxLifetime),所有連接被關閉了

這裏的 ConnMaxLifetime 一定要設置的小於 wait_timeout,可以通過如下方式查看 wait_timeout 值:

img

我建議設置小於5分鐘的值,因爲有些交換機會5分鐘清理一下空閒連接,比如我們在做社交的時候,一般心跳包不會超過5分鐘。具體原因可以看

https://github.com/zeromicro/go-zero/blob/master/core/stores/sqlx/sqlmanager.go#L65

其中 go-sql-driver 的 issue 257 裏有一段也在說 ConnMaxLifetime,如下:

14400 sec is too long. One minutes is enough for most use cases.

Even if you configure entire your DC (OS, switch, router, etc...), TCP connection may be lost from various reasons. (bug in router firmware, unstable power voltage, electric nose, etc...)

所以如果你不知道 MySQL 連接池參數怎麼設置,可以參考 go-zero 的設置。

另外,ConnMaxIdleTime 對上述壓測結果沒有影響,其實你也不需要設置它。

如果你對上述設置有疑問,或者覺得哪裏有誤,歡迎在 go-zero 羣裏一起討論。

項目地址

tproxy: https://github.com/kevwan/tproxy

go-zero: https://github.com/zeromicro/go-zero

歡迎使用並 star 支持我們!

微信交流羣

關注『微服務實踐』公衆號並點擊 交流羣 獲取社區羣二維碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章