redis 做分布式限流
参看来源:
https://blog.51cto.com/u_15708799/5703440
测试有效代码:我们要做的是:当并发请求超出了限定阈值时,要延迟请求,而不是直接丢弃 。当然也可以把结果给业务端,看业务端是提醒用户下次,还是延迟处理,还是丢弃。
@Test public void testLimitWait() throws InterruptedException { ExecutorService pool = Executors.newCachedThreadPool(); log.info("--------{}", redisTemplate.opsForValue().get("abc")); for (int j = 1; j <= 5; j++) { int i=j; pool.execute(() -> { Thread.currentThread().setName( Thread.currentThread().getName().replace("-","_")); limitWait("abc", 3, 1); log.info(i + ":" + true + " ttl:" + redisTemplate.getExpire("abc", TimeUnit.MILLISECONDS)); try { // 线程等待,模拟执行业务逻辑 Thread.sleep(new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } }); } pool.shutdown(); pool.awaitTermination(2,TimeUnit.SECONDS); } /** * 达到限流时,则等待,直到新的间隔。 * * @param key 可以是ip + 当前秒 或者 是 uid + 当前秒 或者 固定的一个入口 key * @param limitCount 一定时间内最多访问次数 * @param limitSecond 给定的时间范围 单位(秒) */ public void limitWait(String key, int limitCount, int limitSecond) { boolean ok;//放行标志 do { ok = limit(key, limitCount, limitSecond); log.info("放行标志={}", ok); if (!ok) { Long ttl = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS); if (null != ttl && ttl > 0) { try { Thread.sleep(ttl); log.info("sleeped:{}", ttl); } catch (InterruptedException e) { e.printStackTrace(); } } } } while (!ok); } /** * 限流方法 true-放行;false-限流 * * @param key * @param limitCount * @param limitSecond * @return */ public boolean limit(String key, int limitCount, int limitSecond) { List<String> keys = Collections.singletonList(key); String luaScript = buildLuaScript(); RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class); Long count = redisTemplate.execute(redisScript, keys, limitCount, limitSecond); log.info("Access try count is {} for key = {}", count, key); if (count != null && count.intValue() <= limitCount) { return true;//放行 } else { return false;//限流 // throw new RuntimeException("You have been dragged into the blacklist"); } } /** * 编写 redis Lua 限流脚本 */ public String buildLuaScript() { StringBuilder lua = new StringBuilder(); lua.append("local c"); lua.append("\nc = redis.call('get',KEYS[1])"); // 实际调用次数超过阈值,则直接返回 lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then"); lua.append("\nreturn c;"); lua.append("\nend"); // 执行计算器自加 lua.append("\nc = redis.call('incr',KEYS[1])"); lua.append("\nif tonumber(c) == 1 then"); // 从第一次调用开始限流,设置对应键值的过期 lua.append("\nredis.call('expire',KEYS[1],ARGV[2])"); lua.append("\nend"); lua.append("\nreturn c;"); return lua.toString(); }
关于TTL(Time to Live)
不管是redis还是jedis,其实都是利用了消息的ttl(Time to Live),即,当消息的ttl=0时,消息会自动过期。ttl还见诸于RabbitMQ的死信队列,队列里的消息会延迟消费,当等待ttl指定的时间后,才会自动转移到实时队列。
redis是使用RedisTemplate.expire来设置ttl;使用RedisTemplate.getExpire(key)或RedisTemplate.getExpire(key,TimeUnit)方法来获取ttl。当然,对于并发限流,我们需要使用后者指定时间单位为TimeUnit.MILLISECONDS来得到精确的剩余毫秒数。
jedis是使用Jedis.expire来设置ttl;使用Jedis.ttl(key)方法来获取ttl,返回的时间是毫秒。
getExpire/ttl返回值:
-2:key不存在
-1:未设置ttl
n:实际的剩余ttl
redis.incr指令说明
关于redis的increment :
当key不存在时,创建key,默认值是delta值(不指定delta的话,则为1)。
当key存在时,按delta来递增。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通