admin 管理员组文章数量: 1086019
中小型项目请求限流设计
何为请求限流?
请求限流是一种控制API或其他Web服务的流量的技术。它的目的是限制客户端对服务器发出的请求的数量或速率,以防止服务器过载或响应时间变慢,从而提高系统的可用性和稳定性。
中小型项目请求限流的需求
- 按IP、用户、全局限流
- 基于不同实现的限流设计(基于Redis或者LRU缓存)
- 基于注解标注哪些接口限流
完整限流设计实现在开源项目中:github.com/valarchie/A…
注解设计
声明一个注解类,主要有以下几个属性
- key(缓存的key)
- time(时间范围)
- maxCount(时间范围内最大的请求次数)
- limitType(按IP/用户/全局进行限流)
- cacheType(基于Redis或者Map来实现限流)
/*** 限流注解** @author valarchie*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {/*** 限流key*/String key() default "None";/*** 限流时间,单位秒*/int time() default 60;/*** 限流次数*/int maxCount() default 100;/*** 限流条件类型*/LimitType limitType() default LimitType.GLOBAL;/*** 限流使用的缓存类型*/CacheType cacheType() default CacheType.REDIS;}
复制代码
LimitType枚举,我们可以将不同限制类型的逻辑直接放在枚举类当中。推荐将逻辑直接放置在枚举类中,代码的组织形式会更好。
enum LimitType {/*** 默认策略全局限流 不区分IP和用户*/GLOBAL{@Overridepublic String generateCombinedKey(RateLimit rateLimiter) {return rateLimiter.key() + this.name();}},/*** 根据请求者IP进行限流*/IP {@Overridepublic String generateCombinedKey(RateLimit rateLimiter) {String clientIP = ServletUtil.getClientIP(ServletHolderUtil.getRequest());return rateLimiter.key() + clientIP;}},/*** 按用户限流*/USER {@Overridepublic String generateCombinedKey(RateLimit rateLimiter) {LoginUser loginUser = AuthenticationUtils.getLoginUser();if (loginUser == null) {throw new ApiException(ErrorCode.Client.COMMON_NO_AUTHORIZATION);}return rateLimiter.key() + loginUser.getUsername();}};public abstract String generateCombinedKey(RateLimit rateLimiter);}复制代码
CacheType, 主要分为Redis和Map, 后续有新的类型可以新增。
enum CacheType {/*** 使用redis做缓存*/REDIS,/*** 使用map做缓存*/Map}复制代码
RateLimitChecker设计
声明一个抽象类,然后将具体实现放在实现类中,便于扩展
/*** @author valarchie*/
public abstract class AbstractRateLimitChecker {/*** 检查是否超出限流* @param rateLimiter*/public abstract void check(RateLimit rateLimiter);}
复制代码
Redis限流实现
/*** @author valarchie*/
@Component
@RequiredArgsConstructor
@Slf4j
public class RedisRateLimitChecker extends AbstractRateLimitChecker{@NonNullprivate RedisTemplate<Object, Object> redisTemplate;private final RedisScript<Long> limitScript = new DefaultRedisScript<>(limitScriptText(), Long.class);@Overridepublic void check(RateLimit rateLimiter) {int maxCount = rateLimiter.maxCount();String combineKey = rateLimiter.limitType().generateCombinedKey(rateLimiter);Long currentCount;try {currentCount = redisTemplate.execute(limitScript, ListUtil.of(combineKey), maxCount, rateLimiter.time());log.info("限制请求:{}, 当前请求次数:{}, 缓存key:{}", combineKey, currentCount, rateLimiter.key());} catch (Exception e) {throw new RuntimeException("redis限流器异常,请确保redis启动正常");}if (currentCount == null) {throw new RuntimeException("redis限流器异常,请稍后再试");}if (currentCount.intValue() > maxCount) {throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TOO_OFTEN);}}/*** 限流脚本*/private static String limitScriptText() {return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +" return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +" redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";}}
复制代码
Map + Guava RateLimiter实现
/*** @author valarchie*/
@SuppressWarnings("UnstableApiUsage")
@Component
@RequiredArgsConstructor
@Slf4j
public class MapRateLimitChecker extends AbstractRateLimitChecker{/*** 最大仅支持4096个key 超出这个key 限流将可能失效*/private final LRUCache<String, RateLimiter> cache = new LRUCache<>(4096);@Overridepublic void check(RateLimit rateLimit) {String combinedKey = rateLimit.limitType().generateCombinedKey(rateLimit);RateLimiter rateLimiter = cache.get(combinedKey,() -> RateLimiter.create((double) rateLimit.maxCount() / rateLimit.time()));if (!rateLimiter.tryAcquire()) {throw new ApiException(ErrorCode.Client.COMMON_REQUEST_TOO_OFTEN);}log.info("限制请求key:{}, combined key:{}", rateLimit.key(), combinedKey);}}
复制代码
限流切面
我们需要在切面中,读取限流注解标注的信息,然后选择不同的限流实现来进行限流。
/*** 限流切面处理** @author valarchie*/
@Aspect
@Component
@Slf4j
@ConditionalOnExpression("'${agileboot.embedded.redis}' != 'true'")
@RequiredArgsConstructor
public class RateLimiterAspect {@NonNullprivate RedisRateLimitChecker redisRateLimitChecker;@NonNullprivate MapRateLimitChecker mapRateLimitChecker;@Before("@annotation(rateLimiter)")public void doBefore(JoinPoint point, RateLimit rateLimiter) {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();log.info("当前限流方法:" + method.toGenericString());switch (rateLimiter.cacheType()) {case REDIS:redisRateLimitChecker.check(rateLimiter);break;case Map:mapRateLimitChecker.check(rateLimiter);return;default:redisRateLimitChecker.check(rateLimiter);}}}
复制代码
注解使用
以下是我们标注的注解例子。
time=10,maxCount=10表明10秒内最多10次请求。
cacheType=Redis表明使用Redis来实现。
limitType=IP表明基于IP来限流。
/*** 生成验证码*/
@Operation(summary = "验证码")
@RateLimit(key = RateLimitKey.LOGIN_CAPTCHA_KEY, time = 10, maxCount = 10, cacheType = CacheType.REDIS,limitType = LimitType.IP)
@GetMapping("/captchaImage")
public ResponseDTO<CaptchaDTO> getCaptchaImg() {CaptchaDTO captchaImg = loginService.generateCaptchaImg();return ResponseDTO.ok(captchaImg);
}
复制代码
这是笔者关于中小型项目关于请求限流的实现,如有不足欢迎大家评论指正。
本文标签: 中小型项目请求限流设计
版权声明:本文标题:中小型项目请求限流设计 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1686559002a10196.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论