Spring Cloud Alibaba 二

Snipaste_2023-01-01_16-22-24.png

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>
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- SpringCloud ailibaba sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- Sentinel热点配置-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.4</version>
</dependency>

</dependencies>
  • application.yml
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
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
port: 8719
web-context-unify: false
  • FlowLimitController
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 {

// Thread.sleep(5000);

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
/**
* 熔断策略 慢调用比例
* @param id
* @return
*/
@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";
}
  • 异常比例 (ERROR_RATIO)

当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%

异常比例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 熔断策略 异常比例
* @param id
* @return
*/
@GetMapping(value = "/test4/{id}")
public String test4(@PathVariable Integer id){

if (id == 10) {
int i = 1 / 0;
}

return "test4";
}
  • 异常数 (ERROR_COUNT)

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断

异常数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 熔断策略 异常数
* @param id
* @return
*/
@GetMapping(value = "/test5/{id}")

public String test5(@PathVariable Integer id){

if (id == 10) {
int i = 1 / 0;
}

return "test5";
}

热点参数限流

经常访问的数据对其进行相应的设置

使用方法

  • pom
1
2
3
4
5
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-parameter-flow-control</artifactId>
<version>1.8.4</version>
</dependency>
  • controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 热点key限制
* @param goodsId
* @param userId
* @return
*/
@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

  • CPU usage(1.5.0+ 版本)
    当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏

  • 平均 RT

当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒

并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护

  • 入口 QPS

当单台机器上所有入口流量的 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 逻辑中,而是会原样抛出

  • controller
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){
//业务逻辑
// int a=1/0;
return "减库存成功";
}
  • CommonHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author xiaobo
* @date 2022/10/28 - 13:13
*/
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模块

  • 两个provider模块的pom
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>
<!-- SpringCloud ailibaba sentinel-->
<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>
  • controller
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);
}

}
  • Goods
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;

}
  • yml
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
# 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的端口
port: 8719
web-context-unify: false #关闭收敛URL
  • app
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

  • pom
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>
<!-- SpringCloud ailibaba sentinel-->
<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>
  • controller
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")
// @SentinelResource(value = "add", fallback = "fallback_add")
@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;
}
}
  • application.yml
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 #nacos
sentinel:
transport:
dashboard: localhost:8080 #sentinel
port: 8719
  • RestTemplateConfig
1
2
3
4
5
6
7
8
9
@Configuration
public class RestTemplateConfig {

@Bean
@LoadBalanced // 声明 LoadBalanced负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
  • app
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调用

  • pom
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • yml中添加
1
2
3
4
#激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
  • GoodsFeign
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);

}
  • GoodsFeignImpl
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;
}
}
  • controller
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

  • pom
1
2
3
4
5
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • application.yml
1
2
3
4
5
6
7
8
9
10
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848 #nacos
dataId: doudalibaba-sentinal-consumer #nacos中对应的配置dataid
groupId: DEFAULT_GROUP #组别,你可以不写,视你项目需求而定,一般都会写,隔离作用,测试时可以先不写,以免出错
data-type: json # 数据类型
rule-type: flow # 规则
# namespace: Deal-Project #命名空间,格式隔离作用
  • nacos添加配置
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中的配置

正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……

-------------本文结束 感谢您的阅读-------------

本文标题:Spring Cloud Alibaba 二

文章作者:小博

发布时间:2023年01月01日 - 16:17

最后更新:2023年01月01日 - 16:23

原始链接:https://codexiaobo.github.io/posts/1253424234/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。