微服务之降级

  |   0 评论   |   0 浏览

一、降级

背景

在系统运行时,为了保证核心服务能正常提供服务,不至于因为一些非核心功能而阻塞服务,需要 对服务进行分级 。当非核心服务影响到核心服务时,能通过配置或者其他手段快速切断非核心服务从而保证核心服务能正常对用户提供服务。
如何切断非核心服务呢?常用的方法有 限流熔断降级 ,市面上也有很多的组件能提供相应的功能,这些组件都提供了很多强大的功能,但引入这些开源组件的同时也会带来一些复杂的配置以及学习成本,另外公司微服务是dubbo构建的,引入spring-cloud的一些组件会比较复杂。
基于此,我们决定自研一个降级组件,集成到公司的各个服务里面,提供最基础的 降级服务

服务故障的场景:

服务故障分为接口级故障系统级故障
系统级的故障 :例如,机器宕机、机房故障、网络故障等问题,这些系统级的故障虽然影响很大,但发生概率较小。
接口级故障 :在实际业务运行过程中,该故障影响可能没有系统级那么大,但发生的概率较高
接口级故障的典型表现就是系统并没有宕机,网络也没有中断,但业务却出现问题了。
例如, 业务响应缓慢、大量访问超时、大量访问出现异常 ,这类问题的主要原因在于 系统压力太大、负载太高,导致无法快速处理业务请求 ,由此引发更多的后续问题。
例如,最常见的 数据库慢查询将数据库的服务器资源耗尽 ,导致读写超时,业务读写数据库时要么无法连接数据库、要么超时,最终用户看到的现象就是访问很慢,一会访问抛出异常,一会访问又是正常结果。接口故障如果处理不及时,严重的时候甚至会引起系统级故障。如数据库慢查询导致数据库cpu升高,查询的服务短时间内频繁fullgc,并因此形成连锁反应,牵一发而动全身,依赖该该服务的其他服务全都不可用,蝴蝶效应引起核心服务的不可用

故障应对策略

优先保证核心业务和优先保证绝大部分用户

降级

降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。 降级的核心思想就是丢车保帅,优先保证核心业务。 例如,对于教育类App学习主链路是核心服务,其他的各种礼品活动弹窗,老师点评服务等如果出问题后不应该影响主学习链路,这时可以停掉这些非核心服务。常见的实现降级的方式有:

  • 系统后门(配置)降级

为每一个可降级服务提供一个业务开关配置,在业务出现故障后通过切换业务开关配置进行手动降级,但主要缺点是如果服务器数量多,需要一台一台去操作,效率比较低,这在故障处理争分夺秒的场景下是比较浪费时间的。

  • 独立降级系统

为了解决系统后门降级方式的缺点,将降级操作独立到一个单独的系统中,可以实现复杂的权限管理、批量操作等功能,但引入独立系统运维,集成等复杂度会相应提高 Hystrix,sentinel等都有相应功能实现

熔断

熔断和降级是两个比较容易混淆的概念,因为单纯从名字上看好像都有禁止某个功能的意思,但其实内在含义是不同的,原因在于降级的目的是应对系统自身的故障,而熔断的目的是应对依赖的外部系统故障的情况。

假设一个这样的场景:A 服务的 X 功能依赖 B 服务的某个接口,当 B 服务的接口响应很慢的时候,A 服务的 X 功能响应肯定也会被拖慢,进一步导致 A 服务的线程都被卡在 X 功能处理上,此时 A 服务的其他功能都会被卡住或者响应非常慢。这时就需要熔断机制了,即:A 服务不再请求 B 服务的这个接口,A 服务内部只要发现是请求 B 服务的这个接口就立即返回错误,从而避免 A 服务整个被拖慢甚至拖死。

  • 熔断机制实现的关键是需要有一个统一的 API 调用层

由 API 调用层来进行采样或者统计,如果接口调用散落在代码各处就没法进行统一处理了。

  • 熔断机制实现的另外一个关键是阈值的设计

