Spring Cloud Alibaba(二)Sentinel

Sentinel介绍

随着微服务的流行,服务与服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel特征

  • 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
  • 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。

Sentinel安装与部署

Sentinel控制台安装

Sentinel控制台是一个轻量级的控制台应用,它可用于实时查看单机资源监控及集群资源的汇总,并提供一系列的规则管理功能,如流控规则、降级规则、热点规则等。

与Nacos一样,Sentinel也是独立安装与部署的。

Sentinel下载地址:https://github.com/alibaba/Sentinel/releases

image-20220622143732400

IDEA运行Sentinel

新建目录

image-20220622144500793

导入Jar包

image-20220622144605589

image-20220622144931169

编辑配置

image-20220622144859856

运行Sentinel

image-20220622145133735

访问http://localhost:8858/

image-20220622145412127

用户和密码都是:sentinel

image-20220622150046689

Sentinel使用

Sentinel Starter 默认为所有的 HTTP 服务提供了限流埋点,同时我们也可以通过使用@SentinelResource注解来自定义一些限流行为。

导入依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

image-20220622171914457

编辑配置

image-20220622172010394

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8101
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
ephemeral: false # 是否是临时实例
namespace: 68928803-bcbf-40a4-ba9a-1bb84ff12238
sentinel:
transport:
dashboard: localhost:8858

启动服务

image-20220622234031959

其他模块同理

image-20220622234737747

Sentinel流量控制

限流的定义和意义

限流就是对于请求的速率进行限制,避免瞬时的大量请求击垮软件系统,在合理避免高比发请求的同时,也在一定程度上保护服务器不受恶意攻击

限流可能会导致用户的请求无法被正确处理,但它是权衡了软件系统的稳定性之后的最优解

常见的限流算法

固定窗口计数器算法

固定窗口就是时间窗口,固定窗口计数器算法规定了单位时间处理的请求数量。

固定窗口对某一个时间段内的请求进行计数和限制,假设规定系统中某个接口1分钟只能访问100词的话,使用固定窗口计数器算法的实现思路如下:

  • 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为0,代表接口当前1分钟内暂未处理请求
  • 1分钟之内每处理一个请求之后就将 counter + 1,当 counter = 100之后,后续的请求就会被全部拒绝
  • 等到1分钟结束后,将couter重置 0,重新开始计数

这种限流算法无法保证限流速率,无法避免突然激增的流量。

固定窗口计数器算法

滑动窗口计数器算法

相对于固定窗口计数器算法,滑动时间窗口算法更加灵活,它可以动态移动窗口,重新进行计算

例如我们接口进行限流,每分钟处理60个请求,可以把1分钟分为60个窗口,每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 60(请求数)/60(窗口数) 的请求,如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。

滑动窗口计数器算法

当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。

虽然滑动时间窗口算法可以避免固定时间窗口的临界问题,但是它比固定窗口更加耗时。

漏桶算法

我们可以把发请求的动作比作注水到桶中,处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

算法的实现可以通过一个队列来实现:准备一个队列保存请求,然后定期从队列中拿请求来执行

漏桶算法

令牌桶算法

和漏桶算法算法相似,但桶是用来存放令牌的,每隔一段时间向桶中丢入一个令牌,速度可以指定,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃。当桶中的令牌数量不足时,取不到的令牌请求将会被丢弃。我们可以根据限流大小,按照一定的速率往桶里添加令牌。此外,如果桶装满了,就不能继续往里面继续添加令牌了。

令牌桶算法

流量控制

簇点链路

打开Sentinel管理页面的簇点链路模块

image-20220623231123397

流控模式

直联:只针对当前接口

关联:当被关联的接口超过阈值时,会导致当前接口被限流

链路:更细粒度的限流,能精确到具体的方法

直联模式

image-20220623231516165

image-20220623231621772

关联模式

image-20220623233146439

Postman批量请求

image-20220623233336646

image-20220623233348177

链路模式

使用@SentinelResource

image-20220623235059181

编辑配置文件

image-20220623235243139

1
2
3
4
sentinel:
transport:
dashboard: localhost:8858
web-context-unify: false # 关闭 context 收敛

image-20220624000212443

image-20220623235944923

image-20220624000010412

流控效果

快速失败:请求在某一时刻高于阈值,不接受新的请求,直接返回一个拒绝信息

Warm up:基于方案一,缓慢的将阈值提高到指定阈值,形成一个缓存保护

排队等待:将请求进入队列,在规定时间内如果可以执行则执行,超时则放弃

系统规则

Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage:当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

image-20220624000732211

image-20220624000759729

自定义限流和异常处理

自定义限流处理Controller类
定义请求映射

image-20220624144520582

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {
/**
* 自定义通用的限流处理逻辑
*/
@GetMapping("/customBlockHandler")
public CommonResult blockHandler() {
return new CommonResult("限流成功", 200);
}
}

设定限流页面

image-20220624144643754

