Dubbo 深度学习 修行之路

-----------------学习dubbo源码,能给你带来什么好处?-----------

1.提升SOA的微服务架构设计能力
   通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕竟SOA的服务治理就是dubbo首先提出来的,比起你去看市面上的SOA微服务架构的书籍,学到的架构原理要更丰富更有深度。

2.提升自己在部门团队的技术影响力
   你所在的部门团队如果使用了dubbo框架,必然需要懂dubbo底层核心的人;如果你把dubbo源码的架构原理掌握好,一般出现dubbo的疑难杂症,只要你参与探讨和解决,你自然是这个团队的核心程序员或是重要的程序员。
 

----------------------duboo底层核心知识点------------------------------

 tcp的单工、半双工、全双工、编码、解码、粘包、拆包。网络通信netty nio 阻塞、非阻塞、异步、同步、线程池、boss线程、work线程。集群负载均衡directory、router、cluster、loadbalance。zookeeper与zkClient、持久节点 和 临时节点。服务的本地暴露和远程暴露、服务降级、服务灰度发布。
 

Dubbo的官方首页在这里: 
http://code.alibabatech.com/wiki/display/dubbo/Home

设计原则

1.   多用组合,少用继承

2.   针对接口编程,不针对实现编程

3.   依赖抽象,不要依赖具体实现类。

设计模式

1.   策略设计模式:Dubbo扩展Spring的xml标签解析

2.   装饰者设计模式:Invoker链组建

3.   模板设计模式:一般的机制都有,抽象父类定义使用流程,子类根据业务需求实现方法内容

4.   动态代理:Invoker对象封装,

5.   单件设计模式:ExtensionLoader实现

6.   适配器设计模式:各个模块的管理对象;动态代生成的管理对象,这里采用了复合设计模式有动态代理(javassist的AOP)+适配置设计模式。

7.   迭代器:对JDK的集合扩展。(忘记编写代码的类,以后再找)

8.   工厂设计模式:ExtensionFactory和ProxyFactory机制。

一、Dubbo整体架构

1、Dubbo与Spring的整合 
Dubbo在使用上可以做到非常简单,不管是Provider还是Consumer都可以通过Spring的配置文件进行配置,配置完之后,就可以像使用spring bean一样进行服务暴露和调用了,完全看不到dubbo api的存在。这是因为dubbo使用了spring提供的可扩展Schema自定义配置支持。在spring配置文件中,可以像、这样进行配置。META-INF下的spring.handlers文件中指定了dubbo的xml解析类:DubboNamespaceHandler。像前面的被解析成ServiceConfig,被解析成ReferenceConfig等等。 
2、jdk spi扩展 
由于Dubbo是开源框架,必须要提供很多的可扩展点。Dubbo是通过扩展jdk spi机制来实现可扩展的。具体来说,就是在META-INF目录下,放置文件名为接口全称,文件中为key、value键值对,value为具体实现类的全类名,key为标志值。由于dubbo使用了url总线的设计,即很多参数通过URL对象来传递,在实际中,具体要用到哪个值,可以通过url中的参数值来指定。 
Dubbo对spi的扩展是通过ExtensionLoader来实现的,查看ExtensionLoader的源码,可以看到Dubbo对jdk spi做了三个方面的扩展:

(1)jdk spi仅仅通过接口类名获取所有实现,而ExtensionLoader则通过接口类名和key值获取一个实现;

(2)Adaptive实现,就是生成一个代理类,这样就可以根据实际调用时的一些参数动态决定要调用的类了。

(3)自动包装实现,这种实现的类一般是自动激活的,常用于包装类,比如Protocol的两个实现类:ProtocolFilterWrapper、ProtocolListenerWrapper。 
3、url总线设计 
Dubbo为了使得各层解耦,采用了url总线的设计。我们通常的设计会把层与层之间的交互参数做成Model,这样层与层之间沟通成本比较大,扩展起来也比较麻烦。因此,Dubbo把各层之间的通信都采用url的形式。比如,注册中心启动时,参数的url为: 

