前提:
前一篇文章简单介绍了通过动态代理完成了Client
端契约接口调用转换为发送RPC
协议请求的功能。这篇文章主要解决一个遗留的技术难题:请求-响应同步化处理。
需要的依赖如下:
JDK1.8+
Netty:4.1.44.Final
SpringBoot:2.2.2.RELEASE
简单分析Netty请求-响应的处理流程
图中已经忽略了编码解码器和其他入站出站处理器,不同颜色的线程代表完全不相同的线程,不同线程之间的处理逻辑是完全异步,也就是Netty IO
线程(n-l-g-1
)接收到Server
端的消息并且解析完成的时候,用户调用线程(u-t-1
)无法感知到解析完毕的消息包,那么这里要做的事情就是让用户调用线程(u-t-1
)获取到Netty IO
线程(n-l-g-1
)接收并且解析完成的消息包。
这里可以用一个简单的例子来说明模拟Client
端调用线程等待Netty IO
线程的处理结果再同步返回的过程。
@Slf4j
public class NettyThreadSyncTest {
@ToString
private static class ResponseFuture {
private final long beginTimestamp = System.currentTimeMillis();
@Getter
private final long timeoutMilliseconds;
@Getter
private final String requestId;
@Setter
@Getter
private volatile boolean sendRequestSucceed = false;
@Setter
@Getter
private volatile Throwable cause;
@Getter
private volatile Object response;
private final CountDownLatch latch = new CountDownLatch(1);
public ResponseFuture(String requestId, long timeoutMilliseconds) {
this.requestId = requestId;
this.timeoutMilliseconds = timeoutMilliseconds;
}
public boolean timeout() {
return System.currentTimeMillis() - beginTimestamp > timeoutMilliseconds;
}
public Object waitResponse(final long timeoutMilliseconds) throws InterruptedException {
latch.await(timeoutMilliseconds, TimeUnit.MILLISECONDS);
return response;
}
public void putResponse(Object response) throws InterruptedException {
this.response = response;
latch.countDown();
}
}
static ExecutorService REQUEST_THREAD;
static ExecutorService NETTY_IO_THREAD;
static Callable<Object> REQUEST_TASK;
static Runnable RESPONSE_TASK;
static String processBusiness(String name) {
return String.format("%s say hello!", name);
}
private static final Map<String /* request id */, ResponseFuture> RESPONSE_FUTURE_TABLE = Maps.newConcurrentMap();
@BeforeClass
public static void beforeClass() throws Exception {
String requestId = UUID.randomUUID().toString();
String requestContent = "throwable";
REQUEST_TASK = () -> {
try {
// 3秒没有得到响应认为超时
ResponseFuture responseFuture = new ResponseFuture(requestId, 3000);
RESPONSE_FUTURE_TABLE.put(requestId, responseFuture);
// 这里忽略发送请求的操作,只打印日志和模拟耗时1秒
Thread.sleep(1000);
log.info("发送请求成功,请求ID:{},请求内容:{}", requestId, requestContent);
// 更新标记属性
responseFuture.setSendRequestSucceed(true);
// 剩余2秒等待时间 - 这里只是粗略计算
return responseFuture.waitResponse(3000 - 1000);
} catch (Exception e) {
log.info("发送请求失败,请求ID:{},请求内容:{}", requestId, requestContent);
throw new RuntimeException(e);
}