一文速通dubbo基础
一:RPC理论
1:什么是RPC
1.1:RPC简介
RPC(Remote Procedure Call Protocol)-远程过程调用协议。
通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
它假定某种传输协议的存在,如TCP,UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层,因分布式,微服务等而兴起
其实简单点来理解,就是比如有一个应用1,通过网络调用了应用2的服务,而不用关心应用2的协议
WebService,restful接口调用其实都是RPC,只是它们的消息组织方式和消息协议不同而已
1.2:为什么是远程过程调用协议
这里的过程指的是业务处理,计算任务,更直白一点,就是程序,就像是在调用本地方法一样
远程调用中间需要网络,而因为网络,响应会慢上好几个数量级,并且不像本地那么可靠
1.3:RPC的C/S模式
RPC采用client-server结构,通过request-response消息模式实现
如果服务器的程序进行了更新,那客户端方面也需要同步更新才能正常使用
RPC的三个过程
- 通讯协议:类似于中国需要联系美国的朋友一样,我们会有很多联系他的方式,比如打电话,开微信等等
- 寻址:我们需要怎么联系,比如打电话我们需要知道电话号码,自己需要知道美国在哪儿这样的,也就是必须知道一个具体的地址
- 数据序列化:序列化的作用也很简单,比如我们已经拨通了这个外国人的电话,但是我说的是中文,他说的是英语,相互听不懂,就没法沟通了,因此序列化就是一个翻译的功能
将上面这三点做好了,RPC才能正常的工作
1.4:RPC & MQ的区别
在功能上,RPC就像打电话,需要一个迅速的回应(接通或者不接通),而MQ就像发微信QQ的消息,发过去了,不急着等回
而架构上的差异是:MQ有一个中间节点Queue作为消息存储点【例如RocketMQ的broker-topic】:
- RPC是同步调用,对于要等待返回结果的实例,RPC处理的比较自然,由于等待结果时Consumer会有线程消耗,如果以异步的方式使用,可以避免Consumer的线程消耗,但它不能做到像MQ一样暂存请求,压力会堆积在服务Provider那
- 而MQ会把请求的压力暂缓,让处理者根据自己的节奏处理,但是由于消息是暂存在了它的队列中,所以这个系统的可靠性会受到这个队列的影响,它是异步单向,发送消息设计为不需要等待消息处理的完成,所以如果有需要设计为同步返回的功能的话,MQ就会变得不好使用
所以如果是非常着急需要等待返回结果的,又或者说是希望使用方便简单,RPC就比较好用,它的使用基于接口,类似于本地调用,异步编程会相对复杂,比如gRPC。
而不希望发送端受限于处理端的速度时,就使用MQ。随着业务增长,也会出现处理端的处理速度不够,从而进行同步调用到异步消息的改造
2:RPC的流程及其协议
2.1:RPC的流程
- 客户端处理过程中调用client stub(就像调用本地方法一样),传入参数
- Client stub将参数编组为消息,然后通过系统调用想服务端发送消息
- 客户端本地操作系统将消息从客户端及其发送到服务端的及其
- 服务端操作系统将接受到的数据包传递给client stub
- server stub解组消息为参数
- server stub再调用服务端的过程,过程执行结果以反方向的相同步骤响应给客户端、
stub是分布式计算中的存根是一段代码,它转换在远程过程调用期间Client & Server之间传递的参数
2.2:整个流程从中需要注意处理的问题
- client stub和server stub的开发问题
- 参数如何编组和解组
- 消息如何发送
- 过程结果如何表示,异常情况如何处理
- 如何实现安全的访问控制
2.3:RPC协议
RPC调用过程中需要将参数编组为消息进行发送,接收方需要解组消息为参数,过程处理结果同样需要经过编组解组
消息由哪些部分构成及消息的表示形式就构成了消息协议。
RPC调用过程中采用的消息协议成为RPC协议
RPC是规定要做的事,RPC协议规定请求响应消息的格式,在TCP之上我们可以选用或自定义消息协议来完成我们的RPC交互,此时我们可以选用http或者https这种通用的标准协议,也可以根据自身的需要定义自己的消息协议(较多)
3:RPC协议
RPC框架封装好了参数编组解组,底层网络通信的RPC程序开发框架,让我们可以直接在其基础之上只需要专注于过程代码编写
Java领域的比较常见的RPC框架:
- webService
- Apache的CXF
- Axis2
- Java自带的jax-ws
- 微服务常见的dubbo,spring cloud,Apache Thrift,ICE
- google的GRPC等
3.1:RPC框架服务的暴露
远程提供者需要以某种形式提供服务调用相关的信息,包括但不限于服务接口定义,数据结构,或者中间态的服务定义文件,服务的调用者需要通过一定的途径或者远程服务调用的相关信息
其实简单点说,就是需要告诉别人怎么调用服务
3.2:RPC框架的远程代理对象
代理处理技术:服务调用者用的服务实际是远程服务的本地代理,其实就是通过动态代理来实现
Java里至少提供了两种方式来提供动态代码生成,一种是jdk动态代理,另一种是字节码生成【CGlib动态代理】
动态代理相比字节码生成使用起来更方便,但动态代理方式在性能上比字节码要差,而字节码生成在代码可读性上要差很多,所以我们一般都是牺牲一些性能来获得代码可读性和可维护性的提高
3.3:RPC框架的通信
RPC框架通信和具体的协议是无关的,它可基于HTTP或TCP协议,webService就是基于http协议的RPC,具有更好的跨平台性,但性能却不如基于TCP协议的RPC
NIO其实不一定会比BIO性能要高,NIO只是为了支持高并发而已,特点就是非阻塞的,适合小数据量高并发的场景,大数据包情况下,BIO会更为合适
3.4:RPC框架的序列化
两个方面会直接影响RPC的性能,一是传输方式,二是序列化
二:dubbo概述
1:dubbo简介
和zookeeper类似,网址只需要记住
首先这个项目叫dubbo,然后它是属于apache下的,然后开源是非营利性组织,也就是org
所以网址就是:http://dubbo.apache.org/zh-cn/
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。
使用 Dubbo 开发的微服务原生具备相互之间的远程地址发现与通信能力, 利用 Dubbo 提供的丰富服务治理特性,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。
Dubbo 被设计为高度可扩展,用户可以方便的实现流量拦截、选址的各种定制逻辑。
在云原生时代,Dubbo 相继衍生出了 Dubbo3、Proxyless Mesh 等架构与解决方案,在易用性、超大规模微服务实践、云原生基础设施适配、安全性等几大方向上进行了全面升级
Apache Dubbo是一款高性能,轻量级的开源java RPC框架,提供了3大核心能力:
- 面向接口的远程方法调用
- 智能容错和负载均衡
- 服务自动注册及发现。
1.1:Dubbo和Spring Cloud的关系
从上图我们可以看出,Dubbo 和 Spring Cloud 有很多相似之处,它们都在整个架构图的相同位置并提供一些相似的功能。
- Dubbo 和 Spring Cloud 都侧重在对分布式系统中常见问题模式的抽象(如服务发现、负载均衡、动态配置等),同时对每一个问题都提供了配套组件实现,形成了一套微服务整体解决方案,让使用 Dubbo 及 Spring Cloud 的用户在开发微服务应用时可以专注在业务逻辑开发上。
- Dubbo 和 Spring Cloud 都完全兼容 Spring 体系的应用开发模式,Dubbo 对 Spring 应用开发框架、Spring Boot 微服务框架都做了很好的适配,由于 Spring Cloud 出自 Spring 体系,在这一点上自然更不必多说。
虽然两者有很多相似之处,但由于它们在诞生背景与架构设计上的巨大差异,两者在性能、适用的微服务集群规模、生产稳定性保障、服务治理等方面都有很大差异。
Spring Cloud 的优势在于:
- 同样都支持 Spring 开发体系的情况下,Spring Cloud 得到更多的原生支持
- 对一些常用的微服务模式做了抽象如服务发现、动态配置、异步消息等,同时包括一些批处理任务、定时任务、持久化数据访问等领域也有涉猎。
- 基于 HTTP 的通信模式,加上相对比较完善的入门文档和演示 demo 和 starters,让开发者在第一感觉上更易于上手
Spring Cloud 的问题有:
- 只提供抽象模式的定义不提供官方稳定实现,开发者只能寻求类似 Netflix、Alibaba、Azure 等不同厂商的实现套件,而每个厂商支持的完善度、稳定性、活跃度各异
- 有微服务全家桶却不是能拿来就用的全家桶,demo 上手容易,但落地推广与长期使用的成本非常高
- 欠缺服务治理能力,尤其是流量管控方面如负载均衡、流量路由方面能力都比较弱
- 编程模型与通信协议绑定 HTTP,在性能、与其他 RPC 体系互通上存在障碍
- 总体架构与实现只适用于小规模微服务集群实践,当集群规模增长后就会遇到地址推送效率、内存占用等各种瓶颈的问题,但此时迁移到其他体系却很难实现
- 很多微服务实践场景的问题需要用户独自解决,比如优雅停机、启动预热、服务测试,再比如双注册、双订阅、延迟注册、服务按分组隔离、集群容错等
而以上这些点,都是 Dubbo 的优势所在:
- 完全支持 Spring & Spring Boot 开发模式,同时在服务发现、动态配置等基础模式上提供与 Spring Cloud 对等的能力。
- 是企业级微服务实践方案的整体输出,Dubbo 考虑到了企业微服务实践中会遇到的各种问题如优雅上下线、多注册中心、流量管理等,因此其在生产环境的长期维护成本更低
- 在通信协议和编码上选择更灵活,包括 rpc 通信层协议如 HTTP、HTTP/2(Triple、gRPC)、TCP 二进制协议、rest等,序列化编码协议Protobuf、JSON、Hessian2 等,支持单端口多协议。
- Dubbo 从设计上突出服务服务治理能力,如权重动态调整、标签路由、条件路由等,支持 Proxyless 等多种模式接入 Service Mesh 体系
- 高性能的 RPC 协议编码与实现,
- Dubbo 是在超大规模微服务集群实践场景下开发的框架,可以做到百万实例规模的集群水平扩容,应对集群增长带来的各种问题
- Dubbo 提供 Java 外的多语言实现,使得构建多语言异构的微服务体系成为可能
1.2:Dubbo和GRPC的关系
Dubbo 与 gRPC 最大的差异在于两者的定位上:
- gRPC 定位为一款 RPC 框架,Google 推出它的核心目标是定义云原生时代的 rpc 通信规范与标准实现;
- Dubbo 定位是一款微服务开发框架,它侧重解决微服务实践从服务定义、开发、通信到治理的问题,因此 Dubbo 同时提供了 RPC 通信、与应用开发框架的适配、服务治理等能力。
Dubbo 不绑定特定的通信协议,即 Dubbo 服务间可通过多种 RPC 协议通信并支持灵活切换。因此,你可以在 Dubbo 开发的微服务中选用 gRPC 通信,Dubbo 完全兼容 gRPC,并将 gRPC 设计为内置原生支持的协议之一。
2:dubbo能做什么
按照微服务架构的定义,采用它的组织能够很好的提高业务迭代效率与系统稳定性,但前提是要先能保证微服务按照期望的方式运行,要做到这一点需要解决服务拆分与定义、数据通信、地址发现、流量管理、数据一致性、系统容错能力等一系列问题。
Dubbo 可以帮助解决如下微服务实践问题:
- 微服务编程范式和工具:Dubbo 支持基于 IDL 或语言特定方式的服务定义,提供多种形式的服务调用形式(如同步、异步、流式等)
- **高性能的RPC通信:**Dubbo 帮助解决微服务组件之间的通信问题,提供了基于 HTTP、HTTP/2、TCP 等的多种高性能通信协议实现,并支持序列化协议扩展,在实现上解决网络连接管理、数据传输等基础问题
- 服务的监控和治理:Dubbo 官方提供的服务发现、动态配置、负载均衡、流量路由等基础组件可以很好的帮助解决微服务基础实践的问题。除此之外,您还可以用 Admin 控制台监控微服务状态,通过周边生态完成限流降级、数据一致性、链路追踪等能力。
- 部署在多种环境:Dubbo 服务可以直接部署在容器、Kubernetes、Service Mesh等多种架构下
同时Dubbo拥有活跃的社区和庞大的用户群体
3:dubbo架构
- Provider:服务的提供者,我们的service,实际执行业务逻辑的服务层
- Consumer:服务的消费者【调用者】,对service进行调用,不关心service的具体的实现
- Registry:注册中心,返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- Monitor:服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
3.1:调用顺序说明
- 服务容器负责启动,加载,运行服务提供者。服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
3.2:下面是一些说明
- 首先注册中心和监控中心是可选的,你可以不要监控,也不要注册中心,直接在配置文件里面写然后提供方和消费方直连。
- 然后注册中心、提供方和消费方之间都是长连接,和监控方不是长连接,并且消费方是直接调用提供方,不经过注册中心。
- 就算注册中心和监控中心宕机了也不会影响到已经正常运行的提供者和消费者,因为消费者有本地缓存提供者的信息。
3.3:分层架构
SPI
SPI(Service Provider Interface),是 JDK 内置的一个服务发现机制,它使得接口和具体实现完全解耦。
我们只声明接口,具体的实现类在配置中选择。
具体的就是你定义了一个接口,然后在META-INF/services目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。这样就通过配置来决定具体用哪个实现!
3.4:调用过程
先从服务提供者开始,看看它是如何工作的。
- 首先 Provider 启动,通过 Proxy 组件根据具体的协议 Protocol 将需要暴露出去的接口封装成 Invoker(核心的组件,代表一个可执行体)。
- 然后再通过 Exporter 包装一下,这是为了在注册中心暴露自己套的一层,然后将 Exporter 通过 Registry 注册到注册中心。
这就是整体服务暴露过程。
然后是服务消费的过程:
- 首先消费者启动会向注册中心拉取服务提供者的元信息,然后调用流程也是从 Proxy 开始,毕竟都需要代理才能无感知。
- Proxy 持有一个 Invoker 对象,调用 invoke 之后需要通过 Cluster 先从 Directory 获取所有可调用的远程服务的 Invoker 列表 - 如果配置了某些路由规则,比如某个接口只能调用某个节点的那就再过滤一遍 Invoker 列表。
- 剩下的 Invoker 再通过 LoadBalance 做负载均衡选取一个。
- 然后再经过 Filter 做一些统计什么的,再通过 Client 做数据传输,比如用 Netty 来传输。
- 传输需要经过 Codec 接口做协议构造,再序列化。最终发往对应的服务提供者。服务提供者接收到之后也会进行 Codec 协议处理,然后反序列化后将请求扔到线程池处理。
- 某个线程会根据请求找到对应的 Exporter ,而找到 Exporter 其实就是找到了 Invoker,但是还会有一层层 Filter,经过一层层过滤链之后最终调用实现类然后原路返回结果。
三:dubbo-demo
1:zookeeper安装测试
1️⃣ 下载zookeeper3.4.13,当然也可以选择其他的版本
2️⃣ 解压之后,在下面创建一个mydata用于存储
3️⃣ 设置配置文件:进入conf目录,复制一份zoo_sample.cfg,重命名为zoo.cfg
修改配置中的内容
4️⃣ 到bin中,启动zkServer.cmd
然后启动zkCli.cmd,测试,具体的参数,请查看上面的Zookeeper介绍
2:zk + dubbo示例
2.1:父pom构建
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 注意父模块中打包方式为pom,表示该模块是一个父模块,不进行打包 -->
<packaging>pom</packaging>
<!--模块-->
<modules>
<module>dubbo-demo-api</module> <!-- 模块:dubbo-demo-api -->
<module>dubbo-demo-provider</module> <!-- 模块:dubbo-demo-provider -->
<module>dubbo-demo-consumer</module> <!-- 模块:dubbo-demo-consumer -->
</modules>
<!--统一版本管理-->
<properties>
<!-- 指定编译源码时使用的 Java 版本 -->
<maven.compiler.source>17</maven.compiler.source>
<!-- 指定编译目标字节码的 Java 版本 -->
<maven.compiler.target>17</maven.compiler.target>
<!-- 指定项目编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Spring Boot Web 模块版本,提供 Web 开发支持,包括 Spring MVC、嵌入式 Tomcat 等 -->
<spring-boot-starter-web.version>2.6.3</spring-boot-starter-web.version>
<!-- Spring Boot 核心启动器版本,提供 Spring Boot 的基础功能 -->
<spring-boot-starter.version>2.6.3</spring-boot-starter.version>
<!-- Curator Recipes 版本,Curator 是 Zookeeper 的客户端库,提供更易用的 API 和高级功能 -->
<curator-recipes.version>5.1.0</curator-recipes.version>
<!--Dubbo-->
<!-- Dubbo Spring Boot 启动器版本,简化 Dubbo 与 Spring Boot 的集成 -->
<dubbo-spring-boot-starter.version>3.2.0</dubbo-spring-boot-starter.version>
<!-- Dubbo Zookeeper 注册中心版本,用于 Dubbo 服务的注册和发现 -->
<dubbo-registry-zookeeper.version>3.2.0</dubbo-registry-zookeeper.version>
<!-- Dubbo Common 版本,提供 Dubbo 的通用工具和基础功能 -->
<dubbo-common.version>3.2.0</dubbo-common.version>
<!--zookeeper-->
<zookeeper.version>3.7.0</zookeeper.version>
<!--curator-framework-->
<curator-framework.version>5.1.0</curator-framework.version>
<!-- Lombok 版本,用于简化 Java 代码,提供注解来省略 getter、setter 等模板代码 -->
<lombok.version>1.18.36</lombok.version>
</properties>
<!--统一版本管理-->
<dependencyManagement>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-starter-web.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring-boot-starter.version}</version>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${dubbo-common.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo-registry-zookeeper.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo-spring-boot-starter.version}</version>
</dependency>
<!-- Curator -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator-recipes.version}</version>
</dependency>
<!-- Zookeeper 依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
</dependency>
<!-- Curator 依赖 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator-framework.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.2:api模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>dubbo-demo-api</artifactId>
<properties>
<!-- 指定编译源码时使用的 Java 版本 -->
<maven.compiler.source>17</maven.compiler.source>
<!-- 指定编译目标字节码的 Java 版本 -->
<maven.compiler.target>17</maven.compiler.target>
<!-- 指定项目编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--依赖包-->
<dependencies>
<!-- Dubbo公共包 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
</dependency>
<!-- curator -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<!-- SpringWeb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Zookeeper -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<!-- DubboStarter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!-- Zookeeper 依赖 -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<exclusions>
<!-- 排除SLF4J-Log4j绑定 -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Curator 依赖 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
vo.ApiResponse
package com.civets.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 统一响应结果封装类
* @author cuihaida
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponse<T> {
/**
* 状态码
*/
private int code;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
/**
* 成功响应构建方法
* @param data 响应数据
* @param <T> 泛型
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "操作成功", data);
}
/**
* 成功响应构建方法(无数据)
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> success() {
return new ApiResponse<>(200, "操作成功", null);
}
/**
* 自定义成功响应构建方法
* @param code 自定义状态码
* @param message 自定义消息
* @param data 响应数据
* @param <T> 泛型
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> success(int code, String message, T data) {
return new ApiResponse<>(code, message, data);
}
/**
* 失败响应构建方法
* @param code 状态码
* @param message 错误消息
* @param <T> 泛型
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> fail(int code, String message) {
return new ApiResponse<>(code, message, null);
}
/**
* 自定义失败响应构建方法(带数据)
* @param code 状态码
* @param message 错误消息
* @param data 响应数据
* @param <T> 泛型
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> fail(int code, String message, T data) {
return new ApiResponse<>(code, message, data);
}
/**
* 常规失败响应构建方法
* @param <T> 泛型
* @return ApiResponse对象
*/
public static <T> ApiResponse<T> fail() {
return new ApiResponse<>(500, "服务器内部错误", null);
}
}
ReflectServiceApi
package com.civets;
import org.springframework.stereotype.Service;
/**
* @author cuihaida
*/
@Service
public interface ReflectServiceApi {
/**
* 测试接口
**/
String reflectMessage(String message);
}
utils.ZookeeperHealthChecker
package com.civets.utils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* @author cuihaida
*/
public class ZookeeperHealthChecker {
public static void check(String host, int port) {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(host, port), 2000);
System.out.println("✅ Zookeeper 正在运行:" + host + ":" + port);
} catch (IOException e) {
System.err.println("❌ Zookeeper 未启动或连接失败:" + host + ":" + port);
System.err.println("💡 请先确保 Zookeeper 已启动");
}
}
}
2.3:provider
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>dubbo-demo-provider</artifactId>
<properties>
<!-- 指定编译源码时使用的 Java 版本 -->
<maven.compiler.source>17</maven.compiler.source>
<!-- 指定编译目标字节码的 Java 版本 -->
<maven.compiler.target>17</maven.compiler.target>
<!-- 指定项目编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--依赖包-->
<dependencies>
<!--API-->
<dependency>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
application.yml
dubbo:
application:
name: dubbo-demo-provider
protocol:
name: dubbo
port: 20880
registry:
address: zookeeper://192.168.10.3:2181
scan:
base-packages: com.civets
spring:
application:
name: dubbo-demo-provider
server:
port: 9100
ProviderApplication
package com.civets;
import com.civets.utils.ZookeeperHealthChecker;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class ProviderApplication {
public static void main(String[] args) {
//检查zookeeper服务器状态
ZookeeperHealthChecker.check("192.168.10.3", 2181);
SpringApplication.run(ProviderApplication.class, args);
System.err.println("生产者启动成功");
}
}
service
package com.civets.com.civets.service;
import org.springframework.stereotype.Service;
/**
* @author cuihaida
*/
@Service
public interface ReflectService {
// 在这里可以写一些自己的方法,不暴露到外部的,仅供内部调用
}
package com.civets.com.civets.service.impl;
import com.civets.ReflectServiceApi;
import com.civets.com.civets.service.ReflectService;
import org.apache.dubbo.config.annotation.DubboService;
@DubboService
public class ReflectServiceImpl implements ReflectServiceApi, ReflectService {
@Override
public String reflectMessage(String name) {
System.err.println("[生产者服务:服务实现类] 收到来自 [生产者服务控制器] 的调用,正在处理并返回数据"+"返回体为:"+"你的名字叫:"+ name);
return "你的名字叫:"+ name;
}
}
controller
package com.civets.com.civets.comtroller;
import org.springframework.web.bind.annotation.RestController;
/**
* @author cuihaida
*/
@RestController
public class DubboTestController {
// 在这里添加生产者自己内部的controller
}
2.4:consumer
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>dubbo-demo-consumer</artifactId>
<properties>
<!-- 指定编译源码时使用的 Java 版本 -->
<maven.compiler.source>17</maven.compiler.source>
<!-- 指定编译目标字节码的 Java 版本 -->
<maven.compiler.target>17</maven.compiler.target>
<!-- 指定项目编码格式 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--依赖包-->
<dependencies>
<!--引入API模块-->
<dependency>
<groupId>com.civets</groupId>
<artifactId>dubbo-demo-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
application.yml
# Spring 配置
---
spring:
application:
name: dubbo-demo-consumer # Spring 应用程序名称
---
# Dubbo 配置
---
dubbo:
application:
name: dubbo-demo-consumer # 应用程序名称,用于在注册中心中标识应用
qos-enable: false # 是否启用 QoS(Quality of Service)服务,用于监控 Dubbo 服务的运行状态
registry: # 注册中心配置
address: zookeeper://192.168.10.3:2181 # Zookeeper注册中心地址
consumer:
check: true # 是否检查服务提供者是否存在,默认为 true
protocol:
port: 20990 # 服务提供者暴露的端口
---
# 配置嵌入式 Tomcat 的端口
---
server:
port: 9000
---
ConsumerApplication
package com.civets;
import com.civets.utils.ZookeeperHealthChecker;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author cuihaida
*/
@SpringBootApplication
@EnableDubbo
public class ConsumerApplication {
public static void main(String[] args) {
//检查zookeeper服务器状态
ZookeeperHealthChecker.check("192.168.10.3", 2181);
SpringApplication.run(ConsumerApplication.class, args);
System.err.println("[消费者模块] 启动成功");
System.out.println("请访问: "+"localhost:9000/test/狸猫玖玖");
}
}
controller.ReflectController
package com.civets.controller;
import com.civets.ReflectServiceApi;
import com.civets.vo.ApiResponse;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author cuihaida
*/
@RestController
public class ReflectController {
@DubboReference
private ReflectServiceApi reflectServiceApi;
/**
* 测试接口 接收http请求,调用内部生产者服务
* @param message 用户输入的消息
* @return 返回用户输入的消息
*/
@GetMapping("/test/{message}")
public ApiResponse reflectMessage(@PathVariable String message){
System.err.println("[消费者服务:控制器] 收到http请求,正在向内部生产者服务发起RPC调用"+"请求的路径参数为:"+message);
return ApiResponse.success(200,"请求成功",reflectServiceApi.reflectMessage(message));
}
}
3:nacos + dubbo示例
写好后注意启动顺序
- MySQL (nacos持久化的依赖)
- Nacos
- Dubbo-admin-service
- Dubbo-admin-ui
- consumer
- provider
- provider2
3.1:dubbo-admin的安装
https://github.com/apache/dubbo-admin/releases
Dubbo-Admin 是基于 Vue和 Java开发的,下载源码后想要运行就需要依赖以下环境
- Node v16.20.2 【18会报错】
- Jdk 1.8
- Maven
1️⃣ 前端启动
进入下面的路径执行npm 或 yarn 命令
/dubbo-admin-0.6.0/dubbo-admin-ui
npm install / yarn install
npm run dev / yarn run dev
2️⃣ 后端启动
配置文件修改为如下,然后用 idea 打开 dubbo-admin-server 项目,下载maven依赖后启动
server.port=38080
dubbo.protocol.port=30880
dubbo.application.qos-port=32222
# centers in dubbo, if you want to add parameters, please add them to the url
admin.registry.address=nacos://127.0.0.1:8848?namespace=70b158a6-7e64-478d-8c5b-699089aa81d1
admin.config-center=nacos://127.0.0.1:8848?namespace=70b158a6-7e64-478d-8c5b-699089aa81d1
admin.metadata-report.address=nacos://127.0.0.1:8848?namespace=70b158a6-7e64-478d-8c5b-699089aa81d1
# dubbo-admin 登录账号密码
admin.root.user.name=root
admin.root.user.password=root
#session timeout, default is one hour
admin.check.sessionTimeoutMilli=3600000
#compress
server.compression.enabled=true
server.compression.mime-types=text/css,text/javascript,application/javascript
server.compression.min-response-size=10240
#token timeout, default is one hour
admin.check.tokenTimeoutMilli=3600000
#Jwt signingKey
admin.check.signSecret=86295dd0c4ef69a1036b0b0c15158d77
#dubbo config
dubbo.application.name=dubbo-admin
dubbo.registry.address=${admin.registry.address}
#dubbo.registry.parameters.namespace=70b158a6-7e64-478d-8c5b-699089aa81d1
# id generate type
mybatis-plus.global-config.db-config.id-type=none
dubbo.application.logger=slf4j
3.2:配置文件声明
dubbo-provider
server:
port: 5656 # server-port
dubbo:
application:
id: xdx-dubbo3-provider
name: xdx-dubbo3-provider
serialize-check-status: WARN
protocol: # dubbo协议声明
id: dubbo
name: dubbo
host: 127.0.0.1
port: 7788
serialization: hessian2
registry: # dubbo注册中心声明使用nacos
address: nacos://${nacos.config.server-addr}
parameters.namespace: ${nacos.config.namespace}
dubboParams: xdx971
dubbo-provider2
server:
port: 5657
dubbo:
application:
id: xdx-dubbo3-provider
name: xdx-dubbo3-provider
serialize-check-status: WARN
protocol:
id: dubbo
name: dubbo
host: 127.0.0.1
port: 7789
serialization: hessian2
registry:
address: nacos://${nacos.config.server-addr}
parameters.namespace: ${nacos.config.namespace}
dubboParams: xdx
dubbo-consumer
dubbo:
application:
id: xdx-dubbo3-consumer
name: xdx-dubbo3-consumer
serialize-check-status: WARN
registry:
address: nacos://${nacos.config.server-addr}
parameters.namespace: ${nacos.config.namespace}
3.3:dubbo-demo搭建
3.4:父pom版本声明
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xdx97</groupId>
<artifactId>dubbo3-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>dubbo3-provider</module>
<module>dubbo3-consumer</module>
<module>dubbo3-api</module>
<module>dubbo3-provider2</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.boot.version>2.7.17</spring.boot.version>
<nacos.boot.version>0.2.12</nacos.boot.version>
<dubbo.version>3.2.9</dubbo.version>
</properties>
</project>
3.5:api模块实现
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo3-demo</artifactId>
<groupId>com.xdx97</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo3-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
DemoService.java - 接口定义
package com.xdx97.dubbo3.api;
public interface DemoService {
String getString();
}
3.6:provider模块实现
dubbo3-provider2 的代码和 dubbo3-provider是一样的,创建2个 provider是为了验证它的负载均衡
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo3-demo</artifactId>
<groupId>com.xdx97</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo3-provider</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos配置中心依赖-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos.boot.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>org.javassist</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- dubbo整合nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- 自定义api -->
<dependency>
<groupId>com.xdx97</groupId>
<artifactId>dubbo3-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
启动类上注解声明
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableDubbo
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
DemoServiceImpl —服务实现类
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.xdx97.dubbo3.api.DemoService;
import org.apache.dubbo.config.annotation.DubboService;
import java.util.concurrent.atomic.AtomicLong;
@DubboService(version = "1.0.0")
public class DemoServiceImpl implements DemoService {
// nacos.config.autoRefresh = true 这个配置要打才可以
@NacosValue(value = "${dubboParams}", autoRefreshed = true)
private String dubboParams;
private AtomicLong atomicLong = new AtomicLong(0);
@Override
public String getString() {
long l = atomicLong.incrementAndGet();
try {
Thread.sleep(1000 - (100 * l));
}catch (Exception e) {
e.printStackTrace();
}
return dubboParams;
}
}
nacos 配置声明
nacos:
config:
# 指定命名空间
namespace: 70b158a6-7e64-478d-8c5b-699089aa81d1
#配置服务地址
server-addr: 127.0.0.1:8848
accessKey:
secretKey:
#data-ids 为新增加的data-id
data-ids: dubbo3-provider
#配置类型
type: yaml
#是否启动刷新配置
autoRefresh: true
#运行时启用
bootstrap:
enable: true
3.7:consumer模块实现
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo3-demo</artifactId>
<groupId>com.xdx97</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo3-consumer</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- spring boot starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos配置中心依赖-->
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos.boot.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.version}</version>
<exclusions>
<exclusion>
<artifactId>javassist</artifactId>
<groupId>org.javassist</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- dubbo整合nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- 自定义api -->
<dependency>
<groupId>com.xdx97</groupId>
<artifactId>dubbo3-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
controller测试
import com.xdx97.dubbo3.api.DemoService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoConsumer {
@DubboReference(version = "1.0.0", timeout = 1000, retries = 1)
private DemoService demoService;
@GetMapping("/fun-test")
public String funTest() {
return demoService.getString();
}
}
四:dubbo四大常用配置
1:启动时检查配置check
- 参数:
check=true/false
- 位置:服务消费端
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=“true”。
可以通过 check=“false” 关闭检查
比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查
*/
@DubboReference(interfaceClass = UserService.class, check = false)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
2:重试次数配置retries
- 参数:
retries=xxx
【不含第一次】 - 位置:服务消费端
失败自动切换,当出现失败,重试其它服务器,但重试会带来更长延迟。
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查,指定失败之后最多重试3次
*/
@DubboReference(interfaceClass = UserService.class, check = false, retries = 3)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
3:超时时间配置timeout
- 参数:
timeout=xxx
- 位置:服务端和消费端都要配置
由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。
为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间**。**
// ========== 服务提供方配置 ===========
@DubboService(interfaceClass = UserService.class, timeout = 5000)
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
// ========== 服务消费方配置 =============
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口
*/
@DubboReference(interfaceClass = UserService.class,
check = false,
retries = 3,
timeout = 5000)
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
4:版本号配置version
- 参数:
version=xxx
- 位置:服务提供方指定版本,消费方声明使用的版本
当一个接口实现,出现不兼容升级时,可以用版本号过渡,版本号不同的服务相互间不引用。
// ========= 服务提供方 ===========
/**
* 暴露远程调用的接口,对外提供服务,指定当前的是1.0.0版本
*/
@DubboService(interfaceClass = UserService.class, version = "1.0.0")
public class UserServiceImpl implements UserService {
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress address2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "1", "王老师", "010-56253825", "N");
return Arrays.asList(address1,address2);
}
}
// ======== 服务消费方 ================
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,指定接口,关闭自启动检查,version=*,表示随机调用,新版本和旧版本都可调用
*/
@DubboReference(interfaceClass = UserService.class, check = false, version = "*")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
5:配置原则
dubbo推荐在Provider上尽量多配置Consumer端属性:
作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作为Consumer的缺省值
否则,Consumer会使用Consumer端的全局设置,这对于Provider不可控的,并且往往是不合理的
配置的覆盖规则:
- 方法级配置别优于接口级别,即小Scope优先
- Consumer端配置 优于 Provider配置 优于 全局配置,
- 最后是Dubbo Hard Code的配置值(见配置文档)
五:高可用配置
1:本地存根配置stub
远程服务后,客户端通常只剩下接口,而实现全在服务器端
但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数等等
此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub 1,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
xml-provider-api 模块中定义了对外提供服务的接口 XMLProviderService ,代码如下:
public interface XMLProviderService {
String say(String message);
}
以及接口存根 XMLProviderServiceStub
public class XMLProviderServiceStub implements XMLProviderService {
private final XMLProviderService xmlProviderService;
public XMLProviderServiceStub(XMLProviderService xmlProviderService) {
this.xmlProviderService = xmlProviderService;
}
@Override
public String say(String message) {
if (StringUtils.isBlank(message)) {
return "message不能为空!";
}
try {
return this.xmlProviderService.say(message);
} catch (Exception e) {
return "远程调用失败:" + e.getMessage();
}
}
}
2:dubbo直连配置url
zookeeper注册中心宕机,还可以消费dubbo暴露的服务 -> 健壮性
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
@Service
public class OrderServiceImpl implements OrderService {
/**
* 调用远程接口,关闭自启动检查,跳过注册中心
*/
@DubboReference(check = false, url = "127.0.0.1:20882")
UserService userService;
@Override
public List<UserAddress> initOrder(String userId) {
return userService.getUserAddressList(userId);
}
}
3:集群下的负载均衡配置loadbalance
在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用
- RandomLoadBalance:加权随机,默认算法,默认权重相同
- RoundRobinLoadBalance:加权轮询
- LeastActiveLoadBalance:最少活跃优先 + 加权随机
- ShortestResponseLoadBalance:最短响应优先 + 加权随机,更加关注响应速度
- ConsistentHashLoadBalance:一致性 Hash,确定的入参,确定的提供者,适用于有状态请求
配置方式:
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 100) // 配置对应的权重
public class UserServiceImpl implements UserService {
public String sayHello() {
return "1.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 200) // 配置对应的权重
public class UserServiceImpl implements UserService {
public String sayHello() {
return "2.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service(weight = 100) // 配置对应的权重
public class UserServiceImpl implements UserService {
public String sayHello() {
return "3.....";
}
@Override
public User findUserById(int id) {
User user = new User(1, "zhangsan", "123");
return user;
}
}
在消费端配置负载均衡策略,4中策略可以通过搜索AbstractLoadBalance的4中实现类中定义的name值查看
@DubboReference(check = false, loadbalance = LoadbalanceRules.RANDOM)
UserService userService;
// 或者
@Reference(loadbalance = "random") // 设置负载均衡策略
private UserService userService;
4:集群容错模式配置cluster
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试
dubbo
也是支持集群容错的,同时也有很多可选的方案,其中,默认的方案是 failover
,也就是重试机制。
1,2模拟超时,3不超时
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("1.....");
User user = new User(1, "zhangsan", "123");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("2.....");
User user = new User(1, "zhangsan", "123");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return user;
}
}
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Service;
@Service
public class UserServiceImpl implements UserService {
public String sayHello() {
return "hello dubbo";
}
@Override
public User findUserById(int id) {
System.out.println("3.....");
User user = new User(1, "zhangsan", "123");
return user;
}
}
搜索cluster的实现类FailOverCluster
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
// 配置集群容错模式为failover失败重试(默认)
@Reference(cluster = "failover")
private UserService userService;
@RequestMapping("/sayHello")
public String sayHello() {
return userService.sayHello();
}
/**
* 根据id查询用户信息
* @param id
* @return
*/
@RequestMapping("/find")
public User find(int id) {
return userService.findUserById(id);
}
}
可以发现,1超时后重试2,2超时后重试3
可以在配置文件中配置全局容错能力
dubbo:
provider:
cluster: failover
retries: 2 # 重试次数
5:服务降级配置Mock
在并发较高的情况下,服务器的资源将要被跑满,此时就可以关闭掉一些不重要的服务,比如为了让支付服务不受到影响,可以选择关闭广告和日志服务,释放掉一些资源,即对服务进行降级处理
服务降级的方式:使用Dubbo定义的表达式:mock=“[fail|force]return|throw xxx”。
[fail|force]
,默认值fail,表示调用失败后,或不进行方法调用直接强制执行mock方法;return xxx
,表示返回指定结果,需要符合接口的返回类型;throw xxx
,表示抛出指定异常。
<dubbo:service interface="com.wyz.api.DubboDemoService" ref="dubboDemoServiceImpl" mock="return fail"/>
<dubbo:service interface="com.wyz.api.DubboDemoService" ref="dubboDemoServiceImpl" mock="force:return false"/>
<dubbo:service interface="com.wyz.api.DubboDemoService" ref="dubboDemoServiceImpl" mock="fail:throw java.lang.NullPointException"/>
例如:
mock=force:return null
表示消费方对这个服务的所有的调用都直接返回Null值,不发起远程调用,用来屏蔽不重要服务不可用时对调用方的影响mock=fail:return null
表示消费放对该服务的方法调用在失败之后,再返回null值,不抛出异常,用来容忍不重要的服务不稳定的时候对调用方的影响
@Reference(mock = "force:return null") // 暴力返回null,不再调用userService服务
private UserService userService;
@Reference(mock = "fail:return null") // 失败后返回null,如服务超时报错
private UserService userService;
6:服务分组配置group
在服务端指定实现类的分组
@DubboService(group = "Annotation-Provider")
public class DubboAnnotationServiceImpl implements DubboAnnotationService {
@Override
public String say(String message) {
return "DubboProviderAnnotation say : " + message;
}
}
@DubboService(group = "New-Annotation-Provider")
public class NewDubboAnnotationServiceImpl implements DubboAnnotationService {
@Override
public String say(String message) {
return "NewDubboProviderAnnotation say : " + message;
}
}
消费端对指定分组的信息进行调用
@Component
public class DubboConsumerAnnotationService implements CommandLineRunner {
@DubboReference(group = "Annotation-Provider")
DubboAnnotationService dubboAnnotationService;
@DubboReference(group = "New-Annotation-Provider")
DubboAnnotationService newDubboAnnotationService;
@Override
public void run(String... args) {
String message = dubboAnnotationService.say("wyz-Annotation");
System.out.println(message);
String newMessage = newDubboAnnotationService.say("wyz-Annotation");
System.out.println(newMessage);
}
}