学习目标
- 能够说出什么是微服务雪崩
- 能够说出常用的微服务保护方案和技术方案
- 能够说出什么是熔断降级
- 能够基于FallbackFactory编写降级方法
- 能够使用Sentinel配置熔断策略并测试通过
- 能够使用Sentinel配置限流策略并测试通过
- 能够基于Sentinel注解编写降级方法
- 能够使用Sentinel配置线程隔离并测试通过
- 能够说出CAP原理
- 能够使用Seata实现分布式事务控制
- 能够说出Seata AT模式的工作原理
1 微服务保护
1.1.微服务保护方案
1.1.1 微服务雪崩问题
上次课我们学习了微服务之间的远程调用,微服务通过远程调用进行协作完成业务流程,试想如果出现下边的现象会导致什么问题:
假如商品服务业务并发较高,占用过多Tomcat连接。可能会导致商品服务的所有接口响应时间增加,延迟变高,甚至是长时间阻塞直至查询失败。
此时查询购物车业务需要等待商品查询结果,从而导致购物车业务的响应时间也变长,甚至也阻塞直至无法访问。而此时如果查询购物车的请求较多,可能导致购物车服务的Tomcat连接占用较多,所有接口的响应时间都会增加,整个服务性能很差, 甚至不可用。
依次类推,整个微服务群中与购物车服务、商品服务等有调用关系的服务可能都会出现问题,最终导致整个集群不可用。
这就是级联失败问题,或者叫雪崩问题。【因为一个底层服务不可用,最终导致整个服务集群不可用】
保证服务运行的健壮性,避免级联失败导致的雪崩问题,就属于微服务保护。这章我们就一起来学习一下微服务保护的常见方案以及对应的技术。
1.1.2 微服务保护方案
1.1.2.1 方案介绍
AI:Spring cloud微服务保护的方案
Spring Cloud微服务架构中的服务保护是非常重要的,它能够确保系统的稳定性和可用性,特别是在面对突发流量或者服务异常的情况下。常用的微服务保护方案包括但不限于以下几个方面:
- 熔断 (Circuit Breaker) 熔断机制用于在服务出现问题时快速失败,避免调用链路中的服务相互等待,导致整体系统响应变慢甚至不可用。
如何快速失败(fast fail)呢?当服务的错误率达到一定程度时,断路器(相当于保险丝)会打开,直接返回错误而不是尝试调用服务。一段时间后,断路器会处于半开状态尝试调用服务,如果服务恢复正常,则关闭断路器。
【知识拓展】
AI:fast fail和safe fail区别
答:
Fast Fail(快速失败):旨在快速暴露问题,防止错误扩散或导致更严重的后果,如医疗、金融场景。缺点是:导致系统中断,影响用户体验【直接抛异常】
Safe Fail(安全失败):旨在最大程度保证系统可用和安全性,如在线服务、云计算平台。缺点是:可能导致问题被掩盖,增加修复难度。【try-catch,返回一个默认值(即降级)】
由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
- 降级 (Degradation) 断路器会统计访问某个服务的请求数量,统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。
降级逻辑 即提供一个简化的响应或者默认的响应来代替正常的服务调用。这样可以保证核心业务不受影响,非核心业务暂时被限制或关闭。
熔断后,接口还通吗?
不通,直接异常
降级后,接口还通吗?
通,但返回的是降级逻辑,即类似一个默认值,故业务逻辑不一定闭环,后续还需要人工补偿
- 超时 (Timeout) 设置合理的超时时间可以避免长时间等待响应导致的问题。当请求超时时,可以选择快速失败并返回错误信息,或者重试等策略。
常见的远程调用框架,都设置了超时机制。
AI:目前Http、Dubbo、WebService都有超时机制吗?
答:是的,HTTP、Dubbo 和 WebService 都支持超时机制,但它们的实现方式和配置方法有所不同
HTTP:连接超时、读取超时
Dubbo:服务调用超时(默认3s),超时后自动重试2次
WebService:连接超时、读取超时
- 线程隔离 (Thread Isolation) 线程隔离是指为每个服务分配独立的线程池,这样即使某个服务出现问题也不会影响到其他服务。
线程隔离的思想来自轮船的舱壁模式:
轮船的船舱会被隔板分割为N个相互隔离的密闭舱,假如轮船触礁进水,只有损坏的部分密闭舱会进水,而其他舱由于相互隔离,并不会进水。这样就把进水控制在部分船体,避免了整个船舱进水而沉没。
为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个接口可以使用的资源范围,也就是将其“隔离”起来。
如图所示,我们给查询购物车业务限定可用线程数量上限为20,这样即便查询购物车的请求因为查询商品服务而出现故障,也不会导致服务器的线程资源被耗尽,不会影响到其它接口。
- 限流 (Rate Limiting) 限流是最常见的服务保护措施之一,其目的是为了防止服务因为过大的流量而崩溃。
对于某些关键资源或者参数的访问,可以采取特殊的限流措施来防止这些热点成为瓶颈。
限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。
可以通过以下几种方式进行限流(有兴趣的可以看看下面两种实现方案,前期可以仅做了解):
- 基于令牌桶算法:允许一定数量的请求通过,超出则拒绝或排队等待。
- 基于滑动窗口:在一段时间内对请求进行计数,超过阈值则触发限流。
1.1.2.2 实现工具
在Spring Cloud生态系统中,实现服务保护通常使用的工具包括:
Hystrix: 提供了熔断、限流、超时等功能,是SpringCloud原生组件。
Resilience4j: 是一个轻量级的库,提供了与Hystrix类似的功能,但设计更为现代和简洁。
Sentinel: 阿里巴巴开源的一款流量控制组件,特别适合微服务架构下的流量管理,提供了限流、熔断、降级等多种服务保护功能,并且支持热更新规则。
本课程讲解Sentinel。
1.2 熔断降级
1.2.1. 方案介绍
熔断降级是解决服务集群雪崩问题的重要手段,包括熔断和降级两个方案。
熔断是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。熔断发生在服务调用方即客户端。
这么多报错(慢请求)是吧?行、都别玩了
降级是当遇到访问失败可以快速返回一些默认数据或者友好提示,用户体验会更好。熔断降级结合后是当线路断开后直接走降级线路避免再次去请求失败线路。降级方法需要在服务调用方即客户端实现。
这么多报错(慢请求)是吧?大哥你这样我就要挂了,小弟帮我顶顶(还有部分可以玩)
断路器控制熔断和放行的流程如下:
断路器包括三个状态:
- closed:关闭状态【默认】,断路器放行所有请求,并开始统计异常比例、慢请求比例、异常数。超过阈值则切换到open状态
- open:打开状态,服务调用被熔断,访问被熔断服务的所有请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
- 请求成功:则切换到closed状态
- 请求失败:则切换到open状态
实现熔断降级做两件事:
- 编写服务降级逻辑:就是服务调用失败后的处理逻辑,根据业务场景,可以抛出异常,也可以返回友好提示或默认数据。
- 异常统计和熔断:统计服务提供方的异常比例,当比例过高表明该接口会影响到其它服务,应该拒绝调用该接口,而是直接走降级逻辑。这里我们用Sentinel完成。
1.2.2. Sentinel安装与集成
1.2.2.1 切换分支
将hmall-micro代码环境切换到dev_02分支。
注意:切换分支前要提交原当前分支的代码。
每位学生在dev_02分支练习完成后提交代码并切换回dev_01分支继续未完成的任务
工作中也经常这样来回切换分支,因为不同需求在不同分支里,我们经常都是并行开发
大家入职后,也可能同时负责3-4个项目,所以尽早习惯【多线程并行的开发模式】
1.2.2.2 安装Sentinel
实现服务保护的工具有很多,Spring Cloud Alibaba技术栈中Sentinel是实现服务保护的中间件。
Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入Spring Cloud Alibaba中。官方网站:
https://sentinelguard.io/zh-cn/
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
Sentinel 的使用可以分为两个部分:
- 核心库(Jar包):不依赖任何框架/库,能够运行于Java8及以上的版本的运行时环境,同时对 Dubbo/Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
- 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
为了方便监控微服务,我们先把Sentinel的控制台搭建出来。
课前提供的虚拟机已经安装了sentinel,如下图:
使用课前提供的虚拟机需要设置sentinel容器的时区,如下:
先启动sentinel
docker start sentinel-dashboard
登录sentinel容器并设置时区
- 进入容器:docker exec -it sentinel-dashboard /bin/bash
- 执行命令:ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezon
设置完成效果如下 :
如果未使用课前提供的虚拟机,需要参考下边的内容安装sentinel:
1)下载jar包
下载地址:https://github.com/alibaba/Sentinel/releases
也可以直接使用课前资料提供的版本:
2)运行
将jar包拷贝到 虚拟机/data/soft/sentinel目录下重命名为sentinel-dashboard.jar:
创建Dockerfile文件
FROM openjdk:11-jdk ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezo ARG SENTINEL_VERSION=1.8.6 # copy sentinel jar ADD ./sentinel-dashboard.jar /home/sentinel-dashboard.jar RUN chmod -R +x /home/sentinel-dashboard.jar ENTRYPOINT ["sh","-c","java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar $JAVA_OPTS /home/sentinel-dashboard.jar"]
执行命令创建镜像:
docker build -t sentinel-dashboard .
创建并启动容器:
docker run --name sentinel-dashboard -d -p 9090:8090 sentinel-dashboard:latest
其它启动时可配置参数可参考官方文档:官网文档链接
3)访问
访问:http://192.168.101.68:9090/ 页面,就可以看到sentinel的控制台了:
需要输入账号和密码,默认都是:sentinel
登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身:
本地运行sentinel
如果在测试时发现虚拟中的sentinel不能用,可以本地运行sentinel。
将sentinel的jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar:
然后运行如下命令启动控制台:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
访问:http://localhost:8090/ 页面
1.2.2.3 项目集成Sentinel
在虚拟机启动sentinel【上面已经执行过,这里是再次提醒、确认一下】
docker start sentinel-dashboard
接下来,我们在项目中集成 sentinel,我们在哪个项目中集成 sentinel?
sentinel要完成熔断降级,熔断是在服务调用方,所以针对购物车服务请求商品服务实现熔断就需要在购物车服务集成 sentienl。
这里可能部分同学有疑问,问什么不是服务提供方呢?所以我们顺便推导一下,假设是提供方熔断:
(1)提供方是熔断了,但是上游调用方还是有大量请求,压力依然存在,只是加快了下游的响应速度,前提是牺牲了原有的业务逻辑实现,并不能保障整体微服务的可靠性
(2)调用方熔断,就是我根本不调用你下游(你此刻慢、报错多那我就先不调用你),而是返回一个默认逻辑,这个默认逻辑实现应该由接口提供方实现
我们在cart-service模块中整合sentinel,连接sentinel-dashboard控制台,步骤如下: 1)引入sentinel依赖
<!--sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2)配置控制台
修改application.yaml文件,添加下面内容:
spring: cloud: sentinel: transport: dashboard: 192.168.101.68:9090 client-ip: 192.168.101.1 http-method-specify: true # 开启请求方式前缀可根据http请求方法区分簇点链路
如果是在本机运行的sentinel要配置:
spring: cloud: sentinel: transport: dashboard: localhost:8090 http-method-specify: true # 开启请求方式前缀可根据http请求方法区分簇点链路
3)访问cart-service的任意端点
重启cart-service、item-service,然后访问查询购物车接口:swagger链接
sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard控制台。并展示出统计信息:
点击簇点链路菜单,会看到下面的页面:
所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口)。
因此,我们看到/carts这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、降级、隔离等保护措施,稍后会详细讲解。
1.2.3. 实现降级
1.2.3.1 开启sentinel
下边我们先编写降级逻辑,再实现服务熔断。
AI(Cursor)提示詞
帮我在已有工程里,对于itemclient实现降级策略,技术使用Sentinel,注意实现方案是implements FallbackFactory,降级类写在api的工程里,并最终在cart-service调用item-service时使用
首先配置Feign使用Sentinel:
在购物车服务application.yml中配置如下(默认已写好):
feign: sentinel: enabled: true # 开启feign对sentinel的支持
1.2.3.2 实现FallbackFactory接口
接下来给FeignClient编写失败后的降级逻辑有两种方式:
- 方式一:FallbackClass,无法捕获到远程调用的异常
定义一个降级类实现FeignClient接口,并在@FeignClient注解中配置fallback 属性,如下:
@FeignClient(name="item-service",path = "/items",fallback = 降级类名.class)
- 方式二:FallbackFactory,可以捕获远程调用的异常,我们一般选择这种方式。
定义一个降级类实现FallbackFactory接口,并在@FeignClient注解中配置fallbackFactory属性
@FeignClient(name="item-service",path = "/items",fallbackFactory= 降级类名.class)
这里我们演示方式二的失败降级处理。
步骤一:在hm-api模块中给ItemClient定义降级处理类,实现FallbackFactory接口:
代码如下:
package com.hmall.api.item; import com.hmall.api.item.dto.ItemDTO; import com.hmall.common.utils.CollUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.List; @Slf4j @Component public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> { @Override public ItemClient create(Throwable cause) { return new ItemClient() { @Override public List<ItemDTO> queryItemByIds(Collection<Long> ids) { log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause); cause.printStackTrace(); // 查询购物车允许失败,查询失败,返回空集合 return CollUtils.emptyList(); } @Override public void deductStock(List<OrderDetailDTO> items) { log.error("远程调用ItemClient#deductStock扣减库存失败,参数:{}",items,cause); } }; } }
1.2.3.3 配置fallbackFactory
步骤二:在hm-api模块中的ItemClient接口中使用ItemClientFallbackFactory:
package com.hmall.api.item; import com.hmall.api.item.dto.ItemDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; import java.util.List; /** * @author Mr.M * @version 1.0 * @description 商品服务Feign接口 * @date 2024/8/3 16:21 */ @FeignClient(name="item-service",path = "/items",fallbackFactory = ItemClientFallbackFactory.class) public interface ItemClient { @GetMapping List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids); @PutMapping("/stock/deduct") public void deductStock(@RequestBody List<OrderDetailDTO> items); }
1.2.3.4 注入降级Bean
步骤三:在cart-service启动类添加扫描包配置,扫描降级类并将bean注册到spring容器
1.2.3.5 降级测试
最后测试降级效果。
重启cart-service,并将item-service停止来模拟item-service服务不可用
请求查询购物车接口,cart-service远程调用item-service,由于item-service不可用,Feign执行ItemClientFallbackFactory 中定义的降级逻辑
在ItemClientFallbackFactory 中打断点,在ItemClientFallbackFactory 中捕获到了远程调用的异常,降级方法返回空List<ItemDTO>。
接口测试最终效果,购物车可以正常显示,但由于获取的商品信息为空这里newPrice显示为null。
启动 ItemServiceApplication,再次请求购物车接口,验证发现此时返回了 newPrice数据
1.2.3.6 小结
feign远程调用怎么实现降级?
- 我们使用的是OpenFeign实现微服务之间的远程调用,使用Sentinel实现熔断降级。
- 首先配置sentinel,引入sentinel的依赖,配置sentinel的地址
- 在服务调用方开启feign使用sentinel
- 在服务调用方编写feign接口并编写降级逻辑,具体方法是编写降级类实现FallbackFactory接口,并在FeignClient注解中配置fallbackFactory。
降级逻辑到底谁实现?假设A调用B,希望当接口异常(异常比例、慢请求达到阈值)做降级时候能兜底
调用方:我来决定你默认返回什么
提供方:我告诉你,默认返回什么
【到底谁实现,需要靠业务来定,个人开发经验:建议提供方做实现(你不要干预一个提供接口的人默认做什么事情,这是别人的领域,也可以保证职责清晰)】
- 服务调用方当无法正常调用服务提供方接口时会走降级逻辑,并捕获到异常。
1.2.4. 服务熔断
1.2.4.1 配置熔断策略
根据熔断方案,sentinel会统计异常比例、慢请求比例、异常数等数据,达到阈值时断路器打开即发生熔断,熔断状态下会走降级路线保证快速响应。
下边在sentinel中配置熔断策略,找到查询商品信息的簇点链路
点击“熔断”,配置异常数,如下图:
2秒内最小请求数为2,异常数达到1发生熔断,熔断时长为20秒。
1.2.4.2 测试异常数
下边进行测试,继续停止item-service商品服务。
启动cart-service购物车服务,连续请求查询购物车接口(达到熔断条件)。
此时在购物车控制台报异常信息如下:
feign.FeignException$ServiceUnavailable: [503] during [GET] to [http://item-service/items?ids=100000006163] [ItemClient#queryItemByIds(Collection)]: [Load balancer does not contain an instance for the service item-service]
当异常数达到Sentinel配置将发生熔断,熔断时间20秒,此时cart-service控制台将不再输出异常信息,因为cart-service走了降级路线不再请求item-service。
通过观察cart-service控制台日志可以发现,发生熔断后sentinel客户端抛出了com.alibaba.csp.sentinel.slots.block.degrade.DegradeException.
熔断时间(20秒)过后断路器成半开状态,再次请求购物车接口, 会尝试请求item-service一次,如果成功此时断路器关闭,如果仍失败断路器打开,关闭item-service日志发现输出一次java.lang.RuntimeException: 测试异常,此时断路器打开继续熔断。
1.2.4.3 测试慢调用比例(自行测试)
下边测试另一种熔断策略:慢调用比例
删除原来的熔断策略
添加新的熔断策略。
这种是按照慢调用比例来做熔断,上述配置的含义是:
- RT (Response Time接口响应时间),超过200毫秒的请求调用就是慢调用
- 统计最近3000ms内的最少2次请求,如果慢调用比例大于等于50%,则触发熔断
- 熔断持续时长20s
修改item-service的查询商品信息接口,添加线程休眠模拟处理时间,这样可以制造慢请求的效果。
重启item-service服务
连续请求查询购物车接口,cart-service连续向item-service发生请求,由于每次请求item-service线程休眠1秒导致接口请求时长肯定大于200毫秒,最终发生了熔断。
参考测试“异常数”熔断策略去观察cart-service和item-service的日志来判断熔断发生的情况。
1.2.4.4 测试异常比例(自行测试)
请大家自行测试熔断策略: 异常比例
举例:
- 统计最近3000ms内的最少2次请求,如果异常比例不低于0.5,则触发熔断
- 熔断持续时长20s
1.2.4.5 面试题
你的项目中熔断降级怎么实现的?
1.3.请求限流
这里马上有同学可能会困惑:Sentinel的降级和下面这个限流,好像很相似,那具体有什么区别呢?
降级:常和熔断搭配使用,统称熔断降级。当触发熔断后走降级逻辑,被降级的请求走默认值。确保核心可用
限流:超过阈值的请求,进行排队等待或直接拒接,没有默认值一说。防止系统过载
1.3.1 配置限流策略
下边我们对item-service的商品查询接口进行限流,找到商品查询的簇点链路,点击"流控"
在弹出的菜单中填写QPS单机阈值为5,表示每秒最多处理5个请求
AI:什么是QPS
QPS 是 "Queries Per Second" 的缩写,中文通常称为“每秒查询率”或“每秒请求数”。它是一个衡量系统性能的重要指标,特别是在评估 Web 服务器、数据库系统、API 接口等的负载能力时经常使用。
QPS 表示系统在一秒钟内能够处理的查询或请求的数量。这个指标可以帮助开发者和运维人员了解系统的处理能力和性能瓶颈。例如,如果一个 Web 服务的 QPS 为 100,则意味着该服务在一秒钟内最多可以处理 100 个请求。
你的主打项目必须要提前准备好:QPS、TPS、RT、下载量、日活、最大数据量的表是什么
1.3.2 限流测试
首先去掉item-service服务中查询商品信息接口的休眠代码。然后启动item服务
下边我们用jmeter压力测试工具进行压力测试:Jmeter快速入门
JMeter 是一个广泛使用的开源性能测试工具,主要用于测试 Web 应用程序的负载和性能。它是由 Apache Software Foundation 开发和维护的,完全用 Java 编写,因此可以在任何支持 Java 的平台上运行。
找到课程资料中的JMeter安装包apache-jmeter-5.4.1.zip,解压到非中文的目录,进入bin下双击打开jmeter.bat
打开软件切换到中文
接下来找到课程资料下的“雪崩测试.jmx” 拖到JMeter面板中
最终效果如下(如有不同,及时修改)
设置模拟线程数等参数:
表示:总共发送1000个请求,用100秒发送,循环次数是1即执行10秒结束。
根据设置参数可知,每秒发送10个秒在100秒可完成1000个请求。
在“HTTP请求”界面设置压力测试请求的地址:确保跟你本地端口一致
右键“限流测试”点击“启动”
测试结果可以在“汇总报告”中查看,每次请求的次数在“察看结果树”中查看。
从吞吐量可以看出每秒发送10个请求,异常数是0
通过sentinel的实时监控界面可以看出对商品查询接口的请求,通过的QPS为5,拒绝QPS为5。
因为上边我们配置了对商品查询接口限流QPS为5,所以每秒10个请求查询/carts,远程调用商品查询服务接口的QPS只有5,说明被限流了。
注意:如果实时监控图无法正常显示需要进入虚拟机进行时间同步。
ntpdate ntp1.aliyun.com 如果无法执行上边的命令需要下载ntpdate yum install ntpdate -y
此时,需要耐心等待一下后重试,就可以看到有结果了:通过只有5。
如果你还是没有,也可以调整Jmeter参数,把1000,100改成:100,10,循环次数:永远
再查看每次的请求和响应数据发现,由于编写了商品查询接口远程调用的降级逻辑,当商品查询接口被限流后将会走降级方法,从jmeter测试结果可以看到被限流的请求无法获取商品信息
再查看cart-service的控制台,在降级方法中捕获到了 com.alibaba.csp.sentinel.slots.block.flow.FlowException 异常,这说明接口被限流后sentinel客户端会抛出FlowException 异常。
查看Jmeter也可以看到大量拒绝信息
1.3.3 小节
sentinel限流怎么实现?
我们项目使用Sentinel实现限流控制。
- 首先在服务提供方配置sentinel,引入sentinel的依赖,配置sentinel的地址
- 通过Sentinel控制台配置限流策略,可以配置QPS阈值、并发线程数等。
- 当方法被限流会走直接拒绝。
1.4. sentinel降级
上面讲的是A--Feign/Dubbo->B服务时候,对B的做熔断、限流,可如果我只想对A本身做限流怎么实现?
1.4.1 问题描述
下边我们对/carts接口进行限流测试,找到GET/carts簇点
点击“流控” 设置QPS 单机阈值为6
截止目前,我们一共设置了两个流控规则
用jmeter测试,通过sentinel进行实时监控,通过QPS为6,拒绝QPS为4,符合我们的预期结果
当/carts接口被限流时我们访问此接口(需要在Jmeter压测期间测试),需要连续多点击几次
结果内容:Blocked by Sentinel (flow limiting),从字面内容看是被sentinel限流。
为什么访问item-service的商品查询接口走了降级方法,而访问/carts没有走降级方法呢?
1.4.2 sentinel实现降级
这里我们需要重新梳理下:
前边cart-service远程调用item-service服务的商品查询接口正常走降级方法,这是因为我们编写了ItemClient接口的降级类ItemClientFallbackFactory,并且我们在cart-service服务中集成了sentinel,在cart-service的application.yml配置文件中配置了feign使用sentinel,如下:
feign: sentinel: enabled: true # 开启feign对sentinel的支持
也就说,在cart-service通过openfeign远程调用item-service时通过sentinel进行降级,最终执行ItemClientFallbackFactory类中指定的降级方法。这里我们是对/carts进行限流,并没有针对此接口编写降级方法。
AI:sentinel实现降级
针对feign远程调用我们通过实现FallbackFactory接口去编写远程调用接口对应的降级逻辑,而针对非feign远程调用的降级逻辑我们需要使用@SentinelResource注解去实现
首先使用 @SentinelResource 定义资源,编写降级方法
@ApiOperation("查询购物车列表") @SentinelResource(value = "queryMyCarts", fallback = "queryMyCartsFallback", blockHandler = "queryMyCartsBlockHandler") @GetMapping public List<CartVO> queryMyCarts(){ return cartService.queryMyCarts(); } //当发生非限流非熔断异常走此方法 public List<CartVO> queryMyCartsFallback(Throwable throwable){ log.error("非限流、非熔断异常执行的降级方法,throwable:", throwable); return new ArrayList<>(); } //当发生熔断、限流走此方法 public List<CartVO> queryMyCartsBlockHandler( BlockException blockException){ log.error("触发限流、熔断时执行的降级方法,blockException:", blockException); return new ArrayList<>(); }
然后在sentinel中设置资源的流控
删除针对/carts设置的流控规则
再次使用jmeter进行压力测试
观察cart-service控制台,当/carts限流后正常执行降级方法,日志如下:
18:37:31:767 ERROR 4696 --- [nio-8082-exec-9] c.hmall.cart.controller.CartController : 触发限流、熔断时执行的降级方法,blockException:
在降级方法中可以返回特殊的信息,或指定特殊的状态码,根据特殊值前端可展示为类似“网络忙请稍后重试” 这样的信息。此时限流期间访问购物车接口,发现就走了降级逻辑,返回空集合:
1.4.3 小节
sentienl降级怎么实现?
对于Feign远程调用对每个远程调用接口实现降级方法,通过实现FallbackFactory接口实现降级方法。
对于非Feign远程调用我们使用@SentinelResource注解编写自定义降级方法。
1.5. 线程隔离
1.5.1 方案介绍
首先我们来看下线程隔离功能,无论是Hystix还是Sentinel都支持线程隔离。不过其实现方式不同。
线程隔离有两种方式实现:
- 线程池隔离:给每个服务调用业务分配一个线程池,利用线程池本身实现隔离效果
- 信号量隔离:不创建线程池,而是计数器模式,记录业务使用的线程数量,达到信号量上限时,禁止新的请求
如图:
Sentinel的线程隔离就是基于信号量隔离实现的,而Hystix两种都支持,但默认是基于线程池隔离。
1.5.2 Sentinel线程隔离(练习)
下边我们使用Sentinel实现基于信号量的线程隔离。
点击查询商品的FeignClient对应的簇点资源后面的流控按钮:
在弹出的表单中填写下面内容:
注意,这里勾选的是并发线程数限制,也就是说这个查询功能最多使用5个线程,而不是5QPS。如果查询商品的接口每秒处理2个请求,则5个线程的实际QPS在10左右,而超出的请求自然会被拒绝。
cart-service调用商品查询控制在5个线程内,通过线程隔离即使商品服务出现问题也不会影响cart-service服务。
下边修改商品查询接口的代码,添加休眠500毫秒的代码,模拟一次请求需要500毫秒,一秒则可处理2次请求,5个线程可接收10 QPS左右。
将/carts的限流规则调大以免影响商品查询接口的限流控制。
下边使用jmeter进行测试,更改线程数为10000,时间为100秒,每秒需要发送100个请求。
启用测试,通过sentinel实时监控,符合我们的预期。
不知道细心的你是否发现了一个事情:我们设置的Sentinel通过数量,为什么不那么精准呢?
- 窗口边界:一个请求可能刚好跨越两个窗口,导致单个窗口的统计值略高于设定值。
- 滑动窗口精度:滑动窗口的划分粒度可能无法完全精确,尤其在请求密集时。
1.6 面试题
你们是怎么做微服务保护的?
Sentinel实现熔断、限流的底层原理是什么?(在面试篇进行详细讲解)