例如 1 分钟内 30% 的请求响应时间超过 1 秒就熔断,这个策略中的“1 分钟”“30%”“1 秒”都对最终的熔断效果有影响。实践中一般都是先根据分析确定阈值,然后上线观察效果,再进行调优。

限流

降级是从系统功能优先级的角度考虑如何应对故障,而 限流则是从用户访问压力的角度来考虑如何应对故障 。限流指只允许系统能够承受的访问量进来,超出系统访问能力的请求将被丢弃。根据限流作用范围,可以分为单机限流分布式限流 ;根据限流方式,又分为计数器、滑动窗口、漏桶限令牌桶限流。

限流一般都是系统内实现的,大致可以分为两类:
  • 基于请求限流

基于请求限流指从外部访问的请求角度考虑限流,常见的方式有:限制总量、限制时间量。

  • 基于资源限流

基于请求限流是从系统外部考虑的,而基于资源限流是从系统内部考虑的,即:找到系统内部影响性能的关键资源,对其使用上限进行限制。常见的内部资源有:连接数、文件句柄、线程数、请求队列等。

 基于资源限流相比基于请求限流能够更加有效地反映当前系统的压力,但实践中设计也面临两个主要的难点:如何确定关键资源,如何确定关键资源的阈值。通常情况下,这也是一个逐步调优的过程,即:设计的时候先根据推断选择某个关键资源和阈值,然后测试验证,再上线观察,如果发现不合理,再进行优化。

排队

排队实际上是限流的一个变种,限流是直接拒绝用户,排队是让用户等待一段时间。

最有名的排队当属12306网站排队了。
排队虽然没有直接拒绝用户,但用户等了很长时间后进入系统,体验并不一定比限流好。

由于排队需要临时缓存大量的业务请求,单个系统内部无法缓存这么多数据,一般情况下,排队需要用独立的系统去实现,例如使用 Kafka,RocketMQ这类消息队列来消费用户请求。

starter原理

springBoot starter基于约定大于配置思想,使用spi机制及自动装配原理,可以将一些通用的功能能够封装成一个独立组件并很方便的集成到不同的项目里面,简化开发,提升代码复用能力。简单来讲就是引入了一些相关依赖和一些初始化的配置。

自定义一个降级starter组件

自定义一个starter组件名

spring官方starter通常命名为 spring-boot-starter-{name}
如spring-boot-starter-web
spring官方建议非官方starter命名应遵循 {name}-spring-boot-starter的格式 例如由mybatis提供的mybatis-spring-boot-starter
因此我们自定义的降级组件就叫degrade-spring-boot-starter

            <dependency>
                <groupId>org.degrade.spring.boot</groupId>
                <artifactId>degrade-spring-boot-starter</artifactId>
                <version>${version}</version>
            </dependency>

自动配置类

自动配置类就是Bean实例的工厂,将组件涉及的一些Bean,配置信息交给spring容器管理。目前降级组件定义了4种降级策略

  • 抛出异常
  • 取指定的默认值
  • 调用指定方法
  • 取apollo上配置的值 apollo相关请参考分布式配置中心 Apollo
@Configuration
@ConditionalOnProperty(
        name = {"degrade.enabled"},
        matchIfMissing = true
)
public class DegradeAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public ServiceDegradeAspect createDegradeAspect() {
    //降级切面核心逻辑
        return new ServiceDegradeAspect();
    }
    @Bean(name = "CALL_METHOD")
    @ConditionalOnMissingBean
    public CallMethodHandler createCallMethodHandler(){
    //调用指定方法降级
        return new CallMethodHandler();
    }
    @Bean(name = "DEFAULT_VALUE")
    @ConditionalOnMissingBean
    public DefaultValueHandler createDefaultValueHandler(){
    //取指定的默认值降级
        return new DefaultValueHandler();
    }
    @Bean(name = "FETCH_CONFIG_VALUE")
    @ConditionalOnMissingBean
    public FetchConfigValueHandler createFetchConfigValueHandler(){
    //取apollo上配置的值降级
        return new FetchConfigValueHandler();
    }
    @Bean(name = "THROW_EXCEPTION")
    @ConditionalOnMissingBean
    public ThrowExceptionHandler createThrowExceptionHandler(){
    //抛出异常降级
        return new ThrowExceptionHandler();
    }
    @Bean
    @ConditionalOnMissingBean
    public NullValueProvider createNullValueProvider(){
        return new NullValueProvider();
    }
}

