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 面试题
你的项目中熔断降级怎么实现的?