什么是分布式系统?
《分布式系统原理与泛型》定义:
1️⃣ “分布式系统是若干个独立的计算机的集合,这些计算机对于用户来说就像单个相关系统”
2️⃣ 分布式系统(distributed system) 是建立在网络之上的软件系统。
为什么需要分布式系统?
随着互联网的飞速发展,网站应用规模的不断扩大,常规的垂直应用已经无法应对分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
什么是 RPC ?
RPC 【Remote Procedure Call】是指的远程过程调用,是一种进程间通信方式,它是一种技术的思想,而不是规范,它允许程序调用另一个地址空间(通常是共享网络上的另一台机器上)的过程或者函数,而不用程序猿显示编码这个远程调用的调节。即程序猿无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
链接:https://pan.baidu.com/s/1XWuselAC6iYcf-LcUeHb6w?pwd=HHXF
提取码:HHXF
–来自百度网盘超级会员V4的分享代码资料 dubbo - admin 的源码在其中哦~
1️⃣ 下载并解压安装对应的 学习环境
我们需要下载一个名为 incubator-dubbo-ops-master.zip
的代码压缩包并且进行解压到你对应的 dubbo 学习测试目录之下
2️⃣ 修改 dubbo-admin 中的 zookeeper 服务注册中心机器配置
resources/application.properties
dubbo.registry.address=zookeeper://192.168.56.103:2181
3️⃣ 打包 当前 dubbo - admin 为 可运行的 jar 文件 并启动
# 需要进入到 dubbo-admin 的资源目录下
mvn clean package
4️⃣ 通过命令行启动对应的 dubbo-admin.jar
http://localhost:7001/
默认用户名称:root
默认用户密码:root
📚 大功告成~
提出需求
某个 电商系统 ,订单服务需要调用用户服务获取某个用户的所有地址;
我们现在 需要创建两个服务模块进行测试
模块 | 功能 |
---|---|
订单服务 web 模块 | 创建订单等 |
用户服务 service 模块 | 查询用户地址等 |
测试预期结果:
订单服务 web 模块在 A 服务器,用户服务模块在 B 服务器,A 可以远程调用 B 功能
工程架构
创建模块
1️⃣ user-service-provider
2️⃣ user-service-consumer
3️⃣ gmall-interface
使用 dubbo 改造
1️⃣ 导入对应的 dubbo(2.6.2) 所需要的相关依赖 以及操作ZooKeeper的客户端的(curator)
<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.0modelVersion>
<groupId>com.alascanfugroupId>
<artifactId>user-service-consumerartifactId>
<version>1.0.0version>
<dependencies>
<dependency>
<groupId>com.alascanfugroupId>
<artifactId>gmall-interfaceartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>dubboartifactId>
<version>2.6.2version>
dependency>
<dependency>
<groupId>org.apache.curatorgroupId>
<artifactId>curator-frameworkartifactId>
<version>2.12.0version>
dependency>
dependencies>
project>
2️⃣ 配置服务提供者的配置文件
resources/provider.xml
1️⃣ 导入 dubbo 对应的命名空间配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
2️⃣ 指定当前 服务 / 应用的名字(同样的服务名称相同,不要和别的服务同名)
<dubbo:application name="user-service-provider">dubbo:application>
3️⃣ 指定配置中心的位置
<dubbo:registry protocol="zookeeper" address="192.168.56.103:2181">dubbo:registry>
4️⃣ 指定通信协议 以及 对应的通信端口
<dubbo:protocol name="dubbo" port="20080" >dubbo:protocol>
5️⃣ 服务暴露 ref 是用来引用具体的服务实现类
<dubbo:service interface="com.alascanfu.gmall.service.UserService" ref="userServiceImpl">dubbo:service>
<bean class="com.alascanfu.gmall.service.impl.UserServiceImpl" id="userServiceImpl">bean>
3️⃣ 创建应用 主 启动类 启动我们的对应配置文件
com.alascanfu.MainApplication
/***
* @author: Alascanfu
* @date : Created in 2022/7/17 11:49
* @description: 程序应用的 主启动类
* @modified By: Alascanfu
**/
public class MainApplication {
public static void main(String[] args) throws IOException {
// 读取配置文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("provider.xml");
// 启动当前 IOC 容器
context.start();
// 读取字符 阻塞 等待结束
System.in.read();
}
}
4️⃣ 启动主程序之后打开我们的 Dubbo-admin 控制台 进行对应的查看 服务是否注册成功
http://localhost:7001/
5️⃣ 配置 consumer 中的 dubbo 相关配置
consumer.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
">
<dubbo:application name="order-service-consumer">dubbo:application>
<dubbo:registry protocol="zookeeper" address="192.168.56.103:2181" >dubbo:registry>
<dubbo:reference id="userService" interface="com.alascanfu.gmall.service.UserService">dubbo:reference>
beans>
6️⃣ 将我们刚才需要远程调用的服务接口 进行注入到我们 IOC 容器当中 并在 Impl 实现类中自动注入 该对应的实例对象 并且进行调用 通过注解方式
配置对应的 consumer 中的对应的扫描自动注入注解的包路径
<context:component-scan base-package="com.alascanfu.gmall.service.impl">context:component-scan>
修改 OrderServiceImpl的具体实现类
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
UserService userService ;
@Override
public void initOrder(String userId) {
List<UserAddress> userAddressList = userService.getUserAddressList(userId);
System.out.println(userAddressList);
}
}
7️⃣ 编写对应 consumer 的主启动类 并且加载对应的 consumer.xml 的配置文件 进行对应的测试
/***
* @author: Alascanfu
* @date : Created in 2022/7/17 12:43
* @description: MainApplication Consumer 的主启动类 MainApplication
* @modified By: Alascanfu
**/
public class MainApplication {
public static void main(String[] args) throws IOException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("consumer.xml");
OrderService orderService = context.getBean(OrderService.class);
orderService.initOrder("1");
System.in.read();
}
}
8️⃣ 启动 consumer 的主应用启动程序 查看控制台是否有对应数据的输出
📚 远程服务调用成功~
1️⃣ 首先先将我们下载好的对应 dubbo - monitor 进行解压到本地
2️⃣ 将对应的 dubbo-monitor 的文件配置进行对应的修改
dubbo.registry.address=zookeeper://192.168.56.103:2181
3️⃣ 然后通过 Maven 工具进行对应的 打包 mvn clean package -Dmaven.test.skip=true
4️⃣ 找到打包好后的文件夹中的 dubbo-monitor-simple-2.0.0-assembly.tar.gz
进行解压到当前目录
5️⃣ 将解压后的文件目录 放入到对应的学习目录下 进入对应的目录之中找到 bin 目录并且启动 start.bat 程序 即可。
6️⃣ 通过浏览器查看监控中心
http://localhost:8080/
7️⃣ 进行文件配置当前 dubbo 的监控中心
<dubbo:monitor protocol="registry">dubbo:monitor>
8️⃣ 随后重启我们的 dubbo-consumer 然后观察 Dubbo 监控中心面板数据
1️⃣ 导入对应的 dubbo - starter 依赖整合对应的 SpringBoot
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.2.0version>
dependency>
2️⃣ 编写 dubbo 对应的配置 于 application.yaml中进行添加
dubbo:
application:
name: user-service-provider
registry:
protocol: zookeeper
address: 192.168.56.103:2181
protocol:
name: dubbo
port: 20080
monitor:
protocol: registry
3️⃣ 通过注解标注哪些 接口 会被作为远程调用服务
/***
* @author: Alascanfu
* @date : Created in 2022/7/16 23:54
* @description: UserServiceImpl
* @modified By: Alascanfu
**/
@Service
@com.alibaba.dubbo.config.annotation.Service // 暴露当前类的服务
public class UserServiceImpl implements UserService {
/**
* 模拟数据库 返回用户数据
* */
@Override
public List<UserAddress> getUserAddressList(String userId) {
UserAddress userAddress1 = new UserAddress(1, "北京市昌平区宏福科技园综合楼3层", "1", "李老师", "010-56253825", "Y");
UserAddress userAddress2 = new UserAddress(2, "深圳市宝安区西部硅谷大厦B座3层(深圳分校)", "2", "王老师", "010-56789225", "Y");
return Arrays.asList(userAddress1 , userAddress2);
}
}
📚 注意点:值得注意的是 需要在SpringBootApplication主运行类上标注 @EnableDubbo 开启基于注解的 Dubbo 功能
4️⃣ 启动 SpringBoot 的应用程序查看 dubbo-admin 中的服务提供者数是否对应增加
5️⃣ 进行配置 boot-order-service-consumer 并且在需要远程调用引用的服务上设置对应的注解 @Reference ,添加完成之后需要回到我们的 SpringBoot Application 的主启动类中添加对应的注解 @EnableDubbo
dubbo:
application:
name: order-service-consumer
registry:
protocol: zookeeper
address: 192.168.56.103:2181
monitor:
protocol: registry
# 防止端口冲突
server:
port: 8081
📚 启动对应的应用主程序
http://localhost:8081/initOrder?uid=1
📚 小结
@Reference 是远程引用对应的服务
@Service 是暴露当前服务到注册中心
启动检查调用
❓ 所遇到的问题,当我们注册服务期间并没有开启我们的 provider 时 直接进行远程调用consumer 就会发生报错信息,Faild to check …
在对应的声明引用时
<dubbo:reference id="userService"
check="false"
interface="com.alascanfu.gmall.service.UserService">dubbo:reference>
📚 当然我们也可以使用 dubbo:consumer 来配置当前消费者的统一规则
<dubbo:consumer check="false">dubbo:consumer>
关闭注册中心启动时检查(注册订阅失败时报错)
<dubbo:registry check="false"/>
dubbo:consumer
其中一个属性为 => timeout 默认的值为 1000 ms
<dubbo:reference id="userService"
check="false"
timeout="3000"
interface="com.alascanfu.gmall.service.UserService">dubbo:reference>
📚 当然也可以配置引用远程调用服务的指定方法的超时时间
<dubbo:reference id="userService"
check="false"
timeout="3000"
interface="com.alascanfu.gmall.service.UserService">
<dubbo:method name="getUserAddressList" timeout="5000">dubbo:method>
dubbo:reference>
配置生效优先覆盖
1️⃣ 精确优先配置——方法级优先,接口级次之,全局配置再次之
2️⃣ 消费者设置优先 —— 如果级别一样,则消费方优先,提供方次之
retries=‘3’
重试次数,不包含第一次调用
幂等(设置重试次数)——方法不管用多少次都是同样结果【查询、删除、修改】
非幂等(不能设置重试次数)【新增】
有点像版本的灰度发布
1️⃣ 当一个接口实现,出现不兼容升级时,可以用版本号国都,版本号不同的服务相互间不引用。
2️⃣ 可以按照以下的步骤进行版本迁移:
老版本服务提供者配置
<dubbo:service interface="com.foo.BarService" version="1.0.0" />
新版本服务提供者配置
<dubbo:service interface="com.foo.BarService" version="2.0.0" />
老版本服务消费者配置
<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
新版本服务消费者配置
<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
随机版本使用
<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等,此时就需要在 API 中带上 Stub,客户端生成 Proxy 实例,会把 Proxy 通过构造函数传给 Stub,然后把 Stub 暴露给用户,Stub 可以决定要不要去调 Proxy。
<dubbo:reference id="userService"
check="false"
timeout="3000"
stub="com.alascanfu.gmall.service.impl.UserServiceStub"
interface="com.alascanfu.gmall.service.UserService">
<dubbo:method name="getUserAddressList" timeout="5000">dubbo:method>
dubbo:reference>
代码
/***
* @author: Alascanfu
* @date : Created in 2022/7/17 23:03
* @description: UserServiceStub
* @modified By: Alascanfu
**/
public class UserServiceStub implements UserService {
private final UserService userService ;
public UserServiceStub(UserService userService) {
this.userService = userService;
}
@Override
public List<UserAddress> getUserAddressList(String userId) {
if (!StringUtils.isBlank(userId)){
return userService.getUserAddressList(userId);
}
return null;
}
}
当SpringBoot 整合需要进行详细配置时——方法的使用
1️⃣ 导入 dubbo - starter ,在 application.properties 配置属性,使用 @Service
【暴露服务】,使用@Reference
【发现服务】,并且需要在主启动类上标注 @EnableDubbo
2️⃣ 保留 dubbo.xml 配置文件 然后在 主启动类上标注 @ImportResource(locations="classpath:xxx.xml")
3️⃣ 注解配置 | Apache Dubbo 使用注解 API 手动创建加入到对应的IOC容器中
❓ 问题现象 => ZooKeeper 注册中心宕机,还可以消费 dubbo 暴露的服务
🌰 原因
健壮性
📚 高可用通过设计,减少系统不能提供服务的时间。
小结:通常会使用 @Reference(url='xxx.xxx.xxx.xxx:xxxxx')
进行直连
在集群负载均衡时,Dubbo 提供了多种的均衡策略,默认方式为 random 随机调用
🌰 负载均衡策略
1️⃣ Random LoadBalance
2️⃣ RoundRobin LoadBalance
3️⃣ LeastActive LoadBalance
4️⃣ ConsistentHash LoadBalance
修改与配置
什么是服务降级?
1️⃣ 当服务器压力剧增的情况下,根据实际业务情况以及流量,对一些服务和页面有策略的不处理或换成简单的方式处理,从而释放掉服务器资源以保证核心交易正常运行或者高效运作。
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
mock=force:return+null
表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。集群调用失败时,Dubbo 提供的容错方案
在集群调用失败时,Dubbo 提供了多种容错方案,默认为 failover 重试。
各节点关系:
Invoker
是 Provider
的一个可调用 Service
的抽象,Invoker
封装了 Provider
地址及 Service
接口信息Directory
代表多个 Invoker
,可以把它看成 List
,但与 List
不同的是,它的值可能是动态变化的,比如注册中心推送变更Cluster
将 Directory
中的多个 Invoker
伪装成一个 Invoker
,对上层透明,伪装过程包含了容错逻辑,调用失败后,重试另一个Router
负责从多个 Invoker
中按路由规则选出子集,比如读写分离,应用隔离等LoadBalance
负责从多个 Invoker
中选出具体的一个用于本次调用,选的过程包含了负载均衡算法,调用失败后,需要重选集群容错策略
1️⃣ Failover Cluster
retries="2"
来设置重试次数(不含第一次)。2️⃣ Failfast Cluster
3️⃣ Failsafe Cluster
4️⃣ Failback Cluster
5️⃣ Forking Cluster
forks="2"
来设置最大并行数。6️⃣ Broadcast Cluster
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
📚 现在广播调用中,可以通过 broadcast.fail.percent 配置节点调用失败的比例,当达到这个比例后,BroadcastClusterInvoker 将不再调用其他节点,直接抛出异常。 broadcast.fail.percent 取值在 0~100 范围内。默认情况下当全部调用失败后,才会抛出异常。 broadcast.fail.percent 只是控制的当失败后是否继续调用其他节点,并不改变结果(任意一台报错则报错)。broadcast.fail.percent 参数 在 dubbo2.7.10 及以上版本生效。
Broadcast Cluster 配置 broadcast.fail.percent。
broadcast.fail.percent=20 代表了当 20% 的节点调用失败就抛出异常,不再调用其他节点。
整合 hystrix
Hystrix 旨在通过控制那些远程系统、服务和第三方库的节点,从而对延迟和故障提供了更为强大的容错能力,Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。
步骤一:配置 Spring-cloud-starter-netflix-hystrix
Springboot 官方提供了对 Hystrix 的集成,只需要导入对应的pom.xml 文件中导入即可。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
<version>1.4.4.RELEASEversion>
dependency>
步骤二:使用注解 @EnableHystrix 标注在主启动类上使得当前应用支持 服务容错
@EnableDubbo
@EnableHystrix // 开启服务容错
@SpringBootApplication
public class UserInfoproviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserInfoproviderApplication.class, args);
}
}
步骤三:在可能会出现服务提供出错情况的接口上标注好 @HystrixCommand 注解
/***
* @author: Alascanfu
* @date : Created in 2022/7/19 20:23
* @description: service.impl.UserServiceImpl
* @modified By: Alascanfu
**/
@Component
@Service
public class UserServiceImpl implements UserService {
@HystrixCommand
@Override
public List<UserAddress> getUserAddress(int uid) {
System.out.println("当前用户访问 ID : " + uid);
UserAddress userAddress = new UserAddress("四川", "资阳",
"雁江", "娇子大道-皇龙路-优品上城B3一单元1801");
return Arrays.asList(userAddress);
}
}
步骤四:出现错误时 即 远程调用的情况下,在调用方也导入对应的 Hystrix 依赖,同时在主程序上标注好对应的 @EnableHystrix 注解开启服务容错,之后在调用的时候 @HystrixCommand(fallbackMethod=“方法”) => 对应方法进行容错处理调用的回调函数
/***
* @author: Alascanfu
* @date : Created in 2022/7/19 20:23
* @description: service.impl.UserServiceImpl
* @modified By: Alascanfu
**/
@Component
@Service
public class UserServiceImpl implements UserService {
@HystrixCommand(fallbackMethod = "getUserAddressErrorFallBackMethod")
@Override
public List<UserAddress> getUserAddress(int uid) {
System.out.println("当前用户访问 ID : " + uid);
UserAddress userAddress = new UserAddress("四川", "资阳",
"雁江", "娇子大道-皇龙路-优品上城B3一单元1801");
return Arrays.asList(userAddress);
}
public List<UserAddress> getUserAddressErrorFallBackMethod(int uid) {
System.out.println("当前用户访问 ID : " + uid);
UserAddress userAddress = new UserAddress("未知", "未知",
"未知", "未知-default-detailsPosition");
return Arrays.asList(userAddress);
}
}
一次完整的 RPC 调用流程(同步调用,异步另说)如下:
1️⃣ 服务消费方(Client) 调用以本地调用方式调用服务;
2️⃣ client stub 接收到调用后服务将方法、参数等组装成能够进行网络通信的消费体。
3️⃣ client stub 找到服务地址,并将消息发送到服务端
4️⃣ server stub 收到消息之后会进行解码;
5️⃣ server stub 根据解码结果调用本地的服务;
6️⃣ 本地服务执行并将结果打包成消息并且发送至消费方;
7️⃣ server stub 将返回结果打包成消息并发送给消费方
8️⃣ client stub 接收消息,并进行解码
9️⃣ 服务消费方得到最终结果。
BIO(Blocking IO)
NIO(Non-Blocking IO)
**Selector 一般称之为选择器,也可以被称之为 多路复用器 **
Netty 原理
图例说明:
各个层次之间的说明
ServiceConfig
, ReferenceConfig
为中心,可以直接初始化配置类,也可以通过 spring 解析配置生成配置类ServiceProxy
为中心,扩展接口为 ProxyFactory
RegistryFactory
, Registry
, RegistryService
Invoker
为中心,扩展接口为 Cluster
, Directory
, Router
, LoadBalance
Statistics
为中心,扩展接口为 MonitorFactory
, Monitor
, MonitorService
Invocation
, Result
为中心,扩展接口为 Protocol
, Invoker
, Exporter
Request
, Response
为中心,扩展接口为 Exchanger
, ExchangeChannel
, ExchangeClient
, ExchangeServer
Message
为中心,扩展接口为 Channel
, Transporter
, Client
, Server
, Codec
Serialization
, ObjectInput
, ObjectOutput
, ThreadPool
【参考资料】: 2021最新版Dubbo-admin+Zookeeper安装教程