詳解連線池引數設定(邊調邊看)

語言: CN / TW / HK

你有同感嗎?

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

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

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

所以我寫了 tproxy

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

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

tproxy 的安裝

$ GOPROXY=https://goproxy.cn/,direct go install github.com/kevwan/[email protected]

或者使用 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

https://gitee.com/kevwan/go-zero

歡迎使用並 star 支援我們!

微信交流群

關注『微服務實踐』公眾號並點選 交流群 獲取社群群二維碼。</remote-port></listen-port></remote-port></remote-port></listen-port></listen-port></remote-port></listen-port></remote-port></remote-port></listen-port></listen-port>