【譯】MySQL挑戰:建立10萬連線

面向Google程式設計發表於2019-03-09

原文地址:www.percona.com/blog/2019/0…

本文的目的是探索一種在一臺MySQL伺服器上建立10w個連線的方法。我們要建立的是可以執行查詢的連線,而不是10w個空閒連線。

你可能會問,我的MySQL伺服器真的需要10w連線嗎?我見過很多不同的部署方案,例如使用連線池,每個應用的連線池裡放1000個連線,部署100個這樣的應用伺服器。還有一些非常糟糕的實踐,使用“查詢慢則重連並重試”的技術。這會造成雪球效應,有可能導致在幾秒內需要建立上千個連線的情況。

所以我決定設定一個“小目標”,看能否實現。

準備階段

先看一下硬體,伺服器由packet.net(一個雲服務商)提供,配置如下:

instance size: c2.medium.x86 Physical Cores @ 2.2 GHz (1 X AMD EPYC 7401P) Memory: 64 GB of ECC RAM Storage : INTEL® SSD DC S4500, 480GB

我們需要5臺這樣的伺服器,1臺用來作MySQL伺服器,其餘4臺作為客戶端。MySQL伺服器使用的是Percona Server的帶有執行緒池外掛的MySQL 8.0.13-4,這個外掛需要支援上千個連線。

初始化伺服器配置

網路設定:

- { name: 'net.core.somaxconn', value: 32768 }
- { name: 'net.core.rmem_max', value: 134217728 }
- { name: 'net.core.wmem_max', value: 134217728 }
- { name: 'net.ipv4.tcp_rmem', value: '4096 87380 134217728' }
- { name: 'net.ipv4.tcp_wmem', value: '4096 87380 134217728' }
- { name: 'net.core.netdev_max_backlog', value: 300000 }
- { name: 'net.ipv4.tcp_moderate_rcvbuf', value: 1 }
- { name: 'net.ipv4.tcp_no_metrics_save', value: 1 }
- { name: 'net.ipv4.tcp_congestion_control', value: 'htcp' }
- { name: 'net.ipv4.tcp_mtu_probing', value: 1 }
- { name: 'net.ipv4.tcp_timestamps', value: 0 }
- { name: 'net.ipv4.tcp_sack', value: 0 }
- { name: 'net.ipv4.tcp_syncookies', value: 1 }
- { name: 'net.ipv4.tcp_max_syn_backlog', value: 4096 }
- { name: 'net.ipv4.tcp_mem', value: '50576   64768 98152' }
- { name: 'net.ipv4.ip_local_port_range', value: '4000 65000' }
- { name: 'net.ipv4.netdev_max_backlog', value: 2500 }
- { name: 'net.ipv4.tcp_tw_reuse', value: 1 }
- { name: 'net.ipv4.tcp_fin_timeout', value: 5 }
複製程式碼

系統限制設定:

[Service]
LimitNOFILE=1000000
LimitNPROC=500000
複製程式碼

相應的MySQL配置(my.cnf檔案):

back_log=3500
max_connections=110000
複製程式碼

客戶端使用的是sysbench0.5版本,而不是1.0.x。具體原因我們在後面做解釋。

執行命令:sysbench --test=sysbench/tests/db/select.lua --mysql-host=139.178.82.47 --mysql-user=sbtest--mysql-password=sbtest --oltp-tables-count=10 --report-interval=1 --num-threads=10000 --max-time=300 --max-requests=0 --oltp-table-size=10000000 --rand-type=uniform --rand-init=on run

第一步,10,000個連線

這一步非常簡單,我們不需要做過多調整就可以實現。這一步只需要一臺機器做客戶端,不過客戶端有可能會有如下錯誤:

FATAL: error 2004: Can't create TCP/IP socket (24)

這是由於開啟檔案數限制,這個限制限制了TCP/IP的sockets數量,可以在客戶端上進行調整:

ulimit -n100000

此時我們來觀察一下效能:

