原文地址:www.percona.com/blog/2019/0…html
本文的目的是探索一种在一台MySQL服务器上创建10w个链接的方法。咱们要创建的是能够执行查询的链接,而不是10w个空闲链接。mysql
你可能会问,个人MySQL服务器真的须要10w链接吗?我见过不少不一样的部署方案,例如使用链接池,每一个应用的链接池里放1000个链接,部署100个这样的应用服务器。还有一些很是糟糕的实践,使用“查询慢则重连并重试”的技术。这会形成雪球效应,有可能致使在几秒内须要创建上千个链接的状况。sql
因此我决定设置一个“小目标”,看可否实现。shell
先看一下硬件,服务器由packet.net(一个云服务商)提供,配置以下:bash
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,这个插件须要支持上千个链接。cookie
网络设置:网络
- { 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 }
复制代码
系统限制设置:socket
[Service]
LimitNOFILE=1000000
LimitNPROC=500000
复制代码
相应的MySQL配置(my.cnf文件):tcp
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
这一步很是简单,咱们不须要作过多调整就能够实现。这一步只须要一台机器作客户端,不过客户端有可能会有以下错误:
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
复制代码
这一步会在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。
到这里,咱们遇到了最大的挑战。首先尝试创建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版本使用了不一样的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%的响应时间长了一倍。这是意料之中的事情,由于与上一步相比,咱们的链接数扩大了一倍。
这一步咱们再增长一台服务器作客户端,每台客户端上一样是跑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
复制代码
终于到站了,这一步一样没什么困难,只须要再开一个客户端,一样跑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链接数是能够实现的,而且能够更多,实现这个目标有三个重要的组件:
最后贴上完整的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
复制代码