registry://0.0.0.0:9090?codec=registry&transporter=netty 

这就表示当前是注册中心,绑定到所有ip,端口是9090,解析器类型是registry,使用的底层网络通信框架是netty。

二、Dubbo启动过程

Dubbo分为注册中心、服务提供者(provider)、服务消费者(consumer)三个部分。 
1、注册中心启动过程 
注册中心的启动过程,主要看两个类:RegistrySynchronizer、RegistryReceiver,两个类的初始化方法都是start。 
RegistrySynchronizer的start方法:

(1)把所有配置信息load到内存;

(2)把当前注册中心信息保存到数据库;

(3)启动5个定时器。 
5个定时器的功能是: 
(1)AutoRedirectTask,自动重定向定时器。默认1小时运行1次。如果当前注册中心的连接数高于平均值的1.2倍,则将多出来的连接数重定向到其他注册中心上,以达到注册中心集群的连接数均衡。 
(2)DirtyCheckTask,脏数据检查定时器。作用是:分别检查缓存provider、数据库provider、缓存consumer、数据库consumer的数据,清除脏数据;清理不存活的provider和consumer数据;对于缓存中的存在的provider或consumer而数据库不存在,重新注册和订阅。 
(3)ChangedClearTask,changes变更表的定时清理任务。作用是读取changes表,清除过期数据。 
(4)AlivedCheckTask,注册中心存活状态定时检查,会定时更新registries表的expire字段,用以判断注册中心的存活状态。如果有新的注册中心,发送同步消息,将当前所有注册中心的地址通知到所有客户端。 
(5)ChangedCheckTask,变更检查定时器。检查changes表的变更,检查类型包括:参数覆盖变更、路由变更、服务消费者变更、权重变更、负载均衡变更。 
RegistryReceiver的start方法:启动注册中心服务。默认使用netty框架,绑定本机的9090端口。最后启动服务的过程是在NettyServer来完成的。接收消息时,抛开dubbo协议的解吗器,调用类的顺序是

NettyHandler->NettyServer->MultiMessageHandler->HeartbeatHandler->AllDispatcher->DecodeHandler
  ->HeaderExchangeHandler->RegistryReceiver->RegistryValidator->RegistryFailover->RegistryExecutor

 2、provider启动过程 
provider的启动过程是从ServiceConfig的export方法开始进行的,具体步骤是: 
(1)进行本地jvm的暴露,不开放任何端口,以提供injvm这种形式的调用,这种调用只是本地调用,不涉及进程间通信。 
(2)调用RegistryProtocol的export。 
(3)调用DubboProtocol的export,默认开启20880端口,用以提供接收consumer的远程调用服务。 
(4)通过新建RemoteRegistry来建立与注册中心的连接。 
(5)将服务地址注册到注册中心。 
(6)去注册中心订阅自己的服务。 
3、consumer启动过程 
consumer的启动过程是通过ReferenceConfig的get方法进行的,具体步骤是: 
(1)通过新建RemoteRegistry来建立与注册中心的连接。 
(2)新建RegistryDirectory并向注册中心订阅服务,RegistryDirectory用以维护注册中心获取的服务相关信息。 
(3)创建代理类,发起consumer远程调用时,实际调用的是InvokerInvocationHandler。

三、实际调用过程 

consumer端发起调用时,实际调用经过的类是: 
1、consumer:

InvokerInvocationHandler->MockClusterInvoker(如果配置了Mock,则直接调用本地Mock类)
  ->FailoverClusterInvoker(负载均衡,容错机制,默认在发生错误的情况下,进行两次重试)
  ->RegistryDirectory$InvokerDelegete->ConsumerContextFilter->FutureFilter->DubboInvoker
 
