SpringCloud
单体应用与分布式应用
什么是单体应用?
单体应用是指将一个应用程序的所有功能模块集中在一个单一的执行单元中,通常打包为一个单独的可执行文件或软件包。这个单体可以是一个WAR包,也可以是一个JAR包、一个EXE文件、一个容器镜像等。
单体应用的优缺点
优点:
整个项目是一个web工程,运行在一个JVM中,整合性较好,开发便捷,容易管理
缺点:
1.项目越大,代码量越大,造成编译、打包 费时,越来越影响效率
2.业务增多时会造成代码冗余重复,代码复用度不高,造成浪费
前后台两个项目 -> 前端:
http://localhost:9999
后端:
http://b:9999/adminLogin.html
例: 两个项目实体类重复
3.可伸缩性差,单体应用中的功能模块的使用场景,并发量,消耗的资源类型各有不同,对于资源的利用又互相影响,这样使我们对各个业务模块的系统容量很难给出较准确的评估
当项目只在一个规定的时间上线一个新功能时,且对并发要求度高,只能对该项目的对应的硬件升级或流量进行扩充,无法对其进行分配,对于一个单体应用来说造成了资源空间的浪费
4.系统错误隔离性差,可用性差,任何一个模块的错误均可能造成整个系统的宕机
实际业务要求其中一个服务崩溃时,其它服务均可独立运行
SOA
面向服务的架构思想
将应用程序划分为一组自治、可独立开发和部署的服务,这些服务通过消息传递或网络调用进行通信。
SOA的三种架构思想
SOAP: 基于HTTP + XML;
webservice
缺点: xml返回的 XML 数据量较大,有大量的数据冗余 例如标签名、属性名等,这会增加数据大小)
优点: 协议严格,稳定性高,安全性高
基于SOAP的天气预报: http://www.webxml.com.cn/zh_cn/weather_icon.aspx
REST: 基于HTTP + JSON;
springcloud
优点: 数据简洁
缺点:字符流数据,明码传输,安全性低
RPC: 基于SOCKET+文本的形式,二进制数据;
alibaba dubbo
典型的RPC架构;字节流数据,可数据加密,安全性高
微服务
什么是微服务?
微服务是SOA的一种落地方案,SOA是一种面向服务的架构思想,微服务也同样推崇这种思想.
微服务架构是将一个大型应用程序分解为多个小型、自治且可独立部署的服务,以提高系统的可维护性、灵活性和扩展性。
使用脚手架实现快速开发
springboot
云服务
什么是云服务?
云服务是一种通过互联网提供的各种资源和服务的模式,例如计算、存储、数据库、分析、机器学习等。
云服务可以根据用户的需求动态地扩展合伙缩减,而无需管理底层的硬件或软件
云服务种类及其区别
微服务与云结合
微服务架构的核心概念之一就是每个服务都被打包和部署为离散的独立程序。服务实例应迅速启动,服务的每一个实例都是完全相同的。
基于云的微服务以弹性的概念为中心。在需要时,可以在云上几分钟之内快速布置启动新的虚拟机和容器,如服务需求下降,可以关闭虚拟服务器。这样可显著提高应用程序的水平可伸缩性,也使应用程序更有弹性.
SpringCloud简介
cloud是一系统框架的集合( 服务注册与发现nacos/eureka, 服务远程调用openFeign/Feign ,服务降级,服务熔断, 服务限流 sentinel/hystrix ,分布式事务seata,配s置中心nacos/spring cloud config, 总线nacos/stream , 网关 gateway, 链路追踪 sleuth 等等,部分 )
cloud与微服务的关系: 微服务是思想, cloud是解决方案
CAP理论
CAP理论指出一个分布式系统中最多满足以下三项中特性其中两项
C: 一致性(Consistency)
A: 可用性(Availability)
P: 分区容错性(Partition tolerance) => 一定满足
给定一个业务场景
1.直接返回旧数据 满足
AP
2.等待同步后返回新数据
CP
服务注册与发现(nacos/eureka)
1.服务注册: Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如IP地址、端口等信息
2.服务心跳: 检测 Nacos Client定时心跳持续通知Nacos Server说明服务可用,防止被删除
3.服务同步: Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
为什么要使用nacos server集群?
防止单体故障
4.服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给NacosServer,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存I
5.服务健康检查: 15s内没接收到服务心跳=>healthy=false,30s没接收到删除该实例
boot与cloud版本的对应
springboot版本: https://spring.io/projects/spring-boot#learn
boot 2.7.0-2.7.10
cloud版本: https://spring.io/projects/spring-cloud#overview
cloud 2021.0.6
Nacos开始
nacos安装
1 | ... |
注册服务到nacos
原理图
添加依赖
1 | <dependency> |
添加配置到application.yml
文件
1 | server: |
启动类添加 @EnableDiscoveryClient
注解 开启服务注册发现功能
1 |
|
运行application启动类注册服务
在浏览器输入
localhost:8848/nacos/index.html
在服务列表即可查看到注册的服务
配置多个服务节点(负载均衡)
查看服务列表的该服务下的服务详情,即可看到服务集群中存在了两个服务节点
配置服务消费端
在父项目下新建一个resorder的模块
1.application.yml
配置
1 | server: |
2.配置启动类
1 |
|
@EnableDiscoveryClient
依赖导入
1 | <!--父项目已经约定了版本,子模块不需要配置版本,直接导入即可--> |
3.新增配置类
resfood模块的config包中加入以下配置文件
1 |
|
4.controller
的配置
1 |
|
启动resorderApp把服务注册到nacos服务器上,在nacos服务列表检查服务是否注册成功
5.在postman中检测该服务是否可用
负载均衡
客户端负载均衡
a.将请求的地址由固定的 ip:端口 的方式改为 通过服务名访问
1 | //负载均衡需要用服务名resfood访问才能起作用 |
b.映入loadbalancer依赖
在resorder模块 客户端(消费端)加入此依赖
1 | <!--引入riboon,客户端保存服务列表信息--> |
c.在客户端组件RestTemplate上加入@LoadBalanced
1 |
|
配置不同的负载均衡策略
1 | //方案一: 全局配置负载均衡策略 |
自定义负载均衡器
通过spring-cloud-starter-loadbalancer
启动配置的源码剖析
1.在外部库中找到此jar包
发现starter类中导入了loadbalancer负载均衡的依赖
2.跟踪spring-cloud-loadbalancer
在外部库的jar包
在此jar包中发现其中的spring工厂中启用了负载均衡的配置类
3.继续跟踪该自动配置类
1 | //是一个Spring配置类,不是代理对象生成,关闭代理模式 |
4.追踪LoadBalancerClientFactory
找到该bean工厂中的getInstance方法
1 | public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) |
该方法的作用是获取指定服务的负载均衡实例,返回一个
ReactiveLoadBalancer
类型的对象。其中,serviceId
参数表示服务的标识符,根据serviceId
获取相关的负载均衡客户端,并返回一个负载均衡实例;
5.继续追踪ReactorServiceInstanceLoadBalancer
1
2 >ReactorLoadBalancer的标识接口,允许选择服务实例对象
>在服务接口中选取一种进行查看,这里选的是轮询负载均衡
6.查看RoundRobinLoadBalancer源码
轮询算法:
原子整型 默认为0
初始时
this.position
的值为 0。
this.position.incrementAndGet()
将this.position
的值加 1,变为 1。& 2147483647
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
进行位与运算:
- 1 的二进制表示为:`00000000000000000000000000000001`
- 2147483647 的二进制表示为:`01111111111111111111111111111111`
- 进行按位与运算:`00000000000000000000000000000001`
- 得到的结果是 1。
>4. `(pos % instances.size())` 计算 1 除以 3 的余数,结果为 1。
>5. 通过 `instances.get(1)` 获取 `instances` 列表中索引为 1 的元素。
>接下来继续执行相同的运算步骤即可
>`(pos % instances.size())`=0时,算法完成一个循环,归0
![](/img/SpringCloud/loadbalancer-starter/6.png)
#### 自定义一个负载均衡器
>只访问一个服务 `OnlyOneLoadBalancer` 负载均衡器
*1.写一个`LoadBalancer`类实现`ReactorServiceInstanceLoadBalancer` 接口*
```java
@Slf4j
public class OnlyOneLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
public OnlyOneLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map((serviceInstances) -> {
return processInstanceResponse(supplier, serviceInstances);
});
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
log.info("自定义负载");
if (instances.isEmpty()) {
return new EmptyResponse();
}
//固定访问第1个服务
ServiceInstance instance = instances.get(0);
return new DefaultResponse(instance);
}
}
2.用于自定义负载均衡器的配置类
定义了一个名为OnlyOneLoadBalancerConfiguration的@Configuration注解类,使用@Bean注解托管,声明了一个名为OnlyOneReactorServiceInstanceLoadBalancer的方法。
1 |
|
3.在客户端组件RestTemplate上加入@LoadBalanced
这段代码配置了使用自定义的负载均衡器去负载均衡访问名为”resfood”和”resorder”的服务,并且通过@LoadBalanced注解将RestTemplate设置为负载均衡的实例。
1 |
|
OpenFeign
OpenFeign
SpringCloud OpenFeign是一种声明式的REST客户端
原理
开发者只需要定义服务接口并用注解描述服务即可,其余都交给
OpenFeign
处理OpenFeign在项目启动时生成动态代理类,代理类集成了负载均衡器,可以自动选择服务实例并发送请求
特点
自动集成Ribbon,实现客户端负载均衡。
自动集成Hystrix,实现服务降级和熔断。
自动集成Eureka或Consul,从服务注册中心获取服务列表。
可以自定义Feign的组件,如编码器,解码器,拦截器等
Feign
HTTP请求调用的轻量级框架(底层jdk面向接口的动态代理)
封装了HTTP 调用了流程,面向接口编程 =>java注解方式调用Http请求
请求模板化,要进行适配,根据传入的参数应用在对应的请求上,进而转化为真正的请求
为什么使用Feign和OpenFeign
Feign和OpenFeign的作用是简化和优化微服务架构中服务之间的HTTP调用。
传统的HTTP调用需要手动编写大量的代码来处理请求的创建、发送、接收和解析等过程,而且还需要处理负载均衡、熔断等场景。这使得开发者要花费大量的精力来处理这些细节,增加了开发的复杂度和工作量。
Feign开始
实现Feign 客户端发送 HTTP 请求到指定的 GitHub 仓库地址,并获取该仓库的贡献者列表,并将结果打印输出的功能。
1.引入依赖
1 | <dependencies> |
2.实体类
OpenFeign代码贡献者的数据实体类
1 | package com.yc; |
3.Feign 的接口定义
该接口使用 Feign 提供的注解和方法规范,声明了一个获取 GitHub 贡献者列表的方法(contributors)
1 | public interface GitHub { |
4.测试
调用 GitHub 接口中的 contributors() 方法,传入 “OpenFeign” 和 “feign” 作为 owner 和 repo 的值,以获取贡献者列表。
1 | public class MyApp { |
OpenFeign-Api暴露
OpenFeign API暴露是指将一个Feign客户端声明为一个Spring Bean并对外暴露,使其它组件可以注入和使用,这样可以方便地在其它组件中使用Feign客户端来进行微服务之间的HTTP调用。
1.导入OpenFeign的依赖
1 | <dependency> |
2.导入项目的bean(实体类)
1
2
3
4
5 ><dependency>
<artifactId>res-bean</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
></dependency>
3.创建api服务,在此服务中加入要公开 API接口
1 | //指定服务名称 |
4.在调用端引入依赖和api服务端
1 | <dependency> |
5.注入api接口到controller或业务层
1 |
|
6.在主启动类上添加@EnableFeignClients
1 |
|
发送请求,控制台查看结果
一般使用步骤: 1.创建api服务. 在此服务中加入要公开 API接口
2. 在这个api服务中开发 接口: @FeignClient(“resfood”) public interface ResfoodApi { @RequestMapping( value=”resfood/detailCountAdd” , method=RequestMethod.GET) public Map<String, Object> detailCountAdd(Integer fid); } 3. 调用端开发: org.springframework.cloud spring-cloud-starter-openfeign res-api org.example 1.0-SNAPSHOT b)开启openfeign的客户端 @EnableFeignClients(basePackages= {“com.yc.api”}) c)注入api接口到controller或业务层. @Autowired private ResfoodApi resfoodApi; org.springframework.cloud spring-cloud-starter-openfeign
Feign中的组件
。。。
OpenFeign日志配置
官方文档地址: https://docs.spring.io/spring-cloud-openfeign/docs/3.1.8/reference/html/#feign-logging
yml配置
在客户端(调用api)的yml配置文件添加以下代码
1 | logging: |
客户端单独配置
1 >Logger.Level
NONE
,无日志记录(默认)。BASIC
,仅记录请求方法和 URL 以及响应状态码和执行时间。HEADERS
,记录基本信息以及请求和响应标头。FULL
,记录请求和响应的标头、正文和元数据。
openfeign客户端单独配置
1 |
|
发送请求查看日志
网络压缩
为什么要使用网络压缩?
在网络传输中数据存在一些冗余的字符,像json数据中的换行符、空格,都会带来大量的流量损耗,而OpenFeign 提供了对 Gzip 压缩的支持,在网络传输中,通过对 HTTP 请求和响应进行 Gzip 压缩,可以减少传输数据的大小,缩短传输时间,降低网络带宽的消耗和服务器压力,从而提高系统的性能和稳定性。
配置
1.yml配置
1 | feign: |
2.源码剖析
理解配置项为什么要这样配置
请参考: https://forestcat.blog.csdn.net/article/details/109077317
扩展
原子整型
AtomicInteger.incrementAndGet();
i++ ;
底层
i=0;
i=i+1;
多线程编程无法保证执行顺序