从dubbo源码了解RPC原理

1、RPC定义与原理

前面我们介绍了如何结合Dubbo+Zookeeper+SSM搭建一个简单的Web项目,Consumer可以在远程调用Provider的服务,这个调用的过程即RPC(Remote Procedure Call)——远程过程调用,具体是如何实现的呢?今天,我们就从原理出发,通过源码来了解这个调用过程到底发生了什么。

1.1 RPC定义

RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。

1.2 工作原理示意图

(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;

(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);

(3) 客户端通过sockets将消息发送到服务端;

(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);

(5) 服务端存根( server stub)根据解码结果调用本地的服务;

(6) 本地服务执行并将结果返回给服务端存根( server stub);

(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);

(8) 服务端(server)通过sockets将消息发送到客户端;

(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);

(10) 客户端(client)得到最终结果。

(上述部分节选自百度百科)RPC的实现包括socket和netty两种,二者都是在传输层实现Consumer与Provider的通信,在这个过程中,我们会产生以下几个问题:

  • Consumer和Provider之间如何通讯?
  • 如何寻址?Consumer在需要服务的时候,如何知道去哪儿找服务呢(服务部署在哪儿)?
  • 通信中的消息协议如何设计?

2、Dubbo中的RPC实现

          如图所示,dubbo的RPC调用模型分为registry、provider、consumer、monitor这几个部分。此图展示了从服务注册、发现、调用的全过程,但dubbo是如何做到的呢?其实这个问题包括了以下几个问题:provider如何注册服务到zookeeper;consumer如何从zookeeper拉取provider信息;provider变化以后,zookeeper如何告知consumer;consumer如何调用provider。

接下来,我们通过debug从源码来分析这每一步是如何实现的。

2.1 Provider将服务暴露给Zookeeper

服务暴露开始于ServiceBean的afterPropertiesSet方法,此方法在ServiceBean的所有属性都被赋值以后被BeanFactory调用,接着从ServiceConfig类中的export()出发,调用至doExprotUrlsFor1Protocal(),由于这个方法太长就不贴出来了,主要做了以下两件事:

1、将url注册到zookeeper,调用至 AbstractZookeeperClient类中的create()方法:

在这里将url注册到zookeeper中,并创建zkclient节点,这里的ephemeral和persistent分别代表zookeeper的两种节点类型,短暂和持久节点。

2、将Provider提供的服务信息存储至exporters(暴露的服务列表)中,核心功能在以下代码段体现:

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter<?> exporter = protocol.export(invoker);
                        exporters.add(exporter);

从debug中可以看到它究竟存储了些什么:

包括我们的注册地址,暴露接口以及invoker信息.

2.2 服务管理

服务管理包括服务注册、订阅以及唤醒,在每次zkclient创建时,会调用ZookeeperRegistry的构造函数,监听reconnect()事件:

在重连过程中,所有暴露的服务都需要重新注册,进入recover()中可以看到:

protected void recover() throws Exception {
        // register
        Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
        if (! recoverRegistered.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover register url " + recoverRegistered);
            }
            for (URL url : recoverRegistered) {
                failedRegistered.add(url);
            }
        }
        // subscribe
        Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
        if (! recoverSubscribed.isEmpty()) {
            if (logger.isInfoEnabled()) {
                logger.info("Recover subscribe url " + recoverSubscribed.keySet());
            }
            for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                URL url = entry.getKey();
                for (NotifyListener listener : entry.getValue()) {
                    addFailedSubscribed(url, listener);
                }
            }
        }
    }

    然而,在recover()中,并没有实现暴露服务或者订阅服务,而只是将需要订阅和暴露的服务用failedRegistered和failedSubscribed存储起来,真正的重新注册订阅和唤醒的工作都放在retry()中:

    // 重试失败的动作
    protected void retry() {
        if (! failedRegistered.isEmpty()) {
            Set<URL> failed = new HashSet<URL>(failedRegistered);
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry register " + failed);
                }
                try {
                    for (URL url : failed) {
                        try {
                            doRegister(url);
                            failedRegistered.remove(url);
                        } catch (Throwable t) { // 忽略所有异常,等待下次重试
                            logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if(! failedUnregistered.isEmpty()) {
            Set<URL> failed = new HashSet<URL>(failedUnregistered);
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry unregister " + failed);
                }
                try {
                    for (URL url : failed) {
                        try {
                            doUnregister(url);
                            failedUnregistered.remove(url);
                        } catch (Throwable t) { // 忽略所有异常,等待下次重试
                            logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (! failedSubscribed.isEmpty()) {
            Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedSubscribed);
            for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry subscribe " + failed);
                }
                try {
                    for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                        URL url = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                doSubscribe(url, listener);
                                listeners.remove(listener);
                            } catch (Throwable t) { // 忽略所有异常,等待下次重试
                                logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (! failedUnsubscribed.isEmpty()) {
            Map<URL, Set<NotifyListener>> failed = new HashMap<URL, Set<NotifyListener>>(failedUnsubscribed);
            for (Map.Entry<URL, Set<NotifyListener>> entry : new HashMap<URL, Set<NotifyListener>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry unsubscribe " + failed);
                }
                try {
                    for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                        URL url = entry.getKey();
                        Set<NotifyListener> listeners = entry.getValue();
                        for (NotifyListener listener : listeners) {
                            try {
                                doUnsubscribe(url, listener);
                                listeners.remove(listener);
                            } catch (Throwable t) { // 忽略所有异常,等待下次重试
                                logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
        if (! failedNotified.isEmpty()) {
            Map<URL, Map<NotifyListener, List<URL>>> failed = new HashMap<URL, Map<NotifyListener, List<URL>>>(failedNotified);
            for (Map.Entry<URL, Map<NotifyListener, List<URL>>> entry : new HashMap<URL, Map<NotifyListener, List<URL>>>(failed).entrySet()) {
                if (entry.getValue() == null || entry.getValue().size() == 0) {
                    failed.remove(entry.getKey());
                }
            }
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry notify " + failed);
                }
                try {
                    for (Map<NotifyListener, List<URL>> values : failed.values()) {
                        for (Map.Entry<NotifyListener, List<URL>> entry : values.entrySet()) {
                            try {
                                NotifyListener listener = entry.getKey();
                                List<URL> urls = entry.getValue();
                                listener.notify(urls);
                                values.remove(listener);
                            } catch (Throwable t) { // 忽略所有异常,等待下次重试
                                logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                            }
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry notify " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
    }

这个方法很长,但是从注释就可以看出它在干什么了,它在重复失败的动作,即定时重新注册、订阅、唤醒需要执行这些操作的服务。

2.3、生成Wrapper类以供调用

在这里利用字节码工具,动态生成一个类根据相应的参数去调真正的实现类(因为反射很耗时),dubbo为每一个暴露的服务生成了这样一个Wrapper类,内部根据方法名用很多if去找到真正的方法进行调用,然后将接口签名和Wrapper构造成一个Map,大致过程如下:(截取部分)

public class Wrapper1 extends Wrapper {
    public static String[] pns;
    public static Map pts;
    public static String[] mns; // all method name array.
    public static String[] dmns;
    public static Class[] mts0;

    public String[] getPropertyNames() {
        return pns;
    }

    public boolean hasProperty(String n) {
        return pts.containsKey($1);
    }

    public Class getPropertyType(String n) {
        return (Class) pts.get($1);
    }

    public String[] getMethodNames() {
        return mns;
    }

    public String[] getDeclaredMethodNames() {
        return dmns;
    }

    public void setPropertyValue(Object o, String n, Object v) {
        dubbo.provider.hello.service.impl.HelloServiceImpl w;
        try {
            w = ((dubbo.provider.hello.service.impl.HelloServiceImpl) $1);
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        throw new com.alibaba.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + $2 + "\" filed or setter method in class dubbo.provider.hello.service.impl.HelloServiceImpl.");
    }

    public Object getPropertyValue(Object o, String n) {
        dubbo.provider.hello.service.impl.HelloServiceImpl w;
        try {
            w = ((dubbo.provider.hello.service.impl.HelloServiceImpl) $1);
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        throw new com.alibaba.dubbo.common.bytecode.NoSuchPropertyException("Not found property \"" + $2 + "\" filed or setter method in class dubbo.provider.hello.service.impl.HelloServiceImpl.");
    }

    public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException {
        dubbo.provider.hello.service.impl.HelloServiceImpl w;
        try {
            w = ((dubbo.provider.hello.service.impl.HelloServiceImpl) $1);
        } catch (Throwable e) {
            throw new IllegalArgumentException(e);
        }
        try {
            if ("sayHello".equals($2) && $3.length == 0) {
                w.sayHello();
                return null;
            }
        } catch (Throwable e) {
            throw new java.lang.reflect.InvocationTargetException(e);
        }
        throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \"" + $2 + "\" in class dubbo.provider.hello.service.impl.HelloServiceImpl.");
    }
}

在调用服务时,则调用 JavassistProxyFactory类中的getInvoker()方法:

 public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper类不能正确处理带$的类名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

看到这里,我们大概就明白是怎么回事了,在调用服务时,将classname、methodname、 parameterTypes、arguments这些数据传输过来,在wrapper.invokeMethod中通过动态代理技术,去找到真正调用实现服务的方法。

2.4 Consumer调用

Consumer在调用服务之前,同样要向zookeeper注册,同服务注册,调用链为RegistryProtocol#doRefer–>RegistryProtocol#doRefer–>FailbackRegistry#register–>FailbackRegistry#doRegister–>ZookeeperRegistry#doRegister–>zkClient#create,同时在dorefer()方法中注册监听器。Consumer在调用时,实质是在调用InvokerInvocationHandler中的invoker()方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        return invoker.invoke(new RpcInvocation(method, args)).recreate();
    }

接着,在invoker,invoke()中调用DubboInvoker类中的doInvoke()方法,对于consumer端而言,服务器会为每一个请求创建一个线程,因为rpc操作是一个慢动作,为了节省资源,当线程发送rpc请求后,需要让当前线程释放资源、进入等待队列,当获取到返回结果以后,再唤醒这个线程,doInvoke()核心代码如下:

 try {
            boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
            boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
            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);
                return (Result) currentClient.request(inv, timeout).get();
            }
        }

RPC请求的过程为:每一个RPC请求都有一个唯一的id,封装在future对象中,RPC请求的时候,会将此id也发送给provider;provider处理完请求后会将此id和返回结果一同返回给consumer;consumer收到返回信息以后解析出id,然后从FUTURES中找到相对应的DefaultFuture,并调用setFuture()方法,将isDone设置为true。DeFaultFuture中的get()方法如下:

   public Object get(int timeout) throws RemotingException {
        if (timeout <= 0) {
            timeout = Constants.DEFAULT_TIMEOUT;
        }
        if (!isDone()) {
            long start = System.currentTimeMillis();
            lock.lock();
            try {
                while (!isDone()) {
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    if (isDone() || System.currentTimeMillis() - start > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            if (!isDone()) {
                throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
            }
        }
        return returnFromResponse();
    }

可以看到当在超时阈值内收到返回结果则设置isDone为true,同时唤醒该线程,返回调用结果。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值