1
2
3
4
5
sentinel:
transport:
dashboard: localhost:8858
web-context-unify: false # 关闭 context 收敛
block-page: /rateLimit/customBlockHandler # 自定义限流映射页面
新增流控规则

image-20220624144809125

image-20220624144843223

自定义限流处理Handler类
定义限流处理逻辑

image-20220624153735076

1
2
3
4
5
6
7
8
9
10
11
12
@SentinelResource(value = "getBorrowByBook", blockHandler = "handlerExceptionByMethod")/*开启方法限流监控*/
@Override
public BookBorrowDetail getBorrowByBook(Long bid) {
List<Borrow> borrows = borrowMapper.getBorrow(null,bid);
List<User> users = borrows.stream().map(borrow -> userService.getUserById(borrow.getUid())).collect(Collectors.toList());
Book book = bookService.getBookById(bid);
return new BookBorrowDetail(users, book);
}

public BookBorrowDetail handlerExceptionByMethod(Long bid , BlockException exception){
return new BookBorrowDetail(Collections.emptyList(), null);
}

image-20220624153640058

热点参数限流

添加热点参数限流测试接口

image-20220625121225907

1
2
3
4
5
6
7
8
9
/**
* 热点参数限流测试
*/
@RequestMapping("/param")
@SentinelResource("param")
public String getParam(@RequestParam(value = "param1", required = false) String param1,
@RequestParam(value = "param2", required = false) String param2) {
return "param1:" + param1 + ",param2:" + param2;
}

image-20220625121536398

新增热点参数限流规则

image-20220625121935694

image-20220625122042115

image-20220625122055925

添加参数例外项

对于请求的参数值进行限流操作

image-20220625123242671

image-20220625123057218

image-20220625123421168

image-20220625123433063

熔断和降级

Sentinel支持对服务间调用进行保护,对故障应用进行熔断操作

image-20220328124619289

image-20220625201603610

熔断策略

image-20220625223846345

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {
/**
* 熔断策略:慢调用测试
* @return
* @throws InterruptedException
*/
@RequestMapping("/sleep")
public CommonResult sleep() throws InterruptedException {
Thread.sleep(1000);
return null;
}

/**
* 熔断策略:异常比例和异常数
* @return
*/
@RequestMapping("/exception")
public CommonResult exception() {
throw new RuntimeException();
}
}
慢调用:

如果一次请求的处理时间超过了指定的RT即最大响应时间,那么就被判定为慢调用,在一个统计时长内,如果请求数目大于最小请求数目,并且被判定为慢调用的请求比例已经超过阈值,将触发熔断。经过熔断时长之后,将会进入到半开状态进行试探

image-20220625222808135

异常比例:

判断出现异常次数的比例

image-20220625224826145

异常数:

判断出现异常的次数

使用RestTemplate

添加RestTemplate注册配置类并使用@SentinelRestTemplate包装

image-20220625192729019

1
2
3
4
5
6
7
8
9
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
@SentinelRestTemplate/*使用@SentinelRestTemplate包装RestTemplate实例*/
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
添加CircleBreakerController类

image-20220625192637561

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
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {
private static final Logger logger = LoggerFactory.getLogger(CircleBreakerController.class);
@Resource(name = "restTemplate")
private RestTemplate restTemplate;
@Value("${service-url.user-service}")
private String userServiceUrl;

@RequestMapping("/fallback/{uid}")
@SentinelResource(value = "fallback", fallback = "handleFallback")
public CommonResult fallback(@PathVariable Long uid) {
User user = restTemplate.getForObject("http://user-service/user/" + uid, User.class);
return new CommonResult(user);
}

@RequestMapping("/fallbackException/{uid}")
@SentinelResource(value = "fallbackException", fallback = "handleFallbackException", exceptionsToIgnore = {NullPointerException.class})
public CommonResult fallbackException(@PathVariable Long uid) {
if (uid == 1) {
throw new IndexOutOfBoundsException();
} else if (uid == 2) {
throw new NullPointerException();
}
return new CommonResult(restTemplate.getForObject(userServiceUrl + "/user/" + uid, User.class, uid));
}

public CommonResult handleFallBack(Long uid) {
User defaultUser = new User(-1L, "defaultUser", null);
return new CommonResult(defaultUser, "服务降级返回", 200);
}

public CommonResult handleFallBackException(Long uid, Throwable e) {
logger.error("handleFallBackException uid:{},throwable class:{}", uid, e.getClass());
User defaultUser = new User(-2L, "defaultUser", null);
return new CommonResult(defaultUser, "服务降级返回", 200);
}
}

image-20220625195026590

image-20220625195103109

与Fegin结合使用

Sentinel可以适配Feign组件,使用Feign来进行服务间调用时,可以使用它进行熔断

导入依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
配置文件开启Sentinel对Feign支持
1
2
3
feign:
sentinel:
enabled: true # 打开Sentinel对feign的支持
定义接口

image-20220625224937819

Feign异常处理实现

image-20220625225041440

使用Nacos存储流控规则