Dubbo的基本认识

主要内容

  1. 为什么需要Dubbo

  2. Dubbo的架构

  3. Dubbo的使用

  4. Dubbo注册中心原理

  5. 如何快速启动Dubbo服务

  6. 多协议支持

  7. 多注册中心支持

  8. 启动检查机制

  9. 基于集群访问

Dubbo初步认识

dubbo中文网站:http://dubbo.apache.org/zh-cn/

dubbo英文网站:http://dubbo.apache.org/en-us/

在传统的远程调用,比如RMI、HTTP协议、WebService等确实能够满足远程调用关系,但是随着用户量的倍增以及系统的复杂性增加,传统的远程调用却满足不了服务治理的需求:

- 地址维护
- 负载均衡
- 限流/容错/降级
- 监控

所以在这一部分,我们引入Dubbo来实现服务治理,我们从三方面讲Dubbo这个RPC远程调用框架。

架构的发展

传统互联网架构
还记得阿里最初的项目是什么样的结构么?就是LAMP(即,Linux+Apache+MySQL+PHP),Tomcat作为web容器,放置的单体项目包括整套业务的所有逻辑,用户服务,订单服务,支付服务等~

分布式架构的演进

如今互联网比较完善的体系就是将业务分离,服务分离,数据库主从设计,读写分离。

带来哪些问题

(1)当服务越来越多时,服务 URL 配置管理变得非常困难,F5 硬件负载均衡器的单点压力也越来越大。

此时需要一个服务注册中心,动态的注册和发现服务,使服务的位置透明。

并通过在消费方获取服务提供方地址列表,实现软负载均衡和 Failover,降低对 F5 硬件负载均衡器的依赖,也能减少部分成本。

(2)当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。

这时,需要自动画出应用间的依赖关系图,以帮助架构师理清理关系。

(3)服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?

**为了解决这些问题,第一步,要将服务现在每天的调用量,响应时间,都统计出来,作为容量规划的参考指标。
其次,要可以动态调整权重,在线上,将某台机器的权重一直加大,并在加大的过程中记录响应时间的变化,直到响应时间到达阀值,记录此时的访问量,再以此访问量乘以机器数反推总容量。**

Dubbo的出现就是为了解决这些问题,Dubbo是一个服务治理技术,如何解释服务治理。

 

服务治理概念:

1. 负载
2. 容错
3. 降级

Dubbo架构

 

老生常谈,随手掏来一个经典的dubbo架构图,图示上已经写的很清楚了,一共有0~5六个部分:

1. start

provider服务提供者:服务启动

1. register

provider服务提供者然后注册register服务

1. subscribe

Consumer服务消费者:消息订阅subscribe、

1. notify

注册中心会将这些服务通过notify到消费者

1. invoke

invoke这条实线按照图上的说明当然同步的意思了
服务消费者随机调用一个服务地址,失败重试另一个地址

1. count

这是一个监控,图中虚线表明Consumer 和Provider通过异步的方式发送消息至Monitor。Monitor在整个架构中是可选的,Monitor功能需要单独配置,不配置或者配置以后,Monitor挂掉并不会影响服务的调用。

Dubbo 案例演

接下来,我们简单的使用一下dubbo~

- 服务端

我这里使用server-api和server-provider作为服务端两个模块

- server-api:服务调用接口
- server-provider:服务提供者

在maven pom文件引入dubbo jar 包依赖

<!--加入服务接口定义-->
<dependency>
    <groupId>com.learn.dubbo</groupId>
    <artifactId>service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.5</version>
</dependency>

在server-api中,就是空实现,用来调用server-provider:
接口:

 1 package com.learn.dubbo;
 2 /**
 3  * 接口定义
 4  * @Author:         cong zhi
 5  * @CreateDate:     2021/2/11 15:49
 6  * @UpdateUser:     cong zhi
 7  * @UpdateDate:     2021/2/11 15:49
 8  * @UpdateRemark:   修改内容
 9  * @Version:        1.0
10  */
11 public interface HelloService {
12 
13     String sayHello(String msg);
14 }

真正的实现就是在server-provider中实现:

 1 package com.learn.dubbo;
 2 
 3 /**
 4  * 服务实现
 5  * @Author: cong zhi
 6  * @CreateDate: 2021/2/11 15:51
 7  * @UpdateUser: cong zhi
 8  * @UpdateDate: 2021/2/11 15:51
 9  * @UpdateRemark: 修改内容
10  * @Version: 1.0
11  */
12 public class HelloServiceImpl implements HelloService {
13     @Override
14     public String sayHello(String msg) {
15         return "Hello," + msg;
16 
17     }
18 }

