一、简介
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案,为微服务提供流量控制、熔断降级、负载保护的功能,它和Hystrix提供的功能一样,可以有效的解决微服务调用产生的雪崩效应,为微服务系统提供了稳定性的解决方案。随着Hytrxi进入了维护期,不再提供新功能,Sentinel是一个不错的替代方案。通常情况,Hystrix采用线程池对服务的调用进行隔离,Sentinel才用了用户线程对接口进行隔离,二者相比,Hystrxi是服务级别的隔离,Sentinel提供了接口级别的隔离,Sentinel隔离级别更加精细,另外Sentinel直接使用用户线程进行限制,相比Hystrix的线程池隔离,减少了线程切换的开销。另外Sentinel的DashBoard提供了在线更改限流规则的配置,也更加的优化。
Sentinel核心分为两个部分:
核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
二、Sentinel服务安装
Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台安装步骤如下:
打开sentinel下载网址 https://github.com/alibaba/Sentinel/releases
下载jar包
在jar包所在的目录打开cmd命令行,执行命令 java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
启动成功后,浏览器输入 http://localhost:8180/
用户名,密码默认都是 sentinel 登录
三、Sentinel限流入门
Sentinel 应用于服务消费方(Consumer),在消费方添加依赖如下:
<!--限流依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--监控依赖(对sentinel暴露一些监控端口)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml,添加sentinel配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #从哪里去查找服务
sentinel:
transport:
port: 8099 #此端口为我们当前服务与sentinel控制台进行通讯的端口,默认为8719,随意指定一个未使用的端口即可
dashboard: localhost:8180 #sentinel 控制台地址
eager: true #服务启动时就向sentinel发送消息进行注册
启动服务提供者,服务消费者,然后在浏览器访问http://localhost:8090/consumer/test
测试
刷新控制台,监测服务列表
说明:Sentinel的控制台其实就是一个SpringBoot编写的程序,我们需要将我们的服务注册到控制台上,即在微服务中指定控制台的地址,并且还要在消费端开启一个与sentinel控制台传递数据端的端口,控制台可以通过此端口调用微服务中的监控程序来获取各种信息。
需求:设置指定接口的流控(流量控制),QPS(每秒请求次数)单机阈值为1,代表每秒请求不能超出1次,要不然就做限流处理,处理方式直接调用失败
第一步:选择要限流的链路
第二部:添加流控策略
访问测试
四、流控规则分析
1 阈值类型分析
- QPS(Queries Per Second):当调用这个api的时,QPS达到单机阈值时,就会限流。
- 线程数:当调用这个api的时,线程数达到单机阈值时,就会限流。
2 流控模式
直接模式
Sentinel默认的流控处理就是【直接->快速失败】
关联模式
解释:当关联的资源达到阈值,就限流自己。
场景:订单服务中有一个读订单接口,和一个写订单接口,在高并发场景下,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;在关联资源上面设置写入接口,资源名设置读取接口就行了;这样就起到了优先写入,一旦写入请求多,就限制读的请求
访问测试
链路模式
链路模式只记录指定链路入口的流量。也就是当多个服务对指定资源调用时,假如流量超出了指定阈值,则进行限流。被调用的方法用@SentinelResource进行注解,然后分别用不同业务方法对此业务进行调用,假如A业务设置了链路模式的限流,在B业务中是不受影响的
定义资源访问对象
@Service
public class ConsumerService {
@SentinelResource("doGetResource")
public String doGetResource() {
return "get resource";
}
}
定义方法对doGetResource进行调用
@GetMapping("/consumer/sentinelResourceTest")
public String sentinelResourceTest() throws InterruptedException {
consumerService.doGetResource();
return "hello SentinelResource";
}
路由规则配置如下:
频繁对链路/consumer/sentinelResourceTest进行刷新访问,检测是否会出现500异常。
3 流控效果
快速失败
流量达到指定阀值,直接返回报异常。(类似路前方坍塌,后面设定路标,让后面的车辆返回)
WarmUp (预热)
秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值
排队等待
匀速排队,让请求以均匀的速度通过,阈值类型必须设成QPS,否则无效。比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进行处理
五、Sentinel降级入门
服务启动后,选择要降级的链路
这里表示熔断策略为慢调用比例,表示链路请求数超过3时,假如平均响应时间假如超过200毫秒的有50%,则对请求进行熔断,熔断时长为10秒钟,10秒以后恢复正常。
对指定链路进行刷新,多次访问测试,假如出现了降级熔断,会出现如下结果:
异常处理
系统提供了默认的异常处理机制,假如默认处理机制不满足我们需求,我们可以自己进行定义。定义方式上可以直接或间接实现BlockExceptionHandler接口
@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
//response.setStatus(601);
//设置响应数据的编码
response.setCharacterEncoding("utf-8");
//告诉客户端要响应的数据类型以及客户端以什么编码呈现数据
response.setContentType("text/html;charset=utf-8");
PrintWriter pw = response.getWriter();
Map<String, Object> map = new HashMap<>();
if (e instanceof DegradeException) {//降级、熔断
map.put("status", 601);
map.put("message", "服务被熔断了!");
} else if (e instanceof FlowException) {
map.put("status", 602);
map.put("message", "服务被限流了!");
} else {
map.put("status", 603);
map.put("message", "Blocked by Sentinel (flow limiting)");
}
//将map对象转换为json格式字符串
String jsonStr = new ObjectMapper().writeValueAsString(map);
pw.println(jsonStr);
pw.flush();
}
}
六、降级策略分析
1慢调用比例
慢调用指耗时大于阈值RT(Response Time)的请求称为慢调用
慢调用逻辑中的状态分析如下:
熔断(OPEN):请求数大于最小请求数并且慢调用的比率大于比例阈值则发生熔断,熔断时长为用户自定义设置。
探测(HALFOPEN):当熔断过了定义的熔断时长,状态由熔断(OPEN)变为探测(HALFOPEN)。
关闭(CLOSED):如果接下来的一个请求小于最大RT,说明慢调用已经恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED),如果接下来的一个请求大于最大RT,说明慢调用未恢复,继续熔断,熔断时长保持一致
注意:Sentinel默认统计的RT上限是4900ms,超出此阈值的都会算作4900ms
2 异常比例
当资源的每秒请求数大于等于最小请求数,并且异常总数占通过量的比例超过比例阈值时,资源进入降级状态
异常比例中的状态分析如下:
熔断(OPEN):当请求数大于最小请求并且异常比例大于设置的阈值时触发熔断,熔断时长由用户设置。
探测(HALFOPEN):当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)
关闭(CLOSED):如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)。如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致。
3 异常数量
当资源近1分钟的异常数目超过阈值(异常数)之后会进行服务降级。注意,由于统计时间窗口是分钟级别的,若熔断时长小于60s,则结束熔断状态后仍可能再次进入熔断状态。其属性说明如下:
基于异常数的状态分析如下:
熔断(OPEN):当请求数大于最小请求并且异常数量大于设置的阈值时触发熔断,熔断时长由用户设置。
探测(HALFOPEN):当超过熔断时长时,由熔断(OPEN)转为探测(HALFOPEN)
关闭(CLOSED):如果接下来的一个请求未发生错误,说明应用恢复,结束熔断,状态由探测(HALF_OPEN)变更为关闭(CLOSED)如果接下来的一个请求继续发生错误,说明应用未恢复,继续熔断,熔断时长保持一致。
七、热点规则分析
热点参数限流会统计传入参数中的热点数据,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。其中,Sentinel会利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
使用场景
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制。
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制。
快速入门
定义热点业务代码
@GetMapping("/consumer/findById")
@SentinelResource("res")
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
设置要限流的热点
热点规则的限流模式只有QPS模式(这才叫热点)。参数索引为@SentinelResource注解的方法参数下标,0代表第一个参数,1代表第二个参数。单机阈值以及统计窗口时长表示在此窗口时间超过阈值就限流。
多次访问热点参数方法,前端会出现如下界面
后台也会出现如下错误
其中,热点参数其实说白了就是特殊的流控,流控设置是针对整个请求的;但是热点参数他可以设置到具体哪个参数,甚至参数针对的值,这样更灵活的进行流控管理。
一般应用在某些特殊资源的特殊处理,如:某些商品流量大,其他商品流量很正常,就可以利用热点参数限流的方案。
特定参数设计
八、系统规则
Sentinel的系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、线程数和CPU使用率五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
九、授权规则
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的**请求来源(origin)**限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过
定义请求解析器,用于对请求进行解析,并返回解析结果,sentinel底层 在拦截到用户请求以后,会对请求数据基于此对象进行解析,判定是否符合黑白名单规则
@Component
public class DefaultRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getParameter("origin");
return origin;
}
}