单体服务在用户量上来后,并发问题会日益突出。或者自己在性能测试时,随着并发数增加,很多请求都会超时失败,甚至会出现死锁。
分析程序的日志之后,你发现系统慢的原因出现在和数据库的交互上。因为数据库的调用方式是先获取数据库的连接,然后依靠这条连接从数据库中查询数据,最后关闭连接释放数据库资源。这种调用方式下,每次执行 SQL 都需要重新建立连接,所以你怀疑,是不是频繁地建立数据库连接耗费时间长导致了访问慢的问题。
tcpdump -i bond0 -nn -tttt port 4490,或者使用抓包工具,进行一次SQL交互会花费多少时间。
连接过程大概消耗了 4ms(969012-964904)!执行时间却很短,可以自己在业务中进行相关的打印。
如何解决呢?
只要使用连接池将数据库连接预先建立好,这样在使用的时候就不需要频繁地创建连接了。调整之后,你发现 1s 就可以执行 1000 次的数据库查询,查询性能大大的提升了。
在开发过程中我们会用到很多的连接池,像是数据库连接池、HTTP 连接池、Redis 连接池等等。
数据库连接池有两个最重要的配置:最小连接数和最大连接数,它们控制着从连接池中获取连接的流程:
- 如果当前连接数小于最小连接数,则创建新的连接处理数据库请求;
- 如果连接池中有空闲连接则复用空闲连接;
- 如果空闲池中没有连接并且当前连接数小于最大连接数,则创建新的连接处理请求;
- 如果当前连接数已经大于等于最大连接数,则按照配置中设定的时间(C3P0 的连接池配置是 checkoutTimeout)等待旧的连接可用;
- 如果等待超过了这个设定时间则向用户抛出错误。
如果参数设置的不合理,且看此文章:连接池参数设置不合理,并发请求超时失败https://blog.csdn.net/Be_insighted/article/details/121024508?spm=1001.2014.3001.5501
- 池子的最大值和最小值的设置很重要,初期可以依据经验来设置,后面还是需要根据实际运行情况做调整。
- 池子中的对象需要在使用之前预先初始化完成,这叫做池子的预热,比方说使用线程池时就需要预先初始化所有的核心线程。如果池子未经过预热可能会导致系统重启后产生比较多的慢请求。
- 池化技术核心是一种空间换时间优化方法的实践,所以要关注空间占用情况,避免出现空间过度使用出现内存泄露或者频繁垃圾回收等问题。
JDK线程池监控关键:
ThreadpoolExecutor
核心线程预热:prestartAllCoreThreads方法可以预先启动核心线程;
线程池任务队列监控:executor.getQueue().size();
最大线程数:千万不要使用无界队列,否则这个参数永远不会涉及;