高可用之——超时与重试机制

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/l1028386804/article/details/102498210

最重要的超时设置是网络连接/读/写的超时时间设置。

  • 代理层超时与重试:如Haproxy、Nginx、Twemproxy等组件可以实现代理功能,如Haproxy和Nginx可以实现请求的负载均衡。而Twemproxy可以实现Redis的分片代理。需要设置代理与后端真实服务器之间的网络连接/读/写超时时间。
  • Web容器超时:如Tomcat、Jetty等,提供HTTP服务运行环境的,需要设置客户端与容器之间的网络连接/读/写超时时间,和在此容器中默认Socket网络连接读/写/超时时间。
  • 中间件客户端超时与重试:JSF(京东SOA框架)、Dubbo、JMQ(京东消息中间件)、CXF、Httpclient等,需要设置客户端的网络连接/读/写超时时间与失败重试机制。
  • -数据库客户端超时:如MySQL、Oracle,需要分别设置JDBC Connection、Statement的网络连接/读/写超时时间,事务超时时间,获取连接池连接等待时间。
  • NoSQL客户端超时:如Mongo、Redis,需要设置其网络连接/读/写超时时间,获取连接池连接等待时间。
  • 业务超时:如订单取消任务、超时活动关闭,还有通过Future#get(timeout, unit)限制某个接口的超时时间。
  • 前端Ajax超时:浏览器通过Ajax访问时的网络连接/读/写超时时间。

其中最重要的超时设置是网络相关的超时设置。

一、代理层超时与重试

Nginx的超时与重试

Nginx主要有4类超时设置:客户端超时设置、DNS解析超时设置、代理超时设置,如果使用ngx_lua,还有Lua相关的超时设置。

1.客户端超时设置

对于客户端超时主要设置有读取请求头超时时间、读取请求体超时时间、发送响应超时时间、长连接超时时间。

  • client_header_timeout time:设置读取客户端请求头超时时间,默认为60s,如果在此超时时间内客户端没有发送完请求头,则响应408(Request Time-out)状态码给客户端。
  • client_body_timeout time:设置读取客户端内容体超时时间,默认为60s,此超时时间指的是两次成功读操作间隔时间,而不是发送整个请求体的超时时间,如果在此超时时间内客户端没有发送任何请求体,则响应408(Request Time-out)状态码给客户端。
  • send_timeout time:设置发送响应到客户端的超时时间,默认60s,此超时时间指的是两次成功写操作间隔时间,而不是发送整个响应的超时时间。如果在此超时时间内客户端没有接收任何响应,则Nginx关闭此连接。
  • keepalive_timeout timeout [header_timeout]:设置Http长连接超时时间。其中,第一个参数timeout告诉Nginx长连接超时时间是多少,默认75s。第二个参数header_timeout用于设置响应头“Keep-Alive: timeout=time”,即告知客户端长连接超时时间。两个参数可以不一样,“Keep-Alive: timeout=time”响应头在Mozilla和Konqueror系列浏览器中起作用,而MSIE长连接默认大约为60s,而不会使用“Keep-Alive: timeout=time”。如Httpclient框架会使用“Keep-Alive: timeout=time”响应头的超时(如果不设置默认,则认为是永久)。如果timeout设置为0,则表示禁用长连接。

此参数要配合keepalive_disable和keeplive_requests一起使用。keepalive_disable表示禁用哪些浏览器的长连接,默认为msie6,即禁用一些老版本的MSIE的长连接支持。keepalive_requests参数的作用是一个客户端可以通过此长连接的请求次数,默认为100。

http/1.0默认是关闭长连接的,需要添加HTTP请求头“Connection: keep-alive”才能启用。http/1.1默认启用长连接,需要添加HTTP请求头“Connection:close”进行关闭。

keepalive_timeout和keepalive_requests是控制长连接的两个维度,只要其中一个到达设置的阈值,连接就会关闭。

2.DNS解析超时设置

resolver_timeout 30s:设置DNS解析超时时间,默认30s。配合resolver address...[valid=time]进行DNS域名解析。当在Nginx中使用域名时,需要考虑设置这两个参数。在Nginx社区版中采用如下配置。

upstream backend {
	server back1.binghe.com;
	server back2.binghe.com;
}

以上两个域名会在Nginx解析配置文件的阶段被解析成IP地址并记录到upstream上,当这两个域名对应的IP地址发生变化时,该upstream不会被更新,Nginx商业版支持动态更新。
可以使用如下方式,每次都会动态解析域名,这种情况在多域名情况下比较麻烦,实现不优雅。

location /test {
	proxy_pass http://back.binghe.com;
}

如果使用OpenResty,则可以通过Lua库lua-resty-dns进行DNS解析。

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

当使用Nginx 1.5.8、1.7.4会报错,可以将Nginx升级到1.6.2、1.7.5或者在Nginx本机部署dnsmasq提升DNS解析性能。

3.代理超时设置

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;
	}
}

主要有三组配置:网络连接/读/写超时设置、失败重试机制设置、upstream存活超时设置。
(1)网络连接/读/写超时设置

  • proxy_connect_timeout time: 与后端、上游服务器建立连接的超时时间,默认为60s,此时间不超过75s。
  • proxy_read_timeout time: 设置从后端/上游服务器读取响应的超时时间,默认为60s,此超时时间指的是两次成功读操作间隔时间,而不是读取整个响应体的超时时间,如果在此超时时间内上游服务器没有发送任何响应,则Nginx关闭此连接。
  • proxy_send_timeout time: 设置往后端、上游服务器发送请求的超时时间,默认为60s,此超时时间指的是两次成功写操作间隔时间,而不是发送整个请求的超时时间,如果在此超时时间内上游服务器没有接收任何响应,则Nginx关闭此连接。

