太多故障是因为没有设置超时或者设置得不对而造成的。而这些故障都是因为没有意识到超时设置的重要性而造成的。
如果应用不设置超时,则可能会导致请求响应慢,慢请求累积导致连锁反应,甚至造成应用雪崩。
而有些中间件或者框架在超时后会进行重试(如设置超时重试两次),读服务天然适合重试,但写服务大多不能重试(如写订单,如果写服务是幂等的,则重试是允许的),重试次数太多会导致多倍请求流量,即模拟了DDoS攻击,后果可能是灾难,因此,务必设置合理的重试机制,并且应该和熔断、快速失败机制配合。
在进行代码Review时,一定记得Review超时与重试机制。
在整个链条中的每一个点都要考虑设置超时与重试机制。而其中最重要的超时设置是网络连接/读/写的超时时间设置。
对于客户端超时主要设置有读取请求头超时时间、读取请求体超时时间、发送响应超时时间、长连接超时时间。通过客户端超时设置避免客户端恶意或者网络状况不佳造成连接长期被占用,影响服务器端的可处理能力。
两个域名会在Nginx解析配置文件的阶段被解析成IP地址并记录到upstream上,当这两个域名对应的IP地址发生变化时,该upstream不会被更新。而Nginx商业版是支持动态更新的。
任务型:比如,订单超时未支付取消超时活动自动关闭等,这属于任务型超时,可以通过Worker定期扫描数据库修改状态。有时需要调用的远程服务超时了(比如,用户注册成功后,需要给用户发放优惠券),可以考虑使用队列或者暂时记录到本地稍后重试。
服务调用型:比如,某个服务的全局超时时间为500ms,但我们有多处服务调用,每处服务调用的超时时间可能不一样,此时,可以简单地使用Future来解决问题,通过如Future.get(3000,TimeUnit.MILLISECONDS)来设置超时。
们使用jQuery来进行Ajax请求,可以在请求时带上timeout参数设置超时时间。
通过配置合理的超时时间,防止出现某服务的依赖服务超时时间太长且响应慢,以致自己响应慢甚至崩溃。?客户端和服务器端都应该设置超时时间,而且客户端根据场景可以设置比服务器端更长的超时时间。
如果存在多级依赖关系,如A调用B,B调用C,则超时设置应该是?A>B>C,否则可能会一直重试,引起DDoS攻击效果。
不过最终如何选择还是要看场景,有时候客户端设置的超时时间就是要比服务器端的短,可以通过在服务器端实施限流/降级等手段防止DDoS攻击。
超时之后应该有相应的策略来处理,常见的策略有重试(等一会儿再试、尝试其他分组服务、尝试其他机房服务,重试算法可考虑使用如指数退避算法)、摘掉不存活节点(负载均衡/分布式缓存场景下)、托底(返回历史数据/静态数据/缓存数据)、等待页或者错误页。
对于非幂等写服务应避免重试,或者可以考虑提前生成唯一流水号来保证写服务操作通过判断流水号来实现幂等操作。
在进行数据库/缓存服务器操作时,记得经常检查慢查询,慢查询通常是引起服务出问题的罪魁祸首。也要考虑在超时严重时,直接将该服务降级,待该服务修复后再取消降级。
对于有负载均衡的中间件,请考虑配置心跳/存活检查,而不是惰性检查。
超时重试必然导致请求响应时间增加,最坏情况下的响应时间=重试次数×单次超时时间,这很可能严重影响用户体验,导致用户不断刷新页面来重复请求,最后导致服务接收的请求太多而挂掉,因此除了控制单次超时时间,也要控制好用户能忍受的最长超时时间。
超时时间太短会导致服务调用成功率降低,超时时间太长又会导致本应成功的调用却失败了,这也要根据实际场景来选择最适合当前业务的超时时间,甚至是程序动态自动计算超时时间。比如商品详情页的库存状态服务,可以设置较短的超时时间,当超时时降级返回有货,而结算页服务就需要设置稍微长一些的超时时间保证确实有货。