当前位置: 首页 > 科技观察

数据库连接池终于对上了,从100ms优化到3ms!

时间:2023-03-18 00:01:45 科技观察

在研究HikariCP(一个数据库连接池)的时候,无意中看到了HikariCP的Githubwiki上的一篇文章。这篇文章有效地打消了我长久以来的疑惑,读后感觉耳目一新。图片来自Pexels本文95%的内容翻译自这篇文章:https://github.com/brettwoldridge/HikariCP/wiki/About-Pool-Sizing数据库连接池的配置是开发者经常做的地方陷阱。在配置数据库连接池的时候,有几个原则可以说是违反直觉的,需要搞清楚。10,000个并发用户访问假设您有一个网站。虽然压力没有达到Facebook的水平,但是也有10000左右的并发访问量,也就是20000TPS左右。那么这个网站的数据库连接池应该设置多大呢?结果可能会让你大吃一惊,因为问这个问题的正确方式是:“这个网站的数据库连接池应该设置多小?”以下视频是WorldPerformanceGroup发布的OracleReal,请先观看:http://www.dailymotion.com/video/x2s8uec因为这个视频是英文的,没有字幕,所以我给大家做一个简单的总结:视频测试中对Oracle数据库的压力,9600个并发线程进行数据库操作,每两次访问数据库操作之间休眠550ms,一开始设置的中间件线程池大小为2048:初始配置压力测试是这样的运行后:2048个连接性能数据每个请求需要在连接池队列中等待33ms,获取连接后执行SQL需要77ms。这时候数据库的等待事件是这样的:各种bufferbusy等待,各种bufferbusy等待,数据库CPU在95%左右(这张图没有抓到CPU)。接下来将中间件连接池减少到1024(并发和一切不变),性能数据变成这样:连接池减少到1024后,获取连接的等待时间变化不大,但是时间-减少了执行SQL的消耗。下图中,上半部分是等待,下半部分是吞吐量:可以看到等待和吞吐量。中间件连接池从2048减半后,吞吐量没有变化,但是等待事件减少了一半。接下来,将数据库连接池减少到96个,并发线程数不变为9600个。当有96个连接时,性能数据队列平均等待1ms,执行SQL的平均时间为2ms。等待事件几乎没有了,吞吐量上升了。在不做任何调整的情况下,仅仅缩小中间件层的数据库连接池,就将请求响应时间从100ms左右缩短到了3ms。但为什么?为什么Nginx在100个进程只有4个线程的情况下性能优于ApacheHTTPD?回顾一下计算机科学的基础知识,答案其实很明显。即使是只有一个CPU内核的计算机也可以“同时”运行数百个线程。但我们都[应该]知道,这只是操作系统在时间分片上玩的一个把戏。一个CPU内核一次只能执行一个线程,然后操作系统切换上下文,内核开始执行另一个线程的代码,如此往复。给定一个CPU核,顺序执行A和B总是比通过时间分片“同时”执行A和B更快,这是计算机科学的基本定律。一旦线程数超过CPU核心数,增加线程数只会让系统变慢,不会变快。差不多就是这个道理了。。。上面资源有限的说法只能说是接近真相了,但并没有那么简单,还有一些其他的因素需要补充。当我们寻找数据库性能瓶颈时,我们总是可以将它们分为三类:CPU、磁盘、网络。加内存没有错,但是相对于磁盘和网络,内存的带宽要高几个数量级,所以就不加了。如果我们忽略磁盘和网络,结论很简单。在8核服务器上,将连接数/线程数设置为8可以提供最佳性能,增加连接数会因为失去上下文切换而导致性能下降。数据库通常将数据存储在磁盘上,磁盘通常由旋转的金属盘片和安装在步进电机上的读/写磁头组成。读/写磁头一次只能在一个地方,然后它必须“寻址”到另一个位置才能执行另一次读或写操作。因此,就存在着寻址的耗时,除了旋转耗时之外,读写头还需要等待盘片上的目标数据“旋转到位”后才能进行操作。使用缓存当然可以提高性能,但以上原则仍然成立。在此期间(“I/O等待”),线程被“阻塞”等待磁盘,操作系统可以使用空闲的CPU核心来为其他线程提供服务。因此,由于线程总是阻塞在I/O上,我们可以拥有比CPU内核更多的线程/连接,以便在相同的时间内完成更多的工作。那么还有多少?这取决于磁盘。较新的SSD不需要寻址,也没有旋转盘片。不要想当然地认为“SSD更快,所以我们应该增加线程数”,相反,没有寻址和没有回滚时间消耗意味着更少的阻塞,所以更少的线程(更接近CPU核心数)将表现出更高的性能。只有阻塞创造了更多的执行机会,更多的线程才能获得更好的性能。网络和磁盘是相似的。通过以太网接口读写数据也会出现阻塞。10G带宽比1G带宽阻塞少,1G带宽比100M带宽阻塞少。网络通常是第三个考虑因素,有些人在性能计算中忽略它们。上图是PostgreSQL的基准数据。可以看出,TPS增长率从50个连接开始放缓。在上面的Oracle视频中,他们将连接数从2048减少到96,实际上96太高了,除非服务器有16或32个内核。计算公式以下公式由PostgreSQL提供,但我们认为它们可以广泛应用于大多数数据库产品。您应该模拟预期的流量并从这个公式开始测试您的应用程序以找到最合适的连接值。连接数=((核心数*2)+有效磁盘数)核心数不应该包括超线程(hyperthread),即使开启了超线程。如果缓存了所有活动数据,则有效磁盘数为0。随着缓存命中率的降低,有效磁盘数逐渐接近实际磁盘数。尚未分析此公式对SSD的效果如何。根据这个公式,你的4核i7数据库服务器的连接池大小应该是:((4*2)+1)=9。四舍五入到10。你觉得它太小了吗?运行性能测试,我们保证它可以轻松应对3000个用户以6000TPS的速率并发执行简单查询的场景。如果连接池大小超过10,您将看到响应时间开始增加并且TPS开始下降。作者注:这个公式不仅仅适用于数据库连接池的计算,大部分涉及计算和I/O的程序,线程数的设置都可以参考这个公式。之前在压测一个Netty写的消息服务的时候,最后测到的最佳线程数恰好是CPU核数的两倍。您需要一个小型连接池和一个充满等待连接的线程的队列。如果你有10,000个并发用户,设置10,000个连接池几乎是你的想法。1000还是吓人的。即使是100也太多了。你需要一个10个左右连接的小连接池,然后让剩下的业务线程在队列中等待。连接池中的连接数应等于您的数据库可以同时有效执行的查询任务数(通常不高于2*CPU内核)。我们经常看到一些小型Web应用程序,处理大约十几个并发用户,但使用100个连接的连接池。这会给您的数据库带来极其不必要的负担。请注意,连接池的大小最终与系统特性有关。例如,混合长事务和短事务的系统通常很难对任何连接池进行调优。最好的方法是创建两个连接池,一个用于长事务,一个用于短事务。再比如,系统执行一个任务队列,只允许同时执行一定数量的任务。这时候并发任务数应该和连接池中的连接数相适应,而不是相反。作者:kelgon编辑:陶佳龙来源:https://www.jianshu.com/p/a8f653fc0c54