对于内网高并发服务,可以调整这几个参数,比如内网服务TP999为1s,可以将连接超时设置为100~500ms,读超时可以为1.5~3s左右。
(2)失败重试机制设置
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表示上游服务器返回空的或错误的响应头。http_XXX表示上游服务器返回特定的状态码。non_idempotent表示RFC-2616定义的非幂等HTTP方法(POST、LOCK、PATCH),也可以在失败后重试下一台上游服务器(即默认幂等方法GET、HEAD、PUT、DELETE、OPTIONS、TRANCE才可以重试)。off表示禁用重试。

重试不能无限制进行,需要使用如下两个指令控制重试次数和重试超时时间。

  • proxy_next_upstream_tries number:设置重试次数,默认0表示不限制,注意此重试次数指的是所有请求次数(包括第一次和之后的重试次数之和)。
  • proxy_next_upstream_timeout time: 设置重试最大超时时间,默认0表示不限制。

即在proxy_next_upstream_timeout时间内允许proxy_next_upstream_tries次重试。如果超过了其中一个设置,则Nginx也会结束重试并返回客户端响应(可能是错误码)。

如下配置表示当error/timeout时重试upstream中的下一台上游服务器,如果重试的总时间超过6s或者重试了1次,则表示重试失败(因为之前已经请求一次了,所以还能重试1次),Nginx结束重试并返回客户端响应。

proxy_next_upstream error timeout;
proxy_next_upstream_timeout 6s;
proxy_next_upstream_tries 2;

(3)upstream存活超时设置

  • max_fails和fail_timeout:配置什么时候Nginx将上游服务器认定为不可用/不存活。当上游服务器在fail_timeout时间内失败了max_fails次,则认为该上游服务器不可用/不存活。并在接下来的fail_timeout时间内从upstream摘掉该节点(即请求不会转发到该上游服务器)。

max_fails设置为0表示不检查服务器是否可用(即认为一直可用),如果upstream中仅剩一台上游服务器,则该服务器是不会被摘除的,将从不被认为不可用。

(4)ngx_lua超时设置
当我们使用ngx_lua时,应考虑设置如下网络连接/读/写超时。

lua_socket_connect_timeout 100ms;
lua_socket_send_timeout 200ms;
lua_socket_read_timeout 500ms;

在使用Lua时,按照如下策略重试。

if (status == 502 or status == 503 or status == 504) and request_time < 200 then 
	resp = capture(proxy_uri)
	status = resp.status
	body = resp.body
	request_time = request_time + tonumber(var.request_time) * 1000
end

即如果状态码是500/502/503/504,并且该次请求耗时在200ms以内,则进行一次重试。

Twemproxy

Twemproxy是Twitter开源的Redis和Memcached代理中间件,目的是减少与后端缓存服务器的连接数。

  • timeout: 表示与后端服务器建立连接、接收响应的超时时间,默认永不超时。
  • server_retry_timeout和server_failure_limit:当开启auto_eject_hosts,即当后端服务器不可用时自动摘除这些节点并在一定时间后进行重试。server_failure_limit设置连续失败多少次后将节点临时摘除,server_retry_timeout设置摘除节点后等待多久进行重试,从而保证不永久性地将节点摘除。

二、Web容器超时

以Tomcat为例

  • connectionTimeout: 配置与客户端建立连接的超时时间,从接收到连接后,在配置的时间内没有接收到客户端请求行,将被认定为连接超时,默认60000(60s)。
  • socket.soTimeout: 从客户端读取请求数据的超时时间,默认同connectionTimeout,NIO和NIO2支持该配置。
  • asyncTimeout:Servlet3异步请求的超时时间,默认为30000(30s)。
  • disableUploadTimeout和connectionUploadTimeout:当配置disableUploadTimeout为false时(默认为true,和connectionTimeout一样),文件上传将使用connectionUploadTimeout作为超时时间。
  • keepAliveTimeout和maxKeepAliveRequests: 和Nginx配置类似。keepAliveTimeout默认为connectionTimeout,配置为-1表示永不超时。maxKeepAliveRequests默认为100。

三、中间件客户端超时与重试

四、数据库客户端超时与重试

五、NoSQL客户端超时与重试

六、业务超时

  • 任务型:比如,订单超时未支付取消超时活动自动关闭等,属于任务型超时,可以通过Worker定期扫描数据库修改状态。有时需要调用的远程服务超时了*(比如,用户注册成功了,需要给用户发放优惠券等),可以考虑使用队列或者暂时记录到本地稍后重试。
  • 服务调用型:比如,某个服务的全局超时时间为500ms,但有多处服务调用,每处服务调用的超时时间可能不一样,此时,可以简单的使用Future来解决问题,通过如Future.get(3000, TimeUnit.MILLISECONDS)来设置超时。

七、前端Ajax超时

——总结自涛哥的《亿级流量网站架构核心技术》

猜你喜欢

转载自blog.csdn.net/l1028386804/article/details/102498210