Pigeon线程池打满问题

一、问题介绍

线上服务A上线后,一切正常。等到晚上八点左右,服务A开始报警,很多接口出现超时的问题,因为降级做的不好,进而引起连锁反应,导致很多其他服务也开始出现超时,不可用等报警。

二、问题原因

服务A上线是为了修复一个异步发送PUSH的问题,最初的问题是发现线上PUSH很多没有发出,初步定位的是异步线程没有执行,于是同事把异步改成同步调用,修复上线了,并线上验证通过。
在晚上八点左右,有一个JOB批量的调用这个发送PUSH的接口,接口是同步HTTP调用第三方接口,同时HttpClient没有设置超时时间,而第三方接口的响应时间较长,于是很多线程阻塞在HTTP调用上,又因为接口量调用较大,导致Pigeon线程池打满,该接口出现大面积超时。

三、分析

这个事故发生根本原因是两个问题:

  • 对Pigeon线程模型没有深入了解,把会长时间阻塞线程的任务放在同步线程池中执行
  • HttpClient不设置超时时间,第三方接口响应时间不确定。

Pigeon是一个类似dubbo的服务化框架,支持远程调用,服务注册,服务发现,负载均衡等功能。
他的底层网络处理依赖Netty,所以分析Pigeon的线程模型,需要从Pigeon和Netty两个方面来看。

3.1、Netty线程模型

Netty是一款NIO框架,它封装了JAVA NIO ,帮助我们解决了很多的网络处理的底层问题,提供出易用的API,方便我们快速的开发出NIO的程序。
Netty的采用了Reactor模型,事件驱动。

Reactor

其中Accept Pool 只负责处理新的连接请求和一些简单的非业务逻辑,比如权限认证,登录等。
IO Pool 负责处理channel的读写,编码解码和业务逻辑
我们再看一下Netty是如何对应这种模式的:
Netty服务端启动

服务端启动时新建了两个EventLoopGroup ,其中bossGroup就对应上图的Accept Pool ,workerGroup对应IO Pool。
启动时会随机从workerGroup选择一个NIOEventLoop作为Accept Thread 处理所有的网络连接,连接成功后,会把新建的socketChannel 扔给workerGroup,workerGroup会随机选择一个NIOEventLoop来负责处理这个Channel的读写事件。
一个NIOEventLoop处理很多Channel,而一个Channel的编码,解码,业务逻辑,都是在一个线程中完成,不会出现多个线程处理一个Channel的问题,也就不需要锁。
进而如果你在业务逻辑中有IO操作,比如读写数据库,HTTP调用等。就会阻塞这个线程,导致这个线程处理的所有Channel都会收到影响,降低吞吐率。所以Netty 里面的Handler一定不要有同步的阻塞操作,这一部分要放到异步线程池中执行。

3.2、Pigeon线程模型

阅读Pigeon源码,我们可以发现,Pigeon执行业务逻辑前,需要选择线程池

private ThreadPool selectThreadPool(final InvocationRequest request) {
        ThreadPool pool = null;
        String serviceKey = request.getServiceName();
        String methodKey = serviceKey + "#" + request.getMethodName();

        // spring poolConfig
        pool = getConfigThreadPool(request);

        // spring actives
        if (pool == null && !CollectionUtils.isEmpty(methodThreadPools)) {
            pool = methodThreadPools.get(methodKey);
        }
        if (pool == null && !CollectionUtils.isEmpty(serviceThreadPools)) {
            pool = serviceThreadPools.get(serviceKey);
        }

        // lion poolConfig
        if (pool == null && poolConfigSwitchable && !CollectionUtils.isEmpty(apiPoolNameMapping)) {
            PoolConfig poolConfig = null;
            String poolName = apiPoolNameMapping.get(methodKey);
            if (StringUtils.isNotBlank(poolName)) { // 方法级别
                poolConfig = poolConfigs.get(poolName);
                if (poolConfig != null) {
                    pool = DynamicThreadPoolFactory.getThreadPool(poolConfig);
                }
            } else { // 服务级别
                poolName = apiPoolNameMapping.get(serviceKey);
                if (StringUtils.isNotBlank(poolName)) {
                    poolConfig = poolConfigs.get(poolName);
                    if (poolConfig != null) {
                        pool = DynamicThreadPoolFactory.getThreadPool(poolConfig);
                    }
                }
            }
        }

        // 默认方式
        if (pool == null) {
            if (enableSlowPool && requestTimeoutListener.isSlowRequest(request)) {
                pool = slowRequestProcessThreadPool;
            } else {
                pool = sharedRequestProcessThreadPool;
            }
        }

        return pool;
    }

首先去看spring配置里有没有对指定接口,指定方法需独立的线程池,如果有,则用独立的线程池,如果没有,则去看是否已存在方法级别或者接口级别的线程池,如果没有,则确认方法是否是满调用,如果是则用满调用线程池,如果不是则用共享线程池。
通过这种方式,我们可以利用配置在方法维度上进行隔离,防止一个接口出问题拖垮整个服务。

四、总结

  • 理解netty pigeon 线程模型
  • IO任务尽量采用异步执行,并做好线程池任务队列的监控。
  • HttpClient 一定要设置超时时间,避免线程长时间阻塞
  • 做好code review
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,391评论 11 349
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,087评论 19 139
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 综述 netty通...
    jiangmo阅读 5,941评论 0 13
  • 1、Netty基础入门 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应...
    我是嘻哈大哥阅读 4,710评论 0 31
  • 有一天,会有一个人,看你写过的所有状态,读完写的所有微博,看你从小到大的所有照片,甚至去别的地方寻找关于你的信息,...
    灼落翼阅读 193评论 0 0