一、综述
java 客户端与服务端交互过程中,采用NIO通讯是异步的,客户端基本采用同一处理范式,来进行同异步的调用处理。
处理模型有以下几个要素:
1. NIO发送消息后返回的Future
2. 每次发送请求生成的Callback ,回调对象保存有请求数据,获取数据时阻塞线程,服务端返回时唤醒被阻塞的业务线程 并返回数据操作
3. 一个Map 保存有请求id 与 callback实例。 一般 key= reqId, value= callback
4. 一个TimeChecker 超时检测线程, 用户循环检测map里面的请求是否超时,超时的数据之间删除。
以上4个要素基本构成了目前客户端与服务端异步通讯时的处理模式。 目前dubbo、一些mq 框架都采用此模式,理解这个模式对阅读源码非常重要。
二、 处理流程图
流程说明:
1. 业务线程操作
1.1 通过NIO的channel ,write数据,同时返回 future
1.2 将req与future 组成callback实例,放入reqMap
1.3 调用callbak的get() 方法。此时线程会被阻塞一定时间,等待被唤醒(持有callback的锁)。
2. 发送消息后,开始监听返回消息
2.1 网络消息received事件,会触发listenser,根据reqId从reqMap里面获取callback实例,放入线程池执行
2.2 callback实例的 回调方法,标识结果已返回,设置response, 调用notifyAll()方法,唤醒在1.3 被阻塞的线程,返回
3. TimeoutCheckerThread线程
timeoutCheckerThread 负责轮询reqMap,将超时的数据从map里面删除。超时回调删除后,1.3步骤 被阻塞的线程睡眠醒来,就会抛出超时异常
以上几个步骤与流程,就是目前通用的 client 异步操作模式, 3个独立的线程+一个Map 完成整个操作。
在dubbo、各类mq 生产端,都是如此,部分可能有所差异。例如 dubbo的超时检测,用了HashedWheelTimer,比轮询效率更高,但本质不变
三、 实例代码
代码实例在idea 中运行通过,依赖lombock插件, netty3组件,详细代码请看 git:https://github.com/xujianguo1/practise/ 下的nettydemo目录。
对于netty的具体使用不做过多解读,毕竟netty4、5 与netty3 的差异太大。
Invoker接口
packagecom.luguo.nettydemo.client.handler;importcom.luguo.nettydemo.model.RequestMsg;importcom.luguo.nettydemo.model.AckMsg;public interfaceInvoker {/*** 同步调用,直接返回消息*/
public AckMsg invokeSync(RequestMsg request) throwsException;/*** 异步调用,返回future*/
publicSimpleFuture invokeAsyc(RequestMsg requestMsg);/*** 收到消息返回时,调用。*/
public voidinvokeAck(AckMsg ackMsg);
}
Invoker 接口默认实现 DefaultInvoker ,单例
packagecom.luguo.nettydemo.client.handler;importcom.luguo.nettydemo.model.AckMsg;importcom.luguo.nettydemo.model.RequestMsg;importlombok.extern.slf4j.