数据库连接池配置策略:高并发下的性能优化实践
数据库连接池的配置是开发者们常常踩坑的地方。在配置数据库连接池时,有几个原则可以说是和直觉背道而驰的,明确这些原则至关重要。
1 万并发用户访问场景
想象你有一个网站,压力虽然还没到 Facebook 那个级别,但也有个 1 万上下的并发访问——也就是说差不多 2 万左右的 TPS。那么这个网站的数据库连接池应该设置成多大呢?结果可能会让你惊讶,因为这个问题的正确问法是:
- '这个网站的数据库连接池应该设置成多小呢?'
下面是一个经典的 Oracle Real World Performance Group 发布的压力测试案例,我们先来看看数据表现。
视频中对 Oracle 数据库进行压力测试,9600 并发线程进行数据库操作,每两次访问数据库的操作之间 sleep 550ms。一开始设置的中间件线程池大小为 2048:
压测跑起来之后是这个样子的:
每个请求要在连接池队列里等待 33ms,获得连接后执行 SQL 需要 77ms。
此时数据库的等待事件是这个熊样的:
各种 buffer busy waits,数据库 CPU 在 95% 左右。
接下来,把中间件连接池减到 1024(并发什么的都不变),性能数据变成了这样:
获取链接等待时长没怎么变,但是执行 SQL 的耗时减少了。
下面这张图,上半部分是 wait,下半部分是吞吐量:
能看到,中间件连接池从 2048 减半之后,吞吐量没变,但 wait 事件减少了一半。
接下来,把数据库连接池减到 96,并发线程数仍然是 9600 不变。
队列平均等待 1ms,执行 SQL 平均耗时 2ms。
wait 事件几乎没了,吞吐量上升。
没有调整任何其他东西,仅仅只是缩小了中间件层的数据库连接池,就把请求响应时间从 100ms 左右缩短到了 3ms。
背后的原理是什么?
为什么 nginx 只用 4 个线程发挥出的性能就大大超越了 100 个进程的 Apache HTTPD?回想一下计算机科学的基础知识,答案其实是很明显的。
即使是单核 CPU 的计算机也能'同时'运行数百个线程。但我们都应该知道这只不过是操作系统用时间分片玩的一个小把戏。一颗 CPU 核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推。给定一颗 CPU 核心,其顺序执行 A 和 B 永远比通过时间分片'同时'执行 A 和 B 要快,这是一条计算机科学的基本法则。一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快。
这几乎就是真理了……
有限的资源
上面的说法只能说是接近真理,但还并没有这么简单,有一些其他的因素需要加入。当我们寻找数据库的性能瓶颈时,总是可以将其归为三类:CPU、磁盘、网络。把内存加进来也没有错,但比起磁盘和网络,内存的带宽要高出好几个数量级,所以就先不加了。
如果我们无视磁盘和网络,那么结论就非常简单。在一个 8 核的服务器上,设定连接/线程数为 8 能够提供最优的性能,再增加连接数就会因上下文切换的损耗导致性能下降。数据库通常把数据存储在磁盘上,磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个地方,然后它必须'寻址'到另外一个位置来执行另一次读写操作。所以就有了寻址的耗时,此外还有旋回耗时,读写头需要等待碟片上的目标数据'旋转到位'才能进行操作。使用缓存当然是能够提升性能的,但上述原理仍然成立。


