小伙伴們知道,在 Spring Boot 中,我們其實(shí)更習(xí)慣使用 Spring Data Redis 來操作 Redis,不過默認(rèn)的 RedisTemplate 有一個(gè)小坑,就是序列化用的是 JdkSerializationRedisSerializer,不知道小伙伴們有沒有注意過,直接用這個(gè)序列化工具將來存到 Redis 上的 key 和 value 都會(huì)莫名其妙多一些前綴,這就導(dǎo)致你用命令讀取的時(shí)候可能會(huì)出錯(cuò)。
例如存儲(chǔ)的時(shí)候,key 是 name,value 是 javaboy,但是當(dāng)你在命令行操作的時(shí)候, 卻獲取不到你想要的數(shù)據(jù),原因就是存到 redis 之后 name 前面多了一些字符,此時(shí)只能繼續(xù)使用 RedisTemplate 將之讀取出來。get name
local key = KEYS[1] local count = tonumber(ARGV[1]) local time = tonumber(ARGV[2]) local current = redis.call('get', key) if current and tonumber(current) > count then return tonumber(current) end current = redis.call('incr', key) if tonumber(current) == 1 then redis.call('expire', key, time) end return tonumber(current)
@Bean public DefaultRedisScript limitScript() { DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua"))); redisScript.setResultType(Long.class); return redisScript; }
可以啦,我們的 Lua 腳本現(xiàn)在就準(zhǔn)備好了。
5. 注解解析
接下來我們就需要自定義切面,來解析這個(gè)注解了,我們來看看切面的定義:
@Aspect @Component public class RateLimiterAspect { private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
@Autowired private RedisTemplate redisTemplate;
@Autowired private RedisScript limitScript;
@Before("@annotation(rateLimiter)") public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable { String key = rateLimiter.key(); int time = rateLimiter.time(); int count = rateLimiter.count();
String combineKey = getCombineKey(rateLimiter, point); List keys = Collections.singletonList(combineKey); try { Long number = redisTemplate.execute(limitScript, keys, count, time); if (number==null || number.intValue() > count) { throw new ServiceException("訪問過于頻繁,請(qǐng)稍候再試"); } log.info("限制請(qǐng)求'{}',當(dāng)前請(qǐng)求'{}',緩存key'{}'", count, number.intValue(), key); } catch (ServiceException e) { throw e; } catch (Exception e) { throw new RuntimeException("服務(wù)器限流異常,請(qǐng)稍候再試"); } }
獲取一個(gè)組合的 key,所謂的組合的 key,就是在注解的 key 屬性基礎(chǔ)上,再加上方法的完整路徑,如果是 IP 模式的話,就再加上 IP 地址。以 IP 模式為例,最終生成的 key 類似這樣:(如果不是 IP 模式,那么生成的 key 中就不包含 IP 地址)。rate_limit:127.0.0.1-org.javaboy.ratelimiter.controller.HelloController-hello