Dubbo的请求和响应如何关联起来的 ------- DefaultFuture 类详解

前言

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的源码边学习中, 期待后面会弄懂原理的。

加油

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值