@Cacheable,阻塞redis性能的罪魁祸首

557 阅读3分钟

背景:

在进行某金融企业大型金融产品压测时,系统的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中常用的锁,属于乐观锁类型,多线程并发情况下。能保证共享数据安全性,线程间有序性