Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

dubbo原理学习

2016-04-12

1. Dubbo原理

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单说,dubbo是个服务框架,就是个远程服务调用的分布式框架.

其核心部分包括:

  • 远程通讯:提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化以及”请求-响应”模式的信息交换方式.
  • 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,以及软件负载均衡,失败容错,地址裸游,动态配置集群支持.
  • 自动发现:基于注册中心目录服务,是服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器.

功能

  • 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需要简单的配置,没有任何API侵入
  • 负载君合以及容错机制,可以内网替代F5等硬件负载均衡器,降低成本,减少单点
  • 服务自动注册与发现,不需要写死服务提供方地址,注册中心基于接口查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者.

Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。

dubbo服务的架构

image

  • provider:暴露服务的服务提供方
  • Consumer:调用远程服务的服务消费方
  • Register:服务注册与发现的注册中心
  • Monitor:统计服务的调用次数和调用时间的监控中心
  • Container:服务运行容器

调用关系说明

  • 服务容器负责启动,加载,运行服务提供者
  • 服务提供者在启动时,向注册中心注册自己提供的服务
  • 服务消费者在启动时,向注册中心订阅自己所需要的服务
  • 注册中心返回服务提供者列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
  • 服务消费者,从提供者地址列表中,基于负载均衡算法,选择一台提供者进行调研,如果调用失败,再选择另外一台调用
  • 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心.

spring结合dubbo搭建

dubbo服务端编写

新建接口

public interface DemoService {  
    String sayHello(String name);  
} 


实现接口
public class DemoServiceImpl implements DemoService{  
  
  
    @Override  
    public String sayHello(String name) {  
        return "Hello Dubbo,Hello " + name;  
    }  
  
  
}
配置Spring启动文件
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    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  
        ">  
    <import resource="dubbo-provider.xml"></import>  
</beans>


配置服务端的dubbo-provider.xml文件

这里的bean都是通过添加bean到xml的形式添加到spring容器里面去,在实际开发项目的时候我们可以通过扫描的方式添加到spring容器中:

<?xml version="1.0" encoding="UTF-8"?>  
<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"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans          
    http://www.springframework.org/schema/beans/spring-beans.xsd          
    http://code.alibabatech.com/schema/dubbo          
    http://code.alibabatech.com/schema/dubbo/dubbo.xsd">  
   
    <!-- 提供方应用信息,用于计算依赖关系 -->  
    <dubbo:application name="hello-world-app"  />  
   
    <!-- 使用multicast广播注册中心暴露服务地址 -->  
   <!--  <dubbo:registry address="multicast://224.5.6.7:1234" /> -->  
   
    <!-- 使用zookeeper注册中心暴露服务地址 -->  
    <!-- <dubbo:registry address="zookeeper://115.28.189.59:2181" /> -->  
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />  
   
    <!-- 用dubbo协议在20880端口暴露服务 -->  
    <dubbo:protocol name="dubbo" port="20880" />  
   
    <!-- 声明需要暴露的服务接口 -->  
    <dubbo:service interface="com.znn.provider.DemoService" ref="demoService" />  
   
    <!-- 和本地bean一样实现服务 -->  
    <bean id="demoService" class="com.znn.provider.DemoServiceImpl" />  
</beans>  



dubbo客户端编写

spring启动文件配置:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    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  
        ">  
    <import resource="dubbo-consumer.xml"/>  
</beans>  




客户端dubbo文件配置

<?xml version="1.0" encoding="UTF-8"?>  
<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"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans          
    http://www.springframework.org/schema/beans/spring-beans.xsd          
    http://code.alibabatech.com/schema/dubbo          
    http://code.alibabatech.com/schema/dubbo/dubbo.xsd">  
    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->  
    <dubbo:application name="consumer-of-helloworld-app"/>  
    <!-- 使用multicast广播注册中心暴露发现服务地址 -->  
    <!-- <dubbo:registry address="multicast://224.5.6.7:1234" /> -->  
    <dubbo:registry address="zookeeper://192.168.1.34:2181" />  
    <!-- 生成远程服务代理,可以和本地bean一样使用demoService -->  
    <dubbo:reference id="demoService" interface="com.znn.provider.DemoService" />  