如何发布服务,需要将需要暴露的服务接口发布出去供客户端调用,需要在java同级目录新建一个resources目录,然后将resoureces目录标记成Test Resoureces Root,然后在esources目录下新建MATE-INF.spring目录,在该目录下添加配置文件dubbo-server.xml文件

dubbo的服务端 dubbo-server.xml 配置文件如下

<?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="dubbo-server" owner="mic"/>

    <!--注册中心 暴露服务地址-->
    <dubbo:registry address="N/A"/>

    <!--用dubbo协议在20880 端口暴露服务-->
    <dubbo:protocol port="20880" name="dubbo"/>

    <!--声明需要暴露的服务接口,指定协议为dubbo-->
    <dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService"/>
  

    <!--对应的服务实现服务-->
    <bean id="helloService" class="com.learn.dubbo.HelloServiceImpl"/>

</beans>

服务端调用就加载这个配置文件:

 1 package com.learn.dubbo;
 2 
 3 import org.springframework.context.support.ClassPathXmlApplicationContext;
 4 
 5 import java.io.IOException;
 6 
 7 /**
 8  * 编写Main方法,用spring容器来启动服务
 9  *
10  * @Author: cong zhi
11  * @CreateDate: 2021/2/11 16:01
12  * @UpdateUser: cong zhi
13  * @UpdateDate: 2021/2/11 16:01
14  * @UpdateRemark: 修改内容
15  * @Version: 1.0
16  */
17 public class Bootstrap {
18     public static void main(String[] args) throws IOException {
19         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-server.xml");
20         context.start();
21         // 阻塞当前进程
22         System.in.read();
23     }
24 }

启动服务,运行结果如下:

Dubbo 2.6.5启动报 java.lang.NoClassDefFoundError: io/netty/channel/EventLoopGroup

原因:缺少netty-all的jar包,在pom.xml中添加jar依赖即可,如下:

<!--学习dubbo 集成zookeeper时运行时报错,需要添加netty依赖-->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.32.Final</version>
</dependency>

对外发布服务如下:

协议地址 : ip:port interfaceName

dubbo://192.168.137.1:20880/com.learn.dubbo.HelloService

客户端

在maven pom文件引入dubbo jar 包依赖

<dependency>
    <groupId>com.learn.dubbo</groupId>
    <artifactId>service-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.5</version>
</dependency>

客户端 dubbo-client.xml 配置文件

<?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="dubbo-client" owner="mic"/>

    <!--注册中心 暴露服务地址-->
    <dubbo:registry address="N/A"/>
    <!--用dubbo协议在20880 端口暴露服务-->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!--调用dubbo远端服务,需要指定url地址就能发起远端调用-->
    <dubbo:reference id="helloService" interface="com.learn.dubbo.HelloService" url="dubbo://192.168.137.1:20880/com.learn.dubbo.HelloService"/>

</beans>

客户端dubbo调用远端服务

 1 /**
 2  * 客户端调用Dubbo远端服务
 3  * @Author:         cong zhi
 4  * @CreateDate:     2021/2/11 16:31
 5  * @UpdateUser:     cong zhi
 6  * @UpdateDate:     2021/2/11 16:31
 7  * @UpdateRemark:   修改内容
 8  * @Version:        1.0
 9  */
10 public class Bootstrap {
11     public static void main(String[] args) throws IOException {
12 
13         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-client.xml");
14         HelloService helloService = (HelloService) context.getBean("helloService");
15 
16         System.out.println(helloService.sayHello("cong zhi"));
17     }
18 }

启动客户端,运行结果如下:

调用过程

​ dubbo底层基于netty完成远程通信 和TCP协议完成数据交互,然后从服务端拿到相应的服务配置信息,服务端声明需要暴露的服务接口和协议地址,客户端就可以通过指定的服务和协议地址调用远端服务,通过对应的服务找到发布的容器里面service服务地址得到一个Bean,就可以通过Bean反射去调用对应的Method去完成调用,最终返回数据,中间还有一个序列化和反序列过程。这是没有使用注册中心的,如果基于URL的方式还是点对点,也就是没有办法完成服务地址的管理和维护,因此需要用到中间件维护服务地址。

Dubbo 支持的注册中心

引入zookeeper jar 包

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>

dubbo-service.xml 配置文件改成zookeeper 注册中心地址

<?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="dubbo-server" owner="mic"/>

    <!--zookeeper 注册中心 暴露服务地址-->
    <dubbo:registry  address="zookeeper://192.168.1.101:2181"/>

    <!--用dubbo协议在20880 端口暴露服务-->
    <dubbo:protocol port="20880" name="dubbo"/>

    <!--声明需要暴露的服务接口,指定协议为dubbo,设置版本号1.1.1-->
    <dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService" />
    
    <!--对应的服务实现服务-->
    <bean id="helloService" class="com.learn.dubbo.HelloServiceImpl"/>

</beans>

