spring cloud Alibaba
Sentinel实现熔断与限流
Sentinel介绍
替代hystrix
下载地址https://github.com/alibaba/Sentinel/releases
文档介绍https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel特征
丰富的应用场景
完备的实时监控
广泛的开源生态
完善的SPI扩展机制
Sentinel两部分
不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持
基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器
运行
1
| java -jar sentinel-dashboard-1.8.4.jar
|
访问
http://localhost:8080
账号和密码都是sentinel
简单搭建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>1.8.4</version> </dependency>
</dependencies>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| server: port: 8000
spring: application: name: spring-cloud-alibaba-sentinal-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 web-context-unify: false
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @RestController @RequestMapping(value = "/sentinel") public class FlowLimitController {
@Autowired private GoodsService goodsService;
@GetMapping(value = "/test1") public String test1() throws InterruptedException {
goodsService.findAll();
return "test1"; }
@GetMapping(value = "/test2") public String test2(){
goodsService.findAll();
return "test2"; } }
|
流量控制
唯一名称,默认请求路径
sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
QPS(每秒钟的请求数量)
当调用该api就QPS达到阈值的时候,进行限流
线程数
当调用该api的线程数达到阈值的时候,进行限流
是否集群
是 AND 否
流控模式
直接
:api达到限流条件时,直接限流。分为QPS和线程数
关联
:当关联的资到阈值时,就限流自己
链路
:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)
流控效果
快速失败
:直接抛异常 Blocked by Sentinel (flow limiting)
warm up
:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值
排队等待
:匀速排队,让请求以均匀的速度通过,阈值类型必须设置为QPS,否则无效
熔断降级
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
熔断策略
- 慢调用比例 (SLOW_REQUEST_RATIO)
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@GetMapping(value = "/test3/{id}") public String test3(@PathVariable Integer id){
if (id == 10) { try { Thread.sleep(2000); } catch (InterruptedException e) { throw new RuntimeException(e); } }
return "test3"; }
|
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@GetMapping(value = "/test4/{id}") public String test4(@PathVariable Integer id){
if (id == 10) { int i = 1 / 0; }
return "test4"; }
|
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@GetMapping(value = "/test5/{id}")
public String test5(@PathVariable Integer id){
if (id == 10) { int i = 1 / 0; }
return "test5"; }
|
热点参数限流
经常访问的数据对其进行相应的设置
使用方法
1 2 3 4 5
| <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>1.8.4</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@GetMapping(value = "/order") @SentinelResource(value = "hotKeys",blockHandler = "fail_order") public String order(@RequestParam(value = "goodsId") String goodsId, @RequestParam(value = "userId") String userId){
return "用户下单成功"; }
public String fail_order(@RequestParam(value = "goodsId",required = false) String goodsId, @RequestParam(value = "userId",required = false) String userId, BlockException exception){
System.out.println(exception);
return "用户下单失败,请重试"; }
|
业务上的错,sentinel不会管,只管热点key限制
系统自适应
当系统负载高于某个阈值,就禁止或者减少流量的进入;当 load 开始好转,则恢复流量的进入
保证系统不被拖垮
在系统稳定的前提下,保持系统的吞吐量
系统规则
- Load 自适应 (仅对 Linux/Unix-like 机器生效)
系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护
当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
@SentinelResource
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项
@SentinelResource属性
value
:资源名称,必需项不能为空
entryType
:entry 类型,可选项,默认为 EntryType.OUT
blockHandler / blockHandlerClass
: blockHandler对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
fallback/fallbackClass
:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常,除了exceptionsToIgnore里面排除掉的异常类型,进行处理
fallback函数签名和位置要求
返回值类型必须与原函数返回值类型一致
方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
defaultFallback(since 1.6.0)
:默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效
defaultFallback 函数签名要求
返回值类型必须与原函数返回值类型一致
方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常
defaultFallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出
1 2 3 4 5 6 7 8 9 10
| @GetMapping("/resume") @SentinelResource(value = "resume", blockHandlerClass = CommonHandler.class, blockHandler = "blockHandler2") public String resume(@RequestParam(value = "goodsId",required = false)String goodsId){
return "减库存成功"; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class CommonHandler {
public static String blockHandler1(BlockException exception){
return "系统异常,请稍后重试"; }
public static String blockHandler2(@RequestParam(value = "goodsId",required = false) String goodsId, BlockException exception){ return "系统异常,请稍后重试"; } }
|
降级
调用方,提供方错了,返回给客户一个友好对象
演示一下
spring-cloud-alibaba-provider3模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @RestController @RequestMapping(value = "/goods") public class GoodsController {
@Value("${server.port}") private String port;
@GetMapping(value = "/findById/{id}") public Goods findById(@PathVariable(value = "id") Integer id){
return new Goods(id,"mac book pro " + port,20000.00,100); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data @NoArgsConstructor @AllArgsConstructor @ToString public class Goods implements Serializable {
private Integer goodId;
private String title;
private double price;
private Integer stock;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| server: port: 8002 spring: application: name: nacos-provider cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 web-context-unify: false
|
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableDiscoveryClient public class ProviderApp3 {
public static void main(String[] args) { SpringApplication.run(ProviderApp3.class, args); } }
|
另一个provider模块相同
spring-cloud-alibaba-consumer2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> <version>3.1.1</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @RestController @RequestMapping("/order") public class OrderController {
@Autowired private RestTemplate restTemplate;
@GetMapping(value = "/add/{id}")
@SentinelResource(value = "add", blockHandler = "fail_add",fallback = "fallback_add") public Goods add(@PathVariable("id") Integer id){
Goods goods = restTemplate.getForObject("http://nacos-provider/goods/findById/" + id, Goods.class);
if(id<0){ throw new IllegalArgumentException("非法参数"); }else if(id>100){ throw new NullPointerException("查无此商品"); }
return goods; }
public Goods fail_add(@PathVariable("id") Integer id ,BlockException e){
Goods goods =new Goods(); goods.setGoodId(-1); goods.setPrice(-1); goods.setStock(-1); goods.setTitle("限流之后的特殊对象");
return goods; }
public Goods fallback_add(@PathVariable("id") Integer id,Throwable throwable){
Goods goods =new Goods(); goods.setGoodId(-2); goods.setPrice(-2); goods.setStock(-2); goods.setTitle("业务出错后的特殊对象");
return goods; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| server: port: 9001
spring: application: name: nacos-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: dashboard: localhost:8080 port: 8719
|
1 2 3 4 5 6 7 8 9
| @Configuration public class RestTemplateConfig {
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
|
1 2 3 4 5 6 7 8
| @SpringBootApplication @EnableDiscoveryClient public class ConsumerApp2 {
public static void main(String[] args) { SpringApplication.run(ConsumerApp2.class, args); } }
|
Feign调用
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
|
1 2 3 4
| feign: sentinel: enabled: true
|
1 2 3 4 5 6 7
| @FeignClient(value = "nacos-provider",fallback = GoodsFeignImpl.class) public interface GoodsFeign {
@GetMapping(value = "/goods/findById/{id}") public Goods findById(@PathVariable(value = "id") Integer id);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Component public class GoodsFeignImpl implements GoodsFeign {
@Override public Goods findById(Integer id) {
Goods goods =new Goods(); goods.setGoodId(-3); goods.setPrice(-3); goods.setStock(-3); goods.setTitle("feign出错之后的特殊对象");
return goods; } }
|
1 2 3 4 5 6 7 8
| @Autowired GoodsFeign goodsFeign;
@GetMapping("/add1/{id}") public Goods add1(@PathVariable("id")Integer id){ Goods goods = goodsFeign.findById(id); return goods; }
|
主启动类中添加EnableFeignClients
配置持久化
sentinel的流控配置是临时的,所以我们可以把配置持久化到nacos
1 2 3 4 5
| <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
|
1 2 3 4 5 6 7 8 9 10
| sentinel: datasource: ds1: nacos: server-addr: localhost:8848 dataId: doudalibaba-sentinal-consumer groupId: DEFAULT_GROUP data-type: json rule-type: flow
|
1 2 3 4 5 6 7 8 9 10 11
| [ { "resource": "/order/add1/{id}", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "controlBehavior": 0, "clusterMode": false } ]
|
resource
:资源名称
limitApp
:来源应用
grade
:阈值类型,0表示线程数,1表示QPS
count
:单机阈值
strategy
:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior
:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
clusterMode
:是否集群
测试对应接口sentinel中没有设置限流,可是访问接口时仍被限流,因为读取到了nacos中的配置
正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……