---
title: "异常处理与排障"
---

先判断调用失败发生在哪一层，再决定看哪里。Nami 源码的调用链大致是：

```text
接口代理 → 解析上游 → 选择 Channel → 发请求 → Result → assertSuccess → Decoder
```

### 1、 常见异常类型

| 阶段 | 常见现象 | 首查内容 |
|---|---|---|
| 上游解析 | 找不到服务实例、upstream 为 null | name、group、Discovery/LoadBalance |
| Channel | `There is no channel available` | URL scheme、`nami-channel-*` |
| 连接 | 连接拒绝、连接超时 | 服务是否启动、端口、网络、防火墙 |
| 响应 | `NamiResponseException` | HTTP 状态码、服务端路由和业务日志 |
| 解码 | `NamiDecodeException` 或字段转换失败 | Content-Type、coder/serialization、DTO |
| 业务 | 返回的异常被包装 | 服务端异常堆栈、接口语义和幂等性 |

### 2、 处理 HTTP 错误

`Result.assertSuccess()` 在状态码 `>= 400` 时抛出 `NamiResponseException`。它包含状态码和响应描述：

```java
import org.noear.nami.exception.NamiResponseException;

try {
    User user = userService.getById(1L);
} catch (NamiResponseException e) {
    int status = e.getCode();
    String description = e.getDescription();
    // 记录 status、description、接口和 traceId
}
```

响应体被读取为字符串后会被清理，排查时不要反复假设还能读取同一 body。连接、配置和通道问题通常是 `NamiException` 或带 cause 的包装异常，日志应保留完整异常链。

### 3、 固定 URL 优先排查

推荐严格按以下顺序恢复功能：

1. 服务端是否以 `Solon.start` 启动，端口是否监听。
2. 用固定 URL 调用，暂时去掉服务发现和 LoadBalance。
3. 对照 `@Mapping` 检查完整路径、尾斜杠和 HTTP 方法。
4. 检查请求参数：query、表单、`@NamiBody`/`@Body` 是否一致。
5. 检查 `http/https/tcp/ws` 与已注册 channel 是否一致。
6. 检查 Content-Type、Accept 以及双方编解码组件。
7. 最后恢复发现、过滤器和自定义负载策略。

### 4、 一个实用的最小日志字段

```text
traceId, service/group, resolved upstream, path, action, timeout, cost, status
```

不要记录 Token、Cookie、完整个人信息和大请求体。过滤器中可以统计耗时，但必须确保最终调用 `inv.invoke()`：

```java
Filter filter = inv -> {
    long start = System.currentTimeMillis();
    try {
        return inv.invoke();
    } finally {
        LOG.debug("url=" + inv.url + ", cost="
                + (System.currentTimeMillis() - start));
    }
};
```

### 5、 超时与重试

超时只说明客户端在期限内没有拿到结果，不代表服务端没有执行。对写操作盲目重试可能产生重复数据；重试前要有幂等键或明确的幂等设计。服务发现异常也不要通过无限重试掩盖配置错误。