自定义降级注解

降级注解里面标识了需要降级的业务,场景降级后的结果,降级结果就是核心,支持四种策略的配置,所以降级结果的配置也是放在注解里的

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Degrade {
    /**
     * 降级业务key
     */
    String businessKey();
    /**
     * 降级场景key
     */
    String sceneKey() default StringUtils.EMPTY;
    /**
     * 降级后的结果(支持多种降级策略)
     */
    DegradeResult result();


}

降级结果的注解

public @interface DegradeResult {

    /**
     * 支持的降级处理枚举(降级策略)
     */
    DegradeResultEnum resultType();

    /**
     * 从apollo上获取指定值的key,与DegradeResultEnum#FETCH_CONFIG_VALUE 配合使用
     */
    String fetchKey() default StringUtils.EMPTY;

    /**
     * 将获取的配置内容转变成指定的对象
     */
    Class<?> fetchResult() default Void.class;

    /**
     * 执行回调的方法名称,与DegradeResultEnum#CALL_BACK_VALUE 配合使用
     */
    String methodName() default StringUtils.EMPTY;

    /**
     * 回调的class
     */
    Class<?> methodClass() default Void.class;

    /**
     * 默认值提供者,NullValueProvider默认提供,自定义复杂对象的返回值构建可以实现该接口
     */
    Class<? extends DegradeValueProvider> defaultValueProvider() default NullValueProvider.class;
}

class NullValueProvider implements DegradeValueProvider<Void> {
    @Override
    public Void buildDegradeValue() {
        return null;
    }
}

降级配置

组件采用了从apollo上获取业务配置的方式来进行降级,与Apollo耦合比较严重。如果不想采用apollo配置的方式进行业务降级配置,可以采用@ConfigurationProperties把配置在yml或者properties配置文件中的配置参数信息封装到配置的bean里,一般结合@EnableConfigurationProperties注解使用

@Data
public class ServiceDegradeConfig implements Serializable {

    private static final long serialVersionUID = -1628960982004214364L;
    /**
     * 降级总开关状态:true-全局开启服务降级;false-全局关闭服务降级
     */
    private Boolean state;

    /**
     * 场景开关
     */
    private Map<String, Boolean> sceneState;
}

降级处理器

采用策略模式,定义降级处理逻辑,具体的降级策略实现该接口即可,提供可扩展性的降级策略

public interface DegradeHandler {

    /**
     * 降级处理
     *
     * @return 处理后的结果
     */
    Object doDegrade(Degrade degrade, ProceedingJoinPoint point);
}

1.调用指定方法降级策略

