深入理解dubbo之服务发布源码分析

来源:互联网 发布:软件开发工程师 翻译 编辑:程序博客网 时间:2024/06/06 02:21

dubbo 是阿里开源的一个分布式服务框架,它的最大特点是按照分层的方式来架构,使各层之间充分解耦,并且它是无侵入性的,dubbo可以无缝与spring整合,更重要的是dubbo还提供了强大的容错和监控功能。 对于业务方来说,dubbo使用上手足够简单,调用过程对业务方透明,对开发人员友好。

Demo

在spring项目中添加如下pom包:

 <dependency>            <groupId>com.alibaba</groupId>            <artifactId>dubbo</artifactId>        </dependency>         <dependency>            <groupId>org.apache.zookeeper</groupId>            <artifactId>zookeeper</artifactId>        </dependency>        <dependency>            <groupId>com.github.sgroschupf</groupId>            <artifactId>zkclient</artifactId>        </dependency>

如果你的是springboot项目也可以用springboot的starter包

<dependency>            <groupId>io.dubbo.springboot</groupId>            <artifactId>spring-boot-starter-dubbo</artifactId>            <version>1.0.0</version>        </dependency>

添加配置文件:
spring-dubbo.xml

<beans xmlns="http://www.springframework.org/schema/beans"       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"       xmlns:context="http://www.springframework.org/schema/context"       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd        http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">    <dubbo:application  name="ifenqu-web"  />    <dubbo:registry protocol="zookeeper" address="${dubbo.address}"  />    <dubbo:protocol name="dubbo" port="${dubbo.port}"/>    <dubbo:consumer timeout="60000" check="false"/>    <dubbo:service interface="com.xxx.xxx.xxxService" ref="xxxService" timeout="5000"/></beans>

调用的时候也很简单

@Autowired
private xxxService xxxService;

demo 接介绍到这,今天的重点不是讲如何使用dubbo,今天的重点是说一说dubbo的架构设计。

源码分析

dubbo框架设计总共分了10层:
1. 服务接口层(Service):该层是与实际业务逻辑相关,就如上面demo配置的
<dubbo:service interface="com.xxx.xxx.xxxService" ref="xxxService" timeout="5000"/>,这个service就是业务方自己定义的接口与其实现。

  1. 配置层(Config):该层是将业务方的service信息,配置文件的信息收集起来,主要是以ServiceConfig和ReferenceConfig为中心,ServiceConfig是服务提供方的配置,当Spring启动的时候会相应的启动provider服务发布和注册的过程,主要是加入一个ServiceBean继承ServiceConfig在Spring注册。同理ReferenceConfig是consumer方的配置,当消费方启动时,会启动consumer的发现服务订阅服务的过程,当然也是使用一个ReferenceBean继承ReferenceConfig注册在spring上。
  2. 服务代理层(Proxy):对服务接口进行透明代理,生成服务的客户端和服务器端,使服务的远程调用就像在本地调用一样。默认使用JavassistProxyFactory,返回一个Invoker,Invoker则是个可执行核心实体,Invoker的invoke方法通过反射执行service方法。
  3. 服务注册层(Registry):封装服务地址的注册和发现,以服务URL为中心,基于zk。
  4. 集群层(Cluster):提供多个节点并桥接注册中心,主要负责loadBanlance、容错。
  5. 监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
  6. 远程调用层(Protocol):封装RPC调用,provider通过export方法进行暴露服务/consumer通过refer方法调用服务。而Protocol依赖的是Invoker。通过上面说的Proxy获得的Invoker,包装成Exporter。
  7. 信息交换层(Exchange):该层封装了请求响应模型,将同步转为异步,信息交换层依赖Exporter,最终将通过网络传输层接收调用请求RequestFuture和ResponseFuture。
  8. 网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
  9. 数据序列化层:该层无需多言,将数据序列化反序列化。

服务发布过程

通过上面的框架了解我们大致知道了dubbo是怎么工作的,接下来我们来通过代码来具体看看dubbo的服务发布过程,进一步理解dubbo的工作原理。
先看到demo中的spring-dubbo配置文件。这些配置文件全都会被装配成RegistryConfig,其属性如下:

