早上刚到公司,咖啡还没泡好,测试组的电话就打过来了:‘你写的那个订单查询接口又崩了,页面直接白屏!’你心里一紧,赶紧打开日志,发现一堆超时异常堆在那儿——Connection timed out。这已经是本周第三次因为网络超时导致服务挂掉了。
超时不是问题,没处理才是
很多人觉得网络超时是外部因素,程序没法控制。但其实,真正的坑往往出在对超时的应对上。比如一个常见的场景:你的服务调用支付网关,设置了默认超时时间,结果对方系统临时抖动,30秒都没响应。而你的线程池只有10个线程,全卡在这儿,新请求进不来,整个服务就僵住了。
这种情况,本质上不是网络不行,而是程序缺乏“自保能力”。
连接和读取,超时得分开设
很多开发者在配置HTTP客户端时,只设了一个timeout,以为万事大吉。实际上,连接超时(connect timeout)和读取超时(read timeout)应该分开控制。
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.readTimeout(Duration.ofSeconds(10))
.build();
连接超时设短点,快速发现网络不通;读取超时稍长,留给后端处理时间。这样即使对方慢,也不会长时间占用你的资源。
别让单点故障拖垮整条链路
微服务环境下,A调B,B调C,C一卡,B等超时,A也跟着等。最后用户刷个页面,三个服务都堆满线程,集体瘫痪。这就是典型的雪崩效应。
解决办法之一是加熔断机制。比如用Resilience4j,在调用下游服务时设置超时和熔断规则:
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.timeoutDuration(Duration.ofSeconds(3))
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.build();
一旦失败率超标,直接拒绝请求,让问题局限在局部,而不是扩散到整个系统。
日志里藏着真相
有次线上报警,服务突然大量报错。查监控网络延迟正常,但日志里全是 read timeout。深入一看,原来是数据库连接池配置了最大等待时间30秒,而应用层超时只有5秒。结果是应用已经放弃,连接还在排队,资源越积越多。
这种不匹配的超时配置,就像两个人说话对不上频道,一个说‘等你三秒’,另一个却说‘我最多能等半分钟’,最后谁都搞不清谁。
本地测试也要模拟超时
很多问题上线才暴露,是因为本地开发从不模拟网络抖动。你可以用工具像Toxiproxy,在测试环境人为注入延迟或丢包,看看程序会不会崩。
或者写个简单的拦截器,随机延迟某些请求:
public class TimeoutInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
if (Math.random() < 0.1) { // 10%概率延迟
Thread.sleep(5000);
}
return chain.proceed(chain.request());
}
}
提前发现问题,总比半夜被叫醒强。
超时时间不是越短越好
有人一听超时危险,立马把所有超时改成1秒。结果正常请求也频繁失败。超时时间要结合业务来定:查个用户信息2秒够了,跑个报表可能需要30秒。
关键是做好分级。核心接口严控超时,非关键任务可以放宽,但必须配上限,不能无限等下去。
监控得看得见“快要超时”
等到真的超时再报警,往往已经晚了。你应该监控P99响应时间趋势,比如平时200ms,突然涨到800ms,哪怕还没超时,也得警惕。
就像开车,看到前方车流变慢就得减速,不能等到追尾才踩刹车。