常识来了
白蓝主题五 · 清爽阅读
首页  > 软件进阶

微服务异常处理:别让一个小毛病拖垮整个系统

你有没有遇到过这种情况:线上系统突然卡住,用户投诉不断,查了半天发现只是一个微服务返回了个空指针?

微服务架构把一个大应用拆成多个小模块,各自独立部署、通信协作。好处是灵活、可扩展,坏处也很明显——一个服务“感冒”,其他服务可能跟着“发烧”。这时候,异常处理就成了关键。

异常不该被“吞掉”

很多开发者习惯在 catch 块里写个 log.info() 就完事,以为万事大吉。可问题是,上游服务根本不知道下游出了问题,继续往下走,结果数据错乱、流程中断,排查起来像在迷宫里打转。

比如订单服务调用库存服务减库存,库存服务因为数据库连接超时返回了 null,但没抛出明确异常,订单服务却当成成功处理,用户买了东西,库存没扣,等到盘点才发现少了货——这锅谁背?

统一异常响应格式

每个服务返回的错误信息五花八门,有的是字符串,有的是对象,还有的直接返回 500。调用方解析起来头疼。

建议所有微服务使用统一的错误结构:

{
  "code": "SERVICE_002",
  "message": "库存不足,无法完成下单",
  "timestamp": "2024-03-15T10:30:00Z",
  "traceId": "a1b2c3d4e5"
}

这样前端或网关能快速识别错误类型,用户也能看到友好提示,而不是一个冷冰冰的“系统错误”。

别忘了超时和重试

网络不是铁板一块。A 服务调 B 服务,B 慢悠悠处理了 10 秒才返回,A 的线程就被占着,请求一多,整个服务就卡死了。

合理设置超时时间很重要。比如 Feign 调用可以这样配置:

feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 3000

同时配合重试机制,对幂等操作(比如查询)可以自动再试一次,提升系统韧性。

熔断与降级:给系统装个保险

如果某个服务已经连续失败 10 次,你还一次次发请求,只会让调用方也跟着挂。这时候该断则断。

像 Hystrix 或 Sentinel 这类工具,可以在错误率超过阈值时自动熔断,后续请求直接返回默认值或缓存数据。比如用户详情服务挂了,首页暂时显示“加载中”或本地缓存头像,至少页面还能用。

链路追踪不能少

异常发生时,最怕的就是“知道出事,但不知道哪出的事”。引入 traceId,在每个请求开始时生成唯一标识,贯穿所有服务调用。

日志里都带上这个 ID,出问题后直接根据 traceId 查完整调用链,从入口到故障点一目了然。不然就得登录三四台服务器,翻七八个日志文件,效率低还容易漏。

异常分类要清晰

不是所有异常都要报警。用户输错密码算异常吗?算。但这是业务逻辑的一部分,不该触发告警。

可以把异常分成三类:

  • 业务异常:如账户不存在、余额不足,属于正常流程,记录日志即可;
  • 系统异常:空指针、数组越界,属于 bug,必须报警;
  • 外部异常:数据库断连、第三方接口超时,需要监控趋势,判断是否扩容或联系合作方。

分清楚哪些是“可控的意外”,哪些是“真正的事故”,运维才能睡个安稳觉。