[  26s] threads: 10000, tps: 0.00, reads: 33367.48, writes: 0.00, response time: 3681.42ms (95%), errors: 0.00, reconnects:  0.00
[  27s] threads: 10000, tps: 0.00, reads: 33289.74, writes: 0.00, response time: 3690.25ms (95%), errors: 0.00, reconnects:  0.00
複製程式碼

第二步,25,000個連線

這一步會在MySQL服務端發生錯誤:

Can't create a new thread (errno 11); if you are not out of available memory, you can consult the manualfor a possible OS-dependent bug

關於這個問題的解決辦法可以看這個連結:

https://www.percona.com/blog/2013/02/04/cant_create_thread_errno_11/

不過這個辦法不適用於我們現在的情況,因為我們已經把所有限制調到最高:

cat /proc/`pidof mysqld`/limits
Limit                     Soft Limit Hard Limit           Units
Max cpu time              unlimited  unlimited            seconds
Max file size             unlimited  unlimited            bytes
Max data size             unlimited  unlimited            bytes
Max stack size            8388608    unlimited            bytes
Max core file size        0          unlimited            bytes
Max resident set          unlimited  unlimited            bytes
Max processes             500000     500000               processes
Max open files            1000000    1000000              files
Max locked memory         16777216   16777216             bytes
Max address space         unlimited  unlimited            bytes
Max file locks            unlimited  unlimited            locks
Max pending signals       255051     255051               signals
Max msgqueue size         819200     819200               bytes
Max nice priority         0          0
Max realtime priority     0          0
Max realtime timeout      unlimited unlimited            us
複製程式碼

這也是為什麼我們最開始要選擇有執行緒池的服務:https://www.percona.com/doc/percona-server/8.0/performance/threadpool.html

在my.cnf檔案中加上下面這行設定,然後重啟服務

thread_handling=pool-of-threads

檢視一下結果

[   7s] threads: 25000, tps: 0.00, reads: 33332.57, writes: 0.00, response time: 974.56ms (95%), errors: 0.00, reconnects:  0.00
[   8s] threads: 25000, tps: 0.00, reads: 33187.01, writes: 0.00, response time: 979.24ms (95%), errors: 0.00, reconnects:  0.00
複製程式碼

吞吐量相同,但是又95%的響應從3690 ms降到了979 ms。

第三步,50,000個連線

到這裡,我們遇到了最大的挑戰。首先嚐試建立5w連線的時候,sysbench報錯:

FATAL: error 2003: Can't connect to MySQL server on '139.178.82.47' (99)

Error (99)錯誤比較神祕,它意味著不能分配指定的地址。這個問題是由一個應用可以開啟的埠數限制引起的,我們的系統預設配置是:

cat /proc/sys/net/ipv4/ip_local_port_range : 32768 60999

這表示我們只有28,231可用埠(60999減32768),或者是你最多能建立的到指定IP地址的TCP連線數。你可以在伺服器和客戶端擴寬這個範圍:

echo 4000 65000 > /proc/sys/net/ipv4/ip_local_port_range

這樣我們就能建立61,000個連線了,這已經接近一個IP可用埠的最大限制了(65535)。這裡的關鍵點是,如果我們想要達到10w連線,就需要為MySQL伺服器分配更多的IP地址,所以我為MySQL伺服器分配了兩個IP地址。

解決了埠個數問題後,我們又遇到了新的問題:

sysbench 0.5:  multi-threaded system evaluation benchmark
Running the test with following options:
Number of threads: 50000
FATAL: pthread_create() for thread #32352 failed. errno = 12 (Cannot allocate memory)
複製程式碼

這個問題是由sysbench的記憶體分配問題引起的。sysbench只能分配的記憶體只能建立32,351個連線,這個問題在1.0.x版本中更為嚴重。

Sysbench 1.0.x的限制

Sysbench 1.0.x版本使用了不同的Lua編譯器,導致我們不可能建立超過4000個連線。所以看起來Sysbench比 Percona Server更早到達了極限,所以我們需要使用更多的客戶端。每個客戶端最多32,351個連線的話,我們最少要使用4個客戶端才能達到10w連線的目標。

