• 调优zuul1.x(基于arthas)


    1. 说在前面

    虽然标题是"调优zuul",但"你一个做业务开发的,轮不到你来做基础技术底层的调优;正如JVM不需要调优,需要调优的是你的业务代码"。本次最终找到的原因也再次印证了这个思路。

    所以本文的重点也不是介绍具体的调优点,主要是希望借着这个机会总结下:

    1. 调优过程中arthas命令的使用
    2. 期间走过的弯路,以及带来的经验

    最终目的是下次类似的问题出现时候,能够尽量短的时间内解决。

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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+)。
    图1
    图2

    4. 额外收获

    4.1 在线动态启停ZuulFilter:
    zuul:
      debug:
        # 开启这个对于性能影响巨大
        request: false 
    # =================> 
    #   ZuulFilter.isFilterDisabled() 方法中生效
    # 以下配置无需预先定义, 在需要时候添加即可
      PostApiVersionPostFilter:   # Filter的名称
        post:                     # Filter的类型
          disable: false          # 是否禁用, 默认是false, 即启用该Filter.
      Pre3rdPartyRequestFilter:
        pre:
          disable: false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    4.2 快速共享文件

    在不同的服务器之间快速传递文件,除了scp或者FTP等工具之外,还可以借助linux服务器上内置的python:

    ############# 1. 导航到您想要共享文件的目录
    cd /x/y
    
    ############# 2. 开启web静态文件访问服务
    # python3
    python -m http.server
    
    # python2
    python -m SimpleHTTPServer
    
    ############# 3. 服务器将会启动并监听默认端口 8000。您可以在浏览器中输入 http://:8000
    http://<ip>:8000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    5.2 优化后

    90ms 降低到 35+ms
    在这里插入图片描述

    6. 经验总结

    1. 基础框架不需要调优,需要调优的是你的代码。
    2. zuul1.x中,zuul.debug.request平时不要开。
    3. 千万级别流量,zuul1劣势不大。
    4. 相信监控直观反应的结果。大胆假设,小心求证;求证过程中要相信监控反馈的结果,不要预设结论之后找证据
    5. 先montior看平均时间,确定方向后再trace,否则容易被少数情况迷惑住,搞偏了方向绕一大圈。
    6. 这种能够稳定复现的,解决起来是最简单的了。
    7. arthas很好用。

    7. 参考

    1. Arthas-文档 本次的最大功臣,虽然因为自身预估出现偏差,它进一步强化了这种偏差…但归根到底,还是人的问题
  • 相关阅读:
    Python GDAL+numpy遥感图像处理过程中背景像元处理方法
    un7.28:redis客户端常用命令。
    建模杂谈系列145 Mysql To Mongo By Python Processing
    macOS端React的项目WebPack热更新(HMR)失效问题分析及解决,原因竟是Windows文件系统不区分大小写导致
    不要使用短路逻辑编写 stl sorter 多条件比较
    【git 介绍】AhuntSun
    2023年亚太杯数学建模思路 - 案例:异常检测
    java计算机毕业设计家庭理财管理系统源码+数据库+lw文档+系统
    记录每天学习的新知识:Composing builds
    【模电实验】【超值1 + 1】【验证性实验——分立元件“OTL“功率放大器实验】【验证性实验——分立元件稳压电源实验】
  • 原文地址:https://blog.csdn.net/lqzkcx3/article/details/133787688