关于设置请求超时(涉及异步servlet)

/ 后端 / 没有评论 / 644浏览

注意

一般来说,超时都是设置在客户端的;如H5的ajax,安卓的okhttp;亦或者是网关nginx设置代理的后端服务器连接超时时间等~ 如果在后台服务端设置超时,当然也是可以的,但是一般并不这么做,因为一般服务端的业务并不能取一个时间值来限制他的执行时间;客户端由于一些需要,比如用户交互,资源占用等,如果太长时间不返回会影响用户体验,则需要设置一个默认的超时时间;

设置

1.nginx

client_header_timeout

语法 client_header_timeout time
默认值 60s
上下文 http server
说明 指定等待client发送一个请求头的超时时间(例如:GET / HTTP/1.1).仅当在一次read中,没有收到请求头,才会算成超时。如果在超时时间内,client没发送任何东西,nginx返回HTTP状态码408(“Request timed out”)

client_body_timeout 

语法 client_body_timeout time
默认值 60s
上下文 http server location
说明 该指令设置请求体(request body)的读超时时间。仅当在一次readstep中,没有得到请求体,就会设为超时。超时后,nginx返回HTTP状态码408(“Request timed out”)

keepalive_timeout 

语法 keepalive_timeout timeout [ header_timeout ]
默认值 75s
上下文 http server location
说明 第一个参数指定了与client的keep-alive连接超时时间。服务器将会在这个时间后关闭连接。可选的第二个参数指定了在响应头Keep-Alive: timeout=time中的time值。这个头能够让一些浏览器主动关闭连接,这样服务器就不必要去关闭连接了。没有这个参数,nginx不会发送Keep-Alive响应头(尽管并不是由这个头来决定连接是否“keep-alive”)
两个参数的值可并不相同

注意不同浏览器怎么处理“keep-alive”头
MSIE和Opera忽略掉"Keep-Alive: timeout=<N>" header.
MSIE保持连接大约60-65秒,然后发送TCP RST
Opera永久保持长连接
Mozilla keeps the connection alive for N plus about 1-10 seconds.
Konqueror保持长连接N秒
lingering_timeout

语法 lingering_timeout time
默认值 5s
上下文 http server location
说明 lingering_close生效后,在关闭连接前,会检测是否有用户发送的数据到达服务器,如果超过lingering_timeout时间后还没有数据可读,就直接关闭连接;否则,必须在读取完连接缓冲区上的数据并丢弃掉后才会关闭连接。

resolver_timeout

语法 resolver_timeout time 
默认值 30s
上下文 http server location
说明 该指令设置DNS解析超时时间

proxy_connect_timeout

语法 proxy_connect_timeout time 
默认值 60s
上下文 http server location
说明 该指令设置与upstream server的连接超时时间,有必要记住,这个超时不能超过75秒。
这个不是等待后端返回页面的时间,那是由proxy_read_timeout声明的。如果你的upstream服务器起来了,但是hanging住了(例如,没有足够的线程处理请求,所以把你的请求放到请求池里稍后处理),那么这个声明是没有用的,由于与upstream服务器的连接已经建立了。

proxy_read_timeout

语法 proxy_read_timeout time 
默认值 60s
上下文 http server location
说明 该指令设置与代理服务器的读超时时间。它决定了nginx会等待多长时间来获得请求的响应。这个时间不是获得整个response的时间,而是两次reading操作的时间。

proxy_send_timeout

语法 proxy_send_timeout time 
默认值 60s
上下文 http server location
说明 这个指定设置了发送请求给upstream服务器的超时时间。超时设置不是为了整个发送期间,而是在两次write操作期间。如果超时后,upstream没有收到新的数据,nginx会关闭连接

proxy_upstream_fail_timeout(fail_timeout)

语法 server address [fail_timeout=30s]
默认值 10s
上下文 upstream
说明 Upstream模块下 server指令的参数,设置了某一个upstream后端失败了指定次数(max_fails)后,该后端不可操作的时间,默认为10秒

2.springboot请求超时(需使用异步servlet,场景为长轮询请求等)

可以有效减少tomcat自身线程的占用,将耗时任务提交到其他工作线程执行,提高tomcat处理连接;

异步处理带来的是可以控制响应时间,但是其他业务依然会在其他线程继续执行;

配置文件方式:

spring.mvc.async.request-timeout=20000

代码方式:


public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(20000);
        configurer.registerCallableInterceptors(timeoutInterceptor());
    }
    @Bean
    public TimeoutCallableProcessingInterceptor timeoutInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }
}

