Dubbo源码解析

https://www.cnblogs.com/sunshine-2015/category/1132847.html

下图展示了dubbo框架的主要接口和类,以及之间的关系和调用方法名。请看官网的详细解释,还有时序图 调用图

http://dubbo.apache.org/zh-cn/docs/dev/design.html

消费者调用

请结合官网调用源码导读http://dubbo.apache.org/zh-cn/docs/source_code_guide/service-invoking-process.html

下面介绍消费者调用流程中一些重要的方法。

dubbo接口方法调用后,首先使用动态代理方式执行com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke方法,如果设置Failover容错机制,则向下执行最终走到com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke方法,doInvoke中执行方法调用成功返回Result,失败重试

//com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke

@Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();
        LoadBalance loadbalance = null;
        //根据invocation匹配可被调用的providers
        List<Invoker<T>> invokers = list(invocation);
        if (invokers != null && !invokers.isEmpty()) {
            //取得loadbalance 
            loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
                    .getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
        }
        //如果异步则attachment添加InvocationId,注意此id不是requestid
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);
    }

此处的invokers 包含zk中当前方法可以调用的所有服务提供者provider信息和dubbo的配置参数。测试时只启动了一个Provider服务,所以下图invokers 中只有一个invoker。

 根据配置的retries次数,开始for循环调用,每次循环通过负载均衡机制选出目标provider(invoker)

//com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        //可被调用的providers
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);
        //重试次数
        int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop. 循环重试
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                //本次为重试
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }

            //执行负载均衡 路由provider
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);            
            //记入已调用provider
            invoked.add(invoker);

            //将已调用provider放入RpcContext
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                //调用provider(执行到com.alibaba.dubbo.rpc.filter.ConsumerContextFilter#invoke)
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                    logger.warn("Although retry the method " + invocation.getMethodName()
                            + " in the service " + getInterface().getName()
                            + " was successful by the provider " + invoker.getUrl().getAddress()
                            + ", but there have been failed providers " + providers
                            + " (" + providers.size() + "/" + copyinvokers.size()
                            + ") from the registry " + directory.getUrl().getAddress()
                            + " on the consumer " + NetUtils.getLocalHost()
                            + " using the dubbo version " + Version.getVersion() + ". Last error is: "
                            + le.getMessage(), le);
                }
                return result;
            } catch (RpcException e) {
                if (e.isBiz()) { // biz exception.
                    throw e;
                }
                le = e;
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {
                providers.add(invoker.getUrl().getAddress());
            }
        }
        throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
                + invocation.getMethodName() + " in the service " + getInterface().getName()
                + ". Tried " + len + " times of the providers " + providers
                + " (" + providers.size() + "/" + copyinvokers.size()
                + ") from the registry " + directory.getUrl().getAddress()
                + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
                + Version.getVersion() + ". Last error is: "
                + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
    }

负载均衡(以加权Random为例)

官网源码导读 http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

//com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // Number of invokers
        int totalWeight = 0; // The sum of weights
        boolean sameWeight = true; // Every invoker has the same weight?
        //计算所有invokers总权重
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // Sum
            
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                //如果与前一个invoker权重不同 
                sameWeight = false;
            }
        }

        //如果权重不同
        if (totalWeight > 0 && !sameWeight) {            
            //取小于总权重的随机数 做偏移量
            int offset = random.nextInt(totalWeight);

            //遍历invokers,offset-其权重,直到offset小于零返回invoker
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(random.nextInt(length));
    }

 RpcContext装配信息,RpcInvocation装配invoker

//com.alibaba.dubbo.rpc.filter.ConsumerContextFilter#invoke
//设置RpcContext
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            //向下调用provider
            return invoker.invoke(invocation);
        } finally {
            RpcContext.getContext().clearAttachments();
        }
    }

 检验当前方法并发调用量active(AtomicLong)   ,开始计时,累计并调用发量active 

//com.alibaba.dubbo.rpc.filter.ActiveLimitFilter#invoke

