需要保存什么数据在redis中?
有了上述的几个问题,便能很容易的实现。
1、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
2、 springboot代码实现
/** * 重新注入模板 */ @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配置拦截器的代码就不贴了,以上就是完整的代码,至此分布式限流就完成了。
如果觉得作者写的好,有所收获的话,点个关注推荐一下哟!!!
如对本文有疑问, 点击进行留言回复!!
[杭电多校2020]第一场 1004 Distinct Sub-palindromes
Swift -- 将本地生成的UIImage进行持久化保存(存到文件中fileManager.createFile)
网友评论