目录
RPC理论概述
RPC的基本思想
RPC的实现组成部分
RPC的实现流程
RPC的核心思想
RPC调用分类
初识Dubbo
Dubbo特性
Dubbo设计架构
zookeeper环境搭建
搭建注册中心环境
搭建监控中心环境
Dubbo入门案例(Dubbo + Spring)
实现步骤
搭建中介者组件共享资源
打包为jar,共享数据
服务提供者为共享Service实现具体逻辑
服务消费者为共享Service实现具体逻辑
导入远程调用依赖
服务提供者注册服务到Zookeeper上
测试注册是否成功
服务消费者到Zookeeper上订阅服务,实例化共享Service
测试订阅是否成功
Dubbo与SpringBoot整合
导入以下依赖
配置 application.properties(直接CV模板吧)
服务提供者配置
服务消费者配置
重点!服务提供者和消费者的启动类上都要开启Dubbo功能
服务暴露 / 订阅
服务提供者暴露服务
服务消费者订阅服务
Dubbo配置
覆盖策略
配置:启动时检查
配置:全局超时配置
SpringBoot与Dubbo整合的三种方式
使用默认application.properties和注解的方式
引入dubbo.xml配置文件
使用注解API的方式
高可用
zookeeper宕机与Dubbo直连
zookeeper宕机
Dubbo直连
Dubbo的"直连"(Direct Connection)指的是消费者直接连接到指定的服务提供者,而不通过注册中心进行服务的发现和路由。
应用场景
直连配置
集群下dubbo负载均衡配置
Random LoadBalance 基于权重的随机负载均衡机制
RoundRobin LoadBalance 基于权重的轮询负载均衡机制
LeastActive LoadBalance最少活跃数负载均衡机制
ConsistentHash LoadBalance一致性hash 负载均衡机制
ShortestResponse LoadBalance
权重配置
1、静态配置
2、动态配置
选型建议
服务降级
什么是服务降级?
解决策略
集群容错
容错模式
整合hystrix
配置spring-cloud-starter-netflix-hystrix
案例实践
配置Provider端
Dubbo原理
RPC原理
netty通信原理
大白话体会Dubbo原理
服务暴露原理
服务订阅原理
RPC理论概述
远程过程调用(Remote Procedure Call,简称RPC)是一种通信协议和编程技术,用于实现分布式系统中的不同进程或计算机之间的相互调用。
RPC的基本思想
在一个进程中的代码可以通过像调用本地函数一样调用远程计算机上的函数。在RPC中,客户端调用远程服务的过程就像调用本地函数一样简单,而不需要关心底层细节。
RPC的实现组成部分
- 客户端:发起远程调用的一方,向远程服务请求执行某个函数或方法。
- 服务端:提供远程服务的一方,接收客户端的调用请求,并执行相应的函数或方法。
- 接口定义语言(IDL):客户端和服务端之间通信需要定义一个接口,IDL用于描述函数或方法的参数与返回值类型,以确保双方对接口的理解一致。
- 序列化:在网络上传输数据时,需要将数据按照一定的格式进行编码和解码,序列化将数据转换为可传输的二进制数据,而反序列化则将接收到的二进制数据还原为原始数据。
- 网络通信:RPC通过网络进行客户端和服务端的通信,可以使用传输层协议(如TCP、HTTP等)来传输数据。
- 服务注册与发现:在分布式环境中,需要有一种机制来管理和发现可用的服务,通常使用服务注册与发现机制来实现,如使用ZooKeeper、Consul等。
RPC的实现流程
- Client像调用本地服务似的调用远程服务;
- Client stub接收到调用后,将方法、参数序列化
- 客户端通过sockets将消息发送到服务端
- Server stub 收到消息后进行解码(将消息对象反序列化)
- Server stub 根据解码结果调用本地的服务
- 本地服务执行(对于服务端来说是本地执行)并将结果返回给Server stub
- Server stub将返回结果打包成消息(将结果消息对象序列化)
- 服务端通过sockets将消息发送到客户端
- Client stub接收到结果消息,并进行解码(将结果消息发序列化)
- 客户端得到最终结果。
RPC的核心思想
通过封装网络通信的细节,使得客户端和服务端之间的远程调用过程更加简洁、透明。它可以加速开发过程,并方便地构建分布式系统。不过需要注意的是,RPC并不是解决所有分布式系统问题的万能工具,仍然需要根据具体的业务要求和场景选择适合的技术和架构。
RPC调用分类
同步调用:客户方等待调用执行完成并返回结果。
异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
初识Dubbo
Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,服务自动注册和发现。分布式系统是将一个系统拆分为多个不同的服务
Dubbo特性
(1)服务注册中心
- 相比Hessian类RPC框架,Dubbo有自己的服务中心, 写好的服务可以注册到服务中心, 客户端从服务中心寻找服务,然后再到相应的服务提供者机器获取服务。通过服务中心可以实现集群、负载均衡、高可用(容错) 等重要功能。
- 服务中心一般使用 zookeeper 实现,也有redis和其他一些方式。以使用zookeeper作为服务中心为例,服务提供者启动后会在zookeeper的/dubbo节点下创建提供的服务节点,包含服务提供者ip、port等信息。服务提供者关闭时会从zookeeper中移除对应的服务。
- 服务使用者会从注册中心zookeeper中寻找服务,同一个服务可能会有多个提供者,Dubbo会帮我们找到合适的服务提供者,也就是针对服务提供者的负载均衡。
(2)负载均衡
- 当同一个服务有多个提供者在提供服务时,客户端如何正确的选择提供者实现负载均衡呢?dubbo也给我们提供了几种方案:
- random
随机
选提供者,并可以给提供者设置权重 - roundrobin
轮询
选择提供者 - leastactive 最少活跃调用数,相同活跃数的随机,活跃数:指调用前后计数差。使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
- consistenthash 一致性hash,相同参数的请求发到同一台机器上。
- random
(3)简化测试,允许直连提供者
在开发阶段为了方便测试,通常系统客户端能指定调用某个服务提供者,那么可以在引用服务时加一个url参数去指定服务提供者。 配置如下:
<dubbo:reference id="xxxService"interface="com.alibaba.xxx.XxxService"url="dubbo://localhost:20890"/>
(4)服务版本,服务分组
在Dubbo配置文件中可以通过制定版本实现连接制定提供者,也就是通过服务版本可以控制服务的不兼容升级;当同一个服务有多种实现时,可以使用服务分组进行区分。
Dubbo设计架构
服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
zookeeper环境搭建
搭建注册中心环境
Dubbo官方文档: http://dubbo.apache.org/en-us/docs/user/quick-start.html
在zookeeper官网下载zookeeper
http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/
在bin文件下,启动zkServer.cmd会有报错,处理需要在condif文件中将zoo_sample.cfg文件复制一份,将名字改为zoo.cfg。在zookeeper的文件夹下创建data文件夹,打开zoo.cfg,修改datadir,将dataDir数据保存为我们自定义的文件中
(此步骤可省略)
配置完毕后,我们再次在conf下启动zkServer.cmd,这次可以成功启动
继续运行zkCli.cmd,可以连接到zookeeper的服务器。
此时,我们zookeeper的注册中心及环境以及搭建完毕。
搭建监控中心环境
下载dubbo-admin
dubbo-admin下载地址 :https://github.com/apache/dubbo-admin/tree/master
解压后进入目录修改指定zookeeper地址
进入如下地址:
dubbo-admin-master\dubbo-admin\src\main\resources\application.properties"
将zookeeper的监控中心的地址配置为本地端口
#注册中心的地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
配置完毕后,我们在dubo-zookeeper\dubbo-admin-master\dubbo-admin文件夹下cmd打包测试下。
mvn clean package
在target文件中打包完成的jar包
cmd命令 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
运行打包好的jar包
启动成功后,可以看到一个7001的端口
此时我们的zookeeper的服务都为启动状态,在浏览器中访问 localhost:7001,访问到注册中心,输入账号密码root。
此时,我们zookeeper监控中心的配置完成。注意,要访问到监控中心,一定要启动zookeeper注册中心的启动类
Dubbo入门案例(Dubbo + Spring)
基于以下图实现服务 提供者、消费者
实现步骤
- 将涉及了组件间交互的一些共有资源(比如Bean、Service接口)独立写在第三方中介人组件上
- 将该中介人组件打包为jar包引入到服务提供者和消费者的pom文件中,这样子就可以实现这些资源的共享
- 在服务提供者和消费者的组件中实现对应的服务实现逻辑(ServiceImpl类)
- 导入相关的依赖(Dubbo + Zookeeper)
- 服务提供者配置好要将自己的哪个接口的哪个实现类注册到Zookeeper注册中心的哪个位置上
- 服务消费者配置好自己要去Zookeeper注册中心要订阅什么服务,生成代理对象实例化共享Service
搭建中介者组件共享资源
将 提供者 和 消费者 项目中的需要进行远程调用的 Service接口类 和 实体类复制到中介者组件gmail-interface相关的文件包下
打包为jar,共享数据
把服务提供者和消费者项目中引入以下依赖(其实就是相当于把整个中介者组件都纳入进类库中)
<dependency>
<groupId>com.lemon.gmail</groupId>
<artifactId>gmail-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
服务提供者为共享Service实现具体逻辑
UserServiceImp
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "河南省郑州巩义市宋陵大厦2F", "1", "安然", "150360313x", "Y");
UserAddress address2 = new UserAddress(2, "北京市昌平区沙河镇沙阳路", "1", "情话", "1766666395x", "N");
return Arrays.asList(address1,address2);
}
}
服务消费者为共享Service实现具体逻辑
OrderServiceImpl
public class OrderServiceImpl implements OrderService {
public void initOrder(String userID) {
//查询用户的收货地址
xxxxxxxxx
}
}
导入远程调用依赖
<!--dubbo-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
<!--注册中心是 zookeeper,引入zookeeper客户端-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
服务提供者注册服务到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://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--1、指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="user-service-provider"></dubbo:application>
<!--2、指定注册中心的位置-->
<!--<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>-->
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
<!--3、指定通信规则(通信协议? 服务端口)-->
<dubbo:protocol name="dubbo" port="20880"></dubbo:protocol>
<!--4、暴露服务 让别人调用 ref指向服务的真正实现对象-->
<dubbo:service interface="com.lemon.gmail.service.UserService" ref="userServiceImpl"></dubbo:service>
<!--服务的实现-->
<bean id="userServiceImpl" class="com.lemon.gmail.service.impl.UserServiceImpl"></bean>
</beans>
测试注册是否成功
编写一个ProviderApplication
启动类程序,运行测试配置
public class MailApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext applicationContext= new ClassPathXmlApplicationContext("provider.xml");
applicationContext.start();
System.in.read();
}
}
仿照前面的 zookeeper环境搭建 的步骤,启动zookeeper注册中心的的zkServer.cmd、和zkCli.cmd服务在dubbo-admin target中cmd运行 java -jar dubbo-admin-0.0.1-SNAPSHOT.jar再次启动项目,我们可以看到在zookeeper中已经发现服务提供者。
服务消费者到Zookeeper上订阅服务,实例化共享Service
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!--包扫描-->
<context:component-scan base-package="com.lemon.gmail.service.impl"/>
<!--指定当前服务/应用的名字(同样的服务名字相同,不要和别的服务同名)-->
<dubbo:application name="order-service-consumer"></dubbo:application>
<!--指定注册中心的位置-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"></dubbo:registry>
<!--指定interface调用远程暴露的服务,生成远程服务代理初始化本地共享Service id自定义,是消费者的订阅id-->
<dubbo:reference interface="com.lemon.gmail.service.UserService" id="userService">
</dubbo:reference>
</beans>
DI注入代理Bean就好
@Service
public class OrderServiceImpl implements OrderService {
// 直接注入就好,订阅的该接口实现类服务已经被拉取下来放到Spring中管理了
@Autowired
public UserService userService;
public void initOrder(String userID) {
//查询用户的收货地址
List<UserAddress> userAddressList = userService.getUserAddressList(userID);
//为了直观的看到得到的数据,以下内容也可不写
System.out.println("当前接收到的userId=> "+userID);
System.out.println("**********");
System.out.println("查询到的所有地址为:");
}
}
测试订阅是否成功
编写一个ConsumerApplication
启动类程序,运行测试配置
public class ConsumerApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = applicationContext.getBean(OrderService.class);
//调用方法查询出数据
orderService.initOrder("1");
System.out.println("调用完成...");
System.in.read();
}
}
注意:消费者的运行测试需要先启动提供者。
启动服务提供者、消费者。及zookeeper的和dubbo-admin,查看监控信息。
localhost:7001
Dubbo与SpringBoot整合
导入以下依赖
其实根本不需要导入啥,因为Dubbo和Zookeeper依赖都在spring-boot-starter里内置了,导入个共享组件的jar包就差不多了
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>
<dependencies>
// 共享组件的Jar
<dependency>
<groupId>com.lemon.gmail</groupId>
<artifactId>gmail-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
</dependencies>
配置 application.properties(直接CV模板吧)
服务提供者配置
照着写就行
dubbo.application.name=boot-user-service-provider
dubbo.registry.address=127.0.0.1:2181
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
#暴露服务不需要在这写,直接在想要暴露的ServiceImpl类上加个注解标注就好
服务消费者配置
照着写就行
server.port=8081
dubbo.application.name=boot-order-service-consumer
dubbo.registry.address=zookeeper://127.0.0.1:2181
#连接监控中心 注册中心协议
dubbo.monitor.protocol=registry
重点!服务提供者和消费者的启动类上都要开启Dubbo功能
@EnableDubbo //开启基于注解的dubbo功能
@SpringBootApplication
public class BootProviderApplication {
public static void main(String[] args) {
SpringApplication.run(BootProviderApplication.class, args);
}
}
服务暴露 / 订阅
当服务提供者使用
@Service
注解标注时,Dubbo会将标注的服务接口类型暴露给Zookeeper注册中心,供服务消费者发现和调用。服务消费者通过@Reference
注解来引用服务接口,Dubbo会根据接口类型与注册中心中已注册的提供者进行匹配。
服务提供者暴露服务
@com.alibaba.dubbo.config.annotation.Service //dubbo的服务暴露
@Service
public class UserServiceImpl implements UserService {
public List<UserAddress> getUserAddressList(String userId) {
UserAddress address1 = new UserAddress(1, "河南省郑州巩义市宋陵大厦2F", "1", "安然", "150360313x", "Y");
UserAddress address2 = new UserAddress(2, "北京市昌平区沙河镇沙阳路", "1", "情话", "1766666395x", "N");
return Arrays.asList(address1,address2);
}
}
服务消费者订阅服务
@Service
public class OrderServiceImpl implements OrderService {
@Reference //引用远程提供者服务
UserService userService;
public List<UserAddress> initOrder(String userID) {
xxxxx
}
}
Dubbo配置
dubbo配置官网参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-service.html
覆盖策略
在Dubbo中,配置的覆盖策略是指当多个配置来源中存在同名配置项时,如何进行优先级顺序及合并处理的策略。
- JVM 启动 -D 参数优先,这样可以使用户在部署和启动时进行参数重写,比如在启动时需改变协议的端口。
- XML 次之,如果在 XML 中有配置,则 dubbo.properties 中的相应配置项无效。
- Properties 最后,相当于缺省值,只有 XML 没有配置时,dubbo.properties 的相应配置项才会生效,通常用于共享公共配置,比如应用名dubbo.protocol.name=dubbo。
配置:启动时检查
Dubbo 缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止 Spring 初始化完成,以便上线时,能及早发现问题,默认 check=“true”
可以通过 check=“false” 关闭检查,比如,测试时,有些服务不关心,或者出现了循环依赖,必须有一方先启动。
另外,如果你的 Spring 容器是懒加载的,或者通过 API 编程延迟引用服务,请关闭 check,否则服务临时不可用时,会抛出异常,拿到 null 引用,如果 check=“false”,总是会返回引用,当服务恢复时,能自动连上。
讲人话就是,Dubbo默认会在启动后对所有注册进来的服务提供者和消费者进行检测,如果发现有的服务不可用的时候,直接初始化错误,启动不了一点。但是如果你想有的就是不可用也能启动的时候,就关掉这个启动时检查
<!--配置当前消费者的统一规则,当前所有的服务都不启动时检查-->
<dubbo:consumer check="false"></dubbo:consumer>
<!--SpringBoot中配置如下——————————————————————————-->
# properties文件配置方式
dubbo.consumer.check=false
# yml文件配置方式
dubbo:
consumer:
check: false
配置:全局超时配置
全局超时配置(配置在dubbo.properties)
<dubbo:provider timeout="5000" />
指定接口以及特定方法超时配置,单位为毫秒值
<dubbo:provider interface="com.foo.BarService" timeout="2000">
<dubbo:method name="sayHello" timeout="3000" />
</dubbo:provider>
以下是SpringBoot项目中的配置方式——————————————————————————————————————————
dubbo.application.timeout=5000
/*如果需要为特定接口或特定方法设置不同的超时时间,可以使用Dubbo的@org.apache.dubbo.config.annotation.Method注解。*/
@Service
public class UserServiceImpl implements UserService {
@Method(timeout = 3000)
@Override
public User getUserById(Long id) {
// ...
}
配置原则
Dubbo推荐在服务提供方(Provider)上配置尽量多的Consumer端属性,主要基于以下两个原则:
1. 服务提供方更清楚服务性能参数:作为服务提供方,通常具有更全面的服务运行环境和性能监控,可以更准确地了解服务的实际情况。因此,在服务提供方上配置调用的超时时间、合理的重试次数等参数,可以更好地适应服务的性能需求。
例如,在Provider配置中设置超时时间为5秒和重试次数为3次:
```properties
dubbo.provider.timeout=5000
dubbo.provider.retries=3
```
这样,服务提供方告知消费方的是超时时间为5秒和最多重试3次,意味着消费方在默认情况下会使用这些配置。
2. Provider配置作为Consumer的缺省值:如果消费方没有显式地配置相关属性,Dubbo会使用Provider配置作为Consumer的默认值,以保持一致。这样的设计可以避免在每个Consumer端都重复配置相同的属性,减少配置的复杂性和出错的可能性。
例如,在Provider上配置超时时间和重试次数:
```properties
dubbo.provider.timeout=5000
dubbo.provider.retries=3
```
如果消费方没有显式地在Consumer端配置这些属性,那么Dubbo将使用Provider上的超时时间和重试次数作为Consumer的默认值,即在Consumer端实际调用时,会使用Provider端配置的超时时间和重试次数。
综合起来,通过在Provider端配置Consumer属性,可以提供默认值,消费方在不需要特定配置时可以复用Provider端的配置,避免重复配置。而如果消费方有特定的需求,可以覆盖Provider的设置,以满足特定的业务需求。这样的配置原则可以提高配置的灵活性和可维护性。
SpringBoot与Dubbo整合的三种方式
使用默认application.properties和注解的方式
导入dubbo-starter,在application.properties配置属性,使用@Service注解来暴露服务,使用@Reference来引用服务。具体可参考 Dubbo整合SpringBoot,这里截取部分代码方便理解。
属性在application.properties中配置
部分配置如“timeout”等可以在注解上添加
引入dubbo.xml配置文件
- 保留 dubbo.xml 配置文件;
- 导入 dubbo-starter 依赖;
- 使用 在启动类 @ImportResource 导入 dubbo 的配置文件即可
- 同时不需要使用 @service 来暴露服务了
使用注解API的方式
依旧使用@Service注解来暴露服务,使用@Reference来引用服务。同时使用注解API的方式来替代xml配置文件中的标签,将每一个组件手动创建到容器中,让dubbo来扫描其他的组件。
注解类的配置主要有三点:①注解类加注解@Configuration;②每个注解项添加@Bean注入到容器中;③准确使用注解API。
@Configuration
public class MyDubboConfig {
//<dubbo:application name="boot-user-service-provider"></dubbo:application>
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("user-service-provider");
return applicationConfig;
}
//<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"></dubbo:registry>
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setProtocol("zookeeper");
registryConfig.setAddress("127.0.0.1:2181");
return registryConfig;
}
使用@DubboComponentScan注解指定dubbo扫描路径。
高可用
zookeeper宕机与Dubbo直连
zookeeper宕机
zookeeper注册中心宕机,还可以消费dubbo暴露的服务
当Zookeeper注册中心宕机时,仍然可以消费Dubbo暴露的服务的原因是Dubbo在启动时会将服务提供者的基本信息(如IP地址、端口等)注册到Zookeeper中,但一旦注册成功,服务提供者与消费者之间的直接通信并不依赖于Zookeeper的实时可用性。
Dubbo的服务提供者在启动时,会连接到Zookeeper注册中心,并将自己的服务信息注册到Zookeeper节点中。消费者在查找可用的服务提供者时,会通过Zookeeper获取可用的服务列表。一旦获取到可用的服务列表后,会缓存到本地。消费者会直接与提供者建立连接进行通信,而不再依赖Zookeeper的通信。
需要注意的是,如果Zookeeper注册中心在服务提供者启动之前就已经宕机,服务提供者将无法将自己的信息注册到Zookeeper中,这将导致消费者无法获取到可用的服务列表。
具体来说,Dubbo使用了一个内置的缓存机制来存储服务提供者的信息。这个内置的缓存是基于JVM内存的数据结构,通常是使用HashMap或ConcurrentHashMap实现的。当Dubbo消费者获取到可用的服务列表后,会将服务提供者的信息缓存到这个内置的缓存中。
由于存储在JVM内存中,Dubbo的缓存机制是将服务提供者的信息存储在消费者进程的堆内存中。这种缓存方式可以快速地进行本地查找和访问,并且不需要网络通信、磁盘读写等开销,能够提供较低的延迟和更高的性能。
请注意,Dubbo的缓存机制仅存储了服务提供者的基本信息,如IP地址、端口号等,而不会存储具体的服务实例。当需要实际调用服务时,Dubbo消费者会根据缓存的服务提供者信息,在运行时动态选择一个可用的服务实例进行调用。这样,Dubbo实现了服务发现与负载均衡的功能。
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
Dubbo直连
Dubbo的"直连"(Direct Connection)指的是消费者直接连接到指定的服务提供者,而不通过注册中心进行服务的发现和路由。
通常情况下,Dubbo的消费者会通过注册中心获取可用的服务列表,并根据负载均衡策略选择其中一个服务提供者进行调用。这种方式下,消费者和服务提供者之间的通信是通过注册中心进行的。
然而,有时候我们可能需要绕过注册中心,直接连接到指定的服务提供者。这种情况下,消费者会直接使用在配置中指定的服务提供者的地址进行通信,而不再通过注册中心进行服务的发现和路由。
应用场景
使用直连的方式可以具有以下一些应用场景和优势:
-
调试和开发环境:在开发和调试阶段,我们可能希望只与特定的服务提供者进行交互,以方便调试和测试。使用直连的方式,我们可以直接指定服务提供者的地址,无需依赖注册中心。
-
特定路由需求:某些特殊的业务需求可能需要直连特定的服务提供者。比如,可能希望将某个重要的服务请求直接发送给指定的服务提供者,以确保服务质量或满足特定的安全需求。
直连配置
要使用Dubbo的直连功能,可以在消费者的配置中指定服务提供者的地址。例如,在Dubbo XML配置文件中,可以使用url
属性直接指定服务提供者的地址:
<dubbo:reference id="xxxService" interface="com.xxx.XXXService" url="dubbo://xxx.xxx.xxx.xxx:20880"/>
另外,在SpringBoot的Dubbo注解配置中,也可以使用url
属性指定直连地址:
@Reference(url = "dubbo://xxx.xxx.xxx.xxx:20880")
private XXXService xxxService;
集群下dubbo负载均衡配置
在集群负载均衡时,Dubbo 提供了多种均衡策略
Random LoadBalance 基于权重的随机负载均衡机制
随机,按权重设置随机概率。 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance 基于权重的轮询负载均衡机制
每次有请求到达时,Dubbo会根据权重选择一个可用的服务提供者。通过轮询的方式,依次遍历服务提供者列表。遍历时根据服务提供者的权重决定选择的次数,权重高的服务提供者会被选择的次数更多。如果某个服务提供者不可用,Dubbo会将其权重减小,避免过多的请求被路由到该服务提供者上。
加权轮询过程过程中,如果某节点权重过大,会存在某段时间内调用过于集中的问题。
例如 ABC 三节点有如下权重:{A: 3, B: 2, C: 1}
那么按照最原始的轮询算法,调用过程将变成:A A A B B C
LeastActive LoadBalance最少活跃数负载均衡机制
- 加权最少活跃调用优先,活跃数越低,越优先调用,相同活跃数的进行加权随机。活跃数指调用前后计数差(针对特定提供者:请求发送数 - 响应返回数),表示特定提供者的任务堆积量,活跃数越低,代表该提供者处理能力越强。
- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大;相对的,处理能力越强的节点,处理更多的请求。
ConsistentHash LoadBalance一致性hash 负载均衡机制
- 一致性 Hash,相同参数的请求(请求同一个服务的多个实例时,根据携带的参数进行绑定)总是发到同一提供者。
- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
ShortestResponse LoadBalance
- 加权最短响应优先,在最近一个滑动窗口中,响应时间越短,越优先调用。相同响应时间的进行加权随机。
- 使得响应时间越快的提供者,处理更多的请求。
- 缺点:可能会造成流量过于集中于高性能节点的问题。
这里的响应时间 = 某个提供者在窗口时间内的平均响应时间,窗口时间默认是 30s。
权重配置
1、静态配置
在服务暴露时指定权重:
@Component
@Service(weight = 100) //设置权重,静态
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);
}
}
2、动态配置
在服务提供者管理页面可以给应用动态配置权重:
选型建议
-
Random LoadBalance(随机负载均衡):
- 使用场景:适用于服务提供者之间资源相似、请求处理能力均衡的情况。
- 选型建议:当服务提供者之间的性能相近,没有特别明显的负载差异时,可采用该策略具有简单、随机的特点。
-
RoundRobin LoadBalance(轮询负载均衡):
- 使用场景:适用于服务提供者之间资源相似、请求处理能力均衡的情况。
- 选型建议:当服务提供者之间的性能相近,希望实现请求的均匀分配时,可采用该策略具有公平、轮询的特点。
-
LeastActive LoadBalance(最少活跃数负载均衡):
- 使用场景:适用于服务提供者之间资源不均衡、请求处理能力有差异的情况。
- 选型建议:当服务提供者之间的性能存在差异,希望将请求尽量分配给负载较低的服务提供者时,可采用该策略,避免资源集中于负载较高的服务提供者。
-
ConsistentHash LoadBalance(一致性哈希负载均衡):
- 使用场景:适用于服务提供者集群规模变化频繁、动态扩容缩容的情况。
- 选型建议:当服务提供者集群规模动态变化,节点增减频繁时,可采用该策略。该策略在增减节点时,只影响局部映射,不会造成整体的调整成本。
-
ShortestResponse LoadBalance(最短响应时间负载均衡):
- 使用场景:适用于关注请求响应时间、希望快速获取响应的场景。
- 选型建议:当关注请求的响应时间,并希望选择最快响应的服务提供者时,可采用该策略。
服务降级
什么是服务降级?
当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
解决策略
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
- mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
集群容错
在集群调用失败时,Dubbo 提供了多种容错方案,缺省为 failover 重试。
容错模式
Failover Cluster
失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数(不含第一次)。
Failfast Cluster
快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
Failsafe Cluster
失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
Failback Cluster
失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
Forking Cluster
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。
Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。
整合hystrix
Hystrix 旨在通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能
配置spring-cloud-starter-netflix-hystrix
spring boot官方提供了对hystrix的集成,直接在pom.xml里加入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>
然后在Application类上增加 @EnableHystrix 来启用hystrix starter:
@SpringBootApplication
@EnableHystrix //开启服务容错功能
public class ProviderApplication {
...启动方法
}
案例实践
配置Provider端
在Dubbo的Provider上增加@HystrixCommand配置,这样子调用就会经过Hystrix代理。
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
@HystrixCommand(commandProperties = {
// 这里就是一些自定义的配置,这个可以参考官方文档进行配置
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
})
@Override
public String sayHello(String name) {
// System.out.println("async provider received: " + name);
// return "annotation: hello, " + name;
throw new RuntimeException("Exception to show hystrix enabled.");
}
}
配置Consumer端
对于Consumer端,则可以增加一层method调用,并在method上配置@HystrixCommand。当调用出错时,会走到fallbackMethod = "reliable"的调用里。注意这个传入的时方法的全限定位置,如果时本类的方法直接写名字就好,注意,这个回调方法的参数和原方法一样
@Reference(version = "1.0.0")
private HelloService demoService;
@HystrixCommand(fallbackMethod = "reliable")
public String doSayHello(String name) {
return demoService.sayHello(name);
}
public String reliable(String name) {
return "hystrix fallback value";
}
Dubbo原理
RPC原理
一次完整的RPC调用流程(同步调用,异步另说)如下:
- 服务消费方(client)调用以本地调用方式调用服务;
- client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
- client stub找到服务地址,并将消息发送到服务端;
- server stub收到消息后进行解码;
- server stub根据解码结果调用本地的服务;(这里主要是采用了反射的机制获取到代理对象来进行方法调用)
- 本地服务执行并将结果返回给server stub;
- server stub将返回结果打包成消息并发送至消费方;
- client stub接收到消息,并进行解码;
- 服务消费方得到最终结果。
dubbo只用了两步1和8,中间的过程是透明的看不到的。RPC框架的目标就是要2~8这些步骤都封装起来,这些细节对用户来说是透明的,不可见的。
netty通信原理
什么?!你怎么知道我也写了Netty的全套详细笔记?!https://blog.csdn.net/weixin_73077810/article/details/133743849
大白话体会Dubbo原理
服务暴露原理
总结起来,Dubbo服务暴露的原理就是服务提供者将自己的服务接口实现注册到Dubbo的注册中心,而消费者通过注册中心获取到服务提供者的地址,然后建立与服务提供者的连接并发送请求。服务提供者接收到请求后进行相应的处理,并将处理结果返回给消费者。通过Dubbo的中间层,实现了服务提供者和消费者之间的解耦和远程调用。
-
服务提供者将自己的服务接口实现注册到Dubbo的注册中心(如Zookeeper等)。这一步是通过配置文件或者编程方式完成的。
-
服务提供者在启动时,将自己的服务接口信息以及对应的服务实现发布到注册中心(类Hash表结构)。这样,消费者就可以通过注册中心获取到服务提供者的地址和相关信息。
-
消费者通过Dubbo的注册中心查找到服务提供者的地址和相关信息。
-
消费者根据获取到的服务提供者地址,建立与服务提供者的连接。这一步可以使用Dubbo框架提供的远程通信协议(如Dubbo协议)来建立连接。
-
消费者向服务提供者发送请求,包括需要调用的服务方法、参数等信息。
-
服务提供者接收到请求后,根据请求内容进行相应的处理,并返回结果给消费者。
-
消费者接收到服务提供者的响应结果,并进行相应的处理。
服务订阅原理
总结起来,Dubbo服务订阅的原理是消费者向注册中心发送订阅请求,获取到所需服务的提供者地址和相关信息,建立连接后进行服务调用。通过注册中心的协调作用,实现了服务的发布和订阅,使得消费者能够找到并调用所需的远程服务。
-
服务提供者将自己的服务接口信息和相关配置注册到Dubbo的注册中心。这个注册中心可以是Zookeeper等分布式协调系统。将服务注册到注册中心后,相当于宣告自己提供了哪些服务。
-
服务消费者启动时,会向注册中心发送订阅请求,告诉注册中心自己需要订阅哪些服务,即需要调用哪些远程服务。比如,消费者可能需要订阅User服务、Order服务等。
-
注册中心收到消费者的订阅请求后,会查找并返回与这些服务相关的服务提供者的地址和相关信息。这样,消费者就能获取到调用所需服务的地址。
-
消费者根据获取到的服务提供者地址,建立与服务提供者之间的连接(通常是基于TCP协议的长连接)。这样,消费者就能够通过网络与服务提供者进行通信。
-
一旦消费者建立了与服务提供者的连接,就可以通过调用服务提供者的接口方法来消费相应的服务。