public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        URL url = invoker.getUrl();
        String methodName = invocation.getMethodName();
        //dubbo配置ACTIVES
        int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
        //获取当前调用方法的RpcStatus (com.alibaba.dubbo.rpc.RpcStatus#getStatus)
        RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());

        if (max > 0) { //配置了active
             //dubbo配置timeout 
            long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
            //计时
            long start = System.currentTimeMillis();
            long remain = timeout;
            int active = count.getActive();
            if (active >= max) {
                //当前并发数active已超dubbo配置
                //syn 当前方法RpcStatus ,阻塞其他线程
                synchronized (count) {    
                    //使用锁为了让线程wait (因为wait必须持锁)               
                    while ((active = count.getActive()) >= max) {
                        //当前active 超过配置
                        try {
                            //当前线程挂起并释放锁,等待被执行完的线程唤醒 或 自然醒
                            count.wait(remain);
                            //wait醒来后必须循环 再次判断条件
                        } catch (InterruptedException e) {
                        }
                        long elapsed = System.currentTimeMillis() - start;
                        remain = timeout - elapsed;
                        if (remain <= 0) {
                            throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                    + invoker.getInterface().getName() + ", method: "
                                    + invocation.getMethodName() + ", elapsed: " + elapsed
                                    + ", timeout: " + timeout + ". concurrent invokes: " + active
                                    + ". max concurrent invoke limit: " + max);
                        }
                    }
                }
            }
        }
        //执行到这里 1 未配置active 2 未到达active限制
        try {
            //调用计时开始
            long begin = System.currentTimeMillis();
            //url 和 method 对应的RpcStatus都active + 1
            RpcStatus.beginCount(url, methodName);
            try {
                //继续向下调用(到com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter#invoke)
                Result result = invoker.invoke(invocation);
                //RpcStatus active - 1
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
                return result;
            } catch (RuntimeException t) {
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
                throw t;
            }
        } finally {
            if (max > 0) {
                synchronized (count) {
                    count.notify();
                }
            }
        }
    }
//com.alibaba.dubbo.rpc.RpcStatus#getStatus
//取得当前方法的RpcStatus
public static RpcStatus getStatus(URL url, String methodName) {
        String uri = url.toIdentityString();
        ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.get(uri);
        if (map == null) {
            //不存在则放入
            METHOD_STATISTICS.putIfAbsent(uri, new ConcurrentHashMap<String, RpcStatus>());
            map = METHOD_STATISTICS.get(uri);
        }
        RpcStatus status = map.get(methodName);
        if (status == null) {
            //不存在则放入map
            map.putIfAbsent(methodName, new RpcStatus());
            //再从map中取出,保证并发安全
            status = map.get(methodName);
        }
        return status;
}

针对 无返回、异步、同步调用进行不同处理

//com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker#doInvoke