看名字也能看出来,Servlet 3.0开始提供了AsyncContext用来支持异步处理请求;

所以如果要支持超时,则需要提供支持异步请求的接口,如:

(1)Servlet方式实现异步请求

	@RequestMapping(value = "/email/servletReq", method = GET)
    public void servletReq (HttpServletRequest request, HttpServletResponse response) {
        AsyncContext asyncContext = request.startAsync();
        //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
		asyncContext.addListener(new AsyncListener() {
            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
                System.out.println("超时了...");
                //做一些超时后的相关操作...
            }
            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {
                System.out.println("线程开始");
            }
            @Override
            public void onError(AsyncEvent event) throws IOException {
                System.out.println("发生错误:"+event.getThrowable());
            }
            @Override
            public void onComplete(AsyncEvent event) throws IOException {
                System.out.println("执行完成");
                //这里可以做一些清理资源的操作...
            }
        });
        //设置超时时间
        asyncContext.setTimeout(20000);
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                    System.out.println("内部线程:" + Thread.currentThread().getName());
                    asyncContext.getResponse().setCharacterEncoding("utf-8");
                    asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                    asyncContext.getResponse().getWriter().println("这是异步的请求返回");
                } catch (Exception e) {
                    System.out.println("异常:"+e);
                }
                //异步请求完成通知
                //此时整个请求才完成
                asyncContext.complete();
            }
        });
        //此时之类 request的线程连接已经释放了
        System.out.println("主线程:" + Thread.currentThread().getName());
    }

(2)直接返回callable,可以继承WebMvcConfigurerAdapter类来设置默认线程池和超时处理

	@RequestMapping(value = "/email/callableReq", method = GET)
    @ResponseBody
    public Callable<String> callableReq () {
        System.out.println("外部线程:" + Thread.currentThread().getName());

        return new Callable<String>() {

            @Override
            public String call() throws Exception {
                Thread.sleep(10000);
                System.out.println("内部线程:" + Thread.currentThread().getName());
                return "callable!";
            }
        };
    }

	@Configuration
	public class RequestAsyncPoolConfig extends WebMvcConfigurerAdapter {
    
    @Resource
    private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
        //处理 callable超时
        configurer.setDefaultTimeout(60*1000);
        configurer.setTaskExecutor(myThreadPoolTaskExecutor);
        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
        return new TimeoutCallableProcessingInterceptor();
    }

}

(3)给WebAsyncTask设置一个超时回调,即可实现超时处理

    @RequestMapping(value = "/email/webAsyncReq", method = GET)
    @ResponseBody
    public WebAsyncTask<String> webAsyncReq () {
        System.out.println("外部线程:" + Thread.currentThread().getName());
        Callable<String> result = () -> {
            System.out.println("内部线程开始:" + Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (Exception e) {
                // TODO: handle exception
            }
            logger.info("副线程返回");
            System.out.println("内部线程返回:" + Thread.currentThread().getName());
            return "success";
        };
        WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
        wat.onTimeout(new Callable<String>() {

            @Override
            public String call() throws Exception {
                // TODO Auto-generated method stub
                return "超时";
            }
        });
        return wat;
    }

(4)DeferredResult

@RequestMapping(value = "/email/deferredResultReq", method = GET)
    @ResponseBody
    public DeferredResult<String> deferredResultReq () {
        System.out.println("外部线程:" + Thread.currentThread().getName());
        //设置超时时间
        DeferredResult<String> result = new DeferredResult<String>(60*1000L);
        //处理超时事件 采用委托机制
        result.onTimeout(new Runnable() {

            @Override
            public void run() {
                System.out.println("DeferredResult超时");
                result.setResult("超时了!");
            }
        });
        result.onCompletion(new Runnable() {

            @Override
            public void run() {
                //完成后
                System.out.println("调用完成");
            }
        });
        myThreadPoolTaskExecutor.execute(new Runnable() {

            @Override
            public void run() {
                //处理业务逻辑
                System.out.println("内部线程:" + Thread.currentThread().getName());
                //返回结果
                result.setResult("DeferredResult!!");
            }
        });
       return result;
    }

3.Tomcat

connectTimeout
指客户端打开连接但未发送或发送请求非常慢时的超时(根据 uri、标头等) http协议

当出现该错误后,java层面会报java.net.SocketTimeoutException错误;

springboot异步请求方法摘自网络https://www.cnblogs.com/baixianlong/p/10661591.html