一、架构演进
发展演变
1.1 单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。
缺点
- 性能扩展比较难
- 协同开发问题
- 不利于升级维护
1.2 垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
缺点
- 公用模块无法重复利用,开发性的浪费
1.3 分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的**分布式服务框架****(RPC)**是关键。
1.4 流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。
1.5 RPC概念
概述
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC两个核心模块
- 通讯
- 序列化
二、注册中心
1、概述
主流的注册中心:
- Nacos
- Zookeeper
- Multicast
- Redis
- Simple
- eureka
2、搭建zk集群
Zookeeper应用及原理_MG-net的博客-CSDN博客
三、RPC及Dubbo
1、什么是PRC
Rpc是一种协议,一种远程过程调用协议,规定了双方通信采用什么格式、以及数据如何传输。
- 指明调用类或者接口
- 指名调用的方法及参数
2、手写远程过程调用
- 准备一个注册中心,保存服务的地址
- 服务提供者
-
- 注册到注册中心
- 告诉注册中心服务实现类
- 准备服务协议,根据不同情况,使用不同的协议如http等
- 接受到参数后,通过参数进行类反射调用
- 最后将结果返回
-
- 服务消费者
-
- 使用代理调用服务提供者的实现类
-
- 封装本次调用的参数,类名、方法名、参数名、参数
- 获取地址列表
- 负载均衡选择服务提供者的地址
- 使用指定协议进行调用
总结,RPC解决的问题:
- 数据怎么封装
- 数据怎么传输,适配不同的协议
3、什么的Dubbo
Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。
4、Dubbo是怎么实现远程调用的
5、Dubbo的初体验
5.1、服务提供者配置
在zk中,可以看到相关的节点文件:
5.2 服务消费者
服务代理的过程:
可以看出,在dubbo中,服务提供者把提供的服务注册到注册中心中,消费者可以在像使用自己本地的Bean一样使用远程的提供者的服务。dubbo底层采用netty框架进行网络通信。
6、Dubbo的内部结构
- 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。
- 服务消费者(Consumer): 调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者
- 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
- 初始化提供者
- 提供者注册到注册中心
- 消费者订阅注册中心中的服务,并且把服务提供者的地址缓存到本地
- 当提供者下线的时候,注册中心需要消费者服务下线
- 消费者同步的调用服务
- 监控
四、Springboot中的dubbo
1、服务提供者
double的pom依赖:
1 <dependency> 2 <groupId>org.apache.dubbo</groupId> 3 <artifactId>dubbo-spring-boot-starter</artifactId> 4 <version>3.0.6</version> 5 </dependency> 6 7 <dependency> 8 <groupId>org.apache.dubbo</groupId> 9 <artifactId>dubbo-registry-zookeeper</artifactId> 10 <version>3.0.6</version> 11 </dependency>
在启动类上增加注解:@EnableDubbo
在实现类上增加注解:@DubboService
配置文件配置dubbo的相关信息:
server:port: 9001
dubbo:application:name: dubbo_providerprotocol:name: dubboport: 20882registry:address: zookeeper://127.0.0.1:2181
启动后,服务提供着就自动注册到zk中了。
2、服务消费者
引入pom依赖:
1 <dependency> 2 <groupId>org.apache.dubbo</groupId> 3 <artifactId>dubbo-spring-boot-starter</artifactId> 4 <version>3.0.6</version> 5 </dependency> 6 7 <dependency> 8 <groupId>org.apache.dubbo</groupId> 9 <artifactId>dubbo-registry-zookeeper</artifactId> 10 <version>3.0.6</version> 11 </dependency>
在启动类上增加注解:@EnableDubbo
注入类的时候,使用注解:@DubboReference,这个注解做了2件事
- 订阅服务
- 建立代理对象
配置文件中配置dubbo信息:
server:port: 8001
dubbo:application:name: consumerregistry:address: zookeeper://127.0.0.1:2181
3、@EnableDubbo的用法
注解流程:
@EnableDubbo->@EnableDubboConfig、@DubboCompentScan
五、dubbo的用法示例
1、version版本号
服务提供者可以指定版本号,这样消费者可以调用需要指定的版本号即可。
2、指定protocol协议
dubbo协议可支持的协议:
- rest:
- http
- dubbo(netty实现)
- 自定义协议
在配置文件:
在配置文件中,可以使用 protocols 进行多协议配置。
在提供者或者消费者端配置需要使用的协议即可:
如果配置服务未指定协议,但是配置文件配置了多个协议,那么就会生成配置个数的服务,例如配置了2种协议dubbo、rest,那么就会生成2个服务一个是dubbo协议、一个是rest协议。
3、使用rest访问dubbo的服务
需要注意依赖的引入。使用到jboss的依赖
1 <!-- Dubbo rest protocol --> 2 <dependency> 3 <groupId>org.jboss.resteasy</groupId> 4 <artifactId>resteasy-jaxrs</artifactId> 5 <version>3.15.1.Final</version> 6 </dependency> 7 8 <dependency> 9 <groupId>javax.validation</groupId> 10 <artifactId>validation-api</artifactId> 11 <version>1.1.0.Final</version> 12 </dependency>
同时需要注意,使用服务器,tomcat
4、消费者通过制定url连接制定服务提供着
如果服务提供者配置了多个协议,那么在启动服务的时候,就会根据不同的协议提供多个服务。
如果在消费者这边 指明消费某个服务,需要配置:
1 @RestController 2 @RequestMapping("site") 3 public class SiteContorller { 4 5 //订阅服务 6 //生成代理对象 7 @DubboReference(version = "v2", url = "dubbo://127.0.0.1:20882/com.mg.boot.api.SiteService") 8 private SiteService siteService; 9 10 @GetMapping("getName") 11 public String getName(String name){ 12 return siteService.getName(name); 13 } 14 15 }
5、服务超时
服务超时,就是调用接口超过服务消费者设置的最长时间。
在dubbo中,直接使用timeout,设置服务的超时时间。
服务提供者:如果超时,会打印超时日志,并且会执行完毕。
1 @DubboService(version = "timeout", timeout = 4000) 2 public class TimeOutSiteSerivce implements SiteService { 3 4 @Override 5 public String getName(String name) { 6 try { 7 Thread.sleep(5000); 8 }catch (Exception e){} 9 System.out.println("服务。。。。"); 10 return "timeout service " + name; 11 } 12 }
服务消费者:如果超时,则进行重试,重试失败抛出异常。
1 @DubboReference(version = "timeout", timeout = 6000) 2 private SiteService siteService;
服务提供者在任何情况下,都会执行业务逻辑。重试2次,就会执行3次(第一次是正常调用)。
6、集群容错
@DubboReference(version = "timeout", timeout = 3000, cluster = "failover", retries = 1)
dubbo提供的集群容错方案:
- failover(默认、推荐):当出现失败的时候,会进行重试其他的服务器,默认重试2次,会出现幂等性问题,但是可以在业务方面进行解决:
- 方案1:把数据的业务ID作为数据库的联合主键,此时业务ID不能重复
- 方案2:使用分布式锁来控制重复消费问题
- failfast:当出现问题的时候,立即放回错误,不重试,通常用于非幂等操作,如新增记录
- failsafe:当出现问题的时候,记录日志
- failback:失败就失败,开启定时任务重发,通常用于通知操作
- forking:并行访问多个服务器,获取一个结果既视为成功,forks=2,会浪费很多服务器资源
- broadcast:广播所有服务提供者,逐个调用,任意一台报错则报错
结论:使用dubbo的时候,不推荐把重试关闭,在非幂等操作场景下,服务提供者需要提供幂等解决方案。
7、服务降级
所谓的服务降级,就是当达到流量高峰的时候,需要保留核心业务的使用。
mock:接口提供假数据的功能。
- mock=force:return+null 表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时,对调用放的影响。
- mock=fail:return+null 表示消费方对该服务方法在调用失败后,在返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
1 @DubboReference(version = "timeout", timeout = 3000, mock = "fail:return timeout") 2 private SiteService siteService;
同时在接口的相同包下建立存根类,这样就会在注入接口的时候,在封装一层存根类的逻辑:
1 public class SiteServiceStub implements SiteService { 2 3 private final SiteService siteService; 4 5 public SiteServiceStub(SiteService siteService) { 6 this.siteService = siteService; 7 } 8 9 10 @Override 11 public String getName(String name) { 12 try { 13 return siteService.getName(name); 14 }catch (Exception e){ 15 return "stub" + name; 16 } 17 } 18 }
9、参数回调
消费者为服务提供者提供一个回调接口,也就是参数回调。
- 在接口层中增加方法,增加回调参数
1 package com.mg.boot.api; 2 3 public interface SiteServiceCallBack { 4 5 String siteName(String name); 6 7 default String siteName(String name , String key, SiteServiceCallBackListener siteServiceCallBackListener){ 8 return null; 9 } 10 }
- 增加回调接口和实现类
1 2 import java.io.Serializable; 3 4 public class SiteServiceCallBackListenerImpl implements SiteServiceCallBackListener, Serializable { 5 @Override 6 public String change(String data) { 7 System.out.println("change:" + data); 8 return "change:" + data; 9 } 10 }
- 服务提供者,指明了哪个方法、第几个参数是回调参数
1 @DubboService(version = "callback", methods = {@Method(name="siteName", arguments = {@Argument(index = 2, callback = true)})}, callbacks = 3) 2 public class SiteServiceCallBackImpl implements SiteServiceCallBack { 3 4 5 @Override 6 public String siteName(String name) { 7 return null; 8 } 9 10 @Override 11 public String siteName(String name , String key, SiteServiceCallBackListener siteServiceCallBackListener){ 12 siteServiceCallBackListener.change("p data"); 13 return "cc:" + name; 14 } 15 16 }
- 服务消费者
1 @DubboReference(version = "callback") 2 private SiteServiceCallBack siteServiceCallBack; 3 4 @GetMapping("getName") 5 public String getName(String name){ 6 return siteServiceCallBack.siteName(name, "key", new SiteServiceCallBackListenerImpl()); 7 }
10、异步调用
主要使用 CompletableFuture 接口进行一步数据的返回。
1 @Override 2 public CompletableFuture<String> siteNameAsync(String name){ 3 System.out.println("data:" + name); 4 return CompletableFuture.supplyAsync(()->{ 5 return siteName(name); 6 }); 7 }
六、dubbo的负载均衡策略
官方提供的集中负载均衡的策略:
- 随机
- 轮训
- 一致性hash:相同参数的请求,总是发送到一个提供者
- 最少活跃调用数:就是处理慢的提供者(服务配置低)接收到少的请求,根据调用的计数差
- 消费者在本地缓存所有的服务提供者
- 调用的时候,会选择本地所有服务提供者中active最小的
- 选定该服务提供者和后,并对其active+1
- 完成服务后,对其active-1
- 当active越大,则证明该服务提供者处理性能越差
七、安装Dubbo admin监管平台
可以使用本地安装,也可以使用docker安装。这里介绍受用docker安装方式。
docker run --name dubbo-admin -d -p 7001:7001 dubbo-admin:1.0
在admin中,可以配置相关权重、方法路由规则等等。
八、Dubbo的SPI可扩展机制
1、java中的sip机制
典型的使用sip,就是jdbc。java内部之约定了jdbc的接口,并没有提供具体实现。当需要连接mysql数据库的时候,就需要mysql实现这套jdbc的接口。这就是java中的sip机制。
如mysql jdbc:
java的DriverManager就会读取这个文件,获取实现类的全路径。
简单理解就是定义一套规范,实现来自不同的供应者,当需要使用哪个供应者的相关内容,引入即可。
2、sip机制的缺点
自己实现的时候,就使用SeviceLoader 进行加载 配置文件中配置的实现类。
可以看出java中sip存在,当配置多个实现类的时候,使用ServiceLoader可以获取多个,没有办法指定某一个,不够灵活。
3、dubbo中的sip机制
可以看到,在dubbo中,可以指明需要哪个实现类。
在dubbo中,有很多地方使用spi,例如服务调用协议的部分,根据配置文件中的配置,进行协议的使用。
同时,还可以在spi实现类中实现AOP的效果,其实就是在实现代码的时候,在前面或者后面增加相关功能。
九、Dubbo源码分析
1、Dubbo服务调用过程
a、服务消费者获取代理对象
b、 在注册中心获取服务集群(缓存在消费者本地的地址列表),使用ZookeeperRegistry
c、使用服务器集群中的Invoker调用器,Invoker封装了一次调用流程的整体内容
d、参数、方法名称等封装在RpcInvcation中,使用DubboInvoker进行调用
2、关于DubboInvoker的装饰
通过源码可以看出,Invoker调用采用的是装饰着模式,每一层封装不同的功能。
- DubboInvoker:使用dubbo协议调用提供者
- AsyncToSyncInvoker:把异步转换成同步
- ProtocolFilterInvocker:在调用过程中,提供了大量的功能如监控中心发送调用数据、TSP限流等等,所以主要负责维护过滤器链
- ListenerInvocker:负责监听链,提供业务回调窗口
3、权重轮训算法
权重轮训就是根据权重去轮训相关内容,例如A、B、C三台权重6、2、2,那调用应该是ABACA的顺序。
参考:
https://blog.csdn.net/liming0025/article/details/123785898
https://blog.csdn.net/qq_44292366/article/details/124744901
https://segmentfault.com/a/1190000019896723
https://juejin.cn/post/6949558189733969951