protected Result doInvoke(final Invocation invocation) throws Throwable {
        //装填invocation
        RpcInvocation inv = (RpcInvocation) invocation;
        final String methodName = RpcUtils.getMethodName(invocation);
        inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
        inv.setAttachment(Constants.VERSION_KEY, version);

        //选择NettyClient
        ExchangeClient currentClient;
        if (clients.length == 1) {
            currentClient = clients[0];
        } else {
            currentClient = clients[index.getAndIncrement() % clients.length];
        }

        try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);//需要Return?
            int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
            if (isOneway) {
                boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                currentClient.send(inv, isSent);
                RpcContext.getContext().setFuture(null);
                return new RpcResult();
            } else if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout);
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                return new RpcResult();
            } else {
                RpcContext.getContext().setFuture(null);
                //request返回ResponseFuture(DefaultFuture),get()挂起当前线程,等待被接收到响应的线程唤醒
                return (Result) currentClient.request(inv, timeout).get();
            }
        } catch (TimeoutException e) {
            throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        } catch (RemotingException e) {
            throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

 attachments中携带配置信息和自定义KV,传递给服务提供者

Netty客户端列表 

 创建DefaultFuture,Netty发送调用信息,返回DefaultFuture。

//com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int)

public ResponseFuture request(Object request, int timeout) throws RemotingException {
        if (closed) {
            throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
        }
        // create request.
        //Request构造器中分配请求ID(static AtomicLong INVOKE_ID)
        Request req = new Request();
        req.setVersion("2.0.0");
        req.setTwoWay(true);
        req.setData(request);

        //为请求request创建future
        DefaultFuture future = new DefaultFuture(channel, req, timeout);
        try {
            //Netty发送
            channel.send(req);
        } catch (RemotingException e) {
            future.cancel();
            throw e;
        }
        return future;
    }

 

DefaultFuture

每个请求/线程对应一个DefaultFuture,这个类对象是挂起/唤醒请求线程的关键。请求线程发出请求后,调用自己对应的defaultFuture.get()方法挂起等待响应。NettyIO线程接收到响应信息后,根据请求id在FUTURES缓存中找到对应请求的defaultFuture,唤醒其请求线程。

下面是几个主要方法

/**
==================================DefaultFuture类静态内容=========================================
类中包含两个静态容器,一个守护线程扫描响应超时的请求。
*/

//缓存已发出请求的channel <请求ID, Channel>
private static final Map<Long, Channel> CHANNELS = new ConcurrentHashMap<Long, Channel>();

//缓存已发出请求的Future <请求ID, DefaultFuture>
private static final Map<Long, DefaultFuture> FUTURES = new ConcurrentHashMap<Long, DefaultFuture>();

static {
        //从Thread名可以推断,这个守护线程工作是死循环扫描FUTURES,对响应超时的请求doReceived(timeOutResponse)
        Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
        th.setDaemon(true);
        th.start();
    }

/**
==================================DefaultFuture非静态内容=========================================
*/
//一个线程一次请求实例化一个defaultFuture   
//成员变量 
private final long id; // invoke id.
private final Channel channel;
private final Request request;
private final int timeout;
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
private final long start = System.currentTimeMillis();
private volatile long sent;
private volatile Response response;
private volatile ResponseCallback callback;

//DefaultFuture构造器 
public DefaultFuture(Channel channel, Request request, int timeout) {
        this.channel = channel;
        this.request = request;
        //取得请求ID
        this.id = request.getId(); 
        //消费端没有设置超时,则取得ZK中提供者配置
        this.timeout = timeout > 0 ? timeout : channel.getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
        // put into waiting map.
        FUTURES.put(id, this);
        CHANNELS.put(id, channel);
}


//获取结果/挂起当前线程
public Object get(int timeout) throws RemotingException {
        if (timeout <= 0) {
            timeout = Constants.DEFAULT_TIMEOUT;
        }
        if (!isDone()) {
            long start = System.currentTimeMillis();

//=================================Dubbo中实现请求线程挂起的方式begin==================================================
/**
主要利用的是lock.newCondition().await(timeout)实现挂起。这种思路在dubbo多处源码可见。
此处因为defaultFuture对象是每个请求相互独立,且此处lock也是defaultFuture的非静态成员变量。
理论上一个线程一次请求一个defaultFuture,只会有请求/响应线程持有lock,所以不会出现大量线程竞争lock的现象。所以性能上不会有影响。
new Countdownlacth(1)可以作为替换方案
*/
            lock.lock();
            try {
                while (!isDone()) {
                    //done = lock.newCondition();
                    //释放lock + 请求线程挂起timeout时间,等待唤醒  (必须持有lock ,才能执行done.await)
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    /*
                      醒来后 判断超时/响应 ? 继续while
                      情况1:被唤醒,向下执行break。提前被响应线程唤醒
                      情况2:自然醒,向下执行break。timeout超时
                    */
                    if (isDone() || System.currentTimeMillis() - start > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
//=================================Dubbo中实现请求线程挂起的方式end==================================================

            if (!isDone()) {
                throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
            }
        }
        return returnFromResponse();
}

//NettyIO线程输入流中会调用收到方法
//在IDEA中向上搜索方法引用 ,最终找到dubbo的NettyIOHandler
//com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#handleResponse
private void doReceived(Response res) {

//=================================Dubbo中实现请求线程唤醒的方式begin==================================================
        lock.lock();
        try {
            response = res;
            if (done != null) {
                //接收响应的线程在此唤醒请求线程
                done.signal();
            }
        } finally {
            lock.unlock();
        }
//=================================Dubbo中实现请求线程唤醒的方式end==================================================

        if (callback != null) {
            //若设置了回调对象 执行回调
            invokeCallback(callback);
        }
}

 

posted @ 2018-12-20 22:42  sw008  阅读(286)  评论(0)    收藏  举报