压测原因
首页改版,所以要对首页进行压测,首页展示过程中涉及多个接口,需要进行压测。
压测分析
第一次排查
简单查看压测请求情况,初步观察日志可以额看到十个中有一到两个请求出现几百甚至上千毫秒的接口相应,排查TraceID之后发现为:接口调用到普通的测试环境和性能环境,导致部分请求走到测试环境,普通的测试环境只有1C2G,而生产环境的配置为40核64G/92G,所以相应结果较长也在意料之中。
同时排查过程中还发现部分接口调用链极短,这与线上的请求调用是不相符的,排查发现是请求头Header未设置数据导致很多请求被过滤到,所以在请求头上加入对应参数再次压测。
第二次排查
第二次压测过程,惨不忍睹,整体请求成功率只有76%左右,查询部分接口的响应时间很长,甚至直接溢出500,可以看到product服务的(ProductLimitFacade.queryProductLimitByXXX)由于接口请求时间过长,导致sofa线程池被打满,从而出现性能测试曲线出现急剧下降以及请求报错。
- TPS断崖下跌
- 请求时长由毫秒级\十几毫秒增长到上千毫秒,打满清空多次成正弦曲线状态
原本计划采用缓存来提升整体的查询性能,但是发现代码中已经使用了XXCache本地缓存,但是在看日志过程中还是发现存在大量的查询SQL(产品限额信息需要根据产品列表循环查询)。
排查SQL日志过程中,也可以看出有大量的SQL查询日志,导致接口相应太慢,大量线程阻塞在查询限额的方法出,所以目标锁定–代码的XXCache缓存使用不当,导致缓存策略未生效。 (XXCache:内部封装的一种缓存策略,根据参数值可以设置缓存在Redis或者缓存在本地中,可以参考GuavaCache的实现方式)[参考:GuavaCache的认识]
@XXCache(value = "productLimit", key = "productYieldDXXX_#{}_#{}_#{}")
第三次排查
现在已知是缓存策略失效的问题,第一反应为注解使用不当,导致缓存穿透。因为同时查询产品限额信息,以为是未配置allowNullValue的问题导致的,但是XXCache会默认会开启空缓存数据,所以也不是缓存穿透导致的。[参考:缓存穿透、缓存击穿、缓存雪崩区别和解决方案]
- 缓存的正常流程为:先查询缓存然后查询数据库。查询缓存,若存在数据,则返回;如果缓存没有则查询数据库;如果数据库也没有返回空。
- 缓存穿透:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
- 缓存雪崩:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
第四次排查
采用Debug调试过程中,查看发现在XXCache打断点也无法阻断this.queryProductLimitByXXX()方法执行的调试线程。但是在另一处查询产品信息时却可以进入XXCache中阻断调试线程,清晰明了,就是该方法的XXCache并未拦截,导致本地缓存没有生效。
回归代码本源,接口调用的是列表查询接口,但是真正的本地缓存的是单个产品限额查询的接口,出发点很好,毕竟产品编号列表的排列组合太多,会不必要的浪费服务器宝贵的内存资源,问题就出在for循环的this调用,我们知道Spring的AOP核心思想是基于动态代理来实现的,如:
调用的列表查询接口会被代理对象增强,但是this调用不是代理对象执行的方法,而是原对象的原生方法,自然没法拦截增强,于是XXCache本地缓存就没有生效。至此, 问题排查完成。
问题解决
解决办法也很简单:一种是移除this调用改为获取代理对象(实现并注入ApplicationContextAware;然后使用该上下文来获取this服务的代理对象)来执行即可拦截增强,另一种将XXCache注解下移,将注解加到Manager上即可完成。
参考:
|