Dubbo
背景
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
![在这里插入图片描述](https://ucc.alicdn.com/images/user-upload-01/20201127115848974.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzMyMzY2MzI5,size_16,color_FFFFFF,t_70#pic_center)
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
Dubbo架构与实战
什么是Dubbo
Apache Dubbo™ 是一款微服务框架(Microservices Framework),它提供高性能 RPC 通信、服务发现、流量管理等服务治理能力,为你提供构建大规模微服务集群所需的全套解决方案。
Dubbo 3.0新特性
- 面向接口代理的高性能RPC调用
提供高性能的基于代理的远程调用能力,服务以接口为粒度,为开发者屏蔽远程调用底层细节。
- 智能负载均衡
内置多种负载均衡策略,智能感知下游节点健康状况,显著减少调用延迟,提高系统吞吐量。
- 服务自动注册与发现
支持多种注册中心服务,服务实例上下线实时感知。
- 高度可扩展能力
遵循微内核+插件的设计原则,所有核心能力如Protocol、Transport、Serialization被设计为扩展点, 平等对待内置实现和第三方实现。
- 运行期流量调度
内置条件、脚本等路由策略,通过配置不同的路由规则,轻松实现灰度发布,同机房优先等功能
- 可视化的服务治理与运维 。
提供丰富服务治理、运维工具:随时查询服务元数据、服务健康状态及调用统计,实时下发路由策略、调整配置参数。
Dubbo 架构
节点角色说明
节点 | 角色说明 |
Provider | 暴露服务的服务提供方 |
Consumer | 调用远程服务的服务消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Container | 服务运行容器 |
调用关系说明
- 服务容器负责启动,加载,运行服务提供者。
- 服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
架构特点
Dubbo 架构具有连通性、健壮性、伸缩性、以及向未来架构的升级性。
连通性
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
- 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
- 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
健壮性
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
伸缩性
- 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
- 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
升级性
当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力。下图是未来可能的一种架构:
节点角色说明
节点 | 角色说明 |
Deployer | 自动部署服务的本地代理 |
Repository | 仓库用于存储服务应用发布包 |
Scheduler | 调度中心基于访问压力自动增减服务提供者 |
Admin | 统一管理控制台 |
Registry | 服务注册与发现的注册中心 |
Monitor | 统计服务的调用次数和调用时间的监控中心 |
Dubbo 开发实战
Zookeeper安装
从Dubbo架构图中可以看到,Registry注册中心是整个Dubbo的一个重要核心。Dubbo官方推荐使用Zookeeper作为服务注册中心。Zookeeper是Apache Hadoop的子项目,作为Dubbo服务的注册中心强度较高。
以下演示的安装是以 Linux环境、CentOS 7系统为基础,那么在使用之前我们需要做一下配置,如果有环境并且已经处理了可以忽略
- 防火墙问题,CentOS 7防火墙默认是开启的,那么如果不关闭端口就不可以访问,在代码中调用的时候就无法使用默认的2181和其他端口
# CentOS 6处理方法 //临时关闭 service iptables stop //禁止开机启动 chkconfig iptables off
# CentOS 7处理方法,由于CentOS 7以后的防火墙默认使用firewalld,因此处理方法和CentOS 6有差异 //临时关闭 systemctl stop firewalld //禁止开机启动 systemctl disable firewalld Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.sersvice.
# 额外提供的命令 # 防火墙相关命令 1、查看防火墙状态 : systemctl status firewalld.service 注:active是绿的running表示防火墙开启 2、关闭防火墙 :systemctl stop firewalld.service 3、开机禁用防火墙自启命令 :systemctl disable firewalld.service 4、启动防火墙 :systemctl start firewalld.service 5、防火墙随系统开启启动 : systemctl enable firewalld.service 6、重启防火墙 : firewall-cmd --reload # 端口开放命令 1、查询已经开放的端口 :firewall-cmd --list-port 2、查询某个端口是否开放 :firewall-cmd --query-port=80/tcp 3、开启端口 :firewall-cmd --zone=public --add-port=80/tcp --permanent 注:可以是一个端口范围,如1000-2000/tcp 4、移除端口 :firewall-cmd --zone=public --remove-port=80/tcp --permanent 5、命令含义: --zone #作用域 --add-port=80/tcp #添加端口,格式为:端口/通讯协议 --remove-port=80/tcp #移除端口,格式为:端口/通讯协议 --permanent #永久生效,没有此参数重启后失效
单机版
- 下载Zookeeper http://zookeeper.apache.org/releases.html 打开这个网址就可以看到不同的版本直接下载即可
- 在/usr/local/目录下创建zookeeper文件夹,用于存放zookeeper。
mkdir zookeeper
- 将下载的zookeeper.tar.gz压缩包上传到Linux的/usr/local/zookeeper目录下
# 解压 tar -zxvf zookeeper-3.4.14.tar.gz
# 进入conf目录,zookeeper启动是读取zoo.cfg配置文件,所以重命名或者拷贝一份都可以 cp zoo_sample.cfg zoo.cfg
# 在zookeeper目录下创建data文件夹,用于存储文件,不使用默认的路径文件夹 mkdir data # 修改conf/zoo.cfg配置文件的dataDir路径,这个是数据存放的文件路径 dataDir=/usr/local/zookeeper/data
# 命令 ./bin/zkServer.sh start # 启动 ./bin/zkServer.sh stop # 暂停 ./bin/zkServer.sh status # 查看状态
# 启动命令执行的效果 [root@localhost zookeeper-3.4.14-2181]# ./bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/zookeeper-3.4.14-2181/bin/../conf/zoo.cfg Starting zookeeper ... STARTED
# 查看状态命令执行的效果 [root@localhost zookeeper-3.4.14-2181]# ./bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/zookeeper-3.4.14-2181/bin/../conf/zoo.cfg Mode: standalone # standalone表示单机版本
# 暂停命令执行的效果 [root@localhost zookeeper-3.4.14-2181]# ./bin/zkServer.sh stop ZooKeeper JMX enabled by default Using config: /usr/local/zookeeper/zookeeper-3.4.14-2181/bin/../conf/zoo.cfg Stopping zookeeper ... STOPPED
集群版
- 下载http://zookeeper.apache.org/releases.html 打开这个网址就可以看到不同的版本直接下载
- 上传zookeeper.tar.gz压缩包到/usr/local/zookeeper目录下
# 在/usr/local/目录下创建文件夹 mkdir zkcluster
# 解压zookeeper到zkcluster文件夹,-C的作用是指定解压到那个文件夹下 tar -zxvf zookeeper-3.4.14.tar.gz -C /zkcluster
# 修改文件名称,并复制 mv zookeeper-3.4.14 zookeeper01 # 复制2份,集群数量为3 cp -r zookeeper01/ zookeeper02 cp -r zookeeper01/ zookeeper03
# 在每个zookeeper目录下创建data文件夹,并在data目录下创建log文件夹 mkdir data cd data mkdir log
# 修改/conf/zoo_sample.cfg配置文件名 zookeeper01 zookeeper02 zookeeper03都要执行 cp zoo_sample.cfg zoo.cfg
# 修改zoo.cfg配置文件的端口的数据存储就以及日志路径,端口分别是:2181 2182 2183 # 端口 clientPort=2181 # 存储路径 dataDir=/usr/local/zookeeper/zkcluster/zookeeper01/data # 日志路径 dataLogDir=/usr/local/zookeeper/zkcluster/zookeeper01/data/log clientPort=2182 dataDir=/usr/local/zookeeper/zkcluster/zookeeper02/data dataLogDir=/usr/local/zookeeper/zkcluster/zookeeper02/data/log clientPort=2183
dataDir=/usr/local/zookeeper/zkcluster/zookeeper03/data
dataLogDir=/usr/local/zookeeper/zkcluster/zookeeper03/data/log
* ```shell # 配置集群,分别在data目录下创建myid,内容分别是1 2 3用于记录每个服务器的ID,也是集群中zookeeper的唯一标记,不可以重复 touch ./zookeeper01/data/myid touch ./zookeeper02/data/myid touch ./zookeeper03/data/myid
#在每个zookeeper的zoo.cfg文件中配置客户端访问端口(clientPort)和集群服务器IP列表 #server.服务器ID=服务器IP地址:服务器之间通信端口:服务器之间投票选举端口 server.1=192.168.247.100:2881:3881 server.2=192.168.247.100:2882:3882 server.3=192.168.247.100:2883:3883
# 依次启动3个zookeeper ./bin/zkServer.sh start ./bin/zkServer.sh stop ./bin/zkServer.sh status
实战开发
Dubbo开发的所有服务调用都是基于接口进行交互。双方应该协定好Dubbo调用中的接口,服务提供者实现接口并注册到注册中心上。
服务调用者需要引入接口,并将自己注册到服务注册中心。这样就可以利用注册中心来实现集群感知功能,随后就可以对服务提供者调用。
模块说明
模块 | 说明 |
service-api | 服务提供者和消费者定义的接口 |
service-consumer | 服务消费者 |
service-provider | 服务提供者 |
XML模式
- 接口定义service-api模块
public interface HelloService { String sayHello(String name); }
- 服务器提供者service-provider模块
<!-- 这里的Jar包括了后面使用的一些功能Jar --> <dependencies> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <!-- service-api模块GAV坐标 --> <dependency> <groupId>org.example</groupId> <artifactId>service-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-rpc-dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-remoting-netty4</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-serialization-hessian2</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-common</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-nacos</artifactId> </dependency> </dependencies>
// 服务提供者实现接口 public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "hello " + name; } }
<!-- 服务提供者配置文件,包括Zookeeper注册中心信息 --> <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- dubbo.application.name=service-provider-anno dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.protocol.name=dubbo dubbo.protocol.port=20880 --> <dubbo:application name="service-provider-xml"> <!--QOS在线运维命令,具体参数参考ApplicationConfig--> <dubbo:parameter key="qos.enable" value="true"/> <dubbo:parameter key="qos.port" value="11111"/> </dubbo:application> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:protocol name="dubbo" port="20880"/> <bean id="helloService" class="org.example.service.impl.HelloServiceImpl" /> <dubbo:service interface="org.example.service.xml.HelloService" ref="helloService" /> </beans>
// 服务提供者启动类 public class ProviderMain { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:dubbo-provider.xml"); context.start(); System.in.read(); } }
- 服务消费者service-consumer模块
<!-- 这里的Jar包括了后面使用的一些功能Jar --> <dependencies> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <!-- service-api模块GAV坐标 --> <dependency> <groupId>org.example</groupId> <artifactId>service-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.2.0</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-zookeeper</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-rpc-dubbo</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-remoting-netty4</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-serialization-hessian2</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-common</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-registry-nacos</artifactId> </dependency> </dependencies>
<!-- 服务消费者配置文件,包括Zookeeper注册中心信息 --> <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- dubbo.application.name=service-consumer-anno dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.consumer.timeout=3000 --> <dubbo:application name="service-consumer-xml"/> <dubbo:registry address="zookeeper://127.0.0.1:2181"/> <dubbo:consumer timeout="3000" /> <dubbo:reference id="helloService" interface="org.example.service.xml.HelloService" /> </beans>
// 消费者启动类 public class ConsumerMain { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:dubbo-consumer.xml"); context.start(); HelloService bean = (HelloService) context.getBean("helloService"); while (true) { System.in.read(); String result = bean.sayHello("world"); System.out.println(result); } } }
注解模式
注解模式也是基于XML模式的api模块,所以这里只处理服务提供者和消费者等代码逻辑。
- 服务器提供者service-provider模块
import org.apache.dubbo.config.annotation.Service; import org.example.service.HelloService; // 注意看这个注解的不是Spring的而是Dubbo的 @Service public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "hello " + name; } }
// 服务提供者启动类 import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import java.io.IOException; public class ProviderMain { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfig.class); context.start(); System.in.read(); } @Configuration @EnableDubbo(scanBasePackages = "org.example.service.impl") @PropertySource("classpath:/dubbo-provider.properties") static class ProviderConfig { } }
- 服务器消费者service-consumer模块
import org.apache.dubbo.config.annotation.Reference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; // 注意看注解对应的包 @Component public class HelloAction { @Reference // 使用该注解注入远程的实现 Dubbo提供 private HelloService helloService; public String sayHello(String name) { return helloService.sayHello(name); } }
// 服务消费者启动类 import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.example.service.HelloService; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import java.io.IOException; public class ConsumerMain { public static void main(String[] args) throws IOException { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfig.class); context.start(); HelloService bean = context.getBean(HelloService.class); while (true) { System.in.read(); String result = bean.sayHello("world"); System.out.println(result); } } @Configuration @EnableDubbo(scanBasePackages = "org.example.service") @PropertySource("classpath:/dubbo-consumer.properties") @ComponentScan(value = "org.example.service") static class ConsumerConfig { } }
开发方式介绍
- 注解:基于注解的方式可以快速的构建应用并启动实现,不需要多余的配置信息。但是如果项目足够庞大的时候,对于某个服务或者配置信息不容易找。
- XML:XML开发方式一般和Spring框架结合开发,并且Dubbo支持和Spring集成。这样配置信息集中管理,方便定位。
- 基于Java代码:普通开发不推荐使用这种方式,一般是对Dubbo做深度集成才会用。
Dubbo配置项介绍
XML配置介绍
支持的协议
Dubbo支持的协议包括dubbo://、rmi://、hessian://、http://、thrift://、rest://、redis://、gRPC://、memcached://、webservice://
- dubbo://(默认)
Dubbo 缺省协议采用单一长连接和 NIO 异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。 Dubbo协议不适合传送大数据量的服务例如:文件传输、视频传输。但是如果请求量非常少就可以适量使用
- Transporter传输:支持mina, netty, grizzy
- Serialization序列号:支持dubbo, hessian2, java, json
- Dispatcher分发器: 支持all, direct, message, execution, connection
- ThreadPool线程池: 支持fixed, cached
配置
<!-- 配置协议: --> <dubbo:protocol name="dubbo" port="20880" /> <!-- 设置默认协议: --> <dubbo:provider protocol="dubbo" /> <!-- 设置服务协议: --> <dubbo:service protocol="dubbo" /> <!-- 多端口: --> <dubbo:protocol id="dubbo1" name="dubbo" port="20880" /> <dubbo:protocol id="dubbo2" name="dubbo" port="20881" /> <!-- 配置协议完整选项: --> <dubbo:protocol name=“dubbo” port=“9090” server=“netty” client=“netty” codec=“dubbo” serialization=“hessian2” charset=“UTF-8” threadpool=“fixed” threads=“100” queues=“0” iothreads=“9” buffer=“8192” accepts=“1000” payload=“8388608” />
- rmi://RMI 协议采用 JDK 标准的
java.rmi.*
实现,采用阻塞式短连接和 JDK 标准序列化方式。特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:TCP
- 传输方式:同步传输
- 序列化:Java 标准二进制序列化
- 适用范围:传入传出参数数据包大小混合,消费者与提供者个数差不多,可传文件
- 适用场景:常规远程服务方法调用,与原生RMI服务互操作
- 约束
- 参数及返回值需实现
Serializable
接口 - dubbo 配置中的超时时间对 RMI 无效,需使用 java 启动参数设置:
-Dsun.rmi.transport.tcp.responseTimeout=3000
,参见下面的 RMI 配置
- 配置
// dubbo.properties 配置 dubbo.service.protocol=rmi // RMI配置 java -Dsun.rmi.transport.tcp.responseTimeout=3000
<!-- 定义 RMI 协议: --> <dubbo:protocol name="rmi" port="1099" /> <!-- 设置默认协议: --> <dubbo:provider protocol="rmi" /> <!-- 设置服务协议: --> <dubbo:service protocol="rmi" /> <!-- 多端口: --> <dubbo:protocol id="rmi1" name="rmi" port="1099" /> <dubbo:protocol id="rmi2" name="rmi" port="2099" /> <dubbo:service protocol="rmi1" /> <!-- Spring 兼容性: --> <dubbo:protocol name="rmi" codec="spring" />
- hessian://Hessian协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。Dubbo 的 Hessian 协议可以和原生 Hessian 服务互操作,即:
- 提供者用 Dubbo 的 Hessian 协议暴露服务,消费者直接用标准 Hessian 接口调用
- 或者提供方用标准 Hessian 暴露服务,消费方用 Dubbo 的 Hessian 协议调用。
- 特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:同步传输
- 序列化: Hessian二进制序列化
- 适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件
- 适用场景:页面传输,文件传输,或与原生hessian服务互操作
- 依赖
<dependency> <groupId>com.caucho</groupId> <artifactId>hessian</artifactId> <version>4.0.7</version> </dependency>
- 约束
- 参数及返回值需实现
Serializable
接口 - 参数及返回值不能自定义实现
List
,Map
,Number
,Date
,Calendar
等接口,只能用 JDK 自带的实现,因为 hessian 会做特殊处理,自定义实现类中的属性值都会丢失
- 配置
<!-- 定义 hessian 协议: --> <dubbo:protocol name="hessian" port="8080" server="jetty" /> <!-- 设置默认协议: --> <dubbo:provider protocol="hessian" /> <!-- 设置 service 协议: --> <dubbo:service protocol="hessian" /> <!-- 多端口: --> <dubbo:protocol id="hessian1" name="hessian" port="8080" /> <dubbo:protocol id="hessian2" name="hessian" port="8081" /> <!-- 直连: --> <dubbo:reference id="helloService" interface="HelloWorld" url="hessian://10.20.153.10:8080/helloWorld" />
- http://基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现。特性
- 连接个数:多连接
- 连接方式:短连接
- 传输协议:HTTP
- 传输方式:同步传输
- 序列化:表单序列化
- 适用范围: 传入传出参数数据包大小混合,提供者比消费者个数多,可用浏览器查看,可用表单或URL传入参数,暂不支持传文件
- 适用场景: 需同时给应用程序和浏览器 JS 使用的服务
- 约束
- 参数及返回值需符合 Bean 规范 (含有getter/setter方法)
- 配置
- 协议的端口
<dubbo:protocol port="8080" />
必须与 servlet 容器的端口相同 - 协议的上下文路径
<dubbo:protocol contextpath="foo" />
必须与 servlet 应用的上下文路径相同
<!-- 配置协议: --> <dubbo:protocol name="http" port="8080" /> <!-- 配置 Jetty Server (默认): --> <dubbo:protocol ... server="jetty" /> <!-- 配置 Servlet Bridge Server (推荐使用): --> <dubbo:protocol ... server="servlet" /> <!-- 配置 DispatcherServlet: --> <servlet> <servlet-name>dubbo</servlet-name> <servlet-class>org.apache.dubbo.remoting.http.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dubbo</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
- redis://
基于Redis实现的RPC协议。2.3.0以上版本支持。
// 注册Redis服务的地址 RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181")); registry.register(URL.valueOf("redis://10.20.153.11/com.foo.BarService?category=providers&dynamic=false&application=foo&group=member&loadbalance=consistenthash"));
<!-- 客户端引用方式 --> <dubbo:reference id="store" interface="java.util.Map" group="member" /> <!-- 点对点直连 --> <dubbo:reference id="store" interface="java.util.Map" url="redis://10.20.153.10:6379" /> <!-- 使用自定义接口 --> <dubbo:reference id="store" interface="com.foo.StoreService" url="redis://10.20.153.10:6379" /> <!-- 方法名建议和 redis 的标准方法名相同,即:get(key), set(key, value), delete(key)。 如果方法名和 redis 的标准方法名不相同,则需要配置映射关系 --> <dubbo:reference id="cache" interface="com.foo.CacheService" url="redis://10.20.153.10:6379" p:set="putFoo" p:get="getFoo" p:delete="removeFoo" />
- thrift://(不多介绍)
当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等
https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/thrift/ - rest://(不多介绍)
基于标准的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的简写)实现的REST调用支持
https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/
注册中心介绍
Dubbo对于目前主流的技术都支持作为注册中心其中包括:Multicast、Zookeeper(推荐)、Redis、Simple、Nacos
- Multicast
Multicast 注册中心不需要启动任何中心节点,只要广播地址一样,就可以互相发现
1)提供方启动时广播自己的地址
2)消费方启动时广播订阅请求
3)提供方收到订阅请求时,单播自己的地址给订阅者,如果设置了 unicast=false,则广播给订阅者
4)消费方收到提供方地址时,连接该地址进行 RPC 调用
组播受网络结构限制,只适合小规模应用或开发阶段使用。组播地址段: 224.0.0.0 - 239.255.255.255
配置
<dubbo:registry address="multicast://224.5.6.7:1234" /> <dubbo:registry protocol="multicast" address="224.5.6.7:1234" /> <!-- 为了减少广播量,Dubbo 缺省使用单播发送提供者地址信息给消费者,如果一个机器上同时启了多个消费者进程,消费者需声明 unicast=false,否则只会有一个消费者能收到消息;当服务者和消费者运行在同一台机器上,消费者同样需要声明unicast=false,否则消费者无法收到消息,导致No provider available for the service异常: --> <dubbo:registry address="multicast://224.5.6.7:1234?unicast=false" /> <dubbo:registry protocol="multicast" address="224.5.6.7:1234"> <dubbo:parameter key="unicast" value="false" /> </dubbo:registry>
- Zookeeper(推荐)
Zookeeper是 Apache Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用 。
流程说明:
- 服务提供者启动时: 向
/dubbo/com.foo.BarService/providers
目录下写入自己的 URL 地址 - 服务消费者启动时: 订阅
/dubbo/com.foo.BarService/providers
目录下的提供者 URL 地址。并向/dubbo/com.foo.BarService/consumers
目录下写入自己的 URL 地址 - 监控中心启动时: 订阅
/dubbo/com.foo.BarService
目录下的所有提供者和消费者 URL 地址。
支持以下功能:
- 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息
- 当注册中心重启时,能自动恢复注册数据,以及订阅请求
- 当会话过期时,能自动恢复注册数据,以及订阅请求
- 当设置
<dubbo:registry check="false" />
时,记录失败注册和订阅请求,后台定时重试 - 可通过
<dubbo:registry username="admin" password="1234" />
设置 zookeeper 登录信息 - 可通过
<dubbo:registry group="dubbo" />
设置 zookeeper 的根节点,不配置将使用默认的根节点。 - 支持 * 号通配符
<dubbo:reference group="*" version="*" />
,可订阅服务的所有分组和所有版本的提供者
使用
<!-- 在provider和consumer中增加zookeeper的jar依赖 --> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.3.3</version> </dependency>
在2.7.x版本已经移除了zkclient的实现,如果要使用zkclient客户端,需要自行扩展
使用zkclient客户端
从 2.2.0
版本开始缺省为 zkclient 实现,以提升 zookeeper 客户端的健壮性。
<dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency>
<!-- 配置例子 --> <dubbo:registry ... client="zkclient" />
# properties配置 dubbo.registry.client=zkclient # 或者 zookeeper://10.20.153.10:2181?client=zkclient
使用curator客户端
从 2.3.0
版本开始支持可选 curator 实现。
<dependency> <groupId>com.netflix.curator</groupId> <artifactId>curator-framework</artifactId> <version>1.1.10</version> </dependency>
<!-- 配置例子 --> <dubbo:registry ... client="curator" />
# properties配置 dubbo.registry.client=curator # 或者 zookeeper://10.20.153.10:2181?client=curator
zookeeper单机配置
<dubbo:registry address="zookeeper://10.20.153.10:2181" /> <!-- 或者 --> <dubbo:registry protocol="zookeeper" address="10.20.153.10:2181" />
zookeeper集群配置
<dubbo:registry address="zookeeper://10.20.153.10:2181?backup=10.20.153.11:2181,10.20.153.12:2181" /> <!-- 或者 --> <dubbo:registry protocol="zookeeper" address="10.20.153.10:2181,10.20.153.11:2181,10.20.153.12:2181" /> <!-- 同一 Zookeeper,分成多组注册中心 --> <dubbo:registry id="chinaRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="china" /> <dubbo:registry id="intlRegistry" protocol="zookeeper" address="10.20.153.10:2181" group="intl" />
zookeeper的使用
基本使用命令
启动后打开客户端
./bin/zkCli.sh
如果要连接的是远程的zookeeper,那么使用./bin/zkCli.sh -server ip:port
连接指定的服务器
创建节点
create命令用于创建一个zookeeper节点
create [-s][-e] path data 其中,-s或-e分别指定节点特性,顺序或临时节点,若不指定,则创建持久节点;
在执行创建节点之前,可以使用ls /
命令,那么就会显示[zookeeper]
这个是自动生成的持久节点。
节点需要一级一级的创建,不可以一下子创建多级
创建持久节点
[zk: localhost:2181(CONNECTED) 5] create /zk-seq 123 Created /zk-seq [zk: localhost:2181(CONNECTED) 6] ls / # zk-seq已经创建好 [zk-seq, zookeeper]
创建持久顺序节点
注意:
- 顺序节点创建后zookeeper默认会在节点名称后面拼接序列,这个数字序列用于标记节点的顺序。
- 顺序节点的特点就是节点后面会拼接一串数字表示和非顺序节点的区别。
[zk: localhost:2181(CONNECTED) 3] create -s /zk-test 123 Created /zk-test0000000000 [zk: localhost:2181(CONNECTED) 4] ls / # zk-test已经创建好 [zk-test0000000000, zookeeper]
创建临时节点
注意:
- 临时节点的特点是如果会话断开连接,那么节点就会自动被zookeeper删除。
[zk: localhost:2181(CONNECTED) 7] create -e /zk-temp 123 Created /zk-temp [zk: localhost:2181(CONNECTED) 8] ls / # zk-temp已经创建好 [zookeeper, zk-temp] # 退出关闭会话,然后重新登录看临时节点是否还存在 [zk: localhost:2181(CONNECTED) 9] quit Quitting... 2020-08-08 15:29:51,404 [myid:] - INFO [main:ZooKeeper@693] - Session: 0x1000009d3a80000 closed 2020-08-08 15:29:51,407 [myid:] - INFO [main-EventThread:ClientCnxn$EventThread@522] - EventThread shut down for session: 0x1000009d3a80000 # 重新登录后执行ls命令,可以看到临时节点/zk-temp已经被删除 [zk: localhost:2181(CONNECTED) 0] ls /
创建临时顺序节点
[zk: localhost:2181(CONNECTED) 1] create -s -e /zk-temp 123456 Created /zk-temp0000000003 [zk: localhost:2181(CONNECTED) 2] ls / [zk-temp0000000003, zookeeper]
读取节点
读取节点命令主要有ls path
和get path
。ls 命令可以列出zookeeper指定节点的所有子节点,但是只可以查看子一级目录。get命令可以获取zookeeper指定节点的数据和属性信息。
[zk: localhost:2181(CONNECTED) 4] get /zk-temp0000000003 # 获取临时顺序节点的信息 123456 # 节点数据内容 cZxid = 0x7 # 创建时候的事务ID ctime = Sat Aug 08 15:32:00 CST 2020 # 创建时间 mZxid = 0x7 # 修改时候的事务ID mtime = Sat Aug 08 15:32:00 CST 2020 # 修改时间 pZxid = 0x7 # 最新修改的zxid cversion = 0 # 子节点被修改版本 dataVersion = 0 # 数据版本 aclVersion = 0 # acl权限控制版本 ephemeralOwner = 0x1000009d3a80001 dataLength = 6 # 数据长度 numChildren = 0 # 子节点数
更新节点
更新节点使用set path data [version]
命令。一般不需要指定version版本。
# 以下命令非核心打印的信息省略了 [zk: localhost:2181(CONNECTED) 6] get /zk-seq # 获取节点数据 123 # 数据为123 [zk: localhost:2181(CONNECTED) 7] set /zk-seq 456 # 更新节点数据为456 [zk: localhost:2181(CONNECTED) 8] get /zk-seq # 获取节点数据 456 # 数据已经被更新为456
删除节点
删除节点使用delete path [version]
命令。
[zk: localhost:2181(CONNECTED) 0] ls / # 查看节点 [zk-seq, zk-test0000000000, zookeeper] [zk: localhost:2181(CONNECTED) 1] delete /zk-seq # 删除/zk-seq节点 [zk: localhost:2181(CONNECTED) 2] ls / # 查看节点/zk-seq节点已经不存在 [zk-test0000000000, zookeeper]
基本API操作Zookeeper
Zookeeper作为一个分布式框架,主要用于解决分布式一致性问题。所以提供了基于各种版本的原生API,那么下面我们就学习一下Zookeeper的Java API如何操作Zookeeper。
引入Jar
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.14</version> </dependesncy>
创建会话
在进行Zookeeper的节点操作之前,我们需要和Zookeeper创建一个会话连接的过程。但是Zookeeper的原生API进行会话连接是异步操作的,如果你和Zookeeper创建连接,那么Zookeeper会立刻返回给你。但是最终连接成功后会异步的通知。
// 类实现Watcher接口,Zookeeper基于这个异步通知 public class CreateSession implements Watcher { // 使用同步操作阻塞等待Zookeeper异步的通知 private static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws IOException, InterruptedException { // 参数一:Zookeeper的ip:port // 参数二:连接超时时间 // 参数三:异步通知 ZooKeeper zooKeeper = new ZooKeeper("192.168.247.100:2181", 5000, new CreateSession()); System.out.println(zooKeeper.getState()); countDownLatch.await(); System.out.println("zookeeper会话创建成功......"); } // 实现异步通知回调的方法 @Override public void process(WatchedEvent watchedEvent) { // 异步通知的事件类型有很多,只有是创建会话成功的才放行,其他的不处理 if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { countDownLatch.countDown(); } } }
创建节点
public class CreateNode implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); private static ZooKeeper zooKeeper; public static void main(String[] args) throws Exception { zooKeeper = new ZooKeeper("192.168.247.100:2181", 5000, new CreateNode()); System.out.println(zooKeeper.getState()); countDownLatch.await(); System.out.println("zookeeper会话创建成功......"); // 创建节点 createNodeSync(); Thread.sleep(Integer.MAX_VALUE); } /** * 创建节点 * 节点的类型: * CreateMode.PERSISTENT 持久节点 * CreateMode.PERSISTENT_SEQUENTIAL 持久顺序节点 * CreateMode.EPHEMERAL 临时节点 * CreateMode.EPHEMERAL_SEQUENTIAL 临时顺序节点 */ private static void createNodeSync() throws Exception { // 参数一:要创建的节点,一次只可以创建一个子节点,不可以创建多级 // 参数二:节点的内容,是一个字节数组 // 参数三:节点的权限类型 // 参数四:节点的类型,这是持久节点 String s = zooKeeper.create("/lg_persistent", "持久节点内容".getBytes("UTF-8"), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); String s1 = zooKeeper.create("/lg_persistent_sequential", "持久顺序节点内容".getBytes("UTF-8"), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL); String s2 = zooKeeper.create("/lg_ephemeral", "临时节点内容".getBytes("UTF-8"), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); String s3 = zooKeeper.create("/lg_ephemeral_sequential", "临时顺序节点内容".getBytes("UTF-8"), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println("创建的持久节点是:" + s); System.out.println("创建的持久顺序节点是:" + s1); System.out.println("创建的临时节点是:" + s2); System.out.println("创建的临时顺序节点是:" + s3); } @Override public void process(WatchedEvent watchedEvent) { if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { countDownLatch.countDown(); } } }
获取节点数据
public class GetNodeData implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); private static ZooKeeper zooKeeper; public static void main(String[] args) throws Exception { zooKeeper = new ZooKeeper("192.168.247.100:2181", 5000, new GetNodeData()); System.out.println(zooKeeper.getState()); countDownLatch.await(); System.out.println("zookeeper会话创建成功......"); // 获取节点内容 getNodeData(); // 获取所有的节点 getChildrens(); Thread.sleep(Integer.MAX_VALUE); } private static void getChildrens() throws Exception { /* 参数一:path节点路径 参数二:是否要监听,如果子节点变化会触发监听 */ List<String> children = zooKeeper.getChildren("/lg_persistent", true); System.out.println("/lg_persistent子节点为:" + children); } private static void getNodeData() throws Exception { /** * 参数一:要获取内容的节点path * 参数二:是否监听 * 参数三:版本,不填默认最新版本 */ byte[] data = zooKeeper.getData("/lg_persistent", true, null); System.out.println("获取到的节点内容为:" + new String(data)); } @Override public void process(WatchedEvent watchedEvent) { if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { countDownLatch.countDown(); } // 当字节点变化的时候,触发 if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged) { List<String> children = null; try { // true参数继续注册监听 children = zooKeeper.getChildren(watchedEvent.getPath(), true); } catch (KeeperException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(children); } } }
修改节点数据
public class UpdateNodeData implements Watcher { private static CountDownLatch countDownLatch = new CountDownLatch(1); static ZooKeeper zooKeeper; public static void main(String[] args) throws Exception { zooKeeper = new ZooKeeper("192.168.247.100:2181", 5000, new UpdateNodeData()); System.out.println(zooKeeper.getState()); countDownLatch.await(); System.out.println("zookeeper会话创建成功......"); // 修改节点内容 updateNodeData(); Thread.sleep(Integer.MAX_VALUE); } private static void updateNodeData() throws Exception { // 修改前的节点数据 byte[] data = zooKeeper.getData("/lg_persistent/c1", true, null); System.out.println("修改前的数据为:" + new String(data)); // 节点数据修改, 版本为-1表示更新最新版本数据 zooKeeper.setData("/lg_persistent/c1", "节点修改后的数据".getBytes("UTF-8"), -1); // 修改后的节点数据 byte[] data1 = zooKeeper.getData("/lg_persistent/c1", true, null); System.out.println("修改后的数据为:" + new String(data1)); } @Override public void process(WatchedEvent watchedEvent) { if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { countDownLatch.countDown(); } } }
删除节点
public class DeleteNode implements Watcher { static ZooKeeper zooKeeper; public static void main(String[] args) throws IOException, InterruptedException { zooKeeper = new ZooKeeper("192.168.247.100:2181", 5000, new DeleteNode()); System.out.println(zooKeeper.getState()); System.out.println("zookeeper会话创建成功......"); Thread.sleep(Integer.MAX_VALUE); } @Override public void process(WatchedEvent watchedEvent) { // 连接成功后删除节点 try { Stat exists = zooKeeper.exists("/lg_persistent/c1", false); System.out.println("节点是否存在:" + exists); // 删除节点 zooKeeper.delete("/lg_persistent/c1", -1); Stat exists1 = zooKeeper.exists("/lg_persistent/c1", false); System.out.println("节点是否存在:" + exists1); } catch (InterruptedException e) { e.printStackTrace(); } catch (KeeperException e) { e.printStackTrace(); } } }
开源客户端操作Zookeeper
Zookeeper的开源客户端有很多,这里只列举ZkClient进行演示。开源客户端对Zookeeper的原生API进行了大量的封装,使得我们使用起来非常的方便。
引入Jar
<dependency> <groupId>com.101tec</groupId> <artifactId>zkclient</artifactId> <version>0.10</version> </dependency>
创建会话
public class CreateSession { public static void main(String[] args) { /** * 这个创建会话的过程已经同步化 * 参数就是zookeeper的ip:port */ ZkClient zkClient = new ZkClient("192.168.247.100:2181"); System.out.println("客户端会话创建成功"); } }
创建节点
public class CreateNode { public static void main(String[] args) { ZkClient zkClient = new ZkClient("192.168.247.100:2181"); System.out.println("客户端会话创建成功"); /** * 方法名称: * createPersistent 创建持久节点 * createEphemeral 创建临时节点 * createPersistentSequential 创建持久顺序节点 * createEphemeralSequential 创建临时顺序节点 * 参数一:创建的节点path * 参数二:是否创建父节点,如果/lg_zkclient不存在,那么就创建。 */ zkClient.createPersistent("/lg_zkclient/c1", true); System.out.println("节点创建成功"); } }
获取/更新节点数据
public class GetNodeData { public static void main(String[] args) throws InterruptedException { ZkClient zkClient = new ZkClient("192.168.247.100:2181"); System.out.println("客户端会话创建成功"); // 判断节点是否存在 boolean exists = zkClient.exists("/lg_zkclient"); System.out.println("节点是否存在:" + exists); // 获取节点数据 Object readData = zkClient.readData("/lg_zkclient"); System.out.println(readData); // 创建监听事件,监听节点数据变更 zkClient.subscribeDataChanges("/lg_zkclient", new IZkDataListener() { @Override public void handleDataChange(String s, Object o) throws Exception { System.out.println(s + "节点的数据变更了, " + o); } @Override public void handleDataDeleted(String s) throws Exception { System.out.println(s + "节点被删除了"); } }); // 更新节点数据 zkClient.writeData("/lg_zkclient", "更新后的节点数据"); Thread.sleep(Integer.MAX_VALUE); } }
删除节点
public class DeleteNode { public static void main(String[] args) { ZkClient zkClient = new ZkClient("192.168.247.100:2181"); System.out.println("客户端会话创建成功"); // 删除节点 /* 方法说明: delete 删除单个节点 deleteRecursive 循环删除节点 */ zkClient.deleteRecursive("/lg_zkclient/c1"); System.out.println("节点循环删除子成功"); } }
https://dubbo.apache.org/zh/docs/v2.7/user/references/registry/zookeeper/
- Redis
基于 Redis 实现的注册中心 。 Redis 过期数据通过心跳的方式检测脏数据,服务器时间必须同步,并且对服务器有一定压力,否则过期检测会不准确 。
使用 Redis 的 Key/Map 结构存储数据结构:
- 主 Key 为服务名和类型
- Map 中的 Key 为 URL 地址
- Map 中的 Value 为过期时间,用于判断脏数据,脏数据由监控中心删除
使用 Redis 的 Publish/Subscribe 事件通知数据变更:
- 通过事件的值区分事件类型:
register
,unregister
,subscribe
,unsubscribe
- 普通消费者直接订阅指定服务提供者的 Key,只会收到指定服务的
register
,unregister
事件 - 监控中心通过
psubscribe
功能订阅/dubbo/*
,会收到所有服务的所有变更事件
调用过程:
- 服务提供方启动时,向
Key:/dubbo/com.foo.BarService/providers
下,添加当前提供者的地址 - 并向
Channel:/dubbo/com.foo.BarService/providers
发送register
事件 - 服务消费方启动时,从
Channel:/dubbo/com.foo.BarService/providers
订阅register
和unregister
事件 - 并向
Key:/dubbo/com.foo.BarService/consumers
下,添加当前消费者的地址 - 服务消费方收到
register
和unregister
事件后,从Key:/dubbo/com.foo.BarService/providers
下获取提供者地址列表 - 服务监控中心启动时,从
Channel:/dubbo/*
订阅register
和unregister
,以及subscribe
和unsubsribe
事件 - 服务监控中心收到
register
和unregister
事件后,从Key:/dubbo/com.foo.BarService/providers
下获取提供者地址列表 - 服务监控中心收到
subscribe
和unsubsribe
事件后,从Key:/dubbo/com.foo.BarService/consumers
下获取消费者地址列表
配置
<dubbo:registry address="redis://10.20.153.10:6379" /> <!-- 或者 --> <dubbo:registry address="redis://10.20.153.10:6379?backup=10.20.153.11:6379,10.20.153.12:6379" /> <!-- 或者 --> <dubbo:registry protocol="redis" address="10.20.153.10:6379" /> <!-- 或者 --> <dubbo:registry protocol="redis" address="10.20.153.10:6379,10.20.153.11:6379,10.20.153.12:6379" />
选项
- 可通过 <dubbo:registry group=“dubbo” /> 设置 redis 中 key 的前缀,缺省为 dubbo。
- 可通过 <dubbo:registry cluster=“replicate” /> 设置 redis 集群策略,缺省为 failover:
- failover: 只写入和读取任意一台,失败时重试另一台,需要服务器端自行配置数据同步
- replicate: 在客户端同时写入所有服务器,只读取单台,服务器端不需要同步,注册中心集群增大,性能压力也会更大
https://dubbo.apache.org/zh/docs/v2.7/user/references/registry/redis/
- Simple
Simple 注册中心本身就是一个普通的 Dubbo 服务,可以减少第三方依赖,使整体通讯方式一致
<!-- 将 Simple 注册中心暴露成 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://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- 当前应用信息配置 --> <dubbo:application name="simple-registry" /> <!-- 暴露服务协议配置 --> <dubbo:protocol port="9090" /> <!-- 暴露服务配置 --> <dubbo:service interface="org.apache.dubbo.registry.RegistryService" ref="registryService" registry="N/A" ondisconnect="disconnect" callbacks="1000"> <dubbo:method name="subscribe"><dubbo:argument index="1" callback="true" /></dubbo:method> <dubbo:method name="unsubscribe"><dubbo:argument index="1" callback="false" /></dubbo:method> </dubbo:service> <!-- 简单注册中心实现,可自行扩展实现集群和状态同步 --> <bean id="registryService" class="org.apache.dubbo.registry.simple.SimpleRegistryService" /> </beans>
<!-- 引用 Simple Registry 服务: --> <dubbo:registry address="127.0.0.1:9090" /> <!-- 或者: --> <dubbo:service interface="org.apache.dubbo.registry.RegistryService" group="simple" version="1.0.0" ... > <!-- 或者: --> <dubbo:registry address="127.0.0.1:9090" group="simple" version="1.0.0" />
- 此
SimpleRegistryService
只是简单实现,不支持集群,可作为自定义注册中心的参考,但不适合直接用于生产环境。 - NacosNacos 是 Dubbo 生态系统中重要的注册中心实现,其中
dubbo-registry-nacos
则是 Dubbo 融合 Nacos 注册中心的实现。Dubbo 融合 Nacos 成为注册中心的操作步骤非常简单,大致步骤可分为“增加 Maven 依赖”以及“配置注册中心“。增加Maven依赖首先,您需要将dubbo-registry-nacos
的 Maven 依赖添加到您的项目pom.xml
文件中,并且强烈地推荐您使用 Dubbo2.6.5
:
<dependencies> <!-- Dubbo Nacos registry dependency --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo-registry-nacos</artifactId> <version>0.0.2</version> </dependency> <!-- Keep latest Nacos client version --> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>[0.6.1,)</version> </dependency> <!-- Dubbo dependency --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.6.5</version> </dependency> <!-- Alibaba Spring Context extension --> <dependency> <groupId>com.alibaba.spring</groupId> <artifactId>spring-context-support</artifactId> <version>1.0.2</version> </dependency> </dependencies>
- 当项目中添加
dubbo-registry-nacos
后,您无需显式地编程实现服务发现和注册逻辑,实际实现由该三方包提供,接下来配置 Naocs 注册中心。配置注册中心配置中心配置有两种方式可以配置Dubbo Spring外部化配置(推荐)和Spring XML配置文件
- Dubbo Spring外部化配置(推荐)
Dubbo Spring 外部化配置是由 Dubbo2.5.8
引入的新特性,可通过 SpringEnvironment
属性自动地生成并绑定 Dubbo 配置 Bean,实现配置简化,并且降低微服务开发门槛。
假设您的 Nacos Server 同样运行在服务器10.20.153.10
上,并使用默认 Nacos 服务端口8848
,您只需将dubbo.registry.address
属性调整如下:
## 其他配置不用改变 ## Nacos 注册地址 dubbo.registry.address = nacos://10.20.153.10:8848
- Spring XML配置文件
与 Dubbo Spring 外部化配置 配置类似,只需要调整address
属性配置即可:
<?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://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- 提供方应用信息,用于计算依赖关系 --> <dubbo:application name="dubbo-provider-xml-demo" /> <!-- 使用 Nacos 注册中心 --> <dubbo:registry address="nacos://10.20.153.10:8848" /> </beans>
推荐用法
- 在Provider端尽量多配置Consumer端属性原因如下:
- 作服务的提供方,比服务消费方更清楚服务的性能参数,如调用的超时时间、合理的重试次数等
- 在 Provider 端配置后,Consumer 端不配置则会使用 Provider 端的配置,即 Provider 端的配置可以作为 Consumer 的缺省值。否则,Consumer 会使用 Consumer 端的全局设置,这对于 Provider 是不可控的,并且往往是不合理的。
<!-- 示例 --> <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" timeout="300" retries="2" loadbalance="random" actives="0" /> <dubbo:service interface="com.alibaba.hello.api.WorldService" version="1.0.0" ref="helloService" timeout="300" retries="2" loadbalance="random" actives="0" > <dubbo:method name="findAllPerson" timeout="10000" retries="9" loadbalance="leastactive" actives="5" /> <dubbo:service/>
- 建议在 Provider 端配置的 Consumer 端属性有:
timeout
:方法调用的超时时间retries
:失败重试次数,缺省是 2loadbalance
:负载均衡算法,缺省是随机random
。还可以配置轮询roundrobin
、最不活跃优先leastactive
和一致性哈希consistenthash
等actives
:消费者端的最大并发调用限制,即当 Consumer 对一个服务的并发调用到上限后,新调用会阻塞直到超时,在方法上配置dubbo:method
则针对该方法进行并发限制,在接口上配置dubbo:service
,则针对该服务进行并发限制
- 在Provider端配置合理的Provider端属性
<!-- 示例 --> <dubbo:protocol threads="200" /> <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" executes="200" > <dubbo:method name="findAllPerson" executes="50" /> </dubbo:service>
- 建议在 Provider 端配置的 Provider 端属性有:
threads
:服务线程池大小executes
:一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用达到上限后,新调用会阻塞,此时 Consumer 可能会超时。在方法上配置dubbo:method
则针对该方法进行并发限制,在接口上配置dubbo:service
,则针对该服务进行并发限制
- 配置管理信息
目前有负责人信息和组织信息用于区分站点。以便于在发现问题时找到服务对应负责人,建议至少配置两个人以便备份。负责人和组织信息可以在运维平台 (Dubbo Ops) 上看到。
<!-- 在应用层面配置负责人、组织信息: --> <dubbo:application owner=”ding.lid,william.liangf” organization=”intl” /> <!-- 在服务层面(服务端)配置负责人: --> <dubbo:service owner=”ding.lid,william.liangf” /> <!-- 在服务层面(消费端)配置负责人: --> <dubbo:reference owner=”ding.lid,william.liangf” />
- 配置Dubbo缓存文件
<!-- 提供者列表缓存文件: --> <dubbo:registry file=”${user.home}/output/dubbo.cache” />
- 注意:
- 可以根据需要调整缓存文件的路径,保证这个文件不会在发布过程中被清除;
- 如果有多个应用进程,请注意不要使用同一个文件,避免内容被覆盖;
- 该文件会缓存注册中心列表和服务提供者列表。配置缓存文件后,应用重启过程中,若注册中心不可用,应用会从该缓存文件读取服务提供者列表,进一步保证应用可靠性。
- 监控配置
- 使用固定端口暴露服务,而不要使用随机端口
这样在注册中心推送有延迟的情况下,消费者通过缓存列表也能调用到原地址,保证调用成功。 - 使用 Dubbo Admin 监控注册中心上的服务提供方
使用 Dubbo Admin 监控服务在注册中心上的状态,确保注册中心上有该服务的存在。 - 服务提供方可使用 Dubbo Qos 的 telnet 或 shell 监控项
监控服务提供者端口状态:echo status | nc -i 1 20880 | grep OK | wc -l
,其中的 20880 为服务端口 - 服务消费方可通过将服务强制转型为 EchoService,并调用
$echo()
测试该服务的提供者是可用
如assertEqauls(“OK”, ((EchoService)memberService).$echo(“OK”));
- 不要使用dubbo.properties文件配置,推荐使用XML配置
Dubbo 中所有的配置项都可以配置在 Spring 配置文件中,并且可以针对单个服务配置。
的Provider端属性
<!-- 示例 --> <dubbo:protocol threads="200" /> <dubbo:service interface="com.alibaba.hello.api.HelloService" version="1.0.0" ref="helloService" executes="200" > <dubbo:method name="findAllPerson" executes="50" /> </dubbo:service>
建议在 Provider 端配置的 Provider 端属性有:
threads
:服务线程池大小executes
:一个服务提供者并行执行请求上限,即当 Provider 对一个服务的并发调用达到上限后,新调用会阻塞,此时 Consumer 可能会超时。在方法上配置dubbo:method
则针对该方法进行并发限制,在接口上配置dubbo:service
,则针对该服务进行并发限制
- 配置管理信息
目前有负责人信息和组织信息用于区分站点。以便于在发现问题时找到服务对应负责人,建议至少配置两个人以便备份。负责人和组织信息可以在运维平台 (Dubbo Ops) 上看到。
<!-- 在应用层面配置负责人、组织信息: --> <dubbo:application owner=”ding.lid,william.liangf” organization=”intl” /> <!-- 在服务层面(服务端)配置负责人: --> <dubbo:service owner=”ding.lid,william.liangf” /> <!-- 在服务层面(消费端)配置负责人: --> <dubbo:reference owner=”ding.lid,william.liangf” />
- 配置Dubbo缓存文件
<!-- 提供者列表缓存文件: --> <dubbo:registry file=”${user.home}/output/dubbo.cache” />
- 注意:
- 可以根据需要调整缓存文件的路径,保证这个文件不会在发布过程中被清除;
- 如果有多个应用进程,请注意不要使用同一个文件,避免内容被覆盖;
- 该文件会缓存注册中心列表和服务提供者列表。配置缓存文件后,应用重启过程中,若注册中心不可用,应用会从该缓存文件读取服务提供者列表,进一步保证应用可靠性。
- 监控配置
- 使用固定端口暴露服务,而不要使用随机端口
这样在注册中心推送有延迟的情况下,消费者通过缓存列表也能调用到原地址,保证调用成功。 - 使用 Dubbo Admin 监控注册中心上的服务提供方
使用 Dubbo Admin 监控服务在注册中心上的状态,确保注册中心上有该服务的存在。 - 服务提供方可使用 Dubbo Qos 的 telnet 或 shell 监控项
监控服务提供者端口状态:echo status | nc -i 1 20880 | grep OK | wc -l
,其中的 20880 为服务端口 - 服务消费方可通过将服务强制转型为 EchoService,并调用
$echo()
测试该服务的提供者是可用
如assertEqauls(“OK”, ((EchoService)memberService).$echo(“OK”));
- 不要使用dubbo.properties文件配置,推荐使用XML配置
Dubbo 中所有的配置项都可以配置在 Spring 配置文件中,并且可以针对单个服务配置。