</beans> 



编写测试dubbo代码

import org.springframework.context.support.ClassPathXmlApplicationContext;  
  
import com.znn.provider.DemoService;  
  
public class Consumer {  
    public static void main(String[] args) {  
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(ClassLoader.getSystemResource("")+"../"+"/dubbo-consumer.xml");  
        context.start();  
        DemoService demoService = (DemoService)context.getBean("demoService");    
        String hello = demoService.sayHello("world");   
        System.out.println(hello);  
    }  
} 


dubbo原理

  • 初始化过程细节

上图中的第一步start,就是将服务装载容器中,然后准备注册服务,和spring中启动过程类似,spring启动时,将bean装载仅容器中的时候,首先解析bean.所以dubbo也是先读配置文件解析服务

解析服务:

(1) 基于dubbo.jar内的Meta-inf/spring.handlers配置,spring在遇到dubbo名称空间时,会回调DubboNamespaceHandler类

public class DubboNamespaceHandler extends NamespaceHandlerSupport {  
  
    static {  
        Version.checkDuplicate(DubboNamespaceHandler.class);  
    }  
  
    public void init() {  
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));  
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));  
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));  
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));  
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));  
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));  
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));  
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));  
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));  
        registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));  
    }  
  
}  


(2) 所有dubbo标签,都统一用DubboBeanDefinitionParser进行解析,基于一对一属性映射,将XML标签解析为bean对象.spring会根据xml的命名空间调用相应的Parser进行处理,进入 DubboBeanDefinitionParser的解析parse.

private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {  
        RootBeanDefinition beanDefinition = new RootBeanDefinition();  
        beanDefinition.setBeanClass(beanClass);  
        beanDefinition.setLazyInit(false);  
        String id = element.getAttribute("id");  
        省略....   
        if (id != null && id.length() > 0) {  
            if (parserContext.getRegistry().containsBeanDefinition(id))  {  
                throw new IllegalStateException("Duplicate spring bean id " + id);  
            }  
            parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);//这里调用了bean的注册  
            beanDefinition.getPropertyValues().addPropertyValue("id", id);  
        }  
        省略....   
}


在ServiceConfig.export 或者ReferenceConfig.get 初始化时,将Bean对象转会为url格式,将所以Bean属性转成url的参数。然后将URL传给Protocol扩展点,基于扩展点的Adaptive机制,根据URL的协议头,进行不同协议的服务暴露和引用。

暴露服务:

  • 只暴露服务端口

在没有使用注册中心的情况,这种情况一般适用在开发环境下,服务的调用这和提供在同一个IP上,只需要打开服务的端口即可。 即,当配置ServiceConfig解析出的URL的格式为: Dubbo://service-host/com.xxx.TxxService?version=1.0.0 基于扩展点的Adaptiver机制,通过URL的“dubbo://”协议头识别,直接调用DubboProtocol的export()方法,打开服务端口。

  • 向注册中心暴露服务

和上一种的区别:需要将服务的IP和端口一同暴露给注册中心。 ServiceConfig解析出的url格式为: registry://registry-host/com.alibaba.dubbo.registry.RegistryService?export=URL.encode(“dubbo://service-host/com.xxx.TxxService?version=1.0.0”)

基于扩展点的Adaptive机制,通过URL的“registry://”协议头识别,调用RegistryProtocol的export方法,将export参数中的提供者URL先注册到注册中心,再重新传给Protocol扩展点进行暴露: Dubbo://service-host/com.xxx.TxxService?version=1.0

引用服务:

  • 直接引用服务

在没有注册中心的,直连提供者情况下, ReferenceConfig解析出的URL格式为: Dubbo://service-host/com.xxx.TxxService?version=1.0.0

基于扩展点的Adaptive机制,通过url的“dubbo://”协议头识别,直接调用DubboProtocol的refer方法,返回提供者引用。

  • 从注册中心发现引用服务

此时,ReferenceConfig解析出的URL的格式为: registry://registry-host/com.alibaba.dubbo.registry.RegistryService?refer=URL.encode(“consumer://consumer-host/com.foo.FooService?version=1.0.0”)

