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
IDEA运行Sentinel
新建目录
导入Jar包
编辑配置
运行Sentinel
访问http://localhost:8858/
用户和密码都是:sentinel
Sentinel使用
Sentinel Starter 默认为所有的 HTTP 服务提供了限流埋点,同时我们也可以通过使用@SentinelResource注解来自定义一些限流行为。
导入依赖
1 2 3 4
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
|
编辑配置
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
|
启动服务
其他模块同理
Sentinel流量控制
限流的定义和意义
限流就是对于请求的速率进行限制,避免瞬时的大量请求击垮软件系统,在合理避免高比发请求的同时,也在一定程度上保护服务器不受恶意攻击
限流可能会导致用户的请求无法被正确处理,但它是权衡了软件系统的稳定性之后的最优解
常见的限流算法
固定窗口计数器算法
固定窗口就是时间窗口,固定窗口计数器算法规定了单位时间处理的请求数量。
固定窗口对某一个时间段内的请求进行计数和限制,假设规定系统中某个接口1分钟只能访问100词的话,使用固定窗口计数器算法的实现思路如下:
- 给定一个变量 counter 来记录当前接口处理的请求数量,初始值为0,代表接口当前1分钟内暂未处理请求
- 1分钟之内每处理一个请求之后就将 counter + 1,当 counter = 100之后,后续的请求就会被全部拒绝
- 等到1分钟结束后,将couter重置 0,重新开始计数
这种限流算法无法保证限流速率,无法避免突然激增的流量。
滑动窗口计数器算法
相对于固定窗口计数器算法,滑动时间窗口算法更加灵活,它可以动态移动窗口,重新进行计算
例如我们接口进行限流,每分钟处理60个请求,可以把1分钟分为60个窗口,每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 60(请求数)/60(窗口数)
的请求,如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。
虽然滑动时间窗口算法可以避免固定时间窗口的临界问题,但是它比固定窗口更加耗时。
漏桶算法
我们可以把发请求的动作比作注水到桶中,处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
算法的实现可以通过一个队列来实现:准备一个队列保存请求,然后定期从队列中拿请求来执行
令牌桶算法
和漏桶算法算法相似,但桶是用来存放令牌的,每隔一段时间向桶中丢入一个令牌,速度可以指定,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃。当桶中的令牌数量不足时,取不到的令牌请求将会被丢弃。我们可以根据限流大小,按照一定的速率往桶里添加令牌。此外,如果桶装满了,就不能继续往里面继续添加令牌了。
流量控制
簇点链路
打开Sentinel管理页面的簇点链路模块
流控模式
直联:只针对当前接口
关联:当被关联的接口超过阈值时,会导致当前接口被限流
链路:更细粒度的限流,能精确到具体的方法
直联模式
关联模式
Postman批量请求
链路模式
使用@SentinelResource
编辑配置文件
1 2 3 4
| sentinel: transport: dashboard: localhost:8858 web-context-unify: false
|
流控效果
快速失败:请求在某一时刻高于阈值,不接受新的请求,直接返回一个拒绝信息
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 达到阈值即触发系统保护。
自定义限流和异常处理
自定义限流处理Controller类
定义请求映射
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); } }
|
设定限流页面
1 2 3 4 5
| sentinel: transport: dashboard: localhost:8858 web-context-unify: false block-page: /rateLimit/customBlockHandler
|
新增流控规则
自定义限流处理Handler类
定义限流处理逻辑
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); }
|
热点参数限流
添加热点参数限流测试接口
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; }
|
新增热点参数限流规则
添加参数例外项
对于请求的参数值进行限流操作
熔断和降级
Sentinel支持对服务间调用进行保护,对故障应用进行熔断操作
熔断策略
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 {
@RequestMapping("/sleep") public CommonResult sleep() throws InterruptedException { Thread.sleep(1000); return null; }
@RequestMapping("/exception") public CommonResult exception() { throw new RuntimeException(); } }
|
慢调用:
如果一次请求的处理时间超过了指定的RT即最大响应时间,那么就被判定为慢调用
,在一个统计时长内,如果请求数目大于最小请求数目,并且被判定为慢调用
的请求比例已经超过阈值,将触发熔断。经过熔断时长之后,将会进入到半开状态进行试探
异常比例:
判断出现异常次数的比例
异常数:
判断出现异常的次数
使用RestTemplate
添加RestTemplate注册配置类并使用@SentinelRestTemplate包装
1 2 3 4 5 6 7 8 9
| @Configuration public class RibbonConfig { @Bean @LoadBalanced @SentinelRestTemplate public RestTemplate restTemplate() { return new RestTemplate(); } }
|
添加CircleBreakerController类
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); } }
|
与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
|
定义接口
Feign异常处理实现
使用Nacos存储流控规则