前言:何为熔断?
在我们进行系统设计时,必须要考虑系统的高性能、高并发、高可用。高可用中有三大利器:熔断、限流、降级。在服务的依赖调用中,被调用方出现故障时,出于自我保护的目的,调用方会主动停止调用,并根据业务需要进行相应处理。调用方这种主动停止调用的行为我们称之为熔断。
为什么要熔断 ?
假定服务A依赖服务B,当服务B处于正常状态,整个调用是健康的,服务A可以得到服务B的正常响应。当服务B出现故障时,比如响应缓慢或者响应超时,如果服务A继续请求服务B,那么服务A的响应时间也会增加,进而导致服务A响应缓慢。如果服务A不进行熔断处理,服务B的故障会传导至服务A,最终导致服务A也不可用。
本文将介绍目前市面上比较常见的开源熔断组件!!!
hystrix
1、简单使用
引入依赖
版本说明:sprinboot :2.3.2.RELEASE
springboot:2.6.7版本需要spring-cloud-starter-**,需要3.3.#版本以上的,但是spring-cloud-starter-## 3.1.#版本以上已经将hystrix移除,需要单独引入
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
启动类注解
@EnableHystrix//开启hystrix
@SpringBootApplication
public class FusingdemoApplication {
public static void main(String[] args) {
SpringApplication.run(FusingdemoApplication.class, args);
}
}
服务降级
//单一降级方法
@RequestMapping("/hystrix")
@HystrixCommand(fallbackMethod = "errorMethod")
public String test(@RequestParam("data") String data){
if (!data.equals("nykj")){
throw new RuntimeException("运行时异常");
}
logger.info("正常执行 data:{}",data);
return "ok";
}
//发生异常或者执行时间超过指定超时时间会执行降级方法
public String errorMethod(String data){
logger.info("降级执行,errorMethod data:{}",data);
return "error";
}
2、使用fallbackFactory
结合openfeign使用,定义异常回调类
引入openfeign
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.10.RELEASE</version>
</dependency>
启动类添加@EnableFeignClients注解,启动openfeign
定义feign接口
@Repository
@FeignClient(fallbackFactory = HystrixFallBack.class,url = "localhost:8080",name = "feignTest")
public interface FeignTest {
@RequestMapping("/src")
public String test();
}
//fallbackFactory降级回调处理类
回调处理类
@Component
public class HystrixFallBack implements FallbackFactory<FeignTest> {
private Logger logger = LoggerFactory.getLogger(HystrixFallBack.class);
@Override
public FeignTest create(Throwable throwable) {
return new FeignTest() {
@Override
public String test() {
logger.error("FeignTest test接口调用异常,异常信息:{}",throwable.getMessage());
logger.info("hystrixFallBack FeignTest");
return "hystrixFallBack FeignTest";
}
};
}
}
添加配置
feign.hystrix.enabled=true
控制层调用
@RequestMapping("/test02")
public String test02() throws InterruptedException {
String test = feignTest.test();
logger.info("test02 feignTest.test result,{}",test);
return test;
}
3、熔断
启动类添加注解
@EnableFeignClients
@EnableCircuitBreaker//开启熔断
@EnableHystrix
@SpringBootApplication
public class FusingdemoApplication {
public static void main(String[] args) {
SpringApplication.run(FusingdemoApplication.class, args);
}
}
示例
说明:当断路器断开后,每五秒【默认五秒】会接收一次正常请求,如果响应成功,则闭合断路器,失败则重新计时
4、配置信息
//设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
execution.isolation.strategy= "THREAD"
//当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
execution.isolation.semaphore.maxConcurrentRequests= "10"
//配置命令执行的超时时间
execution.isolation.thread.timeoutinMilliseconds= "10"
//是否启用超时时间
execution.timeout.enabled= "true"
//执行超时的时候是否中断
execution.isolation.thread.interruptOnTimeout= "true"
//执行被取消的时候是否中断
execution.isolation.thread.interruptOnCancel= "true"
//允许回调方法执行的最大并发数
fallback.isolation.semaphore.maxConcurrentRequests= "10"
//服务降级是否启用,是否执行回调函数
fallback.enabled= "true"
//是否启用断路器
circuitBreaker.enabled= "true"
//该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
//如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
circuitBreaker.requestVolumeThreshold= "20"
//该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
//就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
circuitBreaker.errorThresholdPercentage= "50"
//该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
//会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
//如果成功就设置为 "关闭" 状态。
circuitBreaker.sleepWindowinMilliseconds= "5000"
//断路器强制打开
circuitBreaker.forceOpen= "false"
//断路器强制关闭
circuitBreaker.forceClosed= "false"
//滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
metrics.rollingStats.timeinMilliseconds= "10000"
//该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
//设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
//比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
metrics.rollingStats.numBuckets= "10"
//该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
metrics.rollingPercentile.enabled= "false"
//该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
metrics.rollingPercentile.timeInMilliseconds= "60000"
//该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
metrics.rollingPercentile.numBuckets= "60000"
//该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
//就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
//那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
metrics.rollingPercentile.bucketSize= "100"
//该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
metrics.healthSnapshot.intervalinMilliseconds= "500"
//是否开启请求缓存
requestCache.enabled= "true"
HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
requestLog.enabled= "true"
官网:
https://github.com/Netflix/Hystrix/wiki/How-To-Use
文档查阅:
http://t.zoukankan.com/xiaohanlin-p-8012561.html https://www.cnblogs.com/ming-blogs/p/14596721.html
sentinel:
sentinel工作:
在项目创建资源时,会创建一系列的功能插槽【slot chain】,这些slot chain大致分为两种类型【数据构建部分、判断部分】
基础slot:
-
NodeSelectorSlot: 建立树状结构(调用链路) -
ClusterBuilderSlot: 根据资源保存统计簇点 -
StatisticSlot: 实时数据统计 -
FlowSlot: 流量控制 -
DegradeSlot: 熔断降级 -
SystemSlot: 系统负载保护
注意: Sentinel 仅支持 JDK 1.8 或者以上版本。
1、sentinel简单使用
springboot 版本:2.3.2.RELEASE
spring-cloud-starter-alibaba-sentinel:2.2.1.RELEASE
1、引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
2、配置文件
spring.application.name=fusingdemo #指定服务名,sentinel-dashboard上根据该配置进行显示
spring.cloud.sentinel.transport.dashboard=localhost:8080 #接入的sentinel-dashboard地址
spring.cloud.sentinel.transport.port=8179 #该端口用于与sentinel-dashboard沟通
3、使用SentinelResource注解
@SentinelResource(value = "test02",fallback = "errorMethod",blockHandler = "blockFallback")
@RequestMapping("test02")
public String test02(@RequestParam("data") String data) throws InterruptedException {
String s = sentinelService.test02();
Thread.sleep(110);
System.out.println(s);
return s;
}
//fallback方法要求入参与接口一直,或者可以加多一个Throuwable,降级处理方法可以在其他类中定义
public String errorMethod(String data,Throwable throwable){
logger.error("触发降级处理 errorMethod");
logger.error(throwable.getMessage());
return "触发降级处理 errorMethod";
}
//blockHandler方法要求入参与接口一直,或者可以加多一个BlockException参数
//流通触发的降级会优先进入blockHandler方法,如果没有则进入fallback方法
public String blockFallback(String data, BlockException ex){
logger.error("触发流控限流处理,blockFallback");
ex.printStackTrace();
return "触发流控限流处理,blockFallback";
}
4、外置类fallbackClass使用
【降级方法与业务代码分离开】
@SentinelResource(value = “test03”,fallback = “test03fallBack”,blockHandler = “test03blockFallback”,
fallbackClass = SentinelFallBackDemo.class,blockHandlerClass = SentinelFallBackDemo.class)
sentinelFallBackDemo
//外置类定义的降级方法需要时静态方法
public static String test03fallBack(Throwable throwable){
logger.error("触发降级处理 test03fallBack");
logger.error(throwable.getMessage());
return "触发降级处理 test03fallBack";
}
public String test03blockFallback(BlockException ex){
logger.error("触发流控限流处理,test03blockFallback");
ex.printStackTrace();
return "触发流控限流处理,test03blockFallback";
}
2、sentinel配合zk使用
使用zk作为配置规则的持久化数据源,需要对sentinel-dashboard进行二次开发,默认情况下sentinel-dashboard将数据保存到内存,以端口的方式【spring.cloud.sentinel.transport.port=8179】与连接端实现降级规则的同步
1、接入方配置zookeeper
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@Component
public class ZookeeperSentinelConfig {
@Value("${spring.application.name}")
private String appName;
@Value("${zookeeper.curator.serverUrl}")
private String zk;
@PostConstruct
public void loadRules() {
//流控规则数据源
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(zk, "/sentinel_rule_config" + "/" + appName + "/" + FlowRule.class.getSimpleName(),
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
//不同规则之间数据结构可能不一致,如果使用流控规则相同的方式,在获取数据时会获取失败
ReadableDataSource<String, List<ParamFlowRule>> paramFlowDataSource = new ZookeeperDataSource<>(zk, "/sentinel_rule_config" + "/" + appName + "/" + ParamFlowRule.class.getSimpleName(),
new Converter<String, List<ParamFlowRule>>() {
@Override
public List<ParamFlowRule> convert(String source) {
if(source!=null && !"".equals(source.trim()) && !"[]".equals(source)){
List<ParamFlowRule> list = new ArrayList<>();
JSONArray array = JSON.parseArray(source);
for (int i=0;i<array.size();i++) {
JSONObject obj = array.getJSONObject(i);
JSONObject rule = obj.getJSONObject("rule");
ParamFlowRule r = JSON.toJavaObject(rule, ParamFlowRule.class);
list.add(r);
}
return list;
}
return null;
}
});
ParamFlowRuleManager.register2Property(paramFlowDataSource.getProperty());
//熔断规则数据源
ReadableDataSource<String, List<DegradeRule>> degradeRuleManagerDataSource = new ZookeeperDataSource<>(zk, "/sentinel_rule_config" + "/" + appName + "/" + DegradeRule.class.getSimpleName(),
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
DegradeRuleManager.register2Property(degradeRuleManagerDataSource.getProperty());
}
}
3、sentinel-dashboard
1、流控
2、熔断
3、热点
4、授权
官网文档:https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B 文档查阅: https://blog.csdn.net/qq_45932374/article/details/122122786 https://cloud.tencent.com/developer/article/1456521
写在最后
当然啦,登山的路有很多,这只是其中一条。如果以后我能找到更好的方案,也会再share一下。
原文始发于微信公众号(Hephaestuses):#微服务 一些常见的熔断组件
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/45012.html