中 进阶
gRPC拦截器与错误处理#
一句话答案#
gRPC 拦截器分客户端/服务端两侧,类似 Spring 的 AOP 切面,用于统一处理日志、认证、限流、链路追踪等横切关注点;错误处理通过 Status Code + Metadata 传递结构化错误信息。
核心要点
拦截器分类#
客户端拦截器(ClientInterceptor):
请求前 → 添加 Token/TraceId/Metadata → 发送 → 响应后 → 记录耗时/错误
服务端拦截器(ServerInterceptor):
收到请求 → 鉴权/限流/日志 → 转发给 Service 实现 → 返回前 → 后处理plaintext服务端拦截器示例#
public class AuthInterceptor implements ServerInterceptor {
@Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
ServerCall<ReqT, RespT> call,
Metadata headers,
ServerCallHandler<ReqT, RespT> next) {
String token = headers.get(Metadata.Key.of("authorization", ASCII_STRING_MARSHALLER));
if (!validateToken(token)) {
call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), new Metadata());
return new ServerCall.Listener<>() {}; // 空实现,不再处理
}
return next.startCall(call, headers); // 放行
}
}
// 注册
Server server = ServerBuilder.forPort(9090)
.addService(ServerInterceptors.intercept(orderService, new AuthInterceptor()))
.build();java客户端拦截器示例#
public class TracingInterceptor implements ClientInterceptor {
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method,
CallOptions callOptions,
Channel next) {
return new ForwardingClientCall.SimpleForwardingClientCall<>(
next.newCall(method, callOptions)) {
@Override
public void start(Listener<RespT> listener, Metadata headers) {
headers.put(TRACE_ID_KEY, TraceContext.getCurrentTraceId());
super.start(listener, headers);
}
};
}
}javagRPC Status Code#
| Code | 含义 | 对应场景 |
|---|---|---|
| OK (0) | 成功 | - |
| CANCELLED (1) | 客户端取消 | 超时取消 |
| INVALID_ARGUMENT (3) | 参数错误 | 校验失败 |
| NOT_FOUND (5) | 资源不存在 | 查询空结果 |
| PERMISSION_DENIED (7) | 无权限 | RBAC 拒绝 |
| UNAUTHENTICATED (16) | 未认证 | Token 无效/过期 |
| RESOURCE_EXHAUSTED (8) | 资源耗尽 | 限流 |
| INTERNAL (13) | 内部错误 | 未知异常 |
| UNAVAILABLE (14) | 服务不可用 | 熔断/宕机 |
| DEADLINE_EXCEEDED (4) | 超时 | deadline 到期 |
结构化错误传递#
// 服务端返回详细错误
Status status = Status.INVALID_ARGUMENT
.withDescription("order_id must be positive");
Metadata trailers = new Metadata();
trailers.put(ERROR_DETAIL_KEY, "{\"field\":\"order_id\",\"reason\":\"negative\"}");
responseObserver.onError(status.asRuntimeException(trailers));
// 客户端解析
try {
stub.getOrder(request);
} catch (StatusRuntimeException e) {
Status status = e.getStatus();
Metadata trailers = e.getTrailers();
// 根据 status.getCode() 做不同处理
}java拦截器链执行顺序#
客户端: Interceptor1 → Interceptor2 → ... → 发送请求
服务端: Interceptor1 → Interceptor2 → ... → Service 实现
(先注册的先执行)plaintext面试回答(2分钟版)
gRPC 拦截器分客户端和服务端两侧,类似 Spring AOP 的切面机制。服务端 ServerInterceptor 拦截请求做鉴权、限流、日志;客户端 ClientInterceptor 在请求前注入 TraceId、Token 等 Metadata,响应后记录耗时。多个拦截器按注册顺序形成链。错误处理方面,gRPC 定义了 16 种 Status Code,比如 UNAUTHENTICATED 未认证、RESOURCE_EXHAUSTED 限流、DEADLINE_EXCEEDED 超时。服务端通过 Status + Metadata 返回结构化错误,客户端 catch StatusRuntimeException 按 Code 分类处理。实践中常用拦截器实现:JWT 鉴权拦截器、Sentinel 限流拦截器、OpenTelemetry 链路追踪拦截器。和 Spring 拦截器的区别是 gRPC 拦截器工作在 RPC 层而非 HTTP 层,粒度更细(可以拦截 Streaming 的每条消息)。
追问与易错
追问方向:
- “拦截器和 Spring AOP 什么关系?”→ 独立的,gRPC 拦截器在 RPC 层,Spring AOP 在 Bean 方法层
- “怎么做全局异常处理?”→ 服务端拦截器 catch 所有异常,统一转为 gRPC Status 返回
- “Deadline 怎么传播?”→ 客户端设置 deadline,通过 Metadata 传到下游服务,每一跳检查剩余时间
易错点:
- ❌ “gRPC 用 HTTP 状态码”——用自己的 16 种 Status Code,和 HTTP 200/404 无关
- ❌ “拦截器能修改请求体”——只能操作 Metadata(header),不能改 message body
- ❌ “UNAVAILABLE 就是服务挂了”——也可能是限流或负载均衡无可用节点