@Slf4j
public class CallMethodHandler implements DegradeHandler {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public Object doDegrade(Degrade degrade, ProceedingJoinPoint point) {
        DegradeResult result = degrade.result();
        String methodName = result.methodName();
        Class<?> handlerClass = result.methodClass();
        Object target = point.getTarget();
        Object targetObj = point.getThis();
        if (handlerClass == Void.class) {
            handlerClass = target.getClass();
        } else {
            targetObj = applicationContext.getBean(handlerClass);
        }
        Object[] args = point.getArgs();
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
        Method m = null;
        try {
            m = handlerClass.getMethod(methodName, parameterTypes);
            return m.invoke(targetObj, args);
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            log.error("degrade call method={} error,message={}", methodName, e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
}

2.获取apollo上的降级配置信息进行降级策略

public class FetchConfigValueHandler implements DegradeHandler {


    @Autowired
    private ApolloUtil apolloUtil;

    @Override
    public Object doDegrade(Degrade degrade, ProceedingJoinPoint point) {
        DegradeResult result = degrade.result();
        Class<?> aClass = result.fetchResult();
        String fetchKey = result.fetchKey();
        if (StringUtils.isEmpty(fetchKey)) {
            return null;
        }
        Optional<?> resultOpt = apolloUtil.getMessage(fetchKey, aClass);
        return resultOpt.orElse(null);
    }

}

3.提供默认返回值的降级处理策略(常用)

public class DefaultValueHandler implements DegradeHandler {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    @SuppressWarnings("rawtypes")
    public Object doDegrade(Degrade degrade, ProceedingJoinPoint point) {
        DegradeResult result = degrade.result();
        Class<? extends DegradeValueProvider> providerClass = result.defaultValueProvider();
        //获取指定的默认返回值构造提供者进行默认值构建并返回
        DegradeValueProvider provider = applicationContext.getBean(providerClass);
        return provider.buildDegradeValue();
    }
}

提供默认返回值的降级处理策略比较常用,但是返回值的类型很多,组件默认提供返回null对象的返回值,但业务上存在其他对象,如Boolean,以及自定义的复杂对象等,因此这里提供了默认返回值提供者函数式接口方便扩展

@FunctionalInterface
public interface DegradeValueProvider<T> {

    /**
     * 构造服务降级后的返回值
     * @return T
     */
    T buildDegradeValue();
}

降级服务的核心逻辑,切面实现

@Slf4j
@Aspect
public class ServiceDegradeAspect {

    @Autowired
    private ApplicationContext applicationContext;
    //apollo配置业务上的降级场景
    @ApolloJsonValue("${app.service.degrade.gray.config:{}}")
    private Map<String, ServiceDegradeConfig> appDegradeConfigMap;

    @Around("@annotation(degrade)")
    public Object doDegrade(ProceedingJoinPoint proceedingJoinPoint, Degrade degrade) throws Throwable {
        //获取注解里面配置的降级key标识
        String businessKey = degrade.businessKey();
        String sceneKey = degrade.sceneKey();
        if (StringUtils.isBlank(sceneKey)) {
            sceneKey = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod().getName();
        }
        boolean needDegrade = false;
        try {
        //检查是否需要降级
            needDegrade = checkNeedDegrade(businessKey, sceneKey);
        } catch (Exception e) {
            log.warn("checkNeedDegrade error。businessKey:{}, sceneKey:{}", businessKey, sceneKey, e);
        }
        if (needDegrade) {   
            //执行降级
            return doDegradeAction(degrade, proceedingJoinPoint);
        }
        return proceedingJoinPoint.proceed();
    }

    private Object doDegradeAction(Degrade degrade, ProceedingJoinPoint point) {
        DegradeResult result = degrade.result();
        DegradeResultEnum degradeResultEnum = result.resultType();
        String name = degradeResultEnum.name();
        //使用具体的降级策略进行降级
        DegradeHandler handler = applicationContext.getBean(name, DegradeHandler.class);
        return handler.doDegrade(degrade, point);
    }

    private boolean checkNeedDegrade(String businessKey, String sceneKey) {
        if (StringUtils.isBlank(businessKey)) {
            return false;
        }
        ServiceDegradeConfig config = appDegradeConfigMap.get(businessKey);
        if (config.getState() == null) {
            return false;
        }
        return config.getState() || (StringUtils.isNotBlank(sceneKey) && Optional.ofNullable(config.getSceneState())
            .map(m -> m.get(sceneKey)).orElse(false));
    }

    @Around("@within(org.degrade.spring.boot.Degrade)")
    public Object degrade(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //方法上的降级注解优先于类上的
        Degrade degrade = AnnotationUtils.findAnnotation(signature.getMethod(), Degrade.class);
        if (Objects.isNull(degrade)) {
            degrade = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), Degrade.class);
        }
        Assert.notNull(degrade, "@Degrade must not be null!");
        return doDegrade(joinPoint, degrade);
    }
}

starter里Bean的发现与注册

META-INF目录下的spring.factories文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.degrade.spring.boot.DegradeAutoConfiguration

降级功能使用

例如针对app应用里面里程碑的一个活动功能进行降级,当该活动出现问题时,通过配置开关打开降级。即可不发送里程碑活动的相关信息,从而保证核心链路的正常访问,不影响用户的核心学习功能

@Degrade(businessKey = "milestone", sceneKey = "app", result = @DegradeResult(resultType = DegradeResultEnum.DEFAULT_VALUE))
public void sendAppNotifyTemporary(ChallengeActivityMessageParam param) {
//具体业务省略
  
    }

降级是一种系统容错机制,用于在系统出现异常或高负载时保证核心功能的可用性。当系统无法正常处理请求或超出负载能力时,降级可以通过减少或关闭某些功能或服务来避免系统崩溃或性能下降。

降级的目的是保证系统的稳定性和可靠性,以及提供良好的用户体验。通过降级,系统可以在异常情况下继续正常运行,避免因为某个模块或服务的故障导致整个系统的不可用。

降级可以通过以下几种方式实现:

1. 服务降级: 将某个服务切换到降级模式,返回预设的默认值或错误信息。这样可以保证系统的其他部分可以正常工作,而不会因为某个服务的故障而导致整个系统的不可用。

2. 功能降级: 暂时关闭某些不重要或耗资源较大的功能模块,以减轻系统负载。通过降低系统的复杂性和资源消耗,可以提高系统的性能和稳定性。

3. 数据降级: 降低数据的精确度或实时性要求,以提高系统的性能。例如,将数据从实时更新改为定时更新,或者将数据的精确度从小数点后两位改为小数点后一位。

4. 级联降级: 当某个服务或模块出现异常时,将其所依赖的其他服务或模块也切换到降级模式,以避免级联故障。这样可以防止一个故障引发整个系统的连锁反应。

总之,降级是一种重要的容错机制,可以在系统出现异常或高负载时保证核心功能的可用性。通过合理的降级策略,可以提高系统的稳定性和可靠性,保证用户的良好体验。

二、为什么要降级

使用降级机制有以下几个好处:

1. 提高系统的稳定性和可靠性 :当系统出现异常或高负载时,通过降级机制可以保证核心功能的可用性,避免整个系统的崩溃或性能下降。降级可以将故障隔离在一个较小的范围内,不会影响整个系统的正常运行。

2. 提升用户体验: 通过降级机制,可以保证系统的核心功能始终可用。即使某些功能或服务被降级,用户仍然可以正常使用系统,而不会遭遇完全无响应或错误的情况。这样可以提升用户的体验,增加用户的满意度。

3. 避免级联故障: 当一个模块或服务出现故障时,降级机制可以将其所依赖的其他模块或服务也切换到降级模式,避免因为一个故障而引发整个系统的连锁反应。这样可以保证系统的稳定性,减少故障的传播范围。

4. 节约资源和成本: 通过降级机制,可以暂时关闭或减少不重要或耗资源较大的功能或服务。这样可以减少系统的负载,提高系统的性能和吞吐量。同时,降级也可以减少对一些昂贵资源的消耗,从而降低系统的运维成本。

5. 快速恢复和故障排查: 当系统出现异常时,通过降级机制可以快速定位和解决问题。降级可以将故障范围缩小到一个较小的模块或服务,方便进行故障排查和修复。同时,降级也可以提供系统的自我修复能力,使系统能够在故障恢复后快速恢复正常运行。

总之,使用降级机制可以提高系统的稳定性和可靠性,提升用户体验,避免级联故障,并节约资源和成本。它是一种重要的容错机制,对于构建健壮的分布式系统非常重要。

三、降级的分类

降级可以根据不同的分类方式进行分类,以下是两种常见的分类方式:

1. 服务级别降级: 根据服务的重要性和优先级进行降级。服务级别降级可以将不同的服务按照重要性和优先级划分为几个级别,在系统出现异常或高负载时,根据实际情况对服务进行降级。例如,将核心功能的服务设置为最高优先级,而将一些次要功能的服务设置为较低优先级,在系统负载过高时先降低较低优先级的服务。

2. 功能级别降级: 根据功能的重要性和耗资源情况进行降级。功能级别降级可以根据不同功能的重要性和耗资源情况,将系统的功能划分为几个级别,在系统出现异常或高负载时,根据实际情况对功能进行降级。例如,将一些不重要或耗资源较大的功能暂时关闭或减少,以减轻系统负载。

这些分类方式可以根据具体的系统需求和业务场景进行选择和组合使用,以实现对系统的精细化降级控制和容错处理。

四、常见的降级技术

市场上常见的降级技术包括以下几种:

1. 熔断器(Circuit Breaker): 熔断器是一种常见的降级技术,用于在服务出现故障或超时时快速返回预设的默认值或错误信息。当服务出现故障或超时时,熔断器会打开,阻止后续的请求,直接返回默认值或错误信息,以保证系统的可用性。

2. 限流(Rate Limiting): 限流是一种常见的降级技术,用于限制系统的请求流量,以避免系统过载。通过设定一个阈值,当请求达到阈值时,系统会拒绝一部分请求或者将其放入队列中,以控制系统的负载。

3. 降级开关(Degrade Switch): 降级开关是一种手动开关,用于控制某个功能或服务的降级。通过设置降级开关,可以在系统出现异常或高负载时手动切换到降级模式,以保证系统的稳定性。

4. 服务降级(Service Degradation): 服务降级是一种常见的降级技术,用于在服务出现故障或不可用时切换到降级模式,返回预设的默认值或错误信息。通过服务降级,可以保证系统的其他部分可以正常工作,而不会因为某个服务的故障而导致整个系统的不可用。

5. 数据降级(Data Degradation): 数据降级是一种常见的降级技术,用于降低数据的精确度或实时性要求,以提高系统的性能。通过降低数据的精确度或实时性要求,系统可以减少对于实时数据的计算和存储,从而提高系统的性能和响应速度。

这些降级技术可以根据实际需求和系统特点选择和组合使用,以实现对系统的降级控制和容错处理。

五、示例

以下是一个使用Java实现的简单服务降级的代码示例:

public class Service {
    private boolean isDegraded;
 
