分布式系统超时重试

在实际开发当中,很多故障的产生都是因为没有意识到超时设置的重要性而造成的。如果不设置超时机制,就有可能导致连锁反应,最终系统雪崩。有些中间件天然集成了超时重试的机制,但是在设计当中需要注意的是:读服务非常适合重试,但是写服务大多不能重试。为了解决写操作的重试问题,一般情况下会使用“幂等重试”的方式。但是跨系统调用又不能无限次数的重试,这里面就需要设置合理的重试次数,并且配合熔断、快速失败等机制。

重试的分类

从系统调用的整个链路来看,可以将其分为以下几种类型。

代理层超时重试:如Haproxy、Nginx等。这里面需要合理的设置代理与后端真实服务之间的网络连接/读/写的超时时间。

Web容器超时:如Tomcat、Jetty等。应用服务器需要设置客户端与容器之间的网络连接/读/写超时时间。以及默认Socket网络连接/读/写的超时时间。

中间件客户端超时重试:如Dubbo、HTTPClient等。需要设置客户端的网络连接/读/写超时时间与重试机制。

数据库客户端超时:如MySQL、MongoDB、Redis等。需要分别设置JDBC Connection、Statement的网络连接/读/写超时时间,事务超时时间,获取连接池连接等待时间。

前端Ajax超时:浏览器通过Ajax访问时的网络连接/读/写超时时间。

Nginx代理层超时

Nginx(OpenResty)相关的主要有4类超时设置:客户端超时设置DNS解析超时设置代理超时设置ngx_lua相关的超时设置

客户端超时设置

对于客户端的超时设置有:读取请求头超时时间、读取请求体超时时间、发送响应超时时间、长连接超时时间。通过客户端的超时时间设置,可以避免一些长连接的占用,以至于影响服务端的性能。

client_header_timeout time:设置读取客户端请求头超时时间,默认为60S。如果在此时间内,客户端没有发送完请求头,则响应408(Request Time-out)状态码。

client_body_timeout time:设置读取客户端请求体超时时间,默认60S。此时间是指2次成功读操作的时间间隔(针对于Nginx本身而言的),而不是客户端发送的时间。超时则响应408状态。

send_timeout time:设置发送响应到客户端的超时时间,默认60S。此处的时间也是2次写操作的时间间隔。如果在此时间内,客户端没有接收任何响应,Nginx就会关闭连接。

keepalive_timeout timeout [header_timeout]:设置HTTP长连接超时时间。其中第一个参数(timeout)是告诉Nginx长连接的超时时间是多少,默认是75S;第二个参数header_timeout用于设置响应头“Keep-Alive: timeout=time”,即告诉客户端长连接超时时间。

DNS解析超时设置

resolver_timeout 30s:设置DNS解析超时时间,默认30S。它需要配合resolver address [valid=time]进行DNS域名解析。

#nginx 默认会根据DNS请求结果里的TTL值来进行缓存,可以通过一个可选的参数valid来设置过期时间
resolver 8.8.8.8 8.8.4.4 valid=30s;

其中当upstream中配置如下时,每次Nginx启动时,都会把domain解析成IP。如果如果此时IP变更,Nginx不会立马更新(社区版本不会立即更新,商业版本可以),只有在valid过期后,才会刷新。

upstream backend {
    server c0.3.cn;
    server c1.3.cn;
}

如果要解决这个问题,可以在proxy_pass中如下设置,但是当多域名时,就比较麻烦了。

location /demo {
    proxy_pass http://c0.3.cn;
}

如果用的是OpenResty,则可以使用lua-resty-dns进行DNS解析。

local resolver = require 'resty.dns.resolver'
local r, err = resolver:new {
    nameservers = {'8.8.8.8', {'8.8.8.8', 53}},
    retrans = 5,
    timeout = 2000,  -- 2 sec
}

代理超时设置

Nginx配置如下

upstream backend_server {
    server 192.168.61.1:9080 max_fails=2 fail_timeout=10s weight=1;
    server 192.168.61.1:9090 max_fails=2 fail_timeout=10s weight=1;
}

server {
    ...
    location /test {
        proxy_connect_timeout 5s;
        proxy_read_timeout 5s;
        proxy_send_timeout 5s;

        proxy_next_upstream error timeout;
        proxy_next_upstream_timeout 0;
        proxy_next_upstream_tries 0;

        proxy_pass http://backend_server;
        add_header upstream_addr $upstream_addr;
    }

}

backend_server定义了2个上游服务器。上面的配置主要有3组配置:网络连接/读/写超时设置失败重试机制设置upstream存活超时设置

网络连接/读/写超时设置

proxy_connect_timeout time:与后端/上游服务器建立连接的超时时间,默认为60s。

proxy_read_timeout time:设置从后端/上游服务器读取响应的超时时间,默认60s。此超时时间指的是2次成功读操作时间间隔,而不是读取整个响应体的超时时间。如果超时时间内没有任何响应,则Nginx将关闭连接。

proxy_send_timeout time:设置往后端/上游服务器发送请求的超时时间,默认60s。此超时时间指的是2次成功读操作时间间隔,而不是读取整个响应体的超时时间。如果超时时间内没有任何响应,则Nginx将关闭连接。

失败重试机制设置

proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | non_idempotent | off ... :配置什么情况下需要向上游服务器进行重试。默认为:error timeout。error表示读写出错;timeout表示超时;invalid_header表示头信息有误;non_idempotent表示RFC-2616定义的非幂等HTTP方法(POST、LOCK、PATCH),也可以在失败后重试(默认幂等方法GET、HEAD、PUT、DELETE、OPTIONS、TRACE);off表示禁用重试。

proxy_next_upstream_tries number:设置重试次数,默认0表示不限制。这里的次数包含第一次请求。

proxy_next_upstream_timeout time:设置重试最大超时时间,默认0表示不限制。

上面2个值的关系是且的关系,在限制时间内或者重试次数达到一个值就会结束重试,并返回客户端响应。

upstream存活超时设置

max_failsfail_timeout:配置什么情况下Nginx将上有服务器认定为不可用。条件为:在fail_timeout时间内失败了max_fails次数。例如上面的配置中,10S失败次数达到2次,就认为上游服务器不可用。

若max_fails设置为0,则表示不检查是否不可用。

ngx_lua超时设置

当我们在使用ngx_lua时,需要考虑的超时设置如下:

lua_socket_connect_timeout 100ms; -- 连接
lua_socket_send_timeout 200ms; -- 发送
lua_socket_read_timeout 500ms; -- 读取

Web容器超时

Web容器的设置,下面以Tomcat为例。

connectionTimeout:设置与客户端简历连接的超时时间。从接收到连接后,在配置的时间内没有接收到客户端请求行,就会被认为连接超时,默认60S。

socket.soTimeout:从客户端读取请求数据的超时时间。默认60S,NIO和NIO2支持该配置。

asyncTimeout:Servlet3异步请求的超时时间,默认30S。

disableUploadTimeoutconnectionUploadTimeout:当配置disableUploadTimeout为false时(默认为true,和connectTimeout一样),文件上传将使用connectionUploadTimeout作为超时时间。

keepAliveTimeoutmaxKeepAliveRequests:keepAliveTimeout默认为connectionTimeout,配置-1表示永不超时。maxKeepAliveRequests默认为100。


参考:《亿级流量网站架构核心技术》