你有没有遇到过这种情况:线上系统突然卡住,用户投诉不断,查了半天发现只是一个微服务返回了个空指针?
微服务架构把一个大应用拆成多个小模块,各自独立部署、通信协作。好处是灵活、可扩展,坏处也很明显——一个服务“感冒”,其他服务可能跟着“发烧”。这时候,异常处理就成了关键。
异常不该被“吞掉”
很多开发者习惯在 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,必须报警;
- 外部异常:数据库断连、第三方接口超时,需要监控趋势,判断是否扩容或联系合作方。
分清楚哪些是“可控的意外”,哪些是“真正的事故”,运维才能睡个安稳觉。