springcloud-单个服务无法处理新的请求

前言:
最近生产上出现一个问题,描述如下:
springcloud分布式环境下,服务B无法处理新进来的请求,且zuul服务一直在走降级逻辑。
服务调用情况:zuul->服务A->服务B

问题分析:
1.首先查看tomcat连接是不是满了,通过netstat -nat|grep -i “服务B端口号”|grep ESTABLISHED|wc -l,连接数为300多,tomcat默认最大线程数配置是200,说明所有tomcat线程可能已经被使用。
2.通过jvisualvm工具,查看服务B的线程数确实在200以上,证明了第一点分析,dump内存线程,发现问题的原因是线程卡在了一段代码执行上。

问题已经改了,但是感觉还是没有完全解决完:
1.是什么原因导致zuul服务走降级逻辑?
2.为什么zuul服务已经走降级逻辑了,但是内部服务B的tomcat连接却还占满着不释放?

针对问题1
1.根据debug了下,发现是报了hystrix超时异常导致走的降级逻辑,查看超时配置是30秒:

hystrix:
  command:
    A:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 30000

针对问题2
1.查看ribbon读取超时配置:ribbon.ReadTimeout: 60000
2.当服务之间建立连接之后调用超过了60秒,就会报ribbon的socket read timeout异常,结合问题1分析,发现zuul请求服务A的hystrix超时配置小于ribbon的读取超时配置,在这种情况下,当前请求超过20秒,该请求就会走降级逻辑,但是服务A调用服务B,还没有到达ribbon的读取超时时间,所以连接还保持着。
结论:这种配置是有点不太合理,建议服务之间调用超时配置小于hystrix超时配置。

这个问题的分析过程中涉及到了tomcat连接数及线程数概念,后来我就想:
那什么时候tomcat连接数才算真的满了?如果不能建立新的请求,那这时会报错么?报什么错?

带着疑惑写代码做了下验证:
1.自定义tomcat容器配置:

@Component
public class MyEmbeddedServletContainerFactory extends TomcatEmbeddedServletContainerFactory {
    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers){
        //设置端口
        this.setPort(8081);
        return super.getEmbeddedServletContainer(initializers);
    }

    @Override
    protected void customizeConnector(Connector connector){
        super.customizeConnector(connector);
        Http11NioProtocol protocol = (Http11NioProtocol)connector.getProtocolHandler();
        //设置最大连接数,默认10000
        protocol.setMaxConnections(50);
        //设置全连接队列大小,默认100
        protocol.setAcceptCount(200);
        //设置最大线程数,默认200
        protocol.setMaxThreads(99);
        //设置连接超时,默认20000ms,tomcat读取超时配置也是使用的这个值
        protocol.setConnectionTimeout(100);
    }
}

2.编写controller:

@RestController
public class ConnectTest {
    private AtomicInteger count = new AtomicInteger();
    @GetMapping("/test")
    public void test(){
        System.out.println(count.decrementAndGet());
        try {
            //休息较长时间,让线程阻塞挂起
            Thread.sleep(1000* 10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.编写测试代码:

public class ControllerTest {
	/**线程数**/
    private static final int THREAD_SIZE = 100;
    public static void main(String[] args){
        String url = "http://127.0.0.1:8081/test";
        for (int i =0; i < THREAD_SIZE; i++){
            Thread t = new Thread(()->{
                RestTemplate template = new RestTemplate();
                template.getForEntity(url , Object.class);
            });
            t.start();
        }
    }
}

测试开始:
1.设置THREAD_SIZE=149(tomcat最大连接数+tomcat最大处理线程数)
预期:全部建连成功
结果:所有请求全部建立连接,符合预期
2.设置THREAD_SIZE=150(tomcat最大连接数+tomcat最大处理线程数)+1
预期:最后一个执行线程会建连失败
结果:所有请求全部建立连接,不符合预期,百度了下网上也有提到accpetCount也会影响连接的上限
3.设置THREAD_SIZE=250(tomcat最大连接数+tomcat全连接队列大小)
预期:全部建连成功
结果:所有请求全部建立连接,符合预期
4.设置THREAD_SIZE=251(tomcat最大连接数+tomcat全连接队列大小)+1
预期:最后一个执行线程会建连失败
结果:最后一个线程请求报错,服务端返回connect refused错误,符合预期

经过反复测试,不断修改配置以及测试代码最终得到一些结论:
1.tomcat容器可以接受的最大连接数计算:最大连接数+(全对列大小/最大线程数 其中较大的那个值)
2.当连接满了之后,服务端会拒绝客户端的请求,客户端报:connect refused错误。

扩展学习:

socket各种超时
1.socket连接超时

摘自java.Socket.connect方法的一段源码:
 * Connects this socket to the server with a specified timeout value.
 * A timeout of zero is interpreted as an infinite timeout. The connection
 * will then block until established or an error occurs.

public void connect(SocketAddress endpoint, int timeout) throws IOException

2.socket读超时

摘自java.Socket.setSoTimeout方法的一段源码:
 *  Enable/disable {@link SocketOptions#SO_TIMEOUT SO_TIMEOUT}
 *  with the specified timeout, in milliseconds. With this option set
 *  to a non-zero timeout, a read() call on the InputStream associated with
 *  this Socket will block for only this amount of time.  If the timeout
 *  expires, a <B>java.net.SocketTimeoutException</B> is raised, though the
 *  Socket is still valid. The option <B>must</B> be enabled
 *  prior to entering the blocking operation to have effect. The
 *  timeout must be {@code > 0}.
 *  A timeout of zero is interpreted as an infinite timeout.

public synchronized void setSoTimeout(int timeout) throws SocketException

3.socket写超时

socket没有写入超时这个概念,可以自己封装实现,例如tomcat1.8使用Nio2SocketWrapper自己封装了写超时。

留下问题(方便自己回过头看时想一想):

1.tcp长连接和短连接区别?
2.怎么理解无状态性?
3.什么是全连接队列?
4.全连接队列大小取值?
全连接队列的大小未必是backlog的值,它是backlog与somaxconn(一个os级别的系统参数)的较小值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值