Dubbo的基本认识
主要内容
-
为什么需要Dubbo
-
Dubbo的架构
-
Dubbo的使用
-
Dubbo注册中心原理
-
如何快速启动Dubbo服务
-
多协议支持
-
多注册中心支持
-
-
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协议在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远端服务,需要指定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