目录
前言
Dubbo 的底层通信默认是使用的Netty来通信的, consumer和provider建立的是tcp连接,双方都可以互相通信。
那么就有一个问题, consumer可能发送很多的请求后, 然后又接受到了很多的provider的响应, 他是如何把Request和Response关联起来的呢?
Dubbo 主要是依赖DefaultFuture
这个类, DefaultFuture
继承自CompletableFuture
, 并且每个Request都有一个唯一的id。 在响应中会带上这个id的。 Dubbo就是根据这个id找到对应的DefaultFuture
的。
DefaultFuture继承自 CompletableFuture
CompletableFuture类实现了CompletionStage和Future接口,
- Future接口是用来获取异步结果的接口类。
- CompletionStage接口是 java提供的方便进行链式调用的接口,定义了很多的方便使用的方法
而Dubbo就是使用Future的功能实现的请求和响应的对应的。
看下构造方法
private DefaultFuture(Channel channel, Request request, int timeout) {
this.channel = channel;
this.request = request;
this.id = request.getId();
this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
// put into waiting map.
FUTURES.put(id, this);
CHANNELS.put(id, channel);
}
public static DefaultFuture newFuture(Channel channel, Request request, int timeout, ExecutorService executor) {
final DefaultFuture future = new DefaultFuture(channel, request, timeout);
future.setExecutor(executor);
// ThreadlessExecutor needs to hold the waiting future in case of circuit return.
if (executor instanceof ThreadlessExecutor) {
((ThreadlessExecutor) executor).setWaitingFuture(future);
}
// timeout check
timeoutCheck(future);
return future;
}
- 构造方法有Request ,Channel和超时时间timeout 三个参数。 其中Request内部含有请求的参数和请求的唯一id
- Channel 是netty的中的通道, 所以每队QA都与对应的Channel关联在一起
- 构造方法是私有的, 真正的创建对象的方法是静态的newFuture方法。 在newFuture内部会启动定时定人任务,但是检查请求是否超时
定时任务检查是否请求超时
在新建DefaultFuture的时候就会启动定时任务检查超时与否的
/**
* check time out of the future
*/
private static void timeoutCheck(DefaultFuture future) {
TimeoutCheckTask task = new TimeoutCheckTask(future.getId());
future.timeoutCheckTask = TIME_OUT_TIMER.newTimeout(task, future.getTimeout(), TimeUnit.MILLISECONDS);
}
/***************************** 看下newTimeout方法 ********************************/
@Override
public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
if (task == null) {
throw new NullPointerException("task");
}
if (unit == null) {
throw new NullPointerException("unit");
}
long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
pendingTimeouts.decrementAndGet();
throw new RejectedExecutionException("Number of pending timeouts ("
+ pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
+ "timeouts (" + maxPendingTimeouts + ")");
}
//启动的是DefaultFuture中静态对象TIME_OUT_TIMER中的线程, 内部只会启动一次,启动会CAS检查状态的,如果线程已启动了,就会跳过的
start();
// Add the timeout to the timeout queue which will be processed on the next tick.
// During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.
long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
// Guard against overflow.
if (delay > 0 && deadline < 0) {
deadline = Long.MAX_VALUE;
}
//构建一个定时任务的对象放入timeouts中, timeouts不是静态字段,
HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
timeouts.add(timeout);
return timeout;
}
- 创建定时任务最后都是交给静态的TIME_OUT_TIMER中来执行的,而TIME_OUT_TIMER是Dubbo自己实现的HashedWheelTimer对象
- 其中newTimeout方法把任务和间隔参数传入。 并在这个方法中启动线程。 注意一个HashedWheelTimer对象内部只会维护一个线程, 并且最多支持512个请求的检查
HashedWheelTimer定时任务实现类
这个类是Netty的实现类,Dubbo直接拿来使用了,我之前没有细看,直接进入分析了。
这个类全部是Dubbo自己实现的 , 内部尽然是自己创建线程来实现的定时的效果, 和我预想的使用java 提供的Executors.newScheduledThreadPool()
类不一样,我们看下内部实现。
构造方法
public HashedWheelTimer(
ThreadFactory threadFactory,
long tickDuration, TimeUnit unit, int ticksPerWheel,
long maxPendingTimeouts) {
if (threadFactory == null) {
throw new NullPointerException("threadFactory");
}
if (unit == null) {
throw new NullPointerException("unit");
}
if (tickDuration <= 0) {
throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration);
}
if (ticksPerWheel <= 0) {
throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel);
}
// Normalize ticksPerWheel to power of two and initialize the wheel.
wheel = createWheel(ticksPerWheel);
mask = wheel.length - 1;
// Convert tickDuration to nanos.
//设置纳秒级别的间隔时间
this.tickDuration = unit.toNanos(tickDuration);
// Prevent overflow.
//校验定时任务的数量是否上限
if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
throw new IllegalArgumentException(String.format(
"tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
tickDuration, Long.MAX_VALUE / wheel.length));
}
//新建一个线程
workerThread = threadFactory.newThread(worker);
//设置最大阻塞时间
this.maxPendingTimeouts = maxPendingTimeouts;
//静态全局对象计数, 并判断是否需要更多的
if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
reportTooManyInstances();
}
}
Worker 定时任务线程的Run方法
private final class Worker implements Runnable {
private final Set<Timeout> unprocessedTimeouts = new HashSet<Timeout>();
private long tick;
@Override
public void run() {
// Initialize the startTime.
startTime = System.nanoTime();
if (startTime == 0) {
// We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
startTime = 1;
}
// Notify the other threads waiting for the initialization at start().
startTimeInitialized.countDown();
do {
final long deadline = waitForNextTick();
if (deadline > 0) {
int idx = (int) (tick & mask);
processCancelledTasks();
HashedWheelBucket bucket =
wheel[idx];
transferTimeoutsToBuckets();
bucket.expireTimeouts(deadline);
tick++;
}
} while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
// Fill the unprocessedTimeouts so we can return them from stop() method.
for (HashedWheelBucket bucket : wheel) {
bucket.clearTimeouts(unprocessedTimeouts);
}
for (; ; ) {
HashedWheelTimeout timeout = timeouts.poll();
if (timeout == null) {
break;
}
if (!timeout.isCancelled()) {
unprocessedTimeouts.add(timeout);
}
}
processCancelledTasks();
}
- 可以看到内部是使用do while无限循环来检查超时时间是否已经超时,超时的修改workerState字段状态, 并使用CAS操作来判断workerState字段来终结循环
DefaultFuture中有个静态HashedWheelTimer对象, 内部维护一个线程, 线程中无限循环来判断每个请求是否超时。
当然内部还有很多的检查超时的细节, 但是经历有限, 先弄明白大致, 细节再说。
看到内部的定时任务最多会维护512个请求,也就是说Dubbo最多同时支持512个请求等待响应。 这是我的理解, 不对的请指正。
DefaultFuture的使用 — 发送请求时创建
org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int, java.util.concurrent.ExecutorService)
@Override
public CompletableFuture<Object> request(Object request, int timeout, ExecutorService executor) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = DefaultFuture.newFuture(channel, req, timeout, executor);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
- 这个方法先构建Request 对象, 再构建DefaultFuture 对象,最后通过channel.send(req);将请求发送出去的, 这里是请求的主要流程。
DefaultFuture的使用 — 接收响应时的处理
org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#handleResponse
这个是处理响应的静态方法。
static void handleResponse(Channel channel, Response response) throws RemotingException {
if (response != null && !response.isHeartbeat()) {
DefaultFuture.received(channel, response);
}
}
方法中就是调用DefaultFuture的静态received方法, 在这个静态方法中,会从静态ConcurrentHashMap中获取到具体的DefaultFuture对象,在调用DefaultFuture对象将响应设置进去,
看下DefaultFuture的doReceived方法, 就是调用complete方法来完成的, 而complete是CompletableFuture的方法, 内部会唤醒阻塞队列,告诉阻塞队列结果已经设置进Future了。
private void doReceived(Response res) {
if (res == null) {
throw new IllegalStateException("response cannot be null");
}
if (res.getStatus() == Response.OK) {
this.complete(res.getResult());
} else if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
this.completeExceptionally(new TimeoutException(res.getStatus() == Response.SERVER_TIMEOUT, channel, res.getErrorMessage()));
} else {
this.completeExceptionally(new RemotingException(channel, res.getErrorMessage()));
}
// the result is returning, but the caller thread may still waiting
// to avoid endless waiting for whatever reason, notify caller thread to return.
if (executor != null && executor instanceof ThreadlessExecutor) {
ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) executor;
if (threadlessExecutor.isWaiting()) {
threadlessExecutor.notifyReturn(new IllegalStateException("The result has returned, but the biz thread is still waiting" +
" which is not an expected state, interrupt the thread manually by returning an exception."));
}
}
}
总结
其实整个DefaultFuture的逻辑并不负责, 器内部就是维护一个线程, 循环检查512个长度数组中的请求是否超时而已, 但是其如何和Dubbo的发送和接收响应相结合的, 这个过程就有点复杂,我在这篇博客中并没有详细的说清楚, 那是因为我能力有限, 并且我也是在边看Dubbo的源码边学习中, 期待后面会弄懂原理的。
加油