sentinel、isitio、hystrix 限流熔断降级

限流:统计和限制访问次数
熔断:服务出错或响应过慢时,直接返回错误信息,或者返回历史数据、默认数据等。
降级:干掉次要功能,保留主要功能

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增加如下内容:
   
        .....
        coding-cloud-degrade
   

   
        .....
        1.8.0
   

           
           
                com.alibaba.csp
                sentinel-core
                ${sentinel.version}
           

           
                com.alibaba.csp
                sentinel-annotation-aspectj
                ${sentinel.version}
           

增加支持注解(@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 rules = Lists.newArrayListWithCapacity(3);

        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行代码

你可能感兴趣的:(istio,性能诊断与调优,流量治理工具,istio,sentinel,熔断降级)