本文介绍如何在自研 Netty RPC 框架中实现超时控制与重试机制。合理的超时策略可以避免调用卡死,重试机制可以提升调用成功率,在高可用系统中不可或缺。
一、为什么要有超时和重试?
RPC 是跨进程调用,失败是常态。常见问题包括:
网络延迟或丢包
对端服务故障或处理慢
请求丢失、写超时或线程池满
没有超时控制会导致:
客户端线程阻塞,资源耗尽
请求堆积,引发服务雪崩
用户体验极差,难以排查
✅ 因此,我们需要:
对每次请求设置合理的超时时间(如 3s)
请求失败时自动重试(如重试 1~3 次)
二、整体设计图
┌──────────────┐
│ RpcClient │
└────┬─────────┘
│
┌────────────▼────────────┐
│ Future/RpcResponseMap │ <── 超时控制:Future 超时失效
└────────────┬────────────┘
│
Netty Channel
│
┌─────────▼──────────┐
│ RpcServerHandler │
└────────────────────┘
三、实现超时控制(基于 Future)
请求发出后,使用 CompletableFuture 持有结果。
设置 timeout,在时间内未响应即抛出异常。
使用定时任务清理过期请求。
public class RpcClient {
private static final Map<String, CompletableFuture<RpcResponse>> FUTURE_MAP = new ConcurrentHashMap<>();
public RpcResponse send(RpcRequest request, long timeoutMillis) throws Exception {
CompletableFuture<RpcResponse> future = new CompletableFuture<>();
FUTURE_MAP.put(request.getRequestId(), future);
// 发起请求
channel.writeAndFlush(request);
// 超时处理
return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
}
public void receive(RpcResponse response) {
CompletableFuture<RpcResponse> future = FUTURE_MAP.remove(response.getRequestId());
if (future != null) {
future.complete(response);
}
}
}
四、实现重试机制
在调用失败或超时时,自动进行 N 次重试(带间隔)。
public class RpcClientWithRetry {
public RpcResponse sendWithRetry(RpcRequest req, int retryCount, long timeoutMillis) throws Exception {
for (int i = 0; i < retryCount; i++) {
try {
return rpcClient.send(req, timeoutMillis);
} catch (TimeoutException | ConnectException e) {
log.warn("调用失败,第{}次重试", i + 1);
Thread.sleep(100); // 简单退避
}
}
throw new RuntimeException("RPC 调用重试失败");
}
}
五、自动化封装
建议支持注解配置:
@RpcReference(retry = 3, timeout = 2000)
private HelloService helloService;
再在代理生成器中读取注解参数:
int retry = field.getAnnotation(RpcReference.class).retry();
long timeout = field.getAnnotation(RpcReference.class).timeout();
六、测试用例模拟超时重试
服务端代码故意 sleep:
@RpcService
public class HelloServiceImpl implements HelloService {
public String hello(String name) {
Thread.sleep(3000); // 模拟超时
return "Hi " + name;
}
}
客户端设置 timeout = 1000ms + retry = 2,观察日志:
WARN 调用失败,第1次重试
WARN 调用失败,第2次重试
ERROR 调用重试失败
七、可拓展建议
指数退避重试(Exponential Backoff)
熔断机制(见 Hystrix/Fuse)
调用监控统计重试成功率
精细化控制(按接口或服务维度配置)
八、总结
通过本篇内容,我们为 RPC 框架增强了健壮性保障机制:
✅ 自定义调用超时
✅ 请求级别自动重试
✅ 注解式参数配置
✅ 支持重试退避逻辑