限流
我们对用户在某一段时间窗口内的请求数进行限制。
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();
}
限流:
- 自定义注解@RateLimiterAccessInterceptor
- 使用AOP对@RateLimiterAccessInterceptor所修饰的方法进行逻辑处理。
- 使用redisson中的RRateLimit作为限流器,定义限流器。
- 使用tryAquire方法尝试获取令牌,如果没有获取到,则说明超出了访问频次、需要进行限流,此时将限流次数+1
- 如果限流次数>blacklistCount,则需要将此用户拉入黑名单24小时。
降级
以Zookeeper为配置中心服务,基于 Zookeeper 的节点监听值变更机制,动态修改应用程序中属性值。通过监听Zookeeper节点来动态更新带有自定义注解 @DCCValue
的字段值。
- 使用自定义注解@DCCValue作为降级开关,所有使用了注解的字段,都会被动态管理。
- 定义zookeeper节点监听动态值的变化。
- 使用反射修改字段的属性。
监听器机制
- 当 Zookeeper 节点下的内容发生变更(
NODE_CHANGED
),就会触发监听器,根据变更的路径更新相应的Bean字段值。 - 通过反射机制获取Bean的字段,利用
field.set(objBean, new String(data.getData()))
将更新的数据设置到Bean对应的字段上。
熔断:
使用hystrixCommand注解进行熔断,设置超时时间和fallback函数。