限流、熔断、降级

1 阅读2分钟

限流

我们对用户在某一段时间窗口内的请求数进行限制。

1、首先我们自定义注解RateLimiterAccessInterceptor:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RateLimiterAccessInterceptor {

    /** 用哪个字段作为拦截标识,未配置则默认走全部 */
    String key() default "all";

    /** 限制频次(每秒请求次数) */
    double permitsPerSecond();

    /** 黑名单拦截(多少次限制后加入黑名单)0 不限制 */
    double blacklistCount() default 0;

    /** 拦截后的执行方法 */
    String fallbackMethod();

}

2、切面拦截,管理所有添加@RateLimiterAccessInterceptor自定义注解的方法。

public Object rateLimiter(ProceedingJoinPoint jp, RateLimiterAccessInterceptor rateLimiterAccessInterceptor) throws Throwable {

    if (StringUtils.isNotBlank(rateLimiterSwitch) && rateLimiterSwitch.equals("close")){
        return jp.proceed();
    }
    String key = rateLimiterAccessInterceptor.key();
    if (StringUtils.isBlank(key)){
        throw new RuntimeException("annotation RateLimiter uId is null!");
    }
    // 获取拦截字段
    String keyAttr = getAttrValue(key, jp.getArgs());
    log.info("aop attr {}", keyAttr);
    String userBlackKey = keyAttr + "_black";
    RAtomicLong userBlackProxy = redissonClient.getAtomicLong(userBlackKey);
    // 判断是否启用黑名单计数逻辑
    if (!"all".equals(keyAttr) && rateLimiterAccessInterceptor.blacklistCount() != 0) {
        // 检查当前用户是否存在黑名单计数器
        boolean exist = userBlackProxy.isExists();

        // 如果黑名单计数器存在且次数超过阈值,直接拦截
        if (exist && userBlackProxy.get() > rateLimiterAccessInterceptor.blacklistCount()) {
            log.info("用户已进入黑名单(24小时): {}, 当前触发次数: {}", userBlackKey, userBlackProxy.get());
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }
    }

    // 获取限流器
    RRateLimiter rateLimiter = redissonClient.getRateLimiter(keyAttr);
    // 如果限流器不存在,初始化限流规则
    if (!rateLimiter.isExists()) {
        //RateType.OVERALL:全局限流;
        //rateLimiterInterceptor.permitsPerSecond() :每秒允许的请求数;
        //时间间隔,表示每 1 秒;
        //RateIntervalUnit.SECONDS`**:时间单位为秒。
        rateLimiter.trySetRate(RateType.OVERALL, rateLimiterAccessInterceptor.permitsPerSecond(), 1, RateIntervalUnit.SECONDS);
    }

    // 尝试获取限流令牌
    if (!rateLimiter.tryAcquire()) {
        // 限流失败时记录触发次数
        long failCount = userBlackProxy.incrementAndGet();

        // 设置黑名单计数器过期时间为 24 小时(首次设置)
        if (!userBlackProxy.isExists()) {
            userBlackProxy.expire(Duration.ofHours(24));
        }

        // 如果限流失败次数达到黑名单阈值,将用户加入黑名单
        if (failCount > rateLimiterAccessInterceptor.blacklistCount()) {
            log.info("用户已触发限流并进入黑名单: {}, 限流失败次数: {}", userBlackKey, failCount);
            return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
        }

        log.info("限流触发,当前触发次数: {}", failCount);
        return fallbackMethodResult(jp, rateLimiterAccessInterceptor.fallbackMethod());
    }
    return jp.proceed();
}

限流:

  1. 自定义注解@RateLimiterAccessInterceptor
  2. 使用AOP对@RateLimiterAccessInterceptor所修饰的方法进行逻辑处理。
  3. 使用redisson中的RRateLimit作为限流器,定义限流器。
  4. 使用tryAquire方法尝试获取令牌,如果没有获取到,则说明超出了访问频次、需要进行限流,此时将限流次数+1
  5. 如果限流次数>blacklistCount,则需要将此用户拉入黑名单24小时。

降级

以Zookeeper为配置中心服务,基于 Zookeeper 的节点监听值变更机制,动态修改应用程序中属性值。通过监听Zookeeper节点来动态更新带有自定义注解 @DCCValue 的字段值。

  1. 使用自定义注解@DCCValue作为降级开关,所有使用了注解的字段,都会被动态管理。
  2. 定义zookeeper节点监听动态值的变化。
  3. 使用反射修改字段的属性。

监听器机制

  • 当 Zookeeper 节点下的内容发生变更(NODE_CHANGED),就会触发监听器,根据变更的路径更新相应的Bean字段值。
  • 通过反射机制获取Bean的字段,利用 field.set(objBean, new String(data.getData())) 将更新的数据设置到Bean对应的字段上。

熔断:

使用hystrixCommand注解进行熔断,设置超时时间和fallback函数。