🔄 项目七:请求响应同步通信(RPC基础)
项目概述
项目名称:itstack-demo-netty-2-07
核心功能:实现类似RPC的同步请求-响应通信模式
技术要点:Future模式、CountDownLatch、请求ID映射、超时控制
为什么需要这个项目?
实际问题:
- Netty默认是异步通信,无法像HTTP一样同步等待响应
- RPC框架需要同步调用(发送请求→等待响应→返回结果)
- 多个请求同时发送,响应可能乱序返回
- 需要超时控制,避免无限等待
解决方案:
- ✅ Future模式:异步转同步,阻塞等待响应
- ✅ CountDownLatch:线程阻塞和唤醒机制
- ✅ 请求ID映射:准确匹配请求和响应
- ✅ 超时控制:避免无限等待,提升系统稳定性
适用场景:
- RPC框架(Dubbo、gRPC的核心原理)
- 同步调用远程服务
- 需要等待响应结果的场景
- 分布式事务(需要确认操作结果)
核心问题
为什么需要同步通信?
普通的Socket通信是异步的(像QQ聊天,发送消息后不需要立即等待回复),但RPC框架需要同步通信(像HTTP调用,必须等待响应返回或超时)。
异步通信(Netty默认):
客户端发送消息 → 继续执行其他代码 → 某个时刻收到响应
同步通信(RPC需要):
客户端发送消息 → 阻塞等待 → 收到响应或超时 → 继续执行
核心知识点
1. Future模式实现同步等待
WriteFuture接口
public interface WriteFuture<T> extends Future<T> {
Throwable cause(); // 获取异常
void setCause(Throwable cause); // 设置异常
boolean isWriteSuccess(); // 是否发送成功
void setWriteResult(boolean result); // 设置发送结果
String requestId(); // 获取请求ID
T response(); // 获取响应
void setResponse(Response response); // 设置响应
boolean isTimeout(); // 是否超时
}
SyncWriteFuture实现类
public class SyncWriteFuture implements WriteFuture<Response> {
private CountDownLatch latch = new CountDownLatch(1); // 核心:阻塞工具
private final long begin = System.currentTimeMillis();
private long timeout;
private Response response;
private final String requestId;
private boolean writeResult;
private Throwable cause;
private boolean isTimeout = false;
// 阻塞等待响应
public Response get(long timeout, TimeUnit unit) {
if (latch.await(timeout, unit)) { // 阻塞等待
return response;
}
return null; // 超时返回null
}
// 设置响应,唤醒等待线程
public void setResponse(Response response) {
this.response = response;
latch.countDown(); // 唤醒等待的线程
}
}
CountDownLatch工作原理:
线程A:发送请求 → latch.await() → 阻塞等待
↓
线程B:收到响应 → setResponse() → latch.countDown() → 唤醒线程A
2. 请求ID映射机制
全局映射表
public class SyncWriteMap {
// 存储 requestId → Future 的映射关系
public static Map<String, WriteFuture> syncKey =
new ConcurrentHashMap<>();
}
为什么需要请求ID?
在Netty中,多个请求可能同时发送,响应返回的顺序可能与发送顺序不同。通过请求ID可以准确匹配请求和响应。
客户端发送:
请求1(ID=uuid-001) → 服务器
请求2(ID=uuid-002) → 服务器
请求3(ID=uuid-003) → 服务器
服务器响应(可能乱序):
响应2(ID=uuid-002) → 客户端(通过ID找到请求2的Future)
响应1(ID=uuid-001) → 客户端(通过ID找到请求1的Future)
响应3(ID=uuid-003) → 客户端(通过ID找到请求3的Future)
3. 完整通信流程
SyncWrite.java - 发送请求并等待响应
public class SyncWrite {
public Response writeAndSync(Channel channel, Request request,
long timeout) throws Exception {
// 1. 生成唯一请求ID
String requestId = UUID.randomUUID().toString();
request.setRequestId(requestId);
// 2. 创建Future并存入全局Map
WriteFuture<Response> future = new SyncWriteFuture(requestId);
SyncWriteMap.syncKey.put(requestId, future);
// 3. 发送请求并阻塞等待响应
Response response = doWriteAndSync(channel, request, timeout, future);
// 4. 清理映射关系
SyncWriteMap.syncKey.remove(requestId);
return response;
}
private Response doWriteAndSync(Channel channel, Request request,
long timeout, WriteFuture<Response> writeFuture) {
// 发送请求,添加监听器
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) {
writeFuture.setWriteResult(future.isSuccess());
writeFuture.setCause(future.cause());
// 发送失败,移除映射
if (!writeFuture.isWriteSuccess()) {
SyncWriteMap.syncKey.remove(writeFuture.requestId());
}
}
});
// 阻塞等待响应
Response response = writeFuture.get(timeout, TimeUnit.MILLISECONDS);
if (response == null) {
if (writeFuture.isTimeout()) {
throw new TimeoutException();
} else {
throw new Exception(writeFuture.cause());
}
}
return response;
}
}
服务端Handler - 处理请求并返回响应
@Override
public void channelRead(ChannelHandlerContext ctx, Object obj) {
Request msg = (Request) obj;
// 构建响应,保持相同的requestId(关键!)
Response response = new Response();
response.setRequestId(msg.getRequestId());
response.setParam(msg.getResult() + " 请求成功");
// 发送响应
ctx.writeAndFlush(response);
}
客户端Handler - 接收响应并唤醒等待线程
@Override
public void channelRead(ChannelHandlerContext ctx, Object obj) {
Response msg = (Response) obj;
String requestId = msg.getRequestId();
// 根据requestId找到对应的Future
SyncWriteFuture future = (SyncWriteFuture) SyncWriteMap.syncKey.get(requestId);
if (future != null) {
future.setResponse(msg); // 设置响应,唤醒等待线程
}
}
4. 完整时序图
客户端线程 Netty IO线程 服务端
| | |
|--1.生成requestId------------>| |
|--2.创建Future并存入Map------->| |
|--3.发送请求----------------->|--4.网络传输-------------->|
|--5.阻塞等待(latch.await) | |
| | |--6.处理请求
| |<--7.返回响应(带requestId)--|
| | |
|<-8.根据requestId找到Future---| |
|<-9.setResponse唤醒线程-------| |
|--10.返回结果 | |
项目总结
核心技术:
- ✅ CountDownLatch:实现线程阻塞和唤醒
- ✅ 请求ID映射:准确匹配请求和响应
- ✅ Future模式:异步转同步
- ✅ 超时控制:避免无限等待
适用场景:
- RPC框架(Dubbo、gRPC)
- 同步调用远程服务
- 需要等待响应结果的场景
关键点:
- 每个请求必须有唯一ID
- 服务端必须原样返回requestId
- 使用ConcurrentHashMap保证线程安全
- 及时清理映射关系,避免内存泄漏
5186

被折叠的 条评论
为什么被折叠?



