背景:
在进行某金融企业大型金融产品压测时,系统的TPS一直上不去。再进行全方位的监控、定位后发现,在资源空闲的情况下,增加并发,系统处理能力上不去,portal服务器上存在大量的redis连接等待线程。于是,我们将瓶颈锁定在了redis身上。
一、对redis进行体检
1.内部超时时间
通过命令:./redis-cli --intrinsic-latency 100 查看内部超时时间,发现平均才49微秒,一般100微妙以内算正常。(其实性能环境超分比较严重的情况下,就算大于100微秒也还行,仅作为一个参考数据)
2. 网络延迟
ping之后发现网络延迟在20微秒到49微秒之间,可以说是非常快了。所以网络方面也没有问题。
3. 慢命令造成的延迟
有了这方面想法以后,就打算通过打印慢日志的方式查看慢命令。
通过Slowlog get number(10或其他数字) 打印慢日志发现:慢日志中记录的最慢的命令也在100ms以内。
4.查看处理的命令数
通过info命令查看Stats中的total_commands_processed参数,发现与我们实际发送的请求数相比,相差甚远。
总结: 经过对redis自身的体检之后,发现Redis自身还远远没有达到瓶颈。所以推测压力应该是阻塞在后台应用那边。
二、对后台应用进行体检
1. 查看配置文件 application.yml
先后使用了Jedis和lettuce ,连接池的配置都为max-active: 400,但是无论使用哪个作为连接redis服务的客户端,均存在阻塞的情况。
2. jstack 打印JVM堆栈信息
就在调优一堆陷入僵局的时候,在打印JVM堆栈信息后,发现大量线程在读取redis进程时等待。查看对应的代码时,发现罪魁祸首竟然是springboot 的@Cacheable注解。
3. 去除@Cacheable,引入Google Guava Cache
因为@Cacheable这种方式存在锁的原因,导致性能十分的差,TPS始终上不去。于是选择
Google Guava Cache。
什么是Google Guava Cache?下面贴上官方介绍:
Guava Cache与ConcurrentMap很相似,都继承于ReetrantLock,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache 不回收元素,它也是很有用的,因为它会自动加载缓存。
通常来说,Guava Cache适用于:
- 你愿意消耗一些内存空间来提升速度。
- 你预料到某些键会被查询一次以上。
- 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
最后,综合本项目的实际场景考量,最终选择了Guava Cache。换成Guava Cache之后,阻塞情况得到解决,TPS也由80笔/s提升到了200笔/s
备注:ReentrantLock是Java中常用的锁,属于乐观锁类型,多线程并发情况下。能保证共享数据安全性,线程间有序性