07 连接池

概念

指标

  1. TPS:每秒事务处理量(Transaction per second),即服务器每秒能处理的事务数。TPS经常包括数据的输入和输出,以及加上用户数据库访问或者一些rpc请求的时间。
  2. QPS:每秒查询率(Queries-per-second),是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。

一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算一个业务事务所使用的时间。

线程池

线程池技术通过预先创建一定数量的线程,在监听到有新的请求时,线程池直接从现有的线程中分配一个线程来提供服务,服务结束后这个线程不会直接销毁,而是又去处理其他的请求。

这样就避免了线程和内存对象频繁创建和销毁,减少了上下文切换,提高了资源利用率,从而在一定程度上提高了系统的性能和稳定性。

连接池

频繁的建立、关闭连接,会极大的减低系统的性能,连接的使用成了系统性能的瓶颈。

  • 连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。
  • 资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把它应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。

测试

并发线程数数据库连接池大小每个请求的等待事件获得连接后的执行时间数据库情况
9600204833ms77ms各种buffer busy waits / CPU 95%
9600102438ms30mswait事件减少一半
9600961ms2ms几乎没有wait事件,吞吐量上升

说明

单核 CPU 的计算机也能 “同时” 运行数百个线程。但这只不过是操作系统用时间分片玩的一个小把戏。一颗 CPU 核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推。

给定一颗 CPU 核心,其顺序执行 A 和 B 永远比通过时间分片 “同时” 执行 A 和 B 要快,这是一条计算机科学的基本法则。一旦线程的数量超过了 CPU 核心的数量,再增加线程数系统就只会更慢,而不是更快。推荐:多线程内容聚合

数据库瓶颈

数据库的性能瓶颈通常分为:CPU、磁盘和网络。

CPU

不考虑磁盘和网络的影响,在一个 8 核的服务器上,设定线程数/连接池大小=8能够提供最优的性能,再增加连接数就会因上下文切换的损耗导致性能下降。

磁盘

数据库通常把数据存储在磁盘上,磁盘通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个地方,然后它必须 “寻址” 到另外一个位置来执行另一次读写操作。

所以就有了寻址的耗时,此外还有旋回耗时,读写头需要等待碟片上的目标数据 “旋转到位” 才能进行操作。使用缓存当然是能够提升性能的,但上述原理仍然成立。

在这一时间段(即 "I/O 等待")内,线程是在 “阻塞” 着等待磁盘,此时操作系统可以将那个空闲的 CPU 核心用于服务其他线程。所以,由于线程总是在 I/O 上阻塞,可以让线程数/连接池大小比 CPU 核心多一些,这样能够在同样的时间内完成更多的工作。

那么应该比CPU核心多多少呢?这要取决于磁盘。

较新型的 SSD 不需要寻址,也没有旋转的碟片,意味着更少的阻塞,所以更少的线程 [更接近于 CPU 核心数] 会发挥出更高的性能。只有当阻塞创造了更多的执行机会时,更多的线程数才能发挥出更好的性能。

网络

网络和磁盘类似,带宽越高阻塞越少,线程数就更少。

总结

PostgreSQL 提供的的公式:连接数 = ((核心数 * 2) + 有效磁盘数) 。

核心数不应包含超线程 (hyper thread),即使打开了 hyperthreading 也是。

  • 如果活跃数据全部被缓存了,那么有效磁盘数是 0,随着缓存命中率的下降,有效磁盘数逐渐趋近于实际的磁盘数。
  • 这一公式作用于 SSD 时的效果尚未有分析。

按这个公式,4 核 i7 数据库服务器的连接池大小应该为 ((4 * 2) + 1) = 9。取个整就算是是 10 吧。

注:这一公式不仅适用于数据库连接池的计算,大部分涉及计算和 I/O 的程序,线程数的设置都可以参考这一公式。

需要一个小连接池,和一个充满了等待连接的线程的队列

连接池的大小最终与系统特性相关:

  • 一个混合了长事务和短事务的系统,通常是任何连接池都难以进行调优的。最好的办法是创建两个连接池,一个服务于长事务,一个服务于短事务。

  • 一个系统执行一个任务队列,只允许一定数量的任务同时执行,此时并发任务数应该去适应连接池连接数,而不是反过来。

操作

show variables like '%thread%'
参数说明
thread_handling该参数是配置线程模型,默认情况是one-thread-per-connection,即不启用线程池;将该参数设置为pool-of-threads即启用了线程池。
thread_pool_size该参数是设置线程池的Group的数量,默认为系统CPU的个数,充分利用CPU资源。
thread_pool_oversubscribe该参数设置group中的最大线程数,每个group的最大线程数为thread_pool_oversubscribe+1,注意listener线程不包含在内。
thread_pool_high_prio_mode高优先级队列的控制参数,有三个值(transactions【对于已经启动事务的语句放到高优先级队列中】/statements【这个模式所有的语句都会放到高优先级队列中,不会使用到低优先级队列】/none【这个模式不使用高优先级队列】),默认是transactions
thread_pool_high_prio_tickets该参数控制每个连接最多语序多少次被放入高优先级队列中,默认为4294967295,注意这个参数只有在thread_pool_high_prio_mode为transactions的时候才有效果。
thread_pool_idle_timeoutworker线程最大空闲时间,默认为60秒,超过限制后会退出。
thread_pool_max_threads该参数用来限制线程池最大的线程数,超过该限制后将无法再创建更多的线程,默认为100000
thread_pool_stall_limit该参数设置timer线程的检测group是否异常的时间间隔,默认为500ms
上次修改: 26 April 2020