一、初始 Sentinel
1、Sentinel主要特征
Sentinel 是阿里中间件团队研发的面向分布式服务架构的轻量级高可用流量控制组件,于2019年7月正式开源。Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户提升服务的稳定性。
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
2、Sentinel vs Hystrix
在SpringCloud Netflix版中,处理熔断降级主要用到是框架是Hystrix,对于Hystrix来说,sentinel吸收了Hystrix的一些很好的思想,并提供了更加强大,更加便捷的功能。
在SpringCloud Netflix版中,处理熔断降级主要用到是框架是Hystrix,对于Hystrix来说,sentinel吸收了Hystrix的一些很好的思想,并提供了更加强大,更加便捷的功能。
sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离、信号量隔离 |
熔断降级策略 | 基于响应时间或失败比例 | 基于失败比例 |
实时指标实现 | 滑动窗口 | 活动窗口(RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件形式 |
注解支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | |
流量整形 | 支持慢启动、匀速器 | 不支持 |
负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则,秒级监控,机器发现等 | 不完善 |
二、搭建 Sentinel
1、服务端
下载服务端jar包运行
java -jar sentinel-dashboard-1.8.2.jar --server.port=8081
账号密码默认为:sentinel
访问:http://127.0.0.1:8080/
2、客户端
- 创建模块
写pom
<!--nacos-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
写yml ```yaml server: port: 8401
spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 # nacos地址 sentinel: transport: dashboard: localhost:8080 port: 8719 # 默认端口8719,加入被占用会自动从8719依次+1扫描
management: endpoints: web: exposure: include: ‘*’
4. 主启动类
```java
@SpringBootApplication
public class Sentinel8401 {
public static void main(String[] args) {
SpringApplication.run(Sentinel8401.class, args);
}
}
测试:http://localhost:8080/ 进入Sentinel控制台,其懒加载机制需要有请求后才会显示。
三、流控规则
1、流控模式
直接流控模式:基于QPS/并发数的流量控制,一种是统计并发线程数,另外一种则是统计 QPS。
- 关联流控模式:基于调用关系的流量控制。关联流控模式指的是,当指定接口关联的接口达到限流条件时,开启对指定接口开启限流。
链路流控模式:根据调用链路入口限流。链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。
2、流控效果
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
- Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能直接把系统打死,预热方式就是慢慢的把阈值增长到设置的阈值(京东抢购茅台第一批人没抢到,第二批人抢到了)。总结:阈值慢慢提升,起初接收的数量少,后面才提升,防止系统在低位时被压垮。
- 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
源码
四、熔断降级
1、熔断策略
- 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
2、使用 Sentinel 做流控
1、定义Sentinel资源点
@SentinelResource 用于定义资源,并提供可选的异常处理和fallback配置项。其主要参数如下: 总结:@SentinelResource注解的blockHandler字段,指定发生Sentinel异常时的处理方法(即触发Sentinel五种类型规则时的处理逻辑)。fallback字段指定发生业务异常时的处理方法。当然,为了省事,可以不指定blockHandler,所有处理逻辑都放在fallback中。
2、配置资源点的流控规则
3、代码编写
@RestController
@RequestMapping("provider")
public class UserController {
@Autowired
private UserService userService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/user/{id}")
@SentinelResource(value = "demoResource1", fallback = "fallbackHandler", blockHandler = "blockHandler")
public String getUser(@PathVariable("id") Integer id) {
// 模拟java运行异常
if (id < 0) {
throw new NullPointerException("空指针异常!");
}
return userService.findById(id) + ", from:" + serverPort;
}
/**
* 处理Java异常
* 1、参数要和原方法相同
* 2、参数列表的最后,允许添加一个Throwable参数,用来接收原方法中发生的异常
* 3、static
*
* @param id
* @param throwable
* @return
*/
public static String fallbackHandler(@PathVariable("id") Integer id, Throwable throwable) {
return "处理java程序异常----" + throwable.getMessage();
}
/**
* 处理Sentinel流控异常
* 1、参数要和原方法相同
* 2、参数列表的最后,允许添加一个BlockException参数,用来接收原方法中发生的Sentinel异常
* 3、static
*
* @param id
* @param blockException
* @return
*/
public static String blockHandler(@PathVariable("id") Integer id, BlockException blockException) {
return "处理违反sentinel流控的请求----" + blockException.getMessage();
}
}
3、OpenFeign整合Sentinel实现熔断降级
1、pom
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.1</version>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yml
feign: sentinel: enabled: true
主启动类
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class AlibabaFeignConsumer80 { public static void main(String[] args) { SpringApplication.run(AlibabaFeignConsumer80.class, args); } }
业务类:FeignClient
@Component @FeignClient(value = "service-user-provider",fallback = UserFeignFallback.class) public interface UserFeignService { /** * 根据ID查用户信息 * * @param userId * @return */ @GetMapping("/provider/user/{id}") String findById(@PathVariable("id") Integer userId); /** * 超时方法 * @param userId * @return */ @GetMapping("/provider/user/{id}/timeout") String getUserByTimeOUt(@PathVariable("id") Integer userId); }
熔断降级处理类 ```java @Component public class UserFeignFallback implements UserFeignService { @Override public String findById(Integer userId) {
return "findById,fallback: 当前服务不可用,userId:" + userId;
}
@Override public String getUserByTimeOUt(Integer userId) {
return "getUserByTimeOUt,fallback: 当前服务不可用,userId:" + userId;
} }
5、设置流控规则,测试
> 如果出现报错:`exception is java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidatateMetadata(Ljava/lang/Class;)Ljava/util/List;`
报错原因:Sentinel框架SentinelContractHolder类中找不到parseAndValidatateMetadata这个方法,是因为这个方法拼写有错误,在Sentinel和OpenFeign新版本中已经修正为parseAndValidateMetadata<br />问题2:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21567217/1639738160896-3cad31cc-5316-4451-907c-468f48624a78.png#clientId=u9b1eb12c-fdc1-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=209&id=uc6cd3715&margin=%5Bobject%20Object%5D&name=image.png&originHeight=418&originWidth=1594&originalType=binary&ratio=1&rotation=0&showTitle=false&size=102130&status=done&style=none&taskId=u2ff49a58-0661-4c6b-827a-e32f46498e8&title=&width=797)
<a name="P2NtP"></a>
# 五、热点参数限流
1. 写pom
```xml
<!--热点key限流-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>
- @SentinelResource 相当于 Hstrix 的 @HstrixCommand
```java
@GetMapping(“/testD”)
@SentinelResource(value = “testHostKey”, blockHandler = “handler_testHostKet”)
public String testD(@RequestParam(value = “p1”, required = false) String p1,
return “—————-testHotKey”; }@RequestParam(value = "p2", required = false) String p2) {
public String handler_testHostKet(String p1, String p2, BlockException blockException) { return “——————热点限流了”; }
3. 配置规则
![image.png](https://cdn.nlark.com/yuque/0/2021/png/21567217/1623303317871-e19b3630-c646-470a-b426-6d23f38194e3.png#clientId=ub5b10563-1853-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=492&id=u214d324f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=984&originWidth=983&originalType=binary&ratio=2&rotation=0&showTitle=false&size=94542&status=done&style=none&taskId=ub4bef8a9-5a3e-4b1c-97a5-3772a131ace&title=&width=491.5)
4. 测试:[http://localhost:8401/testD?p1=a&p2=b](http://localhost:8401/testD?p1=a&p2=b),结论:遵守热点规则不会报错,违背了返回一个友好页面
<a name="BigYZ"></a>
# 六、系统自适应限流
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性,但是不推荐使用。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/21567217/1623303341705-8a20aa5f-04cb-402b-8bce-e7d4a39126bf.png#clientId=ub5b10563-1853-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=346&id=u3b81f1ec&margin=%5Bobject%20Object%5D&name=image.png&originHeight=691&originWidth=1125&originalType=binary&ratio=2&rotation=0&showTitle=false&size=97052&status=done&style=none&taskId=u791ce843-8fc0-4134-a310-ccc268d4f1d&title=&width=562.5)
<a name="yo0Ha"></a>
# 七、Sentinel持久化规则
1. 写pom
```xml
<!--持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 写yml ```yaml server: port: 9002
spring: application: name: service-user-provider cloud:
# 把服务注册到nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 可视化页面地址
dashboard: 127.0.0.1:8080
# 默认端口8719,加入被占用会自动从8719依次+1扫描
port: 8719
# 持久化配置
datasource:
ds1:
nacos:
# nacos地址
server-addr: 127.0.0.1:8848
# 使用微服务名称
data-id: ${spring.application.name}
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow
main:
# 循环引用
allow-circular-references: true
management: endpoints: web: exposure: include: ‘*’
3. 创建测试类
```java
@RestController
public class Test {
@GetMapping("/hello")
public String hello() {
return "hello sentinel";
}
}
在Nacos添加以下配置
[ { "resource": "/hello", "limitApp": "default", "grade": 1, "count": 5, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
测试:访问几次接口之后,就可以在Sentinel Dashboard 中看到在nacos中配置的规则信息了,并且项目服务重启依然存在,持久化完成。