一.初识Sentinel
1.1.1 雪崩问题
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是
雪崩。
微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务。
如果服务提供者I发生了故障,当前的应用的部分业务因为依赖于服务I,因此也会被
阻塞。此时,其它不依赖于服务I的业务似乎不受影响。
但是,依赖服务I的业务请求被阻塞,用户不会得到响应,则tomcat的这个线程不会
释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致
所有其它服务都不可用,那么当前服务也就不可用了。
那么,依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级
联失败,雪崩就发生了:
1.1.2 超时处理
超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止
等待。
1.1.3 仓壁模式
船舱都会被隔板分离为多个独立空间,当船体破损时,只会导致部分空间进入,将故
障控制在一定范围内,避免整个船体都被淹没。
于此类似,我们可以限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,
因此也叫线程隔离。
1.1.4 熔断处理
熔断处理:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截
访问该业务的一切请求。
断路器会统计访问某个服务的请求数量,异常比例如下所示
请求了三次,两次出现异常,一次成功。当发现访问服务D的请求异常比例过高时,
认为服务D有导致雪崩的风险,会拦截访问服务D的一切请求,形成熔断:
触发熔断了以后,当在访问服务A的时候,就不会在通过服务A去访问服务D了,立马
给用户进行返回,返回的是一种默认值,这种返回就是一种兜底方案。这种兜底方案
也将其称之为降级逻辑。
1.1.5 流量控制
流量控制:限制业务访问的QPS(每秒的请求数),避免服务因流量的突增而故障。
限流是一种预防措施,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。其他
的处理方式是一种补救措施,在部分服务故障时,将故障控制在一定范围,避免雪
崩。
总结:
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一
种预防措施。
超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免
雪崩。是一种补救措施。
1.2 Sentinel介绍和安装
1.2.1 Sentinel介绍
Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址:home | Sentinel
Sentinel 具有以下特征:
•丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,
例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量
控制、实时熔断下游不可用应用等。
•完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入
应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
•广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与
Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置
即可快速地接入 Sentinel。
1.2.2 Sentinel安装
下载sentinel
sentinel管理后台下载地址:Releases · alibaba/Sentinel · GitHub
下载完毕以后就会得到一个jar包
启动sentinel
将jar包放到任意非中文目录,执行命令:
java -jar sentinel-dashboard-1.8.6.jar |
如果要修改Sentinel的默认端口、账户、密码,可以通过下列配置
配置项 | 默认值 | 说明 |
server.port | 8080 | 服务端口 |
sentinel.dashboard.auth.username | sentinel | 默认用户名 |
sentinel.dashboard.auth.password | sentinel | 默认密码 |
指定端口启动命令:java -Dserver.port=8090 -jar sentinel-dashboard-1.8.6.jar |
访问sentinel
访问:http://localhost:8080/页面,就可以看到sentinel的控制台了:
需要输入账号和密码,默认都是:sentinel
登录后,发现一片空白,什么都没有:因为还没有监控任何服务。另外,sentinel是
懒加载的,如果服务没有访问,看不到该服务信息。
1.3 整合sentinel
我们在emp-service中整合sentinel,并连接sentinel的控制台,步骤如下:
引入sentinel依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置application.yml文件
spring: cloud: sentinel: transport: dashboard: localhost:8090 # 配置sentinel控制台地址 |
访问emp-service的任意接口
打开浏览器,访问http://localhost:8088/order/101这样才能触发sentinel的监控。然
后再访问sentinel的控制台,查看效果:
1.4 相关概念
簇点链路:当请求进入微服务时,首先会访问DispatcherServlet,然后进入
Controller、Service、Mapper,这样的一个调用链就叫做簇点链路。
资源:簇点链路中被监控的每一个接口就是一个资源,流控、熔断等都是针对簇点链
路中的资源来设置的。
默认情况下sentinel会监控spring mvc的每一个端点(Endpoint,也就是controller
中的方法),因此spring mvc的每一个端点就是调用链路中的一个资源。
例如,我们刚才访问的emp-service中的UserController中的端点:http://localhost:8080/emp/1
我们可以点击对应资源后面的按钮来设置规则:
1.流控:流量控制
2.降级:降级熔断
3.热点:热点参数限流,是限流的一种
4.授权:请求的权限控制
二.流量控制
雪崩问题虽然有四种方案,但是限流是避免服务因突发的流量而发生故障,是对微服
务雪崩问题的预防。我们先学习这种模式。
2.1 快速入门
2.1.1 添加限流规则
需求:点击资源/emp/{id}后面的流控按钮,就可以弹出表单。表单中可以添加流控
规则,如下图所示:
其含义是限制 /emp/{id}这个资源的单机QPS为1,即每秒只允许1次请求,超出的请
求会被拦截并报错。
2.1.2 压力测试工具jmeter
Apache JMeter 是 Apache 组织基于Java 开发的压力测试工具,用于对软件做压力测
试。
下载:可以Apache Jmeter官网下载,地址:Apache JMeter - Download Apache JMeter
安装Jmeter
Jmeter依赖于JDK,所以必须确保当前计算机上已经安装了JDK,并且配置了环境变
量。
因为下载的是zip包,解压缩即可使用,目录结构如下:
其中的bin目录就是执行的脚本,其中包含启动脚本:
运行:
双击即可运行,但是有两点注意:
启动速度比较慢,要耐心等待
启动后黑窗口不能关闭,否则Jmeter也跟着关闭了
设置中文语言
默认Jmeter的语言是英文,需要设置:
注意:上面的配置只能保证本次运行是中文,如果要永久中文,需要修改Jmeter的配
置文件
打开jmeter文件夹,在bin目录中找到 jmeter.properties,添加下面配置:
language=zh_CN |
基本用法:
在测试计划上点鼠标右键,选择添加 > 线程(用户) > 线程组:
在新增的线程组中,填写线程信息:
给线程组点鼠标右键,添加http取样器:
编写取样器内容:
添加监听结果树:
添加聚合报告:
查看结果树
查看聚合报告
2.1.3 利用jmeter测试
需求:给 /emp/{id}这个资源设置流控规则,QPS不能超过 5。利用jmeter测试。
注意:如果测试结果不是上述情况,那是因为sentinel在统计请求的时候,把一部分
的请求统计到了下一秒中导致的。
2.2 流控模式
在添加限流规则时,点击高级选项,可以选择三种流控模式:
直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也是默认的模式
关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
2.2.1 流控模式-关联
关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操
作会争抢数据库锁,产生竞争。业务需求是优先支付和更新订单的业务,因此当修改
订单业务触发阈值时,需要对查询订单业务限流。
当/write资源访问量触发阈值时,就会对/read资源限流,避免影响/write资源。
满足下面条件可以使用关联模式:
两个有竞争关系的资源
一个优先级较高,一个优先级较低
需求:
在EmpController新建两个端点:/emp/query和/emp/update,无需实现业务
配置流控规则,当/emp/ update资源被访问的QPS超过5时,对/emp/query请求限流
定义/emp/query端点,模拟员工查询
java">@GetMapping("/query")
public String query() {
return "查询成功";
}
定义/emp/update端点,模拟员工更新
java">@GetMapping("/update")
public String update() {
return "更新成功";
}
重启服务,查看sentinel控制台的簇点链路:
配置流控规则
在Jmeter测试
2.2.3 流控模式-链路
链路模式:只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
例如有两条请求链路:
/test1 --> /common
/test2 --> /common
如果只希望统计从/test2进入到/common的请求,则可以这样配置:
例如:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查
询商品的请求统计,并设置限流。
需求:有查询员工和创建员工业务,两者都需要查询部门。针对从查询员工进入到查
询部门的请求统计,并设置限流。
在EmpService中添加一个queryDepts方法,不用实现业务
在EmpController中,改造/empr/query端点,调用EmpService中的
queryDepts方法
在EmpController中添加一个/emp/save的端点,调用EmpService的
queryDepts方法
给queryEmps设置限流规则,从/emp/query进入queryDepts的方法限制QPS必
须小于2
在emp-service服务中,给EmpService类添加一个queryDepts方法:
java">public interface EmpService {
public Emp findById(Integer id);
public void queryDepts();
}
java">@Service
public class EmpServiceImpl implements EmpService {
public void queryDepts(){
System.out.println("查询部门");
}
}
在emp-service的EmpController中,修改/emp/query端点的业务逻辑:
java">@GetMapping("/query")
public String query() {
//查询部门
empService.queryDepts();
return "查询成功";
}
在emp-service的EmpController中,修改/order/save端点,模拟新增订单:
java">@GetMapping("/save")
public String save(){
//查询部门
empService.queryDepts();
return "创建成功";
}
给查询部门添加资源标记
Sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用
@SentinelResource注解,示例
java">@SentinelResource("depts")
public void queryDepts(){
System.out.println("查询部门");
}
更改application.yml文件中的sentinel配置
Sentinel默认会将Controller方法做context整合,导致链路模式的流控失效,需要修
改application.yml,添加配置:
spring: cloud: sentinel: web-context-unify: false # 关闭context整合 |
重启服务,访问/emp/query和/emp/save,可以查看到sentinel的簇点链路规则中,
出现了新的资源
添加流控规则
jmeter测试
可以看到这里200个线程,50秒内发完,QPS为4,超过了我们设定的阈值2。
一个http请求是访问/emp/query
另一个是访问/emp/save
运行测试,察看结果树:
访问/emp/save,没有进行限流
访问/emp/query,进行限流了
2.3 流控效果
在流控的高级选项中,还有一个流控效果选项:
流控效果是指请求达到流控阈值时应该采取的措施,包括三种:
快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是
默认的处理方式。
warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈
值会动态变化,从一个较小值逐渐增加到最大阈值。
排队等待:让所有的请求按照先后次序进入到一个队列中进行排队,当某一个请
求最大的预期等待时间超过了所设定的超时时间时同样是拒绝并抛出异常
2.3.1 流控效果-warm up
阈值一般是一个微服务能承担的最大QPS,但是一个服务刚刚启动时,一切资源尚未
初始化(冷启动),如果直接将QPS跑到最大值,可能导致服务瞬间宕机。
warm up也叫预热模式,是应对服务冷启动的一种方案。阈值会动态变化,从一个较
小值逐渐增加到最大阈值。
工作特点:请求阈值初始值是 maxThreshold / coldFactor, 持续指定时长(预热时间)
后,逐渐提高到maxThreshold值,而coldFactor的默认值是3。
例如,设置QPS的maxThreshold为10,预热时间为5秒,那么初始阈值就是 10 / 3
,也就是3,然后在5秒后逐渐增长到10。
需求:给/emp/{id}这个资源设置限流,最大QPS为10,利用warm up效果,预热时
长为5秒
配置流控规则
Jmeter测试
刚刚启动时,大部分请求失败,成功的只有3个,说明QPS被限定在3:
随着时间推移,成功比例越来越高:
2.3.2 流控效果-排队等待
当请求超过QPS阈值时,快速失败和warm up 会拒绝新的请求并抛出异常。
而排队等待则是让所有请求进入一个队列中,然后按照阈值允许的时间间隔依次执
行。后来的请求必须等待前面执行完成,如果请求预期的等待时间超出最大时长,则
会被拒绝。
例如:QPS = 5,意味着每200ms处理一个队列中的请求;timeout = 2000,意味着
预期等待超过2000ms的请求会被拒绝并抛出异常
需求:给/emp/{id}这个资源设置限流,最大QPS为10,利用排队的流控效果,超时
时长设置为5s
添加流控规则
Jmeter测试
QPS为15,已经超过了我们设定的10。
如果是之前的 快速失败、warmup模式,超出的请求应该会直接报错。
但是我们看看队列模式的运行结果:
QPS非常平滑,一致保持在10,但是超出的请求没有被拒绝,而是放入队列。因此响
应时间(等待时间)会越来越长。
当队列满了以后,才会有部分请求失败:
2.4 热点参数限流
之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限
流是分别统计参数值相同的请求,判断是否超过QPS阈值。
在实际开发中,可能部分商品是热点商品,例如秒杀商品,我们希望这部分商品的
QPS限制与其它商品不一样,高一些。那就需要配置热点参数限流了
需求:给/emp/{id}这个资源添加热点参数限流,规则如下:
•默认的热点参数规则是每1秒请求量不超过2
•给2这个参数设置例外:每1秒请求量不超过4
•给3这个参数设置例外:每1秒请求量不超过10
注意事项:热点参数限流对默认的SpringMVC资源无效,需要利用
@SentinelResource注解标记资源
给emp-service中的EmpController中的/emp/{id}资源添加注解:
java">@SentinelResource("hot")
@GetMapping("/{id}")
public Emp findById(@PathVariable("id") Integer id){
Emp emp = empService.findById(id);
return emp;
}
QPS阈值为2
例外项,QPS阈值为4
例外项,QPS阈值为10
三.隔离和降级
限流是一种预防措施,虽然限流可以尽量避免因高并发而引起的服务故障,但服务还
会因为其它原因而故障。
而要将这些故障控制在一定范围,避免雪崩,就要靠线程隔离(舱壁模式)和熔断降
级手段了。
线程隔离之前讲到过:调用者在调用服务提供者时,给每个调用的请求分配独立线程
池,出现故障时,最多消耗这个线程池内资源,避免把调用者的所有资源耗尽。
熔断降级:是在调用方这边加入断路器,统计对服务提供者的调用,如果调用的失败
比例过高,则熔断该业务,不允许访问该服务的提供者了。
可以看到,不管是线程隔离还是熔断降级,都是对客户端(调用方)的保护。需要在
调用方 发起远程调用时做线程隔离、或者服务熔断。
而我们的微服务远程调用都是基于Feign来完成的,因此我们需要将Feign与Sentinel
整合,在Feign里面实现线程隔离和服务熔断。
3.1 FeignClient整合Sentinel
SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合
Feign和Sentinel。
3.1.1 修改配置,开启sentinel功能
修改EmpService的application.yml文件,开启Feign的Sentinel功能:
feign: sentinel: enabled: true # 开启feign对sentinel的支持 |
3.1.2 编写失败降级逻辑
业务失败后,不能直接报错,而应该返回用户一个友好提示或者默认结果,这个就是
失败降级逻辑。
给FeignClient编写失败后的降级逻辑:
方式一:FallbackClass,无法对远程调用的异常做处理
方式二:FallbackFactory,可以对远程调用的异常做处理,我们选择这种
步骤一:在feing-api项目中定义类,实现FallbackFactory:
java">package com.lzw.clients.fallback;
import com.lzw.clients.DeptClient;
import com.lzw.pojo.Dept;
import org.springframework.cloud.openfeign.FallbackFactory;
/**
* @author lzw
* @create 2024-12-27 12:13
*/
public class DeptClientFallbackFactory implements
FallbackFactory<DeptClient>{@Overridepublic DeptClient create(Throwable cause) {//打印远程调用出现的异常到控制台cause.printStackTrace();return new DeptClient() {//服务降级的方法@Overridepublic Dept findById(Integer id) {Dept dept=new Dept();dept.setId(null);dept.setName("默认用户");//根据业务需求返回默认的数据return dept;}};
}
}
步骤二:在feing-api项目中的DefaultFeignConfiguration类中将
DeptClientFallbackFactory注册为一个Bean:
java">@Bean
public DeptClientFallbackFactory deptClientFallbackFactory(){return new DeptClientFallbackFactory();
}
步骤三:在feing-api项目中的DeptClient接口中使用DeptClientFallbackFactory:
java"> @FeignClient(value = "deptserver", configuration = DefaultFeignConfiguration.class,fallbackFactory = DeptClientFallbackFactory.class)public interface DeptClient {@GetMapping("/dept/{id}")public Dept findById(@PathVariable("id") Integer id);
}
重启后,访问一次员工查询业务,然后查看sentinel控制台,可以看到新的簇点链
路:
3.2.线程隔离(舱壁模式)
3.2.1 线程隔离的实现方式
线程隔离有两种方式实现:
线程池隔离
信号量隔离(Sentinel默认采用)
线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号
量上限时,禁止新的请求。
线程池隔离:
优点:支持主动超时,支持异步调用
缺点:线程的额外开销比较大
信号量隔离:
优点:轻量级,无额外开销
缺点:不支持主动超时,不支持异步调用
3.2.2 sentinel的线程隔离
在添加限流规则时,可以选择两种阈值类型:
QPS:就是每秒的请求数
线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数
量,实现线程隔离(舱壁模式)。
案例需求:给 emp-service服务中的DeptClient的查询用户接口设置流控规则,线程
数不能超过 2。然后利用jmeter测试。
选择feign接口后面的流控按钮:
填写表单:
Jmeter测试
一次发生10个请求,有较大概率并发线程数超过2,而超出的请求会走之前定义的失
败降级逻辑。
发现虽然结果都是通过了,不过部分请求得到的响应是降级返回的null信息。
3.3 熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、
慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服
务恢复时,断路器会放行访问该服务的请求。
断路器控制熔断和放行是通过状态机来完成的:
状态机包括三个状态:
closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。
超过阈值则切换到open状态
open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失
败,直接走降级逻辑。Open状态5秒后会进入half-open状态
half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
请求成功:则切换到closed状态
请求失败:则切换到open状态
断路器熔断策略有三种:慢调用、异常比例、异常数
3.3.1 慢调用
慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间
内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
解读:RT超过500ms的调用是慢调用,统计最近1000ms内的请求,如果请求量超过
10次,并且慢调用比例不低于0.5,则触发熔断,熔断时长为5秒。然后进入half-
open状态,放行一次请求做测试。
需求:给 DeptClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计
时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5
为了触发慢调用规则,我们需要修改DeptService中的业务,增加业务耗时:
java">@GetMapping("/{id}")
public Dept findById(@PathVariable("id") Integer id) throws InterruptedException {if(id==1){Thread.sleep(60);}Dept dept = deptService.findById(id);return dept;
}
此时,id=1的员工,关联的是id为1的部门,调用时长为60ms:
id=2的员工,关联的是id为2的部门,调用时长为非常短;
给feign接口设置降级规则:
在浏览器访问:http://localhost:8080/emp/1,快速刷新5次,可以发现:
触发了熔断,请求时长缩短至5ms,快速失败了,并且走降级逻辑,返回的null
在浏览器访问:http://localhost:8080/emp/2,竟然也被熔断了;
3.3.2 异常比例、异常数
异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出
现异常的比例达到设定的比例阈值(或超过指定异常数),则触发熔断。
例如,一个异常比例设置:
解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,
则触发熔断。
一个异常数设置:
解读:统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于2
次,则触发熔断。
需求:给 DeptClient的查询部门接口设置降级规则,统计时间为1秒,最小请求数量
为5,失败阈值比例为0.4,熔断时长为5s
首先,修改dept-service中的/dept/{id}这个接口的业务。手动抛出异常,以触发异常
比例的熔断:
java">@GetMapping("/{id}")
public Dept findById(@PathVariable("id") Integer id) throws InterruptedException {if(id==1){Thread.sleep(60);}else if(id==2){throw new RuntimeException("故意抛出异常");}Dept dept = deptService.findById(id);return dept;
}
给feign接口设置降级规则:
在5次请求中,只要异常比例超过0.4,也就是有2次以上的异常,就会触发熔断。
在浏览器快速访问:http://localhost:8080/emp/2,快速刷新5次,触发熔断:
如果手数没这么快就给最小请求数调小一些
此时,我们去访问本来应该正常的:http://localhost:8080/emp/1
3.4 授权规则
3.4.1 基本规则
授权规则可以对调用方的来源做控制,有白名单和黑名单两种方式。
白名单:来源(origin)在白名单内的调用者允许访问
黑名单:来源(origin)在黑名单内的调用者不允许访问
点击左侧菜单的授权,可以看到授权规则:
资源名:就是受保护的资源,例如/emp/{id}
流控应用:是来源者的名单,
如果是勾选白名单,则名单中的来源被许可访问。
如果是勾选黑名单,则名单中的来源被禁止访问。
比如:
我们允许请求从gateway到emp-service,不允许浏览器访问emp-service,那么白名
单中就要填写网关的来源名称(origin)。
3.4.2 如何获取origin
Sentinel是通过RequestOriginParser这个接口的parseOrigin来获取请求的来源的。
java">public interface RequestOriginParser {
/**
* 从请求request对象中获取origin,获取方式自定义
*/
String parseOrigin(HttpServletRequest request);
}
这个方法的作用就是从request对象中,获取请求者的origin值并返回。
默认情况下,sentinel不管请求者从哪里来,返回值永远是default,也就是说一切请
求的来源都被认为是一样的值default。
因此,我们需要自定义这个接口的实现,让不同的请求,返回不同的origin。
例如emp-service服务中,我们定义一个RequestOriginParser的实现类:
java">package com.lzw.order.sentinel;
import
com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
@Component
public class HeaderOriginParser implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest request) {// 1.获取请求头String origin = request.getHeader("origin");// 2.非空判断if (StringUtils.isEmpty(origin)) {origin = "blank";}return origin;
}
}
3.4.3 给网关添加请求头
既然获取请求origin的方式是从reques-header中获取origin值,我们必须让所有从
gateway路由到微服务的请求都带上origin头。
这个需要利用之前学习的一个GatewayFilter来实现,
AddRequestHeaderGatewayFilter。
修改gateway服务中的application.yml,添加一个defaultFilter:
spring: cloud: gateway: default-filters: - AddRequestHeader=origin,gateway routes: # ...略 |
这样,从gateway路由的所有请求都会带上origin头,值为gateway。而从其它地方
到达微服务的请求则没有这个头。
3.4.4 配置授权规则
接下来,我们添加一个授权规则,放行origin值为gateway的请求。
3.5 自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常到调用方。异常结果都是
flow limmiting(限流)。这样不够友好,无法得知是限流还是降级还是授权拦截。
3.5.1 异常类型
如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口:
java">public interface BlockExceptionHandler {
/**
* 处理请求被限流、降级、授权拦截时抛出的异常:BlockException
*/
void handle(HttpServletRequest request, HttpServletResponseresponse, BlockException e) throws Exception;
}
这个方法有三个参数:
HttpServletRequest request:request对象
HttpServletResponse response:response对象
BlockException e:被sentinel拦截时抛出的异常
这里的BlockException包含多个不同的子类:
异常 | 说明 |
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
3.5.2 自定义异常处理
下面,我们就在emp-service定义一个自定义异常处理类:
java">@Component
public class SentinelExceptionHandler implements
BlockExceptionHandler {@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response, BlockException e) throws Exception {String msg = "未知异常";int status = 429;if (e instanceof FlowException) {msg = "请求被限流了";} else if (e instanceof ParamFlowException) {msg = "请求被热点参数限流";} else if (e instanceof DegradeException) {msg = "请求被降级了";} else if (e instanceof AuthorityException) {msg = "没有权限访问";status = 401;}Map map = new HashMap<>();map.put("msg",msg);map.put("status",status);response.setContentType("application/json;charset=utf-8");response.setStatus(status);//创建核心解析对象ObjectMapper om = new ObjectMapper();//response.getWriter().println(JSON.toJSONString(map));response.getWriter().println(om.writeValueAsString(map));}
}
重启测试,在不同场景下,会返回不同的异常消息。