為了達到5w連線,我們使用了兩臺機器做客戶端,每臺機器開啟25,000個執行緒。結果如下:

[  29s] threads: 25000, tps: 0.00, reads: 16794.09, writes: 0.00, response time: 1799.63ms (95%), errors: 0.00, reconnects:  0.00
[  30s] threads: 25000, tps: 0.00, reads: 16491.03, writes: 0.00, response time: 1800.70ms (95%), errors: 0.00, reconnects:  0.00
複製程式碼

吞吐量和上一步差不多(總的tps是16794*2 = 33588),但是效能降低了,有95%的響應時間長了一倍。這是意料之中的事情,因為與上一步相比,我們的連線數擴大了一倍。

第四步,75,000個連線

這一步我們再增加一臺伺服器做客戶端,每臺客戶端上同樣是跑25,000個執行緒。結果如下:

[ 157s] threads: 25000, tps: 0.00, reads: 11633.87, writes: 0.00, response time: 2651.76ms (95%), errors: 0.00, reconnects:  0.00
[ 158s] threads: 25000, tps: 0.00, reads: 10783.09, writes: 0.00, response time: 2601.44ms (95%), errors: 0.00, reconnects:  0.00
複製程式碼

第五步,100,000個連線

終於到站了,這一步同樣沒什麼困難,只需要再開一個客戶端,同樣跑25,000個執行緒。結果如下:

[ 101s] threads: 25000, tps: 0.00, reads: 8033.83, writes: 0.00, response time: 3320.21ms (95%), errors: 0.00, reconnects:  0.00
[ 102s] threads: 25000, tps: 0.00, reads: 8065.02, writes: 0.00, response time: 3405.77ms (95%), errors: 0.00, reconnects:  0.00
複製程式碼

吞吐量仍然保持在32260的水平(8065*4),95%的響應時間是3405ms。

這裡有個非常重要的事情,想必大家已經發現了:在有執行緒的情況下10w連線數的響應速度甚至要優於沒有執行緒池的情況下的1w連線數的響應速度。執行緒池使得Percona Server可以更加有效的管理資源,然後提供更好的響應速度。

結論

10w連線數是可以實現的,並且可以更多,實現這個目標有三個重要的元件:

  1. Percona Server的執行緒池
  2. 正確的網路設定
  3. 為MySQL伺服器配置多個IP地址(每個IP限制65535個連線)

附錄

最後貼上完整的my.cnf檔案

[mysqld]
datadir {{ mysqldir }}
ssl=0
skip-log-bin
log-error=error.log
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
character_set_server=latin1
collation_server=latin1_swedish_ci
skip-character-set-client-handshake
innodb_undo_log_truncate=off
# general
table_open_cache = 200000
table_open_cache_instances=64
back_log=3500
max_connections=110000
# files
innodb_file_per_table
innodb_log_file_size=15G
innodb_log_files_in_group=2
innodb_open_files=4000
# buffers
innodb_buffer_pool_size= 40G
innodb_buffer_pool_instances=8
innodb_log_buffer_size=64M
# tune
innodb_doublewrite= 1
innodb_thread_concurrency=0
innodb_flush_log_at_trx_commit= 0
innodb_flush_method=O_DIRECT_NO_FSYNC
innodb_max_dirty_pages_pct=90
innodb_max_dirty_pages_pct_lwm=10
innodb_lru_scan_depth=2048
innodb_page_cleaners=4
join_buffer_size=256K
sort_buffer_size=256K
innodb_use_native_aio=1
innodb_stats_persistent = 1
#innodb_spin_wait_delay=96
innodb_adaptive_flushing = 1
innodb_flush_neighbors = 0
innodb_read_io_threads = 16
innodb_write_io_threads = 16
innodb_io_capacity=1500
innodb_io_capacity_max=2500
innodb_purge_threads=4
innodb_adaptive_hash_index=0
max_prepared_stmt_count=1000000
innodb_monitor_enable = '%'
performance_schema = ON
複製程式碼

相關文章