踩坑记录---openFeign高并发阻塞分析与解决

业务场景

公司某业务需要使用http协议去大量调用三方推送服务,因项目本身为Spring Cloud 服务,切通信框架为 OpenFeign,所以调用三方服务时,使用的是 OpenFeign

异常简述

某日上线一个新功能,从ID分发中心获取一个随机ID(openFeign服务化调用,QPS约1000左右),然后调用三方服务(openFeign调用,QPS约1000),上线之前的版本openFeign没有大并发,一切正常,上线之后,发现RabbitMQ存在大量阻塞积压,同时消费者利用率处于3%左右,严重影响业务处理能力

分析过程

  • 排查消费者业务处理线程池,通过编写ThreadPoolEndPoint,观察线程池运行状态,发现线程池队列处于积压状态
    在这里插入图片描述
  • 使用jstack命令,抓取线程运行状态
# 使用ps命令,获取运行进程编号,app.jar 为我的服务简化版名称
ps -ef|grep app.jar
# 假设获取的编号为 26599
jstack 26599 > 26599.jstack
# 下载并分析线程阻塞情况
sz 26599.jstack
  • 使用IBM相关分析工具 JCA469,查看运行情况如下

大量线程处于阻塞情况,全部等待一个锁,疑似某些地方使用了同步处理导致无法高并发

  • 分析线程锁

    发现大量线程openFeign卡在Jackson的编解码器这里,并且在执行Class.forName(),该方法是具有同步锁的,按道理来说,不应该存在并发调用的情况,按照堆栈进行分析发现是调用项目中的解码器配置导致,代码如下
	import feign.codec.Decoder;
 	/**
     * 补充 http 协议消息转换器
     * content-type:text/plan
     * content-type:text/json
     *
     * @param converter 自定义的消息转换器
     * @return openFeign 解码器
     */
    @Bean
    @ConditionalOnBean(TextMappingJackson2HttpMessageConverter.class)
    public Decoder feignDecoder(TextMappingJackson2HttpMessageConverter converter) {
        ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(converter);
        logger.info("==================> 初始化openFeign text/json支持解析器");
        return new SpringDecoder(objectFactory);
    }
  • 分析问题所在,按照堆栈执行流程分析,openFeign在处理解码时,关键源码如下
    踩坑记录---openFeign高并发阻塞分析与解决_第1张图片
    可以看到在 56行,是解码器自己new的处理逻辑,并调用我们配置的解码器,然后在不停的new HttpMessageConverters
    跟踪源码发现,在new HttpMessageConverters 过程中,会获取一个 default的转换器,而该default转换器的最终实现为 MappingJackson2HttpMessageConverter,就等于每次都在new MappingJackson2HttpMessageConverter ,每new 一次,都会执行一次jackson初始化配置
    踩坑记录---openFeign高并发阻塞分析与解决_第2张图片
    踩坑记录---openFeign高并发阻塞分析与解决_第3张图片
    每次执行jackson配置,都会去执行Class.forName去判断相关参数初始化
    踩坑记录---openFeign高并发阻塞分析与解决_第4张图片
    画重点,我的系统中,没有添加joda相关依赖,导致加载速度很慢,从而导致openFeign不支持高并发能力

解决问题

发现了问题所在,那么就能解决问题
不靠谱的方案一:
直接添加joda相关依赖

        <dependency>
            <groupId>io.github.openfeigngroupId>
            <artifactId>feign-jacksonartifactId>
        dependency>

优点:偷懒
缺点:鬼知道有什么其他影响,需要对每个业务进行通信测试,看看是否出现未知异常

靠谱的方案二:
问题的根本原因是在大量调用我们自己配置的解码器,导致不停初始化 jackson 配置
所以。。。。直接对解码器做个单例缓存,初始化一次就好了

	import feign.codec.Decoder;
	/**
	* 单例缓存
	*/
    private HttpMessageConverters messageConverters = null;
    /**
     * 补充 http 协议消息转换器
     * content-type:text/plan
     * content-type:text/json
     *
     * @param converter 自定义的消息转换器
     * @return openFeign 解码器
     */
    @Bean
    @ConditionalOnBean(TextMappingJackson2HttpMessageConverter.class)
    public Decoder feignDecoder(TextMappingJackson2HttpMessageConverter converter) {
        logger.info("==================> 初始化openFeign text/json支持解析器");
        if (messageConverters == null){
            messageConverters = new HttpMessageConverters(converter);
        }
        ObjectFactory<HttpMessageConverters> objectFactory = () -> messageConverters;
        return new SpringDecoder(objectFactory);
    }

结束

系统前后性能对比

机器 16c,64g
踩坑记录---openFeign高并发阻塞分析与解决_第5张图片

你可能感兴趣的:(笔记,spring,cloud,java,springcloud)