public class RegistryConfig extends AbstractConfig {    private static final long serialVersionUID = 5508512956753757169L;    public static final String NO_AVAILABLE = "N/A";    // 注册中心地址    private String            address;    // 注册中心登录用户名    private String            username;    // 注册中心登录密码    private String            password;    // 注册中心缺省端口    private Integer           port;    // 注册中心协议    private String            protocol;    // 客户端实现    private String            transporter;    private String            server;    private String            client;    private String            cluster;    private String            group;    private String            version;    // 注册中心请求超时时间(毫秒)    private Integer           timeout;    // 注册中心会话超时时间(毫秒)    private Integer           session;    // 动态注册中心列表存储文件    private String            file;    // 停止时等候完成通知时间    private Integer           wait;    // 启动时检查注册中心是否存在    private Boolean           check;    // 在该注册中心上注册是动态的还是静态的服务    private Boolean           dynamic;    // 在该注册中心上服务是否暴露    private Boolean           register;    // 在该注册中心上服务是否引用    private Boolean           subscribe;    // 自定义参数    private Map<String, String> parameters;    // 是否为缺省    private Boolean             isDefault;

这些配置文件根据注册中心的个数会被装配拼接成Dubbo的URL(该url是dubbo中自定义的),该URL长这个样子:

registry://sit-zk.host:2181/com.alibaba.dubbo.registry.RegistryService?application=ifenqu-web&dubbo=2.5.3&pid=13168&registry=zookeeper&timestamp=1510828420296

看完配置信息,接下来让我们看下Service发布的核心方法:ServiceConfig类中的doExportUrls

private void doExportUrls() {        //该方法根据配置文件装配成一个URL的list        List<URL> registryURLs = loadRegistries(true);        //根据每一个协议配置来分别暴露服务        for (ProtocolConfig protocolConfig : protocols) {            doExportUrlsFor1Protocol(protocolConfig, registryURLs);        }    }

这个protocols长这个样子<dubbo:protocol name="dubbo" port="20888" id="dubbo" /> protocols也是根据配置装配出来的。接下来让我们进入doExportUrlsFor1Protocol方法看看dubbo具体是怎么样将服务暴露出去的。这个方法特别大,有将近300多行代码,但是其中大部分都是获取类似protocols的name、port、host和一些必要的上下文,代码太长就不全都贴出来了,只贴关键部分。

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) { //........省略获取上下文代码//通过interfaceClass获取要暴露服务的所有要暴露的方法String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();//.......省略非核心代码//根据上下文创建URL对象 URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);//通过proxyFactory来获取Invoker对象 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));//将invoker对象在protocol中封装成Exporter方便提供给信息交换层进行网络传输 Exporter<?> exporter = protocol.export(invoker); //将exporter添加到list中 exporters.add(exporter);

看到这里就比较明白dubbo的工作原理了doExportUrlsFor1Protocol方法,先创建URL,URL创建出来长这样dubbo://192.168.xx.63:20888/com.xxx.xxx.VehicleInfoService?anyhost=true&application=test-web&default.retries=0&dubbo=2.5.3&interface=com.xxx.xxx.VehicleInfoService&methods=get,save,update,del,list&pid=13168&revision=1.2.38&side=provider&timeout=5000&timestamp=1510829644847,是不是觉得这个URL很眼熟,没错在注册中心看到的services的providers信息就是这个,再传入url通过proxyFactory获取Invoker,再将Invoker封装成Exporter的数组,只需要将这个list提供给网络传输层组件,然后consumer执行Invoker的invoke方法就行了。让我们再看看这个proxyFactory的getInvoker方法。proxyFactory下有JDKProxyFactory和JavassistProxyFactory。官方推荐也是默认使用的是JavassistProxyFactory。因为javassist动态代理性能比JDK的高。

public class JavassistProxyFactory extends AbstractProxyFactory {    @SuppressWarnings("unchecked")    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));    }    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);            }        };    }}

可以看到使用了动态代理的方式调用了要暴露的service的方法。并且返回了Invoker对象。在dubbo的服务发布中我们可以看到,这个Invoker贯穿始终,都可以看成是一个context的作用了,让我们进Invoker里面去看看这个Invoker到底是何方神圣。

public interface Invoker<T> extends Node {    /**     * get service interface.     *      * @return service interface.     */    Class<T> getInterface();    /**     * invoke.     *      * @param invocation     * @return result     * @throws RpcException     */    Result invoke(Invocation invocation) throws RpcException;

这个Invoker就两个方法,一个getInterface,也就是要暴露的服务接口,一个就是invoke方法,这个invoke方法在AbstractProxyInvoker中是这样的:

    public Result invoke(Invocation invocation) throws RpcException {        try {        //调用doInvoke方法,返回一个Result            return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));        } catch (InvocationTargetException e) {            return new RpcResult(e.getTargetException());        } catch (Throwable e) {            throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);        }    }

其实看到JavassistProxyFactory大家就应该大概明白了这个Invoker的作用,同时这个类的名字就叫Invoker也可以猜个大概,Invoker就是调用service的方法的实体类。其中doInvoke方法已经在JavassistProxyFactory中定义了,通过反射调用要暴露的service的方法。

服务发布总结

看完源码,我们已经知道了dubbo的主要发布过程,现在我们回过头来结合dubbo的总体架构和源码的分析,总结一下dubbo服务发布。服务发布过程总共五个步骤:

  • 业务方将服务接口和实现编写定义好,添加dubbo相关配置文件。
  • Config层加载配置文件形成上下文,Config层包括:ServiceConfig、ProviderConfig、RegistryConfig等。
  • ServiceConfig根据Protocol类型,根据ProtocolConfig、ProviderConfig加载registry,根据加载的registry创建dubbo的URL。
  • 准备工作做完后ProxyFactory上场,dubbo中有两种代理方式,JDK代理和Javassist代理,默认使用Javassist代理,Proxy代理类根据dubbo配置信息获取到接口信息、通过动态代理方式将接口的所有方法交给Proxy代理类进行代理,并封装进Invoker里面。
  • 将所有需要暴露的service封装的Invoker组成一个list传给信息交换层提供给消费方进行调用。