    public Service() {
        this.isDegraded = false; // 默认未降级
    }
 
    public void performOperation() {
        if (isDegraded) {
            // 降级处理逻辑,返回默认值或错误信息
            System.out.println("Service is degraded. Returning default value.");
            return;
        }
 
        // 正常的服务逻辑
        System.out.println("Performing operation.");
        // 执行服务操作...
    }
 
    public void degradeService() {
        isDegraded = true; // 将服务标记为降级状态
        System.out.println("Service degraded.");
    }
 
    public void restoreService() {
        isDegraded = false; // 恢复服务状态
        System.out.println("Service restored.");
    }
}

在这个示例中,Service类表示一个具体的服务。服务降级通过设置isDegraded属性来实现。当isDegradedtrue时,表示服务处于降级状态,执行performOperation()方法时会返回默认值或错误信息,而不会执行正常的服务逻辑。当需要降级时,可以调用degradeService()方法将服务标记为降级状态,当需要恢复服务时,可以调用restoreService()方法将服务恢复为正常状态。

使用这个示例,可以在服务出现异常或高负载时手动控制服务的降级和恢复,以保证系统的稳定性和可用性。当服务处于降级状态时,可以根据具体需求返回默认值或错误信息,并可以根据实际情况进行相应的降级处理。

转自https://zhuanlan.zhihu.com/p/614168987?utm_id=0

https://zhuanlan.zhihu.com/p/664175526


标题:微服务之降级
作者:michael
地址:https://blog.junxworks.cn/articles/2023/11/14/1699948053825.html