文章目录
- 一. 初识Sentinel
- 1. 雪崩问题及解决方案
- 2. 服务保护技术对比
- 3. Sentinel介绍和安装
- 4. 微服务整合Sentinel
- 二. 限流规则
- 1. 快速入门
- 2. 流控模式
- 3. 流控效果
- 4. 热点参数限流
- 三. 隔离与降级—调用方保护方案
- 1. FeignClinet整合sentinel
- 2. 线程隔离(舱壁模式)
- 3. 熔断降级
- 四. 授权规则
- 1. 授权规则
- 2. 自定义异常结果
- 五. 规则持久化
- 1. 规则管理模式
- 2. 实现push模式
一. 初识Sentinel
1. 雪崩问题及解决方案
微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。
解决雪崩问题的常见方式
- 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待。(缓解了雪崩问题)
- 舱壁模式:限定每个业务能使用的线程数,避免耗尽整个Tomcat资源,因此也叫做线程隔离
- 熔断降级:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求
- 流量控制:限制业务访问的QPS(每秒查询率),避免服务因流量的突增而故障
2. 服务保护技术对比
3. Sentinel介绍和安装
认识Sentinel
Sentinal时阿里巴巴开源的一款微服务控制组件,它具有以下特征:
- 丰富的应用场景:Sentinel承接了阿里巴巴近10年的双十一大促流量核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用
- 完备的实时监控:Sentinel同时提供实时的监控功能。可以在控制台中看接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况
- 广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如Spring Cloud、Dubbo、gRPC的整合。只需要引入相应的依赖并进行简单的配置即可快速接入Sentinal
- 完善的SPI扩展点:Sentinel提供简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快速定制逻辑。例如定制规则管理、适配动态数据源等。
安装Sentinel控制台
Sentinal官方提供了UI控制台,方便我们对系统做限流设置。
- 下载相应的jar包,并启动:
java -jar sentinel-dashboard-2.0.0-alpha-preview.jar
- 访问8080端口即可查看到控制台页面
账号:sentinel
密码:sentinel
- 修改sentinel的配置信息
如果要修改Sentinel的默认端口、账号、密码可以通过以下配置:
配置项 | 默认值 | 说明 |
---|---|---|
server.port | 8080 | 服务端口 |
sentinel.dashboard.auth.username | sentinel | 默认用户名 |
sentinel.dashboard.auth.password | sentinel | 默认密码名 |
java -jar sentinel-dashboard-2.0.0-alpha-preview.jar -Dserver=8090
4. 微服务整合Sentinel
-
导入项目:
项目:百度网盘地址,提取码bzr4
sql文件:百度网盘地址,提取码: ycz7 -
安装nacos
简介:Nacos是Alibaba的产品,是SpringCloud中的一个组件,相比Eureka功能更加丰富
- 官网下载Nacos包
- 在终端进入Nacos包的bin目录下,输入
sh startup.sh -m standalone
即可启动- 进入可视化页面
http://127.0.0.1:8848/nacos/
,账号和密码都是nacos
- 微服务整合Sentinel
这里在上面引入项目的order-service中整合sentinal,并且连接sentinel控制台,步骤如下:
- 引入sentinel依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置控制台地址
spring:
datasource:
url: jdbc:mysql://mysql:3306/cloud_order
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8845
sentinel:
transport:
dashboard: localhost:8080
- 重启微服务测试结果(访问orderservice的任意一个控制器)
可以看到(sentinal成功监控了我们的微服务)
二. 限流规则
1. 快速入门
簇点链路:就是项目内的内调链路,链路中被监控的每个接口就是一个资源。默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint-可以理解为Controller方法),因此SpringMVC的每一个端点(Endpoint)就是调用 链路中的一个资源
流控熔断都是针对链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则:(下面以设置流控为案例)
- 点击流控按钮
2. 案例:给/order/{orderId}这个资源设置流控规则,QPS不能超过5,然后使用Jmeter测试
可以发现失败的请求都被Sentinel阻塞了
查看实时监控
2. 流控模式
在添加限流规则时,点击高级选项,可以选择三种流控模式:
- 直接:统计当前资源的请求,触发阈值时对当前资源直接限流,也就是默认的模式
- 关联:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
- 链路:统计从指定链路访问到本资源的请求,触发阈值时,对指定链路限流
流控模式—关联
- 关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流
- 使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争取数据库锁,产生竞争。业务需求是有限支付和更新订单的业务,因此修改订单业务触发阈值时,需要对查询订单业务限流。
案例:需求
- 在orderController新建两个端点:/order/query 和/order/update,无需业务实现
- 配置流控规则,当/order/update资源被访问的QPS超过5时,对/order/query请求限流
- 编写Controller
@GetMapping("/query")
public String queryOrder() {
// 根据id查询订单并返回
return "查询订单成功";
}
@GetMapping("/update")
public String updateOrder() {
// 根据id查询订单并返回
return "更新订单成功";
}
- 访问相应的断点
3. 对query添加限流
4. jmeter测试
(这里不断请求update100s,期间无法访问query)
流控模式-链路
- 只针对从指定链路访问到本资源的请求做请求统计,判断是否超过阈值
案例:有查询订单和创建订单业务,两者都需要查询商品。针对从查询订单进入到查询商品的请求统计,并设置限流
(1)在OrderService中添加一个queryGoods,不用实现业务
public String queryGoods(){
return "queryGoods方法被调用";
}
(2)在OrderController中,改造/order/query端点,调用OrderService中的queryGoods方法
@GetMapping("/query")
public String queryOrder() {
// 根据id查询订单并返回
return orderService.queryGoods();
}
(3)在OrderController中添加一个/order/save断电,调用OrderService的queryGoods方法
@GetMapping("/save")
public String saveOrder() {
// 根据id查询订单并返回
return orderService.queryGoods();
}
(4)给queryGoods设置限流规则,从/order/query进入queryGoods方法现在QPS必须小于2
- sentinel默认只标记Controller中的方法为资源,如果要标记其它方法,需要利用@SentinelResource注解,示例:
@SentinelResource("goods")
public String queryGoods(){
return "queryGoods方法被调用";
}
- sentinel默认会将Controller方法作Context整合,导致链路模式的流控失效,需要修改application.yml添加配置
spring:
cloud:
sentinel:
web-context-unify: false
给goods添加流控规则
(4)jmeter测试
3. 流控效果
流控效果是指请求达到流控阈值时应该采取的措施,包括三种:
- 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。这是默认的处理方式
- warm up:预热模式,对超出阈值的请求同样是抛出异常。但这个模式阈值会动态变化,从一个较小值逐渐增加到最大阈值
- 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
流控效果-warm up
warm up也叫预热模式,是应对服务冷启动的一种方案,请求阈值初始值是threshold/coldFactor,持续指定时间后每,逐渐提高threshold值,而coldeFactor的默认值是3
例如:我设置QPS的threshold为10,预热时间为5秒,那么初始阈值就是10/3,也就是3,然后在5秒后逐渐增长到10
案例:给/order/{orderId}这个资源设置限流,最大QPS为10,利用warm up 效果,预热时长为5s
- 设置流控效果
2. jmeter测试
qps是波动上升
流控效果-排队等待
当请求超过QPS阈值时,快速失败和warm up会拒绝新的请求并抛出异常。而排队等待是让所有请求进入一个队列中,然后按照阈值运行的时间间隔依次执行。后来的请求必须等待前面的请求执行完成,如果请求等待的预期时间超出最长时长,则会被拒绝
案例:给/order/{orderId}这个资源设置限流,最大QS为10,利用排队的流控效果,超时时长设置为5s
- 设置流控效果
- jmeter测试
达到了流量整形的效果
4. 热点参数限流
之前的限流是统计访问某个资源的所有请求,判断是否超过QPS阈值。而热点参数限流是分别统计参数值相同的请求,是否超过QPS阈值
案例:给/order/{orderId}这个资源添加热点参数限流,规则如下:
- 默认的热点参数规则时1秒请求不超过2
- 给102这个参数设置例外:每1s的请求量不超过4
- 给103这个参数设置例外:每1s的请求量不超过10
- 声明热点资源(注意热点参数对默认的SpringMVC资源无效)
@SentinelResource
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
2. 声明热点参数
3. jmeter测试
三. 隔离与降级—调用方保护方案
上面的限流是为了预防服务故障的发生,而这一节介绍的内容是为了处理服务器已经出现故障而避免引起服务器雪崩问题
1. FeignClinet整合sentinel
springcloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel
- 修改OrderService的application.yml文件,开启Feign的Sentinal功能
feign:
sentinel:
enabled:true #开启feign对于sentinal的支持
- 给Feignclient编写失败后的降级逻辑
(1) 方式一:FallbackClass,无法对远程调用的异常做处理
(2) 方式二:FallbackFactory,可以对远程调用的异常做处理
步骤一:在feign-api项目中定义类,实现FallbackFactory@Slf4j public class UserclientFallbackFactory implements FallbackFactory<UserClient>{ @Override pulbic UserClient create(Throwable throwable){ //创建UserClient接口实现类,实现其中的方法,编写失败降级的处理逻辑 return new UserClient(){ @Override public User findById(Long id){ //记录异常信息 log.error("查询用户失败",throwable); return new User(); } }; } }
步骤二:在feign-api项目中的DefautFeignConfiguration类中将UserCllientFallbackFactory注册为一个bean
@Bean
public UserClientFallbackFactory userclientFallback(){
return new UserClientFallbackFactory();
}
步骤三:在feign-api中的userclient接口中使用UserClientFallbackFactory
@FeignClient(value="userservice", fallbackFactory=UserClientFallbackFactory.class)
pulbic interface Userclient{
@GetMapping("/user/{id}")
User findByid(@PathVariable("id") Long id);
}
2. 线程隔离(舱壁模式)
线程隔离有两种实现方式:
- 线程池隔离
当用户现场请求服务的时候,会给请求业务所依赖的每个服务都创建一个线程池,然后请求会调用线程池的线程取实现Feign的远程调用(没有使用原始的请求线程)
优点
- 支持主动超时和异步调用
缺点
- 线程的额外开销大
场景
- 适合与低扇出
- 信号量隔离(Sentinel默认使用方式)
此种方式不会创建新现场,而是使用原来的线程调用Feign远程调用,但它会维护一个信号量,没有一个请求过来,计数器减一,知道信号量为0不再接收新请求
优点
- 轻量级,无额外开销
缺点
- 不支持主动超时,不支持异步调用
场景
- 高频调用,高扇出
案例:给Userclient的查询用户接口设置流控规则,现场数不超过2。然后利用jmeter测试
- 配置流控规则
- jmeter测试
由于我们做了fallback所以所有访问都会通过,但是会返回空User
3. 熔断降级
除了线程隔离,熔断降级是解决雪崩问题的重要手段,其思路是有断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截该服务的一切请求;当服务恢复时,断路器会放行访问该服务的请求。断路器有三种状态:
断路器熔断策略有三种:
- 慢调用:业务的相应时长(RT)大于指定时长请求认定为慢请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值则触发熔断
案例:给UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1s,最小请求数量为5,失败阈值比例为0.4,熔断时长为5
- 为了触发慢调用的规则,修改Userservice中的业务,增加业务时耗
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) throws InterruptedException {
if (id==1) {
Thread.sleep(60);
}
return userService.queryById(id);
}
- 配置降级规则
3. 测试(手刷页面五次)
熔断出现了空值
- 异常比例或异常数:统计指定时间内的调用,如果调用次数超过指定请求数,并且出现异常的比例达到设定的比例阈值(或超过指定的异常数)则触发熔断(这里不演示了)
四. 授权规则
1. 授权规则
授权规则可以对调用方的来源做控制,有白名单和黑名单两种(对请求者身份进行判读)
- 白名单:来源(origin)在白名单中的调用者允许访问
- 黑名单:来源(origin)在黑名单中的调用者不允许访问
Sentinel是通过RequestOriginParser这个接口的parseOriging来获取请求的来源的(但是默认sentinel该接口都会返回Default不管是网管来的请求还是浏览器来的请求,所以我们需要自己实现这个接口)
public interface RequestOriginParser{
//从请求Request对象中获取Origin的值,获取方式自定义
String parseOrigin(HttpServletRequest request);
}
- 自定义RequestOriginParser
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//尝试获取请求头
String origin = httpServletRequest.getHeader("origin");
//非空判断
if (StringUtils.isEmpty(origin)) {
origin="blank";
}
return origin;
}
}
- 给经过网关过来的请求添加新的请求头(以区分直接从浏览器访问的)
server:
port: 10010 #网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8845
gateway:
routes: #网关配置路由
- id: user-service #路由定义,自定义,只要唯一即可
uri: lb://userservice #路由的目标地址,lb就是负载均衡,后面跟服务的名称
predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** #这个按照路径匹配,只要以/user/开头的就符合要求
- id: order-service #路由定义,自定义,只要唯一即可
uri: lb://orderservice #路由的目标地址,lb就是负载均衡,后面跟服务的名称
predicates: #路由断言,也就是判断请求是否符合路由规则的条件
- Path=/order/** #这个按照路径匹配,只要以/user/开头的就符合要求
filters: #过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! #添加请求头
- AddRequestHeader=origin,gateway! #添加请求头
- 新增授权结果
4. 测试
绕过网关,直接浏览器访问
网关访问
2. 自定义异常结果
默认情况下,发生限流、降级、授权拦截时,都会抛出异常,如上面浏览器直接访问时抛出的异常结果。(所有的异常信息都是Blocked by Sentinel (flow limiting)
)。如果要自定义异常时的返回结果,需要实现BlockExceptionHandler接口
public interface BlockExceptionHandler{
//处理请求被限流、降级、授权拦截时抛出的异常:BlockException
void handler(HttpServletRequest request,HttpServletResponse response,BlockException e) throws Exception;
}
而BlockException包含很多子类,分别对应不同的场景
异常 | 说明 |
---|---|
FlowException | 限流异常 |
ParamFlowException | 热点参数限流的异常 |
DegradeException | 降级异常 |
AuthorityException | 授权规则异常 |
SystemBlockException | 系统规则异常 |
- 在orderservice中实现
BlockExceptionHandler
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, 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;
}
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(status);
httpServletResponse.getWriter().println("{\"msg\":"+msg+",\"status\""+status+"}");
}
}
- 测试
五. 规则持久化
不知道有没有发生,只要我们在idea中重启我们的微服务,之前在Sentinel中配置的各种规则就会消失,这就引出了规则持久化的问题(规则在内存里面)
1. 规则管理模式
Sentinel的控制台规则管理有三种模式:
- 原始模式:Sentinel的默认模式,将规则保存在内存中,重启服务会丢失
- pull模式:控制台将配置规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或者数据库中,以后定时沦陷取本地文件或数据库中查询,更新本地规则(可能会导致规则数据不一致问题)
- push模式:为了解决pull中的数据不一致问题,push模式控制台不会将配置保存在本地,而是将配置规则推送到远程配置中心,例如Nacos。Senitnel客户端监听Nacos,获取配置变更的推送消息,完成本地配置的更新
2. 实现push模式
修改OrderService
- 引入依赖:在orderservice中引入Sentinel监听nacos的依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 配置nacos地址:在order-service中的application.yml文件配置nacos地址及监听的配置信息
cloud:
sentinel:
transport:
dashboard: localhost:8080
web-context-unify: false
datasource:
flow:
nacos:
server-addr: localhost:8845
data-id: orderservice-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow
修改Sentinel-dashboard源码
- github下载sentinel源码,并用idea打开
4. 在sentinel-dashboard源码的pom文件中,nacos的依赖默认的scope是test,只能在测试时使用,这里要去除:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5. 在sentinel-dashboard的test包下,已经编写了对nacos的支持,我们需要将其拷贝到main下
6. 然后,还需要修改测试代码中的NacosConfig类
7. 另外,还需要修改com.alibaba.csp.sentinel.dashboard.controller.v2包下的FlowControllerV2类:
- 接下来,还要修改前端页面,添加一个支持nacos的菜单。修改src/main/webapp/resources/app/scripts/directives/sidebar/目录下的sidebar.html文件:
- 把这段注释打开
- 然后修改内容
9. 重新编译打包:运行IDEA中的maven插件,编译和打包修改好的Sentinel-Dashboard:
10. 运行我们更改后的源码编译出来的jar包