基于扩展点的Apaptive机制,通过URL的“registry://”协议头识别,就会调用RegistryProtocol的refer方法,基于refer参数总的条件,查询提供者URL,如: Dubbo://service-host/com.xxx.TxxService?version=1.0.0

基于扩展点的Adaptive机制,通过提供者URL的“dubbo://”协议头识别,就会调用DubboProtocol的refer()方法,得到提供者引用。 然后RegistryProtocol将多个提供者引用,通过Cluster扩展点,伪装成单个提供这引用返回。

ReferenceConfig这个类的createProxy是用来生成远程服务的本地代理,最终交给RegistryProtocol来处理。两处核心代码:

(1) refprotocol.refer,与注册中心相关

(2) proxyFactory.getProxy,获取代理


private T createProxy(Map<String, String> map) {
		//...
		
		if (isJvmRefer) {
			//...
		} else {
            //...

            if (urls.size() == 1) {
                invoker = refprotocol.refer(interfaceClass, urls.get(0));
            } else {
               //...
            }
        }

        //...
        // 创建服务代理
        return (T) proxyFactory.getProxy(invoker);
    }


回到ReferenceConfig的createProxy方法,最后会调用proxyFactory.getProxy,此方法最终会调用JavassistProxyFactory的getProxy,一看javassist就知道是利用字节码来实现代理功能。

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }


注册中心中subscribe方法会监听注册中心的变化,当获取到服务注册信息后会触发ProtocolListenerWrapper的refer方法:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return (Invoker)("registry".equals(url.getProtocol())?this.protocol.refer(type, url):

new ListenerInvokerWrapper(this.protocol.refer(type, url), 

Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(InvokerListener.class).getActivateExtension(url, "invoker.listener"))));
    }


public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    this.optimizeSerialization(url);
    DubboInvoker invoker = new DubboInvoker(serviceType, url, this.getClients(url), this.invokers);
    this.invokers.add(invoker);
    return invoker;
}



注册中心

由于服务注册到ZK,所以调用端要想调用服务端需要取得服务的注册信息然后建立网络连接。


private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        RegistryDirectory directory = new RegistryDirectory(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(this.protocol);
        URL subscribeUrl = new URL("consumer", NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
        if(!"*".equals(url.getServiceInterface()) && url.getParameter("register", true)) {
            registry.register(subscribeUrl.addParameters(new String[]{"category", "consumers", "check", String.valueOf(false)}));
        }

        directory.subscribe(subscribeUrl.addParameter("category", "providers,configurators,routers"));
        return cluster.join(directory);
    }



上面的this.protocol就是DubboProtocol这个类,此类的其它方法暂时先不关注,只看refer.

private ExchangeClient[] getClients(URL url) {
        boolean service_share_connect = false;
        int connections = url.getParameter("connections", 0);
        if(connections == 0) {
            service_share_connect = true;
            connections = 1;
        }

        ExchangeClient[] clients = new ExchangeClient[connections];

        for(int i = 0; i < clients.length; ++i) {
            if(service_share_connect) {
                clients[i] = this.getSharedClient(url);
            } else {
                clients[i] = this.initClient(url);
            }
        }

        return clients;
    }

远程调用细节

image

首先ServiceConfig类拿到对外提供服务的实际类ref,然后将ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到invoker的转化.接下来就是invoker到Exporter的过程.

Dubbo处理服务暴露的关键就在Invoker转换到Exporter的过程,下面我们以Dubbo和rmi这两种典型协议的实现来进行说明:

Dubbo协议的invoker转化为Exporer发生在DubboProtocol类的export方法,它主要是打开socket侦听服务,并接受客户端发来的各种请求,通讯细节由dubbo自己实现.

服务消费者消费一个服务的详细过程:

image

首先ReferenceConfig类的init方法调用Protocol的refer方法生成Invoker实例。接下来把Invoker转为客户端需要的接口.

转载至:http://blog.csdn.net/chao_19/article/details/51764150

转载至:https://www.cnblogs.com/ASPNET2008/p/6491427.html


评论