国庆期间闲来无事,写了一个简单的小程序,小程序名称叫做 IT藏经楼。目的是分享这些年自己积累的一些学习材料,方面大家查找使用,包括电子书、案例项目、学习视频、面试题和一些PPT模板。里面所有材料都免费分享。目前小程序中只发布了非常小的一部分,后续会陆续上传分享。当前版本的小程序页面也比较简单,还在逐渐的优化中。
基本上来说,大部分项目都需要跟数据库做交互,那么数据库连接池的大小设置成多大合适呢?一些同学可能会很随意的告诉你:没关系,尽量设置的大些,比如设置成200, 这样数据库性能会高写,吞吐量也会大些。 真的是这样吗?
关于数据库连接池大小的设置,每一个开发都有可能在这一环节掉进坑里。事实上大部分程序员可能都会依靠自己的直觉去设置它的大小,设置成100?思量许久后,应该差不多吧?
案例
某个团队对Oracle数据库进行了压力测试,模拟9600个并发线程来操作数据库,每两次数据库操作之间sleep 550ms,最开始的时候设置的线程池大小为2048。测试结果是:每个请求要在连接池队列里面等待33ms,获得连接之后,执行SQL需要耗时77ms,CPU消耗维持在95%左右。
接下来将连接池大小改小点,设置成1024, 其他测试参数不变,测试结果:获取连接等待时长基本不变,但是SQL的执行耗时降低了。
接下来再设置小一些,连接池大小降低到96, 并发数等其他参数不变,测试结果:每个请求再连接池队列中的平均等待时间为1ms,SQL执行耗时为2ms。
我们没有调整任何东西,仅仅只是将数据库连接池的大小降低了,这样就能把之前平均100ms的相应时间缩短到了3ms,吞吐量指数级上升。
为什么会有这种效果?
我们不妨想一下,为啥Nginx内部仅仅使用了4个线程,其性能就大大超越了100个线程的Apache httpd?要知道,即使是单核CPU的计算机也能“同时”运行着数百个线程,但我们其实都知道,这只不过是操作系统快速切换时间片,跟我们玩的一个小把戏罢了。
一核CPU同一时刻只能执行一个线程,然后操作系统切换上下文,CPU核心快速调整,执行另外一个线程的代码,不停反复,给我们造成了所有进程同时运行的假象。
其实,在一核CPU的机器上,顺序执行A和B永远比通过时间分片切换“同时”执行A和B要快,一旦线程的数量超过了CPU核心的数量,再增加线程数系统就只会更慢,而不是更快,因为这里涉及到上下文切换耗费的额外的性能。
其他应该考虑到的因素
当我们再寻找数据库的性能瓶颈时,大致可归为三类:
a. CPU
b. 磁盘IO
c. 网络IO
也许你会说,还有内存,内存的确是需要考虑的,但是比起磁盘IO和网络IO,就稍显微不足道了。
假设我们不考虑磁盘IO和网络IO,那就很好定论了,在一个8核的服务器上,数据库连接数/线程数设置为8能够提供最优的性能,如果再增加连接数,反而会因为上下文切换导致性能下降。
大家都知道,数据库通常把数据存储再磁盘上,而磁盘通常是由一些旋转着的金属碟片和一个读写头组成的。读写头同一时刻只能出现在一个位置,当它需要再次执行读写操作的时候,它必须“寻址”到另外一个位置才能完成任务。所以这里就有了寻址的耗时,此外还有旋转耗时,读写头需要等待磁盘碟片上的目标数据“旋转到位”才能进行读写操作。在这段时间内,线程是出于“阻塞”等待状态,也就是说没干啥正事,此时操作系统可以将这个空闲的CPU核心用于服务其他线程。
这里我们可以总结一下,当你的线程处理的是I/O密集型业务时,便可以让线程/连接数设置的比CPU核心数大一些,这样就能够在同样的时间内,完成更多的工作,提升吞吐量。
那么设置成多少合适呢?这要取决于磁盘,如果你使用的是SSD固态硬盘,它不需要寻址,也不需要旋转碟片,意味着线程阻塞的时间很少,所以接近于CPU核心数的线程会发挥出更高的性能。只有当阻塞密集型时,更多的线程数才能发挥更好的性能。
上面我们了解了磁盘IO,接下来我们谈谈网络IO。网络IO其实也非常的相似,通过以太网接口读取数据时也会造成阻塞,10G带宽会比1G带宽的阻塞耗时少一些,而1G带宽又会比100M带宽的阻塞少一些。通常情况下,我们把网络IO放到第三顺位来考虑。
连接数计算公式
下面公式由PostgreSQL提供,它适用于市面上绝大部分数据库产品。还有,你应该模拟预期的访问量,并通过下面的公式先设置一个偏合理的值,然后在实际的测试中,通过微调来寻找最合适的连接数大小。
连接数 = (核心数 * 2) + 有效磁盘数
按照这个公式,如果你的服务器CPU是4核的,连接池大小应该为(4 * 2)+ 1 = 9,取个整我们就设置为10。如果你跑个性能测试就可以看到,它能轻松支撑3000用户以6000TPS的速率并发执行简单查询的场景。