一 池
工作中常常能见到各种池,如对象池、数据库连接池、线程池、等等。这些池本质上是池化思想的体现。池化思想是一种通过创建和管理可重复使用的对象池来提高性能和资源利用率的编程思想。
核心在于在需要时从池中获取对象,而不是每次都创建新的对象,使用完毕后将对象返回到池中,以供其他代码复用。
通过使用池化思想,可以避免不必要的资源创建和销毁操作,减少系统开销,提高程序的性能和可伸缩性。同时,池化思想还能够更好地管理和控制资源的使用,防止资源过度消耗和浪费。
下面的两例线上故障虽然说都与池有关,但是其外在表现和本质原因都有所不同。
二 慢查询——稳定性的地雷
先来看具体的故障表现
- 线上项目不可用,接口报错,但项目服务器cpu、内存占用均未显示异常
- 数据库服务器cpu占用高。项目重启后占用下降,恢复可用。
从表现上大致可以推断问题来源大致是数据库。而服务器cpu占用高可能的原因如下
- cpu内核被线程持续占用
- 大量的并发线程cpu频繁进行线程的切换
- JVM频繁执行full gc
下面分析一下原因
既然是数据库的服务器cpu占用较高,自然想到第一点原因,由于慢查询导致数据库服务器cpu占用过高。
线上服务器的内核通常以2-4核为多,而一个1s以上的慢查询,只需要在一秒内有2-4的并发就可以将服务器cpu内核占满。
数据库服务器内核占用将导致线上服务卡顿。在业务高峰期服务的卡顿通常会导致用户的刷新行为,继续加重数据库服务器的cpu压力。但这只能解释第二点故障表现。慢查询导致cpu占用过高,项目的其他查询大不了等待呗,为什么会直接不可用呢。
原因在于连接池
在慢查询占用cpu的时刻,其他查询获取到数据库链接,此时cpu忙,于是线程持有连接等待。在业务高峰期,数据库连接池很快就会占用满。后续的线程再请求数据库连接时则抛出超时异常。表现为项目的不可用,但是项目服务器的cpu、内存指标未显示异常。
上一故障,其根本原因在与慢查询的出现,这启发我们在工作中应该时刻注意sql的性能问题,多考虑表的数据量,多检查执行计划有没有走索引,将0.1到0.01秒的sql视为健康的sql。在设计表结构的时候考虑该表是查询业务多还是修改业务多,适当的冗余有助于提升查询的效率,但是会导致修改时的一致性问题。
三 连接池——好心帮倒忙
第二个线上故障的具体表现如下
- 突然某一天出现项目服务器cpu占用过高。
- 业务卡顿但是并项目并未宕机。
- 项目日志中有redis链接超时错误
出现问题的项目有如下特点
- 大量的定时任务需要连接redis
- 新的需要定时任务连接redis业务不断的添加到这一项目中
下面分析一下原因
由第三点故障表现和项目特点可以很容易想到问题大致出在redis连接方向。通过大致估算发现——项目的redis连接已经超过了配置的连接池连接数上限。不断产生的大量的redis连接请求将连接池的连接全部占用,后续部分线程请求超时导致了第三点故障表现。由于redis在内存中操作数据,速度快,连接很快就被释放,后续连接请求部分仍然可以获取,这使得项目业务卡顿,但并不是所有业务都不可用。那为什么会导致服务器cpu占用过高呢?连接池不就是通过创建和管理可重复使用的对象池来提高性能和资源利用率吗?
让我们回顾一下服务器cpu占用高可能的原因
- cpu内核被线程持续占用
- 大量的并发线程cpu频繁进行线程的切换
- JVM频繁执行full gc
答案呼之欲出,当线程池占用满了之后,后续线程在请求连接时只能等待,线程进入等待,cpu切换其他线程。由于项目中大量线程均频繁的使用redis,切换后的线程仍然需要请求redis连接。于是cpu处于反复频繁的线程切换中。线程的上下文切换仍然需要消耗cpu资源。这就导致了项目服务器的cpu占用过高。
将redis连接池的最大连接数增加后,问题暂时解决。但是仍然需要警惕,单台redis的最大并发量大致在10w左右,如果超过这个数量就要考虑redis部署主从架构了。不过目前项目还达不到。
四 总结
通过上述两个线上故障案例分析可以发现,连接池的引入会导致系统复杂性增加。虽然确实可以提高性能和资源利用率,但不合适的配置会导致其他问题的出现,需要适时评估资源的使用量,根据实际情况进行配置。除此之外,详细的故障表现的本质原因很可能完全不同,审慎的对待每次故障,分析问题产生根本原因有助于形成牢固的知识体系,知识落实到实际才能融汇贯通。