Dubbo 集成 zookeeper 踩坑 “java.lang.NoClassDefFoundError: org/apache/curator/RetryPolicy”

版本变化

dubbo 2.6以前的版本,引入zkclient操作zookeeper
dubbo 2.6及以后的版本,引入curator操作zookeeper

客户端  dubbo-client.xml 配置文件改造

<?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="dubbo-client" owner="mic"/>

    <!--zookeeper 注册中心 暴露服务地址-->
    <dubbo:registry  address="zookeeper://192.168.1.101:2181"/>

    <!--调用dubbo远端服务,需要指定url地址就能发起远端调用-->
    <dubbo:reference id="helloService" interface="com.learn.dubbo.HelloService"/>

</beans>

启动运行结果如下:

Dubbo 节点分析

使用dubbo通信成功,我们注意到,此时,dubbo在注册中心创建了“dubbo”的节点

Zookeeper节点状态

注册中心原理

关于缓存

注意:在客户端其实是有缓存的概念的,这样不一定就让client每次都请求到zookeeper

<!--zookeeper 注册中心 暴露服务地址  开启本地缓存-->
<dubbo:registry  address="zookeeper://192.168.1.101:2181" file="D:/dubbo-server"/>

运行后,在本地找到缓存文件中缓存服务信息

Dubbo支持的容器

Spring container (默认)

jetty container

Log4j container

服务启动过程

可以通过dubbo提供的main方法启动容器

 1 package com.learn.dubbo;
 2 
 3 import com.alibaba.dubbo.container.Main;
 4 
 5 /**
 6  * 通过dubbo提供main方法启动容器
 7  *
 8  * @Author: cong zhi
 9  * @CreateDate: 2021/2/12 10:48
10  * @UpdateUser: cong zhi
11  * @UpdateDate: 2021/2/12 10:48
12  * @UpdateRemark: 修改内容
13  * @Version: 1.0
14  */
15 public class DubboMain {
16 
17     public static void main(String[] args) {
18         // 默认情况下会使用spring容器来启动服务
19         Main.main(new String[]{"spring"});
20     }
21 }

