MySQL挑戰:100k連線 - Percona資料庫效能部落格

banq發表於2019-02-26

在這篇文章中,我想探索一種與MySQL建立100,000個連線的方法。不只是空閒連線,而是執行查詢。
你真的需要MySQL100,000個連線,你可能會問?雖然看起來有點過分,但我在客戶部署中看到了很多不同的設定。有些部署了一個應用程式連線池,每個池中有100個應用程式伺服器和1,000個連線。有些應用程式使用“如果查詢太慢,會重新嘗試連線”的技術,這是一種可怕的做法。它可以導致滾雪球效應,並可以在幾秒鐘內建立數千個與MySQL的連線。
所以現在我想設定一個超出預期的目標,看看我們是否能夠實現它。

構建
我將使用以下硬體:

由packet.net提供的裸機伺服器,例項大小:c2.medium.x86 
物理核心@ 2.2 GHz 
(1 X AMD EPYC 7401P)
記憶體:64 GB ECC RAM 
儲存:INTEL SSDDC S4500,480GB

這是伺服器級SATA SSD。
我將使用其中五個盒子,原因如下。一個用於MySQL伺服器的盒子和四個用於客戶端連線的盒子。
對於伺服器,我將使用帶有執行緒池外掛的Percona Server for MySQL 8.0.13-4。該外掛將需要支援數千個連線。

初始伺服器設定

網路設定(Ansible格式):

- { 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 }


這些是推薦用於10Gb網路和高併發工作負載的典型設定。
限制systemd的設定:

[Service]
LimitNOFILE=1000000
LimitNPROC=500000


以及my.cnf中MySQL的相關設定:

back_log=3500
max_connections=110000


對於客戶端,我將使用sysbench版本0.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


1. 設定10,000個連線
這個很容易,因為沒有太多的事要做。我們只用一個客戶就可以做到這一點。但是您可能在客戶端遇到以下錯誤:

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

這是由開啟檔案限制引起的,這也是TCP / IP套接字的限制。這可以透過設定:

ulimit -n 100000

我們觀察到效能:

[  26s] threads: 10000, tps: 0.00, reads: 33367.48, writes: 0.00, response time: 3681.42ms (95%), errors: 0.00, reconnects:  0.00
<p class="indent">[  27s] threads: 10000, tps: 0.00, reads: 33289.74, writes: 0.00, response time: 3690.25ms (95%), errors: 0.00, reconnects:  0.00


2. 設定25,000個連線
當有25,000個連線,我們在MySQL端遇到錯誤:

Can't create a new thread (errno 11); if you are not out of available memory, you can consult the manual for 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 加上:

thread_handling = pool - of - threads

到my.cnf並重啟Percona Server,結果:

[   7s] threads: 25000, tps: 0.00, reads: 33332.57, writes: 0.00, response time: 974.56ms (95%), errors: 0.00, reconnects:  0.00
<p class="indent">[   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毫秒改善到979毫秒(由於執行緒池)。

3. 50,000個連線
這是我們遇到的最大挑戰。首先,嘗試在sysbench中獲取50,000個連線,我們遇到以下錯誤:

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)。這裡的關鍵點是,如果我們想要更多的連線,我們需要為MySQL伺服器分配更多的IP地址。為了實現100,000個連線,我將在執行MySQL的伺服器上使用兩個IP地址。
在整理出埠範圍後,我們遇到了sysbench的以下問題:

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記憶體分配(即lua子系統)的問題。Sysbench只能為32,351個連線分配記憶體。這是一個在sysbench 1.0.x中更嚴重的問題。

Sysbench 1.0.x限制
Sysbench 1.0.x使用不同的Lua JIT,即使有4000個連線也會遇到記憶體問題,所以在sysbench 1.0.x中不可能超過4000連線
因此,與Percona Server相比,我們似乎比sysbench更快地達到了極限。為了使用更多連線,我們需要使用多個sysbench客戶端,如果32,351連線是sysbench的限制,我們必須使用至少四個sysbench客戶端來獲得多達100,000個連線。
對於50,000個連線,我將使用2個伺服器(每個伺服器執行單獨的sysbench),每個伺服器執行來自sysbench的25,000個執行緒。
每個sysbench的結果如下所示:

[  29s] threads: 25000, tps: 0.00, reads: 16794.09, writes: 0.00, response time: 1799.63ms (95%), errors: 0.00, reconnects:  0.00
<p class="indent">[  30s] threads: 25000, tps: 0.00, reads: 16491.03, writes: 0.00, response time: 1800.70ms (95%), errors: 0.00, reconnects:  0.00


所以我們有相同的吞吐量(總共16794 * 2 = 33588 tps),但95%的響應時間翻了一番。這是預料之中的,因為與25,000個連線基準測試相比,我們使用的連線數是原來的兩倍。

3. 75,000個連線
要實現75,000個連線,我們將使用三個帶sysbench的伺服器,每個伺服器執行25,000個執行緒。
每個sysbench的結果:

[ 157s] threads: 25000, tps: 0.00, reads: 11633.87, writes: 0.00, response time: 2651.76ms (95%), errors: 0.00, reconnects:  0.00
<p class="indent">[ 158s] threads: 25000, tps: 0.00, reads: 10783.09, writes: 0.00, response time: 2601.44ms (95%), errors: 0.00, reconnects:  0.00


4. 100,000個連線
實現75k和100k連線沒有任何意義。我們只需啟動一個額外的伺服器並啟動sysbench。對於100,000個連線,我們需要四個伺服器用於sysbench,每個伺服器顯示:

[ 101s] threads: 25000, tps: 0.00, reads: 8033.83, writes: 0.00, response time: 3320.21ms (95%), errors: 0.00, reconnects:  0.00
<p class="indent">[ 102s] threads: 25000, tps: 0.00, reads: 8065.02, writes: 0.00, response time: 3405.77ms (95%), errors: 0.00, reconnects:  0.00


因此我們具有相同的吞吐量(總共8065 * 4 = 32260 tps),響應時間為3405毫秒95%。
這是一個非常重要的要點:使用100k連線並使用執行緒池,95%的響應時間甚至比沒有執行緒池的10k連線更好。執行緒池允許Percona Server更有效地管理資源並提供更好的響應時間。

結論​​​​​​​
MySQL可以實現100k連線,我相信我們可以更進一步。有三個元件可以實現此目的:

  • Percona Server中的執行緒池
  • 正確調整網路限制
  • 在伺服器盒上使用多個IP地址(每個約60k連線一個IP地址)

完整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


 

相關文章