0. 目录
- 1. 说在前面
- 2. 关键arthas命令
- 3. 弯路
- 3.1 铺天盖地的宣传下,对于zuul1.x性能信心不足。
- 3.2 zuul1.x 避免开启`zuul.debug.request`配置,尤其是在性能调优时。
- 3.3 redis的读取存在破20ms+的情况。
- 4. 额外收获
- 4.1 在线动态启停ZuulFilter:
- 4.2 快速共享文件
- 5. 本次优化相关
- 5.1 定位根源
- 5.2 优化后
- 6. 经验总结
- 7. 参考
1. 说在前面
虽然标题是"调优zuul",但"你一个做业务开发的,轮不到你来做基础技术底层的调优;正如JVM不需要调优,需要调优的是你的业务代码"。本次最终找到的原因也再次印证了这个思路。
所以本文的重点也不是介绍具体的调优点,主要是希望借着这个机会总结下:
- 调优过程中arthas命令的使用
- 期间走过的弯路,以及带来的经验
最终目的是下次类似的问题出现时候,能够尽量短的时间内解决。
2. 关键arthas命令
单次的调用性能参考意义不大,重要的是平均的耗时。所以在本次基于arthas的性能优化中,monitor
命令使用频率是最高的。
####### 一次对比dashboard —— 所有的自定义PreXxxFilter与整体的Zuul执行耗时
monitor -c 5 -E com.netflix.zuul.http.ZuulServlet|cn.com.xxx.apigateway.filter.Pre* service|preRoute|route|postRoute|run|shouldFilter
####### 异步执行,重定向
# https://arthas.aliyun.com/doc/async.html
trace cn.com.xxx.apigateway.filter.PreHeaderFilter serviceName_serviceSourceType -n 3 '#cost>10' > 11111XX.out &
####### redis性能验证. 压测20秒里,这样的个数大约在 190+
monitor -c 5 cn.com.xxx.common.util.RedissonUtil get '#cost > 10'
#######压测20秒里,单次读取超过1ms的,这样的个数大约在 949 ; 而总共的读取次数大约是 52795.。。。 占比 1.79%
monitor -c 5 cn.com.xxx.common.util.RedissonUtil get '#cost > 1'
####### 更新完,确认下是否更新到位
jad --source-only com.xxx.yyy.zzz.MController
####### 动态trace
# https://arthas.aliyun.com/doc/trace.html#%E5%8A%A8%E6%80%81-trace
trace cn.com.xxx.apigateway.filter.PreHeaderFilter run -n 3 '#cost>16'
# 另外起一个shell界面
telnet localhost 3658
trace cn.com.xxx.apigateway.filter.PreHeaderFilter serviceName_serviceSourceType --listenerId 9
####### 抓取指定traceId,去查相应的链路日志
watch cn.com.xxx.apigateway.filter.PreHeaderFilter serviceName_serviceSourceType '{#cost,@org.apache.skywalking.apm.toolkit.trace.TraceContext@traceId()}' -n 5 -x 3 '#cost>10' -f
####### AOP切面直接attach
monitor -c 5 cn.com.xxx.common.anno.cache.XxxxExpCacheableAspect getContext
monitor -c 5 cn.com.xxx.common.anno.cache.XxxxExpCacheableAspect * # 所有方法一次性整体预览
# 找出被AOP的, 实际类型
sc *ServerManagerFuncCaller
3. 弯路
待到问题解决,回头看的时候发现确实走了不少弯路。
3.1 铺天盖地的宣传下,对于zuul1.x性能信心不足。
这导致的直接结果就是搞了裸springcloud-gateway, 裸zuul.1x,裸zuul1.x+undertow,移除所有的自定义zuulFilter的当前架构版本,当前架构版本进行反复对比。最终也是证明了springcloud-gateway性能上确实有优势(大约比裸zuul1.x快了一倍),但是与当前待优化的场景(90ms+)关系并不大。
总结:性能优化不要一上来就怀疑基础框架不行,尤其是当你这性能表现远未达到需要优化底层技术架构的时候。
3.2 zuul1.x 避免开启zuul.debug.request
配置,尤其是在性能调优时。
这个配置会导致zuul启用DeepCopy(源码位置:FilterProcessor.processZuulFilter(xxx)
—> RequestContext.copy()
),这会急剧降低zuul1.x的性能表现。(直接破200ms+)
总结:开启这个本来是为了观察每个ZuulFilter的详细请求耗时,但没想到成了主要的性能消耗来源。
3.3 redis的读取存在破20ms+的情况。
这也是优化后期,导致我们额外浪费半天的原因。在过往的意识里一直认为"性能问题最有可能出现在IO上",导致拿arthas去专门捕获这种情况,进一步强化了这种错误的方向排查。
总结:相信监控给出的结果(图2展示了性能瓶颈不在redis),不要预设了方向再去找证据(图1展示了确实存在性能远超一般情况的特殊情况 —— 单次请求耗时在40ms+)。
4. 额外收获
4.1 在线动态启停ZuulFilter:
zuul:
debug:
# 开启这个对于性能影响巨大
request: false
# =================>
# ZuulFilter.isFilterDisabled() 方法中生效
# 以下配置无需预先定义, 在需要时候添加即可
PostApiVersionPostFilter: # Filter的名称
post: # Filter的类型
disable: false # 是否禁用, 默认是false, 即启用该Filter.
Pre3rdPartyRequestFilter:
pre:
disable: false
4.2 快速共享文件
在不同的服务器之间快速传递文件,除了scp或者FTP等工具之外,还可以借助linux服务器上内置的python:
############# 1. 导航到您想要共享文件的目录
cd /x/y
############# 2. 开启web静态文件访问服务
# python3
python -m http.server
# python2
python -m SimpleHTTPServer
############# 3. 服务器将会启动并监听默认端口 8000。您可以在浏览器中输入 http://<ip>:8000
http://<ip>:8000
5. 本次优化相关
5.1 定位根源
// ====== 严重的性能问题...
// private StandardEvaluationContext getContext(Method method, Object[] args) {
// // 获取被拦截方法参数名列表(使用Spring支持类库)
// ------- 这个 LocalVariableTableParameterNameDiscoverer 类上的注释里其实已经说了: 建议尽量缓存该实例, 也就是不要每次都新建...
// LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
// String[] paraNameArr = discoverer.getParameterNames(method);
//
// // SPEL上下文
// StandardEvaluationContext context = new StandardEvaluationContext();
// // 把方法参数放入SPEL上下文中
// for (int i = 0; i < paraNameArr.length; i++) {
// context.setVariable(paraNameArr[i], args[i]);
// }
// return context;
// }
// ====== 替换为以下实现...
/**
* 获取方法上的参数
*
* @param method 方法
* @param args 变量
* @return {SimpleEvaluationContext}
*/
private StandardEvaluationContext getContext(Method method, Object[] args) {
// 初始化Spel表达式上下文
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置表达式支持spring bean
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
for (int i = 0; i < args.length; i++) {
// 读取方法参数
MethodParameter methodParam = getMethodParameter(method, i);
// 设置方法 参数名和值 为sp el变量
context.setVariable(methodParam.getParameterName(), args[i]);
}
return context;
}
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
/**
* 获取方法参数信息
*
* @param method 方法
* @param parameterIndex 参数序号
* @return {MethodParameter}
*/
static MethodParameter getMethodParameter(Method method, int parameterIndex) {
MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
return methodParameter;
}
5.2 优化后
90ms 降低到 35+ms
6. 经验总结
- 基础框架不需要调优,需要调优的是你的代码。
- zuul1.x中,
zuul.debug.request
平时不要开。 - 千万级别流量,zuul1劣势不大。
- 相信监控直观反应的结果。大胆假设,小心求证;求证过程中要相信监控反馈的结果,不要预设结论之后找证据
- 先montior看平均时间,确定方向后再trace,否则容易被少数情况迷惑住,搞偏了方向绕一大圈。
- 这种能够稳定复现的,解决起来是最简单的了。
- arthas很好用。
7. 参考
- Arthas-文档 本次的最大功臣,虽然因为自身预估出现偏差,它进一步强化了这种偏差…但归根到底,还是人的问题