Spring Cloud 核心组件——熔断降级
今天先来说说“服务熔断”和“服务降级”。
服务熔断:在股票市场,熔断这个词大家都不陌生,是指当股指波幅达到某个点后,交易所为控制风险采取的暂停交易措施。相应的,服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。
服务降级:大家都见过女生旅行吧,大号的旅行箱是必备物,平常走走近处绰绰有余,但一旦出个远门,再大的箱子都白搭了,怎么办呢?常见的情景就是把物品拿出来分分堆,比了又比,最后一些非必需品的就忍痛放下了,等到下次箱子够用了,再带上用一用。而服务降级,就是这么回事,整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来。
所以从上述分析来看,两者其实从有些角度看是有一定的类似性的:
1)目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
2)最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
3)粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
4)自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;
而两者的区别也是明显的:
1)触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
2)管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
3)实现方式不太一样
参考文章:https://blog.csdn.net/guwei9111986/article/details/51649240
下面介绍 Hystrix:
在分布式环境中,许多服务依赖项中的一些必然会失败。Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点、停止级联失败和提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。
Hystrix 提供了熔断、隔离、Fallback、Cache、监控等功能。出现错误之后可以 fallback 错误的处理信息,返回一些兜底数据等等。
https://www.cnblogs.com/cjsblog/p/9391819.html
本文的示例承接上一篇文章:https://www.cnblogs.com/jwen1994/p/11408511.html
1. Feign 结合 Hystrix 断路器开发
第一步:加入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
注意新旧版本问题,所以要以官网为主,不然部分注解会丢失
第二步:启动类里面增加注解 @EnableCircuitBreaker
@SpringBootApplication @EnableFeignClients @EnableCircuitBreaker public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
我们也可以使用 SpringCloudApplication 注解,它包含了很多 Spring Cloud 相关的注解
第三步:最外层 api 使用
api 方法上增加 @HystrixCommand(fallbackMethod = "saveOrderFail")。好比异常处理(网络异常,参数或者内部调用问题)
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private ProductOrderService productOrderService; @RequestMapping("save") @HystrixCommand(fallbackMethod = "saveOrderFail") public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id") int productId){ Map<String, Object> data = new HashMap<>(); data.put("code", 0); data.put("data", productOrderService.save(userId, productId)); return data; } //注意,方法签名一定要要和api方法一致 private Object saveOrderFail(int userId, int productId){ Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试"); return msg; } }
注意:编写 fallback 方法实现,方法签名一定要和 api 方法签名一致
第四步:进行测试,我们使用一个会出错的请求,它会返回 saveOrderFail 的返回值
2. 我们调用服务时,如果服务出错,我们希望可以进行一些处理
第一步:开启 Feign 支持 Hystrix (注意,一定要开启,旧版本默认支持,新版本默认关闭)
feign:
hystrix:
enabled: true
第二步:FeignClient(name="xxx", fallback=xxx.class ),class 需要继承当前 FeignClient 的类
@FeignClient(name = "product-service", fallback = ProductClientFallback.class) public interface ProductClient { @GetMapping("/api/v1/product/find") String findById(@RequestParam(value = "id") int id); }
ProductClientFallback 类
@Component public class ProductClientFallback implements ProductClient { @Override public String findById(int id) { System.out.println("feign 调用product-service findbyid 异常"); return null; } }
3. 进一步完善异常报警通知
我们可以试着加入 Redis 来实现一个异常报警
第一步:加入 Redis 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
第二步:配置 Redis 链接信息
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000
第三步:修改代码
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private ProductOrderService productOrderService; @Autowired private StringRedisTemplate redisTemplate; @RequestMapping("save") @HystrixCommand(fallbackMethod = "saveOrderFail") public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id") int productId, HttpServletRequest request){ Map<String, Object> data = new HashMap<>(); data.put("code", 0); data.put("data", productOrderService.save(userId, productId)); return data; } //注意,方法签名一定要要和api方法一致 private Object saveOrderFail(int userId, int productId, HttpServletRequest request){ //监控报警 String saveOrderKye = "save-order"; String sendValue = redisTemplate.opsForValue().get(saveOrderKye); final String ip = request.getRemoteAddr(); new Thread( ()->{ if (StringUtils.isBlank(sendValue)) { System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip); //发送一个http请求,调用短信服务 TODO redisTemplate.opsForValue().set(saveOrderKye, "save-order-fail", 20, TimeUnit.SECONDS); }else{ System.out.println("已经发送过短信,20秒内不重复发送"); } }).start(); Map<String, Object> msg = new HashMap<>(); msg.put("code", -1); msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试"); return msg; } }
4. Hystrix 降级策略和调整
1)查看默认讲解策略 HystrixCommandProperties
这个文件里可以看到所有默认的策略
2)execution.isolation.strategy 隔离策略
Hystrix 有两种隔离策略:THREAD 线程池隔离 (默认)、SEMAPHORE 信号量。信号量适用于接口并发量高的情况,如每秒数千次调用的情况,导致的线程开销过高,通常只适用于非网络调用,执行速度快
3)execution.isolation.thread.timeoutInMilliseconds 超时时间
Hystrix 默认超时时间为1000毫秒
4)execution.timeout.enabled 是否开启超时限制 (一定不要禁用)
Hystrix 默认是开启超时限制的
5)execution.isolation.semaphore.maxConcurrentRequests 隔离策略为信号量的时候,如果达到最大并发数时,后续请求会被拒绝,默认是10
#把hystrix超时时间禁用
#hystrix:
# command:
# default:
# execution:
# timeout:
# enabled: false
#execution.isolation.thread.timeoutInMilliseconds=4000
#设置超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
官方文档:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
5. 断路器 Dashboard 监控仪表盘
生产环境几乎不用,只要做好异常告警就可以
第一步:加入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
第二步:启动类增加注解 @EnableHystrixDashboard
第三步:配置文件增加 endpoint
#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
第四步:访问入口
1)访问:http://localhost:8781/hystrix
2)Hystrix Dashboard 输入: http://localhost:8781/actuator/hystrix.stream
补充: 如果从 Maven 中心仓库下载太慢,可是修改 Maven 仓库地址,使用其他 Maven 仓库。为了使用阿里云的仓库,我们在 pom.xml 中修改
<repositories> <repository> <id>nexus-aliyun</id> <name>Nexus aliyun</name> <layout>default</layout> <url>http://maven.aliyun.com/nexus/content/groups/public</url> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> </releases> </repository> </repositories>