須要保存什麼數據在redis中?java
有了上述的幾個問題,便能很容易的實現。redis
一、lua腳本以下:算法
local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token') local last_time = ratelimit_info[1] local current_token = tonumber(ratelimit_info[2]) local max_token = tonumber(ARGV[1]) local token_rate = tonumber(ARGV[2]) local current_time = tonumber(ARGV[3]) if current_token == nil then current_token = max_token last_time = current_time else local past_time = current_time-last_time if past_time>1000 then current_token = current_token+token_rate last_time = current_time end ## 防止溢出 if current_token>max_token then current_token = max_token last_time = current_time end end local result = 0 if(current_token>0) then result = 1 current_token = current_token-1 last_time = current_time end redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token) return result
二、 SpringBoot代碼實現json
/** * 從新注入模板 */ @Bean(value = "redisTemplate") @Primary public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){ RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); //設置序列化方式,key設置string 方式,value設置成json StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); jsonRedisSerializer.setObjectMapper(objectMapper); template.setEnableDefaultSerializer(false); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); return template; }
/** * @Description 限流工具類 * @Author CJB * @Date 2020/3/19 17:21 */ public class RedisLimiterUtils { private static StringRedisTemplate stringRedisTemplate=ApplicationContextUtils.applicationContext.getBean(StringRedisTemplate.class); /** * lua腳本,限流 */ private final static String TEXT="local ratelimit_info = redis.pcall('HMGET',KEYS[1],'last_time','current_token')\n" + "local last_time = ratelimit_info[1]\n" + "local current_token = tonumber(ratelimit_info[2])\n" + "local max_token = tonumber(ARGV[1])\n" + "local token_rate = tonumber(ARGV[2])\n" + "local current_time = tonumber(ARGV[3])\n" + "if current_token == nil then\n" + " current_token = max_token\n" + " last_time = current_time\n" + "else\n" + " local past_time = current_time-last_time\n" + " \n" + " if past_time>1000 then\n" + "\t current_token = current_token+token_rate\n" + "\t last_time = current_time\n" + " end\n" + "\n" + " if current_token>max_token then\n" + " current_token = max_token\n" + "\tlast_time = current_time\n" + " end\n" + "end\n" + "\n" + "local result = 0\n" + "if(current_token>0) then\n" + " result = 1\n" + " current_token = current_token-1\n" + " last_time = current_time\n" + "end\n" + "redis.call('HMSET',KEYS[1],'last_time',last_time,'current_token',current_token)\n" + "return result"; /** * 獲取令牌 * @param key 請求id * @param max 最大能同時承受多少的併發(桶容量) * @param rate 每秒生成多少的令牌 * @return 獲取令牌返回true,沒有獲取返回false */ public static boolean tryAcquire(String key, int max,int rate) { List<String> keyList = new ArrayList<>(1); keyList.add(key); DefaultRedisScript<Long> script = new DefaultRedisScript<>(); script.setResultType(Long.class); script.setScriptText(TEXT); return Long.valueOf(1).equals(stringRedisTemplate.execute(script,keyList,Integer.toString(max), Integer.toString(rate), Long.toString(System.currentTimeMillis()))); } }
/** * @Description 限流的註解,標註在類上或者方法上。在方法上的註解會覆蓋類上的註解,同@Transactional * @Author CJB * @Date 2020/3/20 13:36 */ @Inherited @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimit { /** * 令牌桶的容量,默認100 * @return */ int capacity() default 100; /** * 每秒鐘默認產生令牌數量,默認10個 * @return */ int rate() default 10; }
/** * @Description 限流的攔器 * @Author CJB * @Date 2020/3/19 14:34 */ @Component public class RateLimiterIntercept implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod){ HandlerMethod handlerMethod=(HandlerMethod)handler; Method method = handlerMethod.getMethod(); /** * 首先獲取方法上的註解 */ RateLimit rateLimit = AnnotationUtils.findAnnotation(method, RateLimit.class); //方法上沒有標註該註解,嘗試獲取類上的註解 if (Objects.isNull(rateLimit)){ //獲取類上的註解 rateLimit = AnnotationUtils.findAnnotation(handlerMethod.getBean().getClass(), RateLimit.class); } //沒有標註註解,放行 if (Objects.isNull(rateLimit)) return true; //嘗試獲取令牌,若是沒有令牌了 if (!RedisLimiterUtils.tryAcquire(request.getRequestURI(),rateLimit.capacity(),rateLimit.rate())){ //拋出請求超時的異常 throw new TimeOutException(); } } return true; } }
SpringBoot配置攔截器的代碼就不貼了,以上就是完整的代碼,至此分佈式限流就完成了。服務器
若是以爲做者寫的好,有所收穫的話,點個關注推薦一下喲!!!
數據結構