限流:统计和限制访问次数
熔断:服务出错或响应过慢时,直接返回错误信息,或者返回历史数据、默认数据等。
降级:干掉次要功能,保留主要功能
sentinel详细文档
https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
istio文档
https://istio.io/latest/docs/tasks/traffic-management/circuit-breaking/
sentinel vs hystrix
发展前景
Netflix已经宣布对Hystrix停止更新。
sentinel在18年开源了,在不断的发展,并且进入serviceMesh和云原生方向挺近。
功能上:
整体上sentinel功能更强
参考:https://sentinelguard.io/zh-cn/blog/sentinel-vs-hystrix.html
sentinel使用(springboot项目引入sentinel),方法层面的降级
pom.xml增加如下内容:
.....
.....
增加支持注解(@SentinelResource)的配置
@Configuration
public class DegradeAopConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
SentinelResourceAspect原理解析:
https://www.jianshu.com/p/2d1d5f886871
配置降级策略:
@Slf4j
@Configuration
public class DegradeCourseConfig {
public static final String DEGRADE_STU_KEY = "DEGRADE-STUDENT";
public static final String DEGRADE_TEA_KEY = "DEGRADE-TEACHER";
@PostConstruct
private void initDegradeRule() {
this.initRule();
}
private void initRule() {
List
DegradeRule stuRule = new DegradeRule(DEGRADE_STU_KEY)
// 错误次数
.setCount(1)
// 熔断时间窗口
.setTimeWindow(30)
// 错误统计时长为60s的窗口
.setStatIntervalMs(60000)
// 熔断的三种方式 错误次数
.setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType());
// DegradeRule stuRule = new DegradeRule(DEGRADE_STU_KEY)
// .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
// // 慢调用临界RT为5ms
// .setCount(5)
// // 触发熔断10s
// .setTimeWindow(10)
// // 慢比例请求 > 30%
// .setSlowRatioThreshold(0.3)
// // 触发熔断的最小请求数为2次请求
// .setMinRequestAmount(2)
// // 统计时长为60s的窗口
// .setStatIntervalMs(60000);
rules.add(stuRule);
rules.add(teaRule);
DegradeRuleManager.loadRules(rules);
log.info("course biz init degrade rule finished, the default rules are:[{}]", rules);
registerStateChangeObserver();
}
/**
* 断路器的状态打印
*/
private void registerStateChangeObserver() {
EventObserverRegistry.getInstance().addStateChangeObserver("logging",
(prevState, newState, rule, snapshotValue) -> {
if (newState == CircuitBreaker.State.OPEN) {
log.info(String.format("%s -> OPEN at %d, snapshotValue=%.2f", prevState.name(),
TimeUtil.currentTimeMillis(), snapshotValue));
} else {
log.info(String.format("%s -> %s at %d", prevState.name(), newState.name(),
TimeUtil.currentTimeMillis()));
}
});
}
}
业务代码中使用
private static AtomicInteger atomicInteger = new AtomicInteger(1);
/**
* 通过熔断提高方法的可用性,当业务系统rpc服务在单位时间内返回状态码不为SUCCESS_CODE的次数超过阀值
* 对此方法进行熔断,熔断后会获取用户最近一次保存在缓存中的数据
* blockHandler中指定的方法是,在服务触发熔断后调用的方法名称,有几个要求:
* 1.该方法不允许使用private进行修饰(官方要求,因为使用cglib进行动态代理。cglib基于继承)
* 2.该方法的参数和SentinelResource注解所在方式参数必须完全一致,且增加BlockException
*
*/
@SentinelResource(value = DegradeCourseConfig.DEGRADE_TEA_KEY, blockHandler = "getDegradeTeacherClasses")
@Cached(name = "XES-CourseBiz.findClassByTeacher", key = "#key", expire = 1, timeUnit = TimeUnit.MINUTES)
public GroupedClassByTeacherDto findClassByTeacher(ClassByTeacherParamDto param, String key) {
try {
entry = SphU.entry(DegradeCourseConfig.DEGRADE_TEA_KEY);
// 业务代码,以及异常可能产生的地方
// 降级数据缓存
this.cacheDegradeTeacherClasses(param, dto);
} catch (Exception exception) {
//blockException熔断异常以及CodingCloudException不进入异常的统计,
//blockException是触发熔断后的异常,CodingCloudException是业务异常
if (!BlockException.isBlockException(exception) &&
!(exception instanceof CodingCloudException)) {
Tracer.traceEntry(exception, entry);
}
log.error("教师端查询班级失败",exception);
// 后来发现,改行代码会导致熔断器无法关闭,无语
throw new RuntimeException("教师端查询班级异常。。。");
} finally {
if (Objects.nonNull(entry)) {
entry.exit();
}
}
return dto;
}
/**
* 降级数据存储,注意:
* 1.该方法不允许使用private进行修饰(官方要求,因为使用cglib进行动态代理。cglib基于继承)
* 2.该方法的参数和SentinelResource注解所在方式参数必须完全一致,且增加BlockException
*/
public void cacheDegradeTeacherClasses(ClassByTeacherParamDto param, GroupedClassByTeacherDto dto) {
redisTemplate.opsForValue().set(param.toDegradeString() + "cacheDegradeTeacherClasses", JSON.toJSONString(dto), 2, TimeUnit.DAYS);
}
/**
* 降级数据获取
*/
public GroupedClassByTeacherDto getDegradeTeacherClasses(ClassByTeacherParamDto param,
String key,
BlockException blockException) {
String str = redisTemplate.opsForValue().get(param.toDegradeString() + "cacheDegradeTeacherClasses");
if (org.apache.commons.lang3.StringUtils.isEmpty(str)) {
return new GroupedClassByTeacherDto();
} else {
GroupedClassByTeacherDto dto = JSON.parseObject(str, GroupedClassByTeacherDto.class);
return dto;
}
}
sentinel使用的坑,
1: rule错误配置,导致sentinel不生效
2: blockHandler书写错误,所有参数+blockException
3: 测试问题。异常一定在如下代码之后产生:entry = SphU.entry(DegradeCourseConfig.DEGRADE_STU_KEY);
4: 正常逻辑应该是tracer之后仍旧抛出运行时异常
sentinel和istio中的服务限流是什么关系?
都可以实现限流和熔断,区别是:
sentinel和hytrix 代码有侵入性,并且附着与服务进程,占用了业务资源。但是好处是可以控制到具体的方法,fall back可以选择:单一的默认值、缓存或者去调用其他服务。
istio则是为服务创建爱你对应的代理(sidecar),主动劫持服务的入和出口流量;更加独立、不侵入代码,但是粒度比较粗:只能针对整个java服务配置连接池和实例驱逐策略。生产 channel的熔断规则入下:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: coding-svc-dr
namespace: pei-cucode
spec:
host: coding-test-svc
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 512
http2MaxRequests: 512
idleTimeout: 360s
maxRequestsPerConnection: 10
maxRetries: 2
tcp:
connectTimeout: 200ms
maxConnections: 1024
tcpKeepalive:
interval: 75s
probes: 3
time: 3600s
outlierDetection:
baseEjectionTime: 30s
consecutiveErrors: 5
interval: 10s
minHealthPercent: 60
https://cloud.tencent.com/developer/article/1475923
sentinel是否有必要启用dashboard等用于生产?
没有必要,sentinel目前的引入已经满足要求,如果有需要的继续手动添加。
然后接口层面的限流和熔断使用istio
=========================================================
add on 21年11月29日
场景描述:
业务系统带宽打满,导致很多rpc接口调用失败,然后就出现了问题。
2小时后,业务系统老师反馈已正常,但是使用了熔断的接口还是继续报错,即熔断器开启后,虽然外部服务已经ok,但是一直未关闭。日志如下图:
最终发现代码有问题,如下:
@SentinelResource(value = DegradeCourseConfig.DEGRADE_TEA_KEY, blockHandler = "getDegraderClasses")
@Cached(name = "CourseBiz.findClassByTeacher", key = "#key", expire = 1, timeUnit = TimeUnit.MINUTES)
public Dto findByTeacher(ParamDto param, String key) {
Entry entry = null;
GroupedClassByTeacherDto dto = null;
try {
entry = SphU.entry(DegradeCourseConfig.DEGRADE_TEA_KEY);
// 业务代码
.......
// 降级数据缓存
this.cacheDegradeTeacherClasses(param, dto);
} catch (Exception exception) {
//blockException熔断异常以及CodingCloudException不进入异常的统计,
//blockException是触发熔断后的异常,CodingCloudException是业务异常
if (!BlockException.isBlockException(exception) &&
!(exception instanceof CodingCloudException)) {
Tracer.traceEntry(exception, entry);
}
log.error("查询失败",exception);
// 该行代码,导致严重的异常与错误。。。
throw new RuntimeException("查询异常。。。");
} finally {
if (Objects.nonNull(entry)) {
entry.exit();
}
}
return dto;
}
本质原因是, throw new RuntimeException("查询异常。。。");,会导致半关闭的时候,每次执行
entry = SphU.entry(DegradeCourseConfig.DEGRADE_TEA_KEY);
都会抛出degradeException,然后就会被catch,然后重新抛出runtimeException,然后就一直抛出,一直异常,所以不会关闭熔断器。
解决方案:去掉上面的22行代码