源码分析

 1 public static void main(String[] args) {
 2     try {
 3         if (args == null || args.length == 0) {
 4             String config = ConfigUtils.getProperty("dubbo.container", loader.getDefaultExtensionName());
 5             args = Constants.COMMA_SPLIT_PATTERN.split(config);
 6         }
 7 
 8         final List<Container> containers = new ArrayList();
 9 
10         for(int i = 0; i < args.length; ++i) {
11             containers.add(loader.getExtension(args[i]));
12         }
13 
14         logger.info("Use container type(" + Arrays.toString(args) + ") to run dubbo serivce.");
15         if ("true".equals(System.getProperty("dubbo.shutdown.hook"))) {
16             Runtime.getRuntime().addShutdownHook(new Thread("dubbo-container-shutdown-hook") {
17                 public void run() {
18                     Iterator var1 = containers.iterator();
19 
20                     while(var1.hasNext()) {
21                         Container container = (Container)var1.next();
22 
23                         try {
24                             container.stop();
25                             Main.logger.info("Dubbo " + container.getClass().getSimpleName() + " stopped!");
26                         } catch (Throwable var8) {
27                             Main.logger.error(var8.getMessage(), var8);
28                         }
29 
30                         try {
31                             Main.LOCK.lock();
32                             Main.STOP.signal();
33                         } finally {
34                             Main.LOCK.unlock();
35                         }
36                     }
37 
38                 }
39             });
40         }
41 
42         Iterator var12 = containers.iterator();
43 
44         while(var12.hasNext()) {
45             Container container = (Container)var12.next();
         // 默认使用Spring 容器
46 container.start(); 47 logger.info("Dubbo " + container.getClass().getSimpleName() + " started!"); 48 } 49 50 System.out.println((new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]")).format(new Date()) + " Dubbo service server started!"); 51 } catch (RuntimeException var10) { 52 var10.printStackTrace(); 53 logger.error(var10.getMessage(), var10); 54 System.exit(1); 55 } 56 57 try { 58 LOCK.lock(); 59 STOP.await(); 60 } catch (InterruptedException var8) { 61 logger.warn("Dubbo service server stopped, interrupted by other thread!", var8); 62 } finally { 63 LOCK.unlock(); 64 } 65 66 }

Dubbo支持多协议

  • RMI

  • hessian (hessian 使用http)

  • webservice

  • http

  • thirft

  • Dubbo(默认)Dubbo使用NIO

优势: 1、不需要修改原本的服务的情况下,方便协议的迁移 2、通过增加相应协议的jar包,快速发布

 

在maven pom 文件中引入 hessian 相关依赖

<!--hessian 相关依赖-->
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.38</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty</artifactId>
    <version>6.1.25</version>
</dependency>

dubbo-service.xml 配置文件改成 hessian协议

<!--用dubbo协议在20880 端口暴露服务-->
<dubbo:protocol port="20880" name="dubbo"/>
<!--用hessian协议在 8080 端口暴露服务-->
<dubbo:protocol port="8080" name="hessian"/>
<!--声明需要暴露的服务接口,改成 hessian协议,设置版本号1.1.1-->
<dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService" protocol="dubbo,hessian" />

服务端运行结果如下:


客户端 dubbo-client.xml 配置文件改成

<!--调用dubbo远端服务,需要指定url地址就能发起远端调用,改成hessian协议-->
<dubbo:reference id="helloService" interface="com.learn.dubbo.HelloService" protocol="hessian"/>

Dubbo 支持多注册中心

dubbo-service.xml 配置文件改成 hessian协议

<!--注册中心 暴露服务地址-->
<dubbo:registry id="zk1"  address="zookeeper://192.168.1.101:2181"/>


<dubbo:registry id="zk2"  address="zookeeper://192.168.1.101:2181"/>

<!--用dubbo协议在20880 端口暴露服务-->
<dubbo:protocol port="20880" name="dubbo"/>
<!--用hessian协议在 8080 端口暴露服务-->
<dubbo:protocol port="8080" name="hessian"/>

<!--声明需要暴露的服务接口,Dubbo支持同一个服务多种协议,指定协议为dubbo,hessian -->
<dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService" registry="zk1" protocol="dubbo,hessian"/>

<!--声明需要暴露的服务接口,指定协议为dubbo,设置版本号1.1.2-->
<dubbo:service interface="com.learn.dubbo.DemoService" ref="demoService" registry="zk2" protocol="dubbo"/>

Dubbo 解决服务循环依赖问题,在客户端dubbo-client.xml 配置文件中配置如下:

Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认check=true。

如果你的Spring容器是懒加载的,或者通过API编程延迟引用服务,请关闭check,否则服务临时不可用时,会抛出异常,拿到null引用,如果check=false,总是会返回引用,当服务恢复时,能自动连上。

 

<!--调用dubbo远端服务,需要指定url地址就能发起远端调用, 可以通过check="false"关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。关闭某个服务的启动时检查:(没有提供者时报错)-->
<dubbo:reference id="helloService" interface="com.learn.dubbo.HelloService" check="false" protocol="hessian"/>

Dubbo基于集群访问,对dubbo-server做负载均衡

dubbo-cluster1.xml 集群配置

<?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="dubbo-server" owner="mic"/>

    <!--注册中心 暴露服务地址-->
    <dubbo:registry id="zk2" address="zookeeper://192.168.1.101:2181"/>


    <!--用dubbo协议在20880 端口暴露服务-->
    <dubbo:protocol port="20880" name="dubbo"/>


    <!--声明需要暴露的服务接口,Dubbo支持同一个服务多种协议,指定协议为dubbo,hessian -->
    <dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService" registry="zk2" protocol="dubbo"/>


    <!--对应的服务实现服务-->
    <bean id="helloService" class="com.learn.dubbo.HelloServiceImpl"/>


</beans>

dubbo-cluster2.xml 集群配置

<?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="dubbo-server" owner="mic"/>


    <dubbo:registry id="zk1" address="zookeeper://192.168.1.101:2181"/>

    <!--用dubbo协议在20880 端口暴露服务-->
    <dubbo:protocol port="20881" name="dubbo"/>


    <!--声明需要暴露的服务接口,Dubbo支持同一个服务多种协议,指定协议为dubbo,hessian -->
    <dubbo:service interface="com.learn.dubbo.HelloService" ref="helloService" registry="zk1" protocol="dubbo"/>

    <!--对应的服务实现服务-->
    <bean id="helloService" class="com.learn.dubbo.HelloServiceImpl2"/>


</beans>
 1 package com.learn.dubbo;
 2 
 3 import org.springframework.context.support.ClassPathXmlApplicationContext;
 4 
 5 import java.io.IOException;
 6 
 7 /**
 8  * 编写Main方法,用spring容器来启动服务
 9  *
10  * @Author: cong zhi
11  * @CreateDate: 2021/2/11 16:01
12  * @UpdateUser: cong zhi
13  * @UpdateDate: 2021/2/11 16:01
14  * @UpdateRemark: 修改内容
15  * @Version: 1.0
16  */
17 public class BootstrapCluster1 {
18     public static void main(String[] args) throws IOException {
19         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/dubbo-cluster1.xml");
20         context.start();
21         // 阻塞当前进程
22         System.in.read();
23     }
24 }

GitHub 地址:https://github.com/lwx57280/Dubbo-learning

原文地址:https://www.cnblogs.com/lwx57280/p/14398783.html