目录
2.3.3. (扩展)@LoadBalanced实现负载均衡与服务发现
3.3.2. 如果我给一个项目imp导入多个配置集,那么此时的优先级呢?
2.7. (补充)blockHander与fallback兜底回调的区别
一、概述
1. 什么是SpringCloud?
pring Cloud 是一套基于 Spring Boot 的微服务架构开发工具集 ,它旨在简化分布式系统的开发,通过整合一系列成熟的分布式技术框架,提供了构建分布式系统所需的多种功能组件,实现了分布式系统开发中的诸多关键技术
2. 什么是微服务?
就是把应用分成一整套小服务模块,小服务可以抽离部署到多个服务器上,利用这些微型服务模块,构成大型的、模块的(可拆解的)、相互独立又有联系的应用。并且由于分离化,小服务构成的技术甚至语言也可以不同, 保持最 低限度的集中式管理。
简⽽⾔之:拒绝⼤型单体应⽤,基于业务边界进⾏服务微化拆分,各个服务独⽴部署运⾏。
3. 微服务应用与传统应用的差异
3.1. 架构设计
- 传统应用:多为单体架构,所有功能模块集成在一个可执行程序中,各模块耦合度高 。比如早期的一些管理系统,用户管理、订单管理等功能模块都在同一程序,模块间通过代码直接调用 。
- 微服务应用:采用分布式架构,将大型应用拆分成多个小服务,每个服务专注单一业务功能,服务间松耦合 。像电商系统,可拆分为用户服务、商品服务、订单服务等,通过接口通信 。
3.2. 开发模式
- 传统应用:通常团队成员共同开发一个项目,技术栈相对统一 。开发时需考虑整个系统架构,牵一发而动全身,新功能或修改易引发连锁问题 。
- 微服务应用:各服务可由不同小团队负责,能根据服务特点选合适技术栈,开发更灵活 。例如用户服务用 Java,商品服务用 Python 。开发人员聚焦单个服务,更易上手 。
3.3. 部署与运维
- 传统应用:作为整体部署,更新或修复需重新部署整个应用,过程复杂、耗时长,期间服务可能中断 。
- 微服务应用:各服务可独立部署、更新和扩展 。某个服务升级不影响其他服务,部署更灵活、高效,利于持续集成与交付 。比如流量高峰时,可单独扩展订单服务 。
3.4. 数据管理
- 传统应用:一般使用单一数据库,不同模块数据可能关联,数据库结构变更困难,易影响多个模块 。
- 微服务应用:每个服务可拥有独立数据库,能按需选合适数据库类型(如关系型、非关系型 ),数据管理更灵活,减少模块间数据耦合 。
3.5. 容错性
- 传统应用:一处故障可能导致整个应用崩溃,因为模块耦合度高,故障易扩散 。
- 微服务应用:单个服务故障通常不影响其他服务,通过服务降级、熔断等机制,可隔离故障,保障系统整体可用性 。
二、Nacos 注册/配置中心
1. 基础
1.1. 概述
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的⾸字⺟简称,⼀个更易于构 建云原⽣应⽤的动态服务发现、配置管理和服务管理平台
官⽹:Nacos官网| Nacos 配置中心 | Nacos 下载| Nacos 官方社区 | Nacos 官网
1.2. 安装
Windows:
- 到官网下载.zip文件,然后解压
Docker:
- docker run -d -p 8848:8848 -p 9848:9848 -e MODE=standalone --name nacos nac os/nacos-server:v2.4.3
1.3. 启动
2.*版本解压后直接进入bin目录然后使用终端指令即可,3.*版本比较麻烦
startup.cmd -m standalone
如果无法使用也可以加个 .\
.\startup.cmd -m standalone
2. 功能:注册中心
注册中心是为了协调联系各模块的相互联系,例如:服务注册与下线、服务发现、服务负载均衡
2.1. 导入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.2. 服务注册
服务注册到注册中心,可以通过注册中心调控客户对服务的访问
2.2.1. 配置nacos路径
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
这个是nacos的默认地址:本地回环ip + 端口8848
2.2.2. 启动
2.3. 服务发现
2.3.1. 配置主类(开启服务发现功能)
@EnableDiscoveryClient // 开启服务发现--核⼼注解
@SpringBootApplication
public class OrderMainApplication {
}
public static void main(String[] args) {
SpringApplication.run(OrderMainApplication.class, args);
也要配置好nacos路径才能使用其发现功能
2.3.2. 使用例
- 创建Order和Product两个服务并完成配置、连接等功能
- 在Order的ServiceImpl中导入 DiscoveryClient
@Autowired
private DiscoveryClient discoveryClient;
- 创建getProductfromRemote方法
/**
* 通过注册中心获取Product的地址并发请求获取Product实例的方法
*/
private Product getProductfromRemote(Long productId){
// 1、通过注册中心调用商品服务的所有ip和port
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
ServiceInstance instance = instances.get(0);// 这里可以做负载均衡,不过我直接使用了第1个服务
// 2、创建服务url
String url = "http://"+
instance.getHost()+// 服务ip
":"
+instance.getPort()// 服务端口
+"/product"
+productId;// 注册的第几个服务
// 3、使用web发请求
return restTemplate.getForObject(url, Product.class);
}
- 调用getProductfromRemote方法
@Override
public Order createOrder(Long userId, Long productId) {
Product product = getProductfromRemote(productId);
Order order = new Order();
order.setId(123456L);
order.setTotalAmount(new BigDecimal("0"));
order.setUserId(userId);
order.setNickName("XX学院");
order.setAddress("胡X");
order.setProductList(Arrays.asList(product));//《---使用Product
return order;
}
- 测试成功:
2.3.3. (扩展)@LoadBalanced实现负载均衡与服务发现
如果我们要在代码内实现负载均衡会有些麻烦,可以为web的RestTemplate标识@LoadBalanced注解,从而简单的实现负载均衡
- 在OrderConfig的RestTemplate标识@LoadBalanced注解
@Configuration
public class OrderConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- 新的服务发现方式
private Product getProductfromRemote(Long productId){
// service-product是指微服务的名字
String url = "http://service-product/product/"+productId;
Product product = restTemplate.getForObject(url,Product.class);
return product;
}
- 测试成功
2.3.4. (面试)注册中心宕机后,远程调用能成功吗?
3. 功能:配置中心
配置中心也是Nacos的一部分功能,开发者可以利用在Nacos中的配置集动态地修改微服务的配置,不用停机后更新配置
3.1. 导入与配置
3.1.1. 导入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
注册中心的artifactId项以discovery
3.1.2. 服务端文件配置
导了配置中心的依赖之后和注册中心一样,需要指定配置nacos地址,并且要指定该项目绑定的配置集(Nacos的云端配置文件)
# 指定配置中⼼地址
spring.cloud.nacos.server-addr=localhost:8848
# 绑定配置文件,整理绑定了order服务的
spring.config.import=nacos:service-order.properties
注意:如果导入了配置中心的依赖而不指定项云端绑定的配置集的话,nacos会检查出问题并报错终止启动,我们可以设置关闭配置集的启动检查 (防止子项目因为没有指定而报错)
- spring.cloud.nacos.config.import-check.enabled=false
3.1.3. Nacos端配置集
- DataID:配置集的名字,用来和服务绑定
- Group:配置集的分组,用来精确匹配
- 配置内容:和一般的配置文件一样可以用yaml、Properties等等配置,配置方式也一样
3.2. 连接和监听
3.2.1. 基础连接
完成以上配置后就能进行基本的使用了,访问Nacos的配置集
- OrderCollection:
@RefreshScope// 开启自动刷新功能的注解
@RestController
public class OrderCollection {
@Value("${order.creater}")// 和项目中读取配置文件一样
String creater;
@GetMapping("/creater")
public String getCreater(){
return creater;
}
}
注意:@RefreshScope 是开启自动刷新功能的注解
- 测试结果
3.2.2. 动态刷新
一般我们不使用基础的配置连接功能,而是把需要动态读取的配置封装到一个类中方便使用
@Component
@ConfigurationProperties(prefix = "order")// 指定类对应配置项,如order.xxx,可以直接用类中的xxx属性接收
@Data
public class OrderProperties {
String creater;
}
注意:@ConfigurationProperties(prefix = "order")指定类对应配置项,如order.xxx,直接用类中的xxx属性接收
此时我们便可直接使用OrderProperties的get方法获取配置
3.2.3. 配置项监听器
顾名思义,可以在每次配置更新时获取/监听配置更新的信息
- 写在启动类中,格式比较死板
@Bean
ApplicationRunner applicationRunner(NacosConfigManager manager){
return args -> {
ConfigService configService = manager.getConfigService();
configService.addListener("service-order.properties", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
@Override
public void receiveConfigInfo(String configInfo) {
System.out.println("更改 = " + configInfo);
}
});
};
}
- 效果
3.3. 面试:Nacos配置集的优先级问题
3.3.1. Nacos配置集和项目配置文件谁的优先级高?
答:从配置中心的设计角度出发,如果项目的配置文件更优先,那么配置中心的管理与更新功能就相当于没有了。故,配置中心的配置集优先级更高
3.3.2. 如果我给一个项目imp导入多个配置集,那么此时的优先级呢?
答:还是从配置中心的设计角度出发,如果需要分块化写入配置而产生了多个不同的配置文件,那么非重复的配置共同生效;而因为我们可能多次更改,所以要以后更改的内容为正确。故,非重复配置叠加,重复就后导入优先
3.4. 数据隔离
在软件开发中会有多个环境,开发、测试、生产等等,而Nacos配置中心也为我们提供了环境数据隔离机制
3.4.1. 名称空间
所谓的名称空间其实就是和服务运行环境对接的分组,在名称空间中定义的配置集就对应了开发环境下的种种配置
配置与方法使用:
- 创建命名空间:
注意:id和名字最好是一样的,不要让系统自动生成id
- 和前面一样,需要注意选择自己创建的配置(这里设置了group分组)
- data id:就是服务的配置文件中的绑定数据集名字
- group:分组id
3.4.2. 基本使用
其实只是添加了个namespace配置项。(在开发中,我们可能还会给配置集分组,即使Data I重名了,也可以通过group分组来准确找到对应配置集)
- 指定名称空间***
spring.cloud.nacos.config.namespace=test // 这里指定的是test,可以通过改变name改
- 添加新的Nacos配置集
spring.config.import=\
nacos:service-order.properties,\
nacos:service-order-date.properties?group=IOS
#下面这个
三、OpenFeign 远程调用
1. 基础配置
1.1. 导依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1.2. 注解式启动
标识在主类/运行类上
@SpringBootApplication
@EnableFeignClients//<---这个
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2. 基础使用:调用业务api
也就是远程调用项目的其他服务(在本地注册中心注册注册了的)(实现了负载均衡)
- 编写接口并使用@FeignClient的Value的属性来指定连接的服务(注册中心的服务名)
- 使用MVC的Mapping注解,标识远程调用的方式等等(Mapping原本是用来接收请求的,这里却用来发送请求)
- 在Mapping标识的抽象方法中指定参数列表和返回值
-
- 参数列表:和MVC反过来,@PathVariable("id")就是把该参数放到"id"的位置,同理如果用@RequestBody则把该数据转换成JSON并发出
- 返回值:用来封装远程调用的结果
@FeignClient(value = "service-product")
public interface ProductFeignClient {
@GetMapping(value = "/productId/{id}")
public Product getProductById(@PathVariable("id") Long productId);
}
MVC注解的两种用法:
- 在@Controller上标识:接收如下参数
- 在@FeignClient上标识:发送如下参数
3. 进阶使用:调用第三方api
Openfeign 除了能调用服务架构内业务服务的api外,在参数满足外部服务的要求时也能进行远程调用。(比如为软件需要一个获取日历的功能,就可以通过调用外部服务简单地完成)
- @FeignClient的value属性随便填,在url属性内填好服务的域名和端口
- 同上,编写远程调用方法(一定要仔细阅读连接服务的接口文档,保证参数完全正确)
使用方式:
@FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com")
public interface WeatherFeignClient {
@PostMapping("/whapi/json/alicityweather/condition")
String getWeather(// 返回值作为JSON串
@RequestHeader("Authorization") String auth,
@RequestParam("token") String token,
@RequestParam("cityId") String cityId);
}
4. 补充
4.1. 小技巧
由于OpenFeign对于MVC的适配性,我们可以直接把要调用的服务的Collection方法剔除方法体拿来用,可以完美适配
4.2. 面试:负载均衡的两种实现
问:客户端负载均衡与服务端负载均衡的区别是什么?
答:
- 客户端负载均衡:是基于注册中心提供服务地址,由客户来选择使用哪个
- 服务端负载均衡:就是网关,在网关中帮客户无感均衡地转发到服务群其中的一个
5. OpenFeign的进阶配置用法
5.1. 日志
使用流程:
- 对feign所在的包设置其下方法的日志级别以开启日志
logging.level.{feign所在的包的项目路径}:{日志级别}
logging.level.com.atguigu.order.feign: debug
- 配置feign的日志组件到一个配置类中
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
feign的日志级别和Spring的不一样:
- NONE:不打印日志
- BASIC: 仅打印请求方法、URL、状态码及执行时间
- HEADERS:在BASIC基础上,打印请求和响应的Header
- FULL:打印所有请求和响应的细节(URL、Header、Body、元数据等)
5.2. 超时控制
使用OpenFeign实现远程调用也是请求响应的过程,所以OpenFeign也有其自带的超时机制。开发中我们可以通过配置文件的方式配置超时机制
使用流程:
添加配置项,到服务配置目录
spring:
cloud:
openfeign:
client:
config:
default: #默认配置
logger-level: full #日志级别
connect-timeout: 1500 #连接超时时间
read-timeout: 1500
service-product: #服务配置 (名字和服务名一般一致)
connect-timeout: 3000
read-timeout: 5000
注意:
- 如果没有指定具体配置时,以默认配置为主
- 在对于OpenFeign的具体服务时,配置名和服务名一般一致,如果feign的Client类中有contextId属性时,则以contextId属性为主
5.3. 重试机制
如果开启了重试机制:OpenFeign在远程调用时如果连接超时,会进入请求重试机制,如果请求不能在本服务配置的重试机制下完成连接,才会认为调用失败。我们可以通过注入组件或者配置文件来启动并配置重试机制
5.3.1. 具体机制
这是默认情况下的的配置:
- period:100 -- 最大超时时间
- duration:1 -- 初始超时时间
- maxAttempts:5 -- 最多重试机会/次数
- 总结 -- 当请求时间超过初始超时时间后会进入重试阶段,每次重试的时间都会扩大到上一次的1.5倍,当总时间超过最大超时时间或是次数用完,认为连接失败
5.3.2. 启用重试机制
在order 服务的OrderConfig中加入重试组件
@Configuration
public class OrderConfig {
@Bean
Retryer retryer() {
return new Retryer.Default();// 这里用的是默认的重试机制,没有配
}
}
5.4. 拦截器
同样也是因为OpenFeign的远程调用机制,要为其配置专门的拦截器。
流程:
添加拦截器到拦截器目录,使用@Component添加到容器(最好新开一个,这里的拦截器继承的是OpenFeign的拦截器)
@Component
public class OrderInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("U-I-D", UUID.randomUUID().toString());// 添加UID到请求头里面
}
}
注:除了直接使用@Component添加拦截器,还可以在配置文件中指定。(必须使用两种方法中的一种)
5.5. Fallback兜底返回(需要熔断机制配合)
当调用失败时,我们可以通过Fallback机制为请求封装 假数据/缓存数据/默认数据。写在发送请求的客户端,实现之前远程调用功能的接口,这样同名方法就会在失败后执行会执行兜底方法
流程:
- 导入sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置sentinel为feign的熔断框架
feign:
sentinel:
enabled: true #开启
- 创建Fallback组件
-
- 要实现远程调用的接口
- 要把该实现类注册到容器中
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long productId) {
System.out.println("兜底回调....");
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNum(0);
return product;
}
}
- 给Client加上回调实现
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping(value = "/productId/{id}")
public Product getProductById(@PathVariable("id") Long productId);
}
注:
可以认为没有通过接口得到数据,于是到其实现类的同名方法中获取数据,很取巧也很有效
对应的远程调用如下:
- product/{id} 就自然改换到本地服务中的Client上了
6. 总结
OpenFeign是一个封装完备的远程调用框架,配置完毕后只要简单的操作就能实现远程调用。如果还需要OpenFeign提供日志、超时、重连、拦截、兜底等机制,可以根据指示完成
四、Sentinel 资源流程控制
1. 基础配置
1.1. 下载Sentinel
sentinel-dashboard-1.8.8:登录 · 语雀
1.2. 启动Sentinel
启动指令:java -jar .\sentinel-dashboard-1.8.8.jar
控制台:
- 账号密码一致为:sentinel
- 路径为:http://localhost:8080/#/login
1.3. 配置文件
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 #默认Sentinel访问路径
eager: true #设置为项目启动就直接读取所有设置了的资源
注意:sentinel默认为懒加载,如果不设置true的话当资源被调用时才会被读取
1.4. 基础使用
- 为服务中某一Controller下的SerciceImpl标识@SentinelResource(value = "资源名")
@SentinelResource(value = "createOrder")
@Override
public Order createOrder(Long userId, Long productId) {
Product product = getProductfromRemote(productId);
Order order = new Order();
order.setId(123456L);
order.setTotalAmount(new BigDecimal("0"));
order.setUserId(userId);
order.setNickName("XX学院"+productId);
order.setAddress("胡X");
order.setProductList(Arrays.asList(product));//《---使用Product
return order;
}
- 启动Sentinel与微服务
- 刷新以连接服务
- 添加流控规则
- 测试,发现快速刷新会导致访问失效
2. 异常处理
2.1. 流程图
- Sentinel规则优先触发顺序:Web接口-->@SentinelResource类
- 对应拦截器触发顺序:SentnelWebinterceptor类 --> blockHandler指定方法
2.2. (回忆)SpringBoot的异常处理器
@ResponseBody
@ControllerAdvice// 全局异常处理器集核心注解
public class GlobalExceptionHandler {
@ExceptionHandler(异常类.class)// 类内部异常处理器
public String XXXerror(对应异常类 error){
/*
异常处理逻辑
*/
return 处理结果;
}
}
- 全局异常处理器集核心注解:@ControllerAdvice
- 类内部异常处理器:@ExceptionHandler(异常类.class)
2.3. 对Web接口的兜底回调
当流程控制规则限制在Controller层级上的路径,会优先触发web接口对应的异常。如果不编写处理逻辑,会使用java默认的异常页来返回信息
做法:写一个BlockExceptionHandler的实现类,用handle方法处理请求
实现:
@Component
public class sentinelHandler implements BlockExceptionHandler {// <----重点
ObjectMapper objectMapper = new ObjectMapper();
public void handle(HttpServletRequest request, HttpServletResponse response,
String resourceName, BlockException e) throws Exception {
// 以下是一个处理流程:
response.setStatus(429); //too many requests
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
R error = R.error(500, resourceName + " 被Sentinel限制了,原因:" + e.getClass());
String json = objectMapper.writeValueAsString(error);
writer.write(json);
writer.flush();
writer.close();
}
}
2.4. blockHandler 兜底回调
当Sentinel的流控规则限制在由@SentinelResource注解标注的方法上时,可以使用两种方法为触发规则的请求进行兜底回调。
做法:
- 为@SentinelResource添加参数blockHandler,参数指向为编写的Fallback处理方法
- Fallback处理方法可以在@SentinelResource方法的基础上添加BlockException exception参数,参数用来接收异常类
实现:
@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
@Override
public Order createOrder(Long userId, Long productId) {
Product product = getProductfromRemote(productId);
Order order = new Order();
order.setId(123456L);
order.setTotalAmount(new BigDecimal("0"));
order.setUserId(userId);
order.setNickName("XX学院"+productId);
order.setAddress("胡X");
order.setProductList(Arrays.asList(product));//《---使用Product
return order;
}
// createOrder的兜底回调
public Order createOrderBlockHandler(Long userId, Long productId, BlockException e){
Order order =new Order();
order.setId(0L);
order.setTotalAmount(new BigDecimal("0"));
order.setUserId(0L);
order.setNickName("未知用户");
order.setAddress("异常信息:"+e.getClass());
return order;
}
2.5. OpenFeign 远程调用兜底回调
feign:
sentinel:
enabled: true #开启
- 创建Fallback组件
-
- 要实现远程调用的接口
- 要把该实现类注册到容器中
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long productId) {
System.out.println("兜底回调....");
Product product = new Product();
product.setId(productId);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNum(0);
return product;
}
}
- 给Client加上回调实现
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping(value = "/productId/{id}")
public Product getProductById(@PathVariable("id") Long productId);
}
2.6. (扩展)SphU硬编码
是以上3种方法的底层,不常用
2.7. (补充)blockHander与fallback兜底回调的区别
相同点:
都作为@SentinelResource注解指定的兜底回调方法
@SentinelResource(value = "createOrder",blockHandler = "createOrderBlockHander",fallback="createOrderFallback")
public Order createOrder(Long userId, Long productId) {
.....
}
不同点:
- blockHander指定的方法处理的是Sentinel内置的异常类
- fallback指定的方法处理的是java的大异常类 Throwable
public Order createOrderBlockHandler(Long userId, Long productId, BlockException e){
...
}
public Order createOrderFallback(Long userId, Long productId, Throwable e){
...
}
3. 流控规则
3.1. 流控模式
在服务中,可能有各种各样的链式调用,为了控制调用链,我们引入流控模式
在编辑规则前,最好给项目配置web-context-unify: false
关闭共用上下文配置,因为后续的配置很多都是对于不同上下文时才能生效
# 关闭共用上下文配置
spring.cloud.sentinel.web-context-unify=false
3.1.1. 关联
3.1.2. 直接
只对编辑的资源进行限制控制:
3.1.3. 链路
对编辑的资源控制,以限制其链路上的入口资源:(比如秒杀功能需要调用创建功能,“秒杀”在调用链上在“创建”前,我们限制“创建”的入口若为“秒杀”,则限制其流量)
关闭共用上下文配置
3.1.4. 关联
对编辑的资源控制,限制可能关联的资源:(比如对于一个资源进行不同优先级操作时,如果优先级高的操作流量过大,为了稳定我们关联限制其他操作,图例是读>写操作)
关闭共用上下文配置
3.2. 流控效果
3.2.1. 快速失败
当超出阈值后直接失败报流控异常:
3.2.2. Warn Up 热启动
初始情况下只有单机阈值的30%能通过,在预热时长内逐渐增长直到达到阈值:(先低功耗,后预热启动)
3.2.3. 匀速排队
控制每秒请求通过数,排队进入,超时则报流控异常
4. 融断规则
融断降级:快速及时切断请求链,防止服务雪崩
4.1. 断路器
- 闭合状态下:请求能正常通过
- 打开状态下:请求无法正常通过
- 半开状态下:允许一个请求通过,根据返回结果选择闭合或是打开
4.2. 熔断策略
- 慢调用比例:慢调用在统计时长内所有请求中所占比例,超过该比例则断路器开启
-
- 慢调用:超出系统设置统计时长的请求
- 异常比例:结果为错误的请求在统计时长内所有请求中所占比例,超过该比例则断路器开启
- 异常数:在统计时长内所有请求中结果为错误的请求个数,超过该数量则断路器开启
5. 热点规则
流控规则的精细化版本,可以针对请求的具体参数进行流控,而不仅仅是资源位置
5.1. 热点参数限流
- 参数索引:标识方法的参数列表中的序列索引,从0开始
注意:受限于对参数的要求,热点规则只能对于被精确设置为资源的方法有效,不能直接限制在Web层级上
5.2. 限流例外项
在添加了热点规则后可以编辑其高级项,添加例外参数列表
五、Gateway 网关
绝大部分请求相关的功能网关都能完成
1. 路由配置
网关就是管理路由
1.1. 模块创建与依赖导入
- 由于网关是独立的功能,常用独立的服务项目作为网关,需要额外导入nacos注册中心和配置中心
- 在新版本中,需要额外引入SpringCloud的负载均衡依赖,网关才能实现负载均衡
<!-- 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 注册中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 额外引入SpringCloud的负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
1.2. 基础使用
配置网关参数:在一般的SpringCloud服务项配置之外,网关需要专门编写配置项来管理其他服务:服务的连接路径、转发路径....
- uri:请求接入网关转发后需要前往的服务,目的地
- predicates:请求前往指定服务、目的地需要满足的规则,可以多个(取并集)
- order:优先级(数值越小优先级越,默认优先级为自上而下)
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#------------网关配置
gateway:
routes: #
- id: order
order: 0
uri: lb://service-order
predicates:
- Path=/api/order/** #路径规则
- id: product
order: 1
uri: lb://service-product
predicates:
- Path=/api/product/**
server:
port: 80
注意:
网关的路径规则匹配优先级很重要,如果兜底的响应被设置为高优先级,可能导致一些深层路径无法匹配:如localhost/**、localhost/product/**
2. 断言规则 predicates
断言是否匹配,才能路由到指定服务
2.1. 断言类型 Query
2.2. 短写法
spring:
cloud:
gateway:
routes:
- id: order
order: 0
uri: lb://service-order
predicates: # 断言
- Path=/api/order/**
2.3. 长写法(断言工厂)
短写法的本质,name(断言规则类型) + 参数列表 + ...
spring:
cloud:
gateway:
routes:
- id: product
order: 1
uri: lb://service-product
predicates: # 断言
- name: Path
args:
patterns: /api/product/**
matchTrailingSlash: true
matchTrailingSlash:
- true:结尾可以多个"/"
- false:结尾多了个"/"就不归我管了
2.4. 自定义断言工厂
略
3. 过滤器 filter
顾名思义,给请求进行加工的工具
3.1. 过滤器类型
和断言规则一样,gateway的过滤器也用[name + 参数] 那一套
3.2. 基础使用
也和断言一样,有长短写法之分
使用例:不再在服务的Web接口指定前缀Mapping--"/order/**",使用拦截器自动去除前缀,便于服务内部连接
spring:
cloud:
gateway:
routes:
- id: order
order: 0
uri: lb://service-order
predicates:
- Path=/api/order/**
filters: # 拦截器
- RewriterPath=/api/order/(?<segment>.*), /${segment}
路径重写:RewriterPath = {原路径正则表达式 },{新路径正则表达式}
3.3. 默认过滤器
无论什么,只要过网关就操作
spring:
cloud:
gateway:
routes:
- id: order
order: 0
uri: lb://service-order
predicates:
- Path=/api/order/**
filters:
- RewriterPath=/api/order/(?<segment>.*), /${segment}
default-filters: # 默认拦截器
- AddResponseHeader=X-Response-def, 666
3.4. 自定义过滤器
跟javaWeb的过滤器类似:重写方法、前后置处理、注入容器
例:为请求响应一个jwt令牌
@Component
public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.fromRunnable(()->{
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
String value = config.getValue();
if ("uuid".equalsIgnoreCase(value)) {
value = UUID.randomUUID().toString();
}
if ("jwt".equalsIgnoreCase(value)) {
value = "My JWT";
}
headers.add(config.getName(), value);
}));
}
};
}
}