NettyServer->MultiMessageHandler->HeartbeatHandler->AllDispatcher->DecodeHandler->HeaderExchangeHandler
  ->DubboProtocol.requestHandler->EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter
  ->ExceptionFilter->TimeoutFilter->MonitorFilter->TraceFilter->实际service

四、Dubbo使用的设计模式 
1、工厂模式 
ServiceConfig中有个字段,代码是这样的:

private static final Protocol protocol = 
  ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 

Dubbo里有很多这种代码。这也是一种工厂模式,只是实现类的获取采用了jdk spi的机制。这么实现的优点是可扩展性强,想要扩展实现,只需要在classpath下增加个文件就可以了,代码零侵入。另外,像上面的Adaptive实现,可以做到调用时动态决定调用哪个实现,但是由于这种实现采用了动态代理,会造成代码调试比较麻烦,需要分析出实际调用的实现类。 
2、装饰器模式 
Dubbo在启动和调用阶段都大量使用了装饰器模式。以Provider提供的调用链为例,具体的调用链代码是在ProtocolFilterWrapper的buildInvokerChain完成的,具体是将注解中含有group=provider的Filter实现,按照order排序,最后的调用顺序是

EchoFilter->ClassLoaderFilter->GenericFilter->ContextFilter->ExceptionFilter->TimeoutFilter
  ->MonitorFilter->TraceFilter

 
更确切地说,这里是装饰器和责任链模式的混合使用。例如,EchoFilter的作用是判断是否是回声测试请求,是的话直接返回内容,这是一种责任链的体现。而像ClassLoaderFilter则只是在主功能上添加了功能,更改当前线程的ClassLoader,这是典型的装饰器模式。 
3、观察者模式 
Dubbo的provider启动时,需要与注册中心交互,先注册自己的服务,再订阅自己的服务,订阅时,采用了观察者模式,开启一个listener。注册中心会每5秒定时检查是否有服务更新,如果有更新,向该服务的提供者发送一个notify消息,provider接受到notify消息后,即运行NotifyListener的notify方法,执行监听器方法。 
4、动态代理模式 
Dubbo扩展jdk spi的类ExtensionLoader的Adaptive实现是典型的动态代理实现。Dubbo需要灵活地控制实现类,即在调用阶段动态地根据参数决定调用哪个实现类,所以采用先生成代理类的方法,能够做到灵活的调用。生成代理类的代码是ExtensionLoader的createAdaptiveExtensionClassCode方法。代理类的主要逻辑是,获取URL参数中指定参数的值作为获取实现类的key。

从头开始搭建一个dubbo+zookeeper平台

本篇主要是来分享从头开始搭建一个dubbo+zookeeper平台的过程,其中会简要介绍下dubbo服务的作用。

  •   首先,看下一般网站架构随着业务的发展,逻辑越来越复杂,数据量越来越大,交互越来越多之后的常规方案演进历程。

      

  •   其次,当服务越来越多之后,我们需要做哪些服务治理?

       

  •   最后,是dubbo的架构图

       
  注册中心的选择


  dubbo支持多种类型的注册中心:

  •   Multicast注册中心
  •   Zookeeper注册中心
  •   Redis注册中心
  •   Simple注册中心

  这里我们选择zookeeper,其实类型的优点缺点可详细查看文档。


  1:zookeeper的安装,还是采用docker这一招鲜的run命令来安装zookeeper

docker run -dit --name zookeeper  --hostname  zookeeper-host  -v /data:/data -p 2181:2181 jplock/zookeeper:latest


  2:安装zkui,可以参考zkui的项目地址来安装,它提供了一个管理界面,可以针对zookeepr的节点值进行CRUD操作,同时也提供了安全认证,按照如下几步就可以完成安装。

  •   mvn clean install,执行前需要安装java环境,maven环境,执行成功后会生成一个jar文件。
  •   将config.cfg复制到上一步生成的jar文件所在目录,然后修改配置文件中的zookeeper地址。
  •   执行 jar. ( nohup java -jar zkui-2.0-SNAPSHOT-jar-with-dependencies.jar & ),注意后面的那个&,是指不退出的意思。
  •   测试,http://localhost:9090,如能看到如下页面则代表zookeeper安装运行正常。

          

         


  下面是创建dubbo服务以及使用dubbo服务的过程:

   dubbo提供者,创建一个java工程,注意以下几点:

  •  包依赖,引入如下三个主要的包就可以了,主要是spring,dubbo以及zkclient  
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-framework.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.4.10</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.3</version>
        </dependency>
  •  定义接口,这里为了演示,简单定义了一个返回产品名称的接口。
