实用百科指南
霓虹主题四 · 更硬核的阅读氛围

分布式系统错误处理方案:从超时到重试的实战策略

发布时间:2025-12-12 10:18:29 阅读:291 次

分布式系统中的常见错误类型

在微服务架构普及的今天,一个用户请求往往会经过多个服务协同完成。比如你下单买咖啡,订单服务要调用库存、支付、物流三个服务。任何一个环节出问题,整个流程就卡住了。网络抖动、服务宕机、数据库锁表,这些都不是稀罕事。

和单体应用不同,分布式环境里没有“绝对可靠”的链路。你不能指望每次调用都成功,必须默认失败是常态。这时候,错误处理不是锦上添花,而是系统能跑起来的基本前提。

超时控制:别让请求无限等下去

最常见的问题是某个服务响应太慢,导致调用方线程被占满。比如支付服务卡住,订单服务几十个线程都在等,很快整个服务就不可用了。

给每个远程调用设置超时时间是最基本的操作。例如使用 HTTP 客户端时:

HttpClient.newBuilder()
    .connectTimeout(1, TimeUnit.SECONDS)
    .readTimeout(2, TimeUnit.SECONDS)
    .build();

连接超过 1 秒或读取超过 2 秒就直接放弃。虽然会损失一次请求,但能保住整体可用性。就像地铁闸机不会为一个人一直开着,超时就是系统的“自动关门”机制。

重试机制:不是所有失败都得立刻报错

网络抖动可能只持续几百毫秒,重试几次说不定就通了。但盲目重试反而会雪崩。比如服务已经彻底挂了,你还不断发请求,等于火上浇油。

合理的做法是结合指数退避。第一次失败等 100ms 重试,第二次等 200ms,第三次 400ms,逐步拉开间隔。Java 中可以用 Resilience4j 实现:

RetryConfig config = RetryConfig.custom()
    .maxAttempts(3)
    .waitDuration(Duration.ofMillis(100))
    .intervalFunction(IntervalFunction.ofExponentialBackoff())
    .build();

同时要区分错误类型。数据库唯一键冲突这种业务错误,重试一万次也没用,只有网络超时、服务不可达才适合重试。

熔断器:及时止损,防止连锁崩溃

当某个下游服务连续失败达到阈值,熔断器会直接拒绝后续请求,避免资源耗尽。这就像家里的保险丝,电流过大就自动断开。

Hystrix 是经典实现之一。配置如下:

@HystrixCommand(fallbackMethod = "getFallbackUser")
public User getUser(Long id) {
    return userService.findById(id);
}

public User getFallbackUser(Long id) {
    return new User("默认用户");
}

当失败率超过 50%,接下来的请求会直接走 fallback 方法,不再发起远程调用。过一段时间再尝试半开状态,试探服务是否恢复。

日志与追踪:出了问题要知道哪里坏了

没有清晰的日志,排查问题就像在黑屋子里找开关。每个服务都要记录关键步骤,并带上统一的请求 ID(traceId),方便跨服务串联。

比如用 MDC 记录上下文:

MDC.put("traceId", UUID.randomUUID().toString());
log.info("开始创建订单");

结合 Zipkin 或 SkyWalking 这类工具,能画出完整的调用链,一眼看出是哪个环节卡住了。

异步补偿:有些事可以回头再做

不是所有操作都必须实时完成。比如发通知失败,可以先记下来,后台任务定期扫描重发。电商系统里的对账、积分发放,很多都是靠异步补偿兜底。

消息队列在这里很管用。下单成功后发一条消息到 Kafka,通知服务消费失败也不怕,消息还在,能反复投递。

关键是设计好幂等性。同一条消息处理十次,结果也得一样。比如加积分前先查是否已到账,避免重复赠送。