关于java:数据库连接池终于搞对了直接从100ms优化到3ms

38次阅读

共计 2935 个字符,预计需要花费 8 分钟才能阅读完成。

送大家以下 java 学习材料

我在钻研 HikariCP(一个数据库连接池)时无意间在 HikariCP 的 Github wiki 上看到了一篇文章(即后面给出的链接),这篇文章无力地打消了我始终以来的疑虑,看完之后感觉神清气爽。故在此做译文分享。

接下来是注释

数据库连接池的配置是开发者们经常搞出坑的中央,在配置数据库连接池时,有几个能够说是和直觉南辕北辙的准则须要明确。

1 万并发用户拜访

设想你有一个网站,压力尽管还没到 Facebook 那个级别,但也有个 1 万高低的并发拜访——也就是说差不多 2 万左右的 TPS。那么这个网站的数据库连接池应该设置成多大呢?后果可能会让你诧异,因为这个问题的正确问法是:

“这个网站的数据库连接池应该设置成多 呢?”

上面这个视频是 Oracle Real World Performance Group 公布的,请先看完:
http://www.dailymotion.com/vi…

(因为这视频是英文讲解且没有字幕,我替大家做一下简略的概括:)
视频中对 Oracle 数据库进行压力测试,9600 并发线程进行数据库操作,每两次拜访数据库的操作之间 sleep 550ms,一开始设置的中间件线程池大小为 2048:

初始的配置

压测跑起来之后是这个样子的:

2048 连贯时的性能数据

每个申请要在连接池队列里期待 33ms,取得连贯后执行 SQL 须要 77ms

此时数据库的期待事件是这个熊样的:

各种 buffer busy waits

各种 buffer busy waits,数据库 CPU 在 95% 左右(这张图里没截到 CPU)

接下来,把中间件连接池减到 1024(并发什么的都不变),性能数据变成了这样:

连接池降到 1024 后

获取链接期待时长没怎么变,然而执行 SQL 的耗时缩小了。
上面这张图,上半局部是 wait,下半局部是吞吐量

wait 和吞吐量

能看到,中间件连接池从 2048 减半之后,吐吞量没变,但 wait 事件缩小了一半。

接下来,把数据库连接池减到 96,并发线程数依然是 9600 不变。

96 个连贯时的性能数据

队列均匀期待 1ms,执行 SQL 均匀耗时 2ms。

wait 事件简直没了,吞吐量回升。

没有调整任何其余货色,仅仅只是放大了中间件层的数据库连接池,就把申请响应工夫从 100ms 左右缩短到了 3ms。

But why?

为什么 nginx 只用 4 个线程施展出的性能就大大超越了 100 个过程的 Apache HTTPD?回忆一下计算机科学的基础知识,答案其实是很显著的。

即便是单核 CPU 的计算机也能“同时”运行数百个线程。但咱们都 [应该] 晓得这只不过是操作系统用工夫分片玩的一个小把戏。一颗 CPU 外围同一时刻只能执行一个线程,而后操作系统切换上下文,外围开始执行另一个线程的代码,以此类推。给定一颗 CPU 外围,其程序执行 AB永远比通过工夫分片“同时”执行 AB要快,这是一条计算机科学的根本法令。一旦线程的数量超过了 CPU 外围的数量,再减少线程数零碎就只会更慢,而不是更快。

这_简直_就是真谛了……

无限的资源

下面的说法只能说是靠近真谛,但还并没有这么简略,有一些其余的因素须要退出。当咱们寻找数据库的性能瓶颈时,总是能够将其归为三类:_CPU、磁盘、网络_。把_内存_加进来也没有错,但比起_磁盘_和_网络_,内存的带宽要高出好几个数量级,所以就先不加了。

如果咱们忽视_磁盘_和_网络_,那么论断就非常简单。在一个 8 核的服务器上,设定连贯 / 线程数为 8 可能提供最优的性能,再减少连接数就会因上下文切换的损耗导致性能降落。

数据库通常把数据存储在磁盘上,磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读 / 写头同一时刻只能呈现在一个中央,而后它必须“寻址”到另外一个地位来执行另一次读写操作。所以就有了寻址的耗时,此外还有旋回耗时,读写头须要期待碟片上的指标数据“旋转到位”能力进行操作。应用缓存当然是可能晋升性能的,但上述原理依然成立。

在这一时间段(即 ”I/ O 期待 ”)内,线程是在“阻塞”着期待磁盘,此时操作系统能够将那个闲暇的 CPU 外围用于服务其余线程。所以,因为线程总是在 I / O 上阻塞,咱们能够让线程 / 连接数比 CPU 外围多一些,这样可能在同样的工夫内实现更多的工作。

那么应该多多少呢?这要取决于_磁盘_。较新型的 SSD 不须要寻址,也没有旋转的碟片。可别想当然地认为“SSD 速度更快,所以咱们应该减少线程数”,恰恰相反,无需寻址和没有旋回耗时意味着 更少的阻塞 ,所以更少的线程[更靠近于 CPU 外围数] 会施展出更高的性能。只有当阻塞发明了更多的执行机会时,更多的线程数能力施展出更好的性能

_网络_和_磁盘_相似。通过以太网接口读写数据时也会造成阻塞,10G 带宽会比 1G 带宽的阻塞少一些,1G 带宽又会比 100M 带宽的阻塞少一些。不过网络通常是放在第三位思考的,有些人会在性能计算中疏忽它们。

上图是 PostgreSQL 的 benchmark 数据,能够看到 TPS 增长率从 50 个连接数开始变缓。在下面 Oracle 的视频中,他们把连接数从 2048 降到了 96,实际上 96 都太高了,除非服务器有 16 或 32 颗外围。

计算公式

上面的公式是由 PostgreSQL 提供的,不过咱们认为能够宽泛地利用于大多数数据库产品。你应该模仿预期的访问量,并从这一公式开始测试你的利用,寻找最合适的连贯数值。

连接数 = ((外围数 * 2) + 无效磁盘数)

外围数不应蕴含超线程(hyper thread),即便关上了 hyperthreading 也是。如果沉闷数据全副被缓存了,那么无效磁盘数是 0,随着缓存命中率的降落,无效磁盘数逐步趋近于理论的磁盘数。这一公式作用于 SSD 时的成果如何尚未有剖析。

按这个公式,你的 4 核 i7 数据库服务器的连接池大小应该为((4 * 2) + 1) = 9。取个整就算是是 10 吧。是不是感觉太小了?跑个性能测试试一下,咱们保障它能轻松搞定 3000 用户以 6000TPS 的速率并发执行简略查问的场景。如果连接池大小超过 10,你会看到响应时长开始减少,TPS 开始降落。

笔者注:
这一公式其实不仅实用于数据库连接池的计算,大部分波及计算和 I / O 的程序,线程数的设置都能够参考这一公式。我之前在对一个应用 Netty 编写的音讯收发服务进行压力测试时,最终测出的最佳线程数就刚好是 CPU 外围数的一倍。

公理:你须要一个小连接池,和一个充斥了期待连贯的线程的队列

如果你有 10000 个并发用户,设置一个 10000 的连接池根本等于失了理智。1000 依然很恐怖。即是 100 也太多了。你须要一个 10 来个连贯的小连接池,而后让剩下的业务线程都在队列里期待。连接池中的连贯数量应该等于你的数据库可能无效同时进行的查问工作数(通常不会高于 2 *CPU 外围数)。

咱们常常见到一些小规模的 web 利用,应酬着大概十来个的并发用户,却应用着一个 100 连接数的连接池。这会对你的数据库造成极其不必要的累赘。

请留神

连接池的大小最终与零碎个性相干。

比方一个混合了长事务和短事务的零碎,通常是任何连接池都难以进行调优的。最好的方法是创立两个连接池,一个服务于长事务,一个服务于短事务。

再例如一个零碎执行一个工作队列,只容许肯定数量的工作同时执行,此时并发工作数应该去适应连接池连接数,而不是反过来。

正文完
 0