public interface IProduct {
    String getProductName();

}
  •  接口实现 
@Service
public class ProductService implements IProduct{
    public String getProductName() {
       
        return "jim";
    }
}
  •  服务启动函数
    • 加载配置文件
    • 调用context.start()
    • 执行一个不退出程序的操作,这里有很多种做法。
public class App {

    private final static Logger logger = LoggerFactory.getLogger(App.class);
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                "classpath*:applicationContext.xml");
        context.start();

        logger.info("dubbo service begin to start");
        try {
            System.in.read();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
  •  服务配置文件,这里需要指出的是:
    • dubbo:service的定义配合了dubbo:annotation,ref="productService",是指定的一个id,实际的实现类通过注解扫描来完成的,并没有在配置文件中指定实现类,后面的消费者配置文件中会有所体现。
    • dubbo:application中,可以指定logger的实现接口。
    • dubbo:protocol中,可以指定是否启动访问日志,这个对有时排查线上问题非常有帮助。
<?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd"
        >
    <context:property-placeholder location="classpath*:config.properties"/>
    <dubbo:application name="jim" logger="slf4j" />

    <dubbo:registry protocol="zookeeper" address="192.168.21.128:2181" />

    <dubbo:protocol accesslog="true" name="dubbo" port="20880" />

    <dubbo:annotation package="jim" />

    <dubbo:service interface="jim.IProduct" ref="productService"/>

    <context:component-scan base-package="jim" />
    <import resource="redis-context.xml"/>


</beans>

         
   dubbo消费者

     

  •    消费者配置文件,它的配置相对提供者要简单很多:
    • 指定消费者的名称,这个可以随意,不需要与提供者做任务相关联的匹配。
    • 指定协定类型,zookeeper地址。
    • 指定引用的服务接口,注意这里的id就与服务提供者定义的ref值相同。
<?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-jim-app"  />

    <dubbo:registry protocol="zookeeper" address="192.168.21.128:2181"/>

    <dubbo:reference interface="jim.IProduct" id="productService" />

</beans>
  •    接口注解定义以及接口调用    
@Controller
public class HomeController {

    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    @Autowired
    private IProduct productService;
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home! The client locale is {}.", locale);
       
        String productName=this.productService.getProductName();
        model.addAttribute("name", productName);

        return "home";
    }

}

  dubbo admin

  有一个UI工具可以针对dubbo服务进行管理,可惜我没有在官方文档提供的链接中下载成功,随后从其它地方虽然下载完成了,但在安装部署方面暂时遇到了一定的问题,需要手续研究解决。

     

   正常应该可以看到如下界面:

  • 管理提供者
  • 管理消费者

    

       服务治理

       

    经过上面的步骤后,就可以启动服务端以及客户端来验证了。上面只是简单的搭建了dubbo环境以及实现了一个hello world的服务接口,要想使用好dubbo还有好多提供的最佳实践,比如服务治理:

  • 本地存根
  • 本地伪装
  • 结果缓存
  • 多版本
  • 服务降级
  • ......

 本文引用:

  1. http://dubbo.io/User+Guide-zh.htm#UserGuide-zh-%E5%A4%9A%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83
  2. http://blog.csdn.net/yinwenjie/article/details/50113139
  3. https://github.com/DeemOpen/zkui

猜你喜欢

转载自my.oschina.net/oosc/blog/1793748