自定义限流注解
一、定义注解类:Limit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.agan.limit.aop;
import java.lang.annotation.*; import java.util.concurrent.TimeUnit;
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented public @interface Limit {
String key() default "";
double limitNum();
long timeout() default 1;
long expire() default 1;
TimeUnit timeunit() default TimeUnit.SECONDS;
String msg() default "系统繁忙,请稍后再试.";
}
|
二、限流接口 LimiterManager
- 定义限流接口
1 2 3 4 5 6 7 8 9 10 11
| package com.agan.limit.manager;
import com.agan.limit.aop.Limit;
public interface LimiterManager { boolean tryAccess(Limit limiter); }
|
- 限流接口实现类 GuavaLimiter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.agan.limit.manager;
import com.agan.limit.aop.Limit; import com.google.common.collect.Maps; import com.google.common.util.concurrent.RateLimiter; import lombok.extern.slf4j.Slf4j;
import java.util.Map;
@Slf4j public class GuavaLimiter implements LimiterManager {
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Override public boolean tryAccess(Limit limit) { String key = limit.key(); RateLimiter rateLimiter = null; if (!limitMap.containsKey(key)) { rateLimiter = RateLimiter.create(limit.limitNum()); limitMap.put(key, rateLimiter); log.debug("新建了令牌桶={},容量={}",key,limit.limitNum()); } rateLimiter = limitMap.get(key); boolean acquire = rateLimiter.tryAcquire(limit.timeout(), limit.timeunit()); if (!acquire) { log.debug("令牌桶={},获取令牌失败",key); return false; } return true; }
}
|
- 限流接口实现类:RedisLimiter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package com.agan.limit.manager;
import com.agan.limit.aop.Limit; import com.agan.limit.exception.RedisLimitException; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.util.StringUtils;
import java.util.ArrayList; import java.util.List;
@Slf4j public class RedisLimiter implements LimiterManager {
private final StringRedisTemplate stringRedisTemplate;
private final DefaultRedisScript<Long> redisScript;
public RedisLimiter(StringRedisTemplate stringRedisTemplate, DefaultRedisScript<Long> redisScript) { this.stringRedisTemplate = stringRedisTemplate; this.redisScript = redisScript; }
@Override public boolean tryAccess(Limit limiter) {
String key = limiter.key(); if (StringUtils.isEmpty(key)) { throw new RedisLimitException( "redis limiter key cannot be null" ); }
List<String> keys = new ArrayList<>(); keys.add(key);
double limit = limiter.limitNum();
long expire = limiter.expire();
Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limit), String.valueOf(expire));
log.debug( "Access try count is {} for key={}", count, key );
return count != null && count != 0; }
}
|
- 定义配置类 LimiterConfigure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.agan.limit.config;
import com.agan.limit.manager.GuavaLimiter; import com.agan.limit.manager.LimiterManager; import com.agan.limit.manager.RedisLimiter; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.scripting.support.ResourceScriptSource;
@Configuration public class LimiterConfigure {
@Bean @ConditionalOnProperty(name = "limit.type",havingValue = "local") public LimiterManager guavaLimiter(){ return new GuavaLimiter(); }
@Bean @ConditionalOnProperty(name = "limit.type",havingValue = "redis") public LimiterManager redisLimiter(StringRedisTemplate stringRedisTemplate){ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua"))); return new RedisLimiter(stringRedisTemplate, redisScript); } }
|
- lua脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| local key = KEYS[1]
local limit = tonumber(ARGV[1])
local curentLimit = tonumber(redis.call('get', key) or "0")
if curentLimit + 1 > limit then return 0 else redis.call('INCRBY', key, 1) redis.call('EXPIRE', key, ARGV[2]) return curentLimit + 1 end
|
6.自定义异常:LimiterException、RedisLimitException
1 2 3 4 5 6 7 8 9 10 11
| package com.agan.limit.exception;
public class LimiterException extends RuntimeException{ public LimiterException(String message) { super(message); } }
|
1 2 3 4 5 6 7 8 9 10 11
| package com.agan.limit.exception;
public class RedisLimitException extends RuntimeException{ public RedisLimitException(String msg) { super( msg ); } }
|
7.自定义常量:ConfigConstant
1 2 3 4 5 6 7 8 9 10 11
| package com.agan.limit.constant;
public class ConfigConstant {
public static final String LIMIT_TYPE = "limit.type";
}
|