范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

redisson分布式限流RRateLimiter源码分析

  分布式限流-单位时间多实例多线程访问次数限制
  接前面聊一聊redisson及优雅实现 和 说一说spring boot优雅集成redisson,简单以源码的方式给大家介绍了redisson的:可重入性、阻塞、续约、红锁、联锁、加锁解锁流程和集成spring boot注意点和优雅实现方式。
  接下来在讲一讲平时用的比较多的限流模块--RRateLimiter1.简单使用 public static void main(String[] args) throws InterruptedException {         RRateLimiter rateLimiter = createLimiter();           int allThreadNum = 20;          CountDownLatch latch = new CountDownLatch(allThreadNum);          long startTime = System.currentTimeMillis();         for (int i = 0; i < allThreadNum; i++) { //            new Thread(() -> {             if(i % 3 == 0) Thread.sleep(1000);             boolean pass = rateLimiter.tryAcquire();             if(pass) {                 log.info("get ");             } else {                 log.info("no");             } //          latch.countDown(); //            }).start();         } //        latch.await();         System.out.println("Elapsed " + (System.currentTimeMillis() - startTime));     }      public static RRateLimiter createLimiter() {         Config config = new Config();         config.useSingleServer()                 .setTimeout(1000000)                 .setPassword("123456")                 .setAddress("redis://xxxx:6379");          RedissonClient redisson = Redisson.create(config);         RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter3");         // 初始化:PER_CLIENT 单实例执行,OVERALL 全实例执行         // 最大流速 = 每10秒钟产生3个令牌         rateLimiter.trySetRate(RateType.OVERALL, 3, 10, RateIntervalUnit.SECONDS);         return rateLimiter;     } 复制代码
  实际结果:[2022-10-29 14:32:46.261][INFO ][main][][] RedisTest - get  [2022-10-29 14:32:46.312][INFO ][main][][] RedisTest - get  [2022-10-29 14:32:46.358][INFO ][main][][] RedisTest - get  [2022-10-29 14:32:47.416][INFO ][main][][] RedisTest - no [2022-10-29 14:32:47.469][INFO ][main][][] RedisTest - no [2022-10-29 14:32:47.517][INFO ][main][][] RedisTest - no [2022-10-29 14:32:48.577][INFO ][main][][] RedisTest - no [2022-10-29 14:32:48.623][INFO ][main][][] RedisTest - no 复制代码2. 实现限流redisson使用了哪些redis数据结构Hash结构 -- 限流器结构:参数rate代表速率参数interval代表多少时间内产生的令牌参数type代表单机还是集群ZSET结构 -- 记录获取令牌的时间戳,用于时间对比。1667025166312 --> 2022-10-29 14:32:461667025166262 --> 2022-10-29 14:32:461667025166215 --> 2022-10-29 14:32:46
  3. String结构 --记录的是当前令牌桶中的令牌数【很明显被我用完了现在是0】
  3. 超过10s,我再次获取一个令牌,数据结构发生的变化ZSET结构。-- 新生成一个ZSET结构,存放获取令牌的时间戳
  String 结构 --当前令牌桶还有2个令牌
  4. 源码浅析 RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter3"); // 初始化 // 最大流速 = 每10秒钟产生3个令牌 rateLimiter.trySetRate(RateType.PER_CLIENT, 3, 10, RateIntervalUnit.SECONDS); 复制代码
  初始化定义没有什么好讲的,就是创建HASH结构
  主要还是讲讲: rateLimiter.tryAcquire() private  RFuture tryAcquireAsync(RedisCommand command, Long value) {  return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, "local rate = redis.call("hget", KEYS[1], "rate");local interval = redis.call("hget", KEYS[1], "interval");local type = redis.call("hget", KEYS[1], "type");assert(rate ~= false and interval ~= false and type ~= false, "RateLimiter is not initialized")local valueName = KEYS[2];local permitsName = KEYS[4];if type == "1" then valueName = KEYS[3];permitsName = KEYS[5];end;local currentValue = redis.call("get", valueName); if currentValue ~= false then local expiredValues = redis.call("zrangebyscore", permitsName, 0, tonumber(ARGV[2]) - interval); local released = 0; for i, v in ipairs(expiredValues) do local random, permits = struct.unpack("fI", v);released = released + permits;end; if released > 0 then redis.call("zrem", permitsName, unpack(expiredValues)); currentValue = tonumber(currentValue) + released; redis.call("set", valueName, currentValue);end;if tonumber(currentValue) < tonumber(ARGV[1]) then local nearest = redis.call("zrangebyscore", permitsName, "(" .. (tonumber(ARGV[2]) - interval), tonumber(ARGV[2]), "withscores", "limit", 0, 1); local random, permits = struct.unpack("fI", nearest[1]);return tonumber(nearest[2]) - (tonumber(ARGV[2]) - interval);else redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1])); redis.call("decrby", valueName, ARGV[1]); return nil; end; else assert(tonumber(rate) >= tonumber(ARGV[1]), "Requested permits amount could not exceed defined rate"); redis.call("set", valueName, rate); redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1])); redis.call("decrby", valueName, ARGV[1]); return nil; end;", Arrays.asList(this.getName(), this.getValueName(), this.getClientValueName(), this.getPermitsName(), this.getClientPermitsName()), new Object[]{value, System.currentTimeMillis(), ThreadLocalRandom.current().nextLong()}); } 复制代码
  主要就是这段lua代码,下面我详细过一下
  作者目前用的3.16.3版本,刚好遇见redisson的bug,见3197,请大家用最新版本,以下为修复后解析。 -- 获取hash结构的速率 local rate = redis.call("hget", KEYS[1], "rate") -- 获取hash结构的时间区间(ms) local interval = redis.call("hget", KEYS[1], "interval") -- 获取hash结构的时间类型 local type = redis.call("hget", KEYS[1], "type") -- 判断是否初始化限流结构 assert(rate ~= false and interval ~= false and type ~= false, "RateLimiter is not initialized")  -- {name}:value string结构,这个key记录的是当前令牌桶中的令牌数 local valueName = KEYS[2]  -- {name}:permits zset结构,记录了请求的令牌数,score则为请求的时间戳 local permitsName = KEYS[4]  -- 单机限流才会用到,集群模式不用关注 if type == "1" then     valueName = KEYS[3]     permitsName = KEYS[5] end  -- 生产速率rate必须比请求的令牌数大 assert(tonumber(rate) >= tonumber(ARGV[1]), "Requested permits amount could not exceed defined rate")  -- 初始化RateLimiter并不会初始化stirng结构,因此第一次获取这里currentValue是null local currentValue = redis.call("get", valueName) if currentValue ~= false then     -- 第二次获取令牌执行     -------------------------- 获取zset结构:统计之前的请求令牌数     -- 范围是0 ~ (第二次请求时间戳 - 令牌生产的时间)     local expiredValues = redis.call("zrangebyscore", permitsName, 0, tonumber(ARGV[2]) - interval)     local released = 0     -- lua迭代器,遍历expiredValues,如果有值,那么released等于之前所有请求的令牌数之和,表示应该释放多少令牌     for i, v in ipairs(expiredValues) do         -- 获取请求数permits         local random, permits = struct.unpack("fI", v)         released = released + permits     end      -- 之前的请求令牌数 > 0, 例如10s产生3个令牌,现在超过10s了,重置周期并计算剩余令牌数     if released > 0 then         -- 移除zset中所有元素【要求是同一个限流器permitsName,不然就移除不了,尴尬】          redis.call("zrem", permitsName, unpack(expiredValues))         currentValue = tonumber(currentValue) + released         ------------------------- 更新string结构:=剩下令牌数+释放令牌数         redis.call("set", valueName, currentValue)     end      -- 如果当前令牌数 < 请求的令牌数     if tonumber(currentValue) < tonumber(ARGV[1]) then         -- 从zset中找到距离当前时间最近的那个请求,也就是上一次放进去的请求信息         local nearest = redis.call("zrangebyscore", permitsName, "(" .. (tonumber(ARGV[2]) - interval), tonumber(ARGV[2]), "withscores", "limit", 0, 1);          local random, permits = struct.unpack("fI", nearest[1])         -- 返回 上一次请求的时间戳 - (当前时间戳 - 令牌生成的时间间隔) 这个值表示还需要多久才能生产出足够的令牌         return tonumber(nearest[2]) - (tonumber(ARGV[2]) - interval)     else         -- 如果当前令牌数 ≥ 请求的令牌数,表示令牌够多,更新zset         ------------------------- 更新zset结构         redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1]))         ------------------------- 更新Stringt结构,减少一个剩下的令牌数         redis.call("decrby", valueName, ARGV[1])         return nil     end else     --------汀雨笔记----------------- 初始化Stringt结构,当前限流器的令牌数     redis.call("set", valueName, rate)        --------汀雨笔记----------------- 初始化zset结构     redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1]))     -- struct.pack第一个参数表示格式字符串,f是浮点数、I是长整数。所以这个格式字符串表示的是把一个浮点数和长整数拼起来的结构体,     -- ARGV[2]就是请求时间戳,ARGV[1]是请求的令牌数,统计会用到,ARGV[3]是当前时间戳为种子的随机数,具体用处还不知道,知道的网友可以留言          ------------------------- 更新Stringt结构,因为这是获取令牌操作,减掉一个令牌     -------------------------【本文作者认为,这里可以直接初始化string结构,值为rate - 1】     redis.call("decrby", valueName, ARGV[1])     return nil end  复制代码
  这段lua代码也并不复杂,令牌桶的数量主要是通过时间窗口来控制,判断上一个请求是否超过了令牌生产周期。
  留下一个疑问? -- 移除zset中所有元素【要求是同一个限流器permitsName,不然就移除不了,尴尬】  redis.call("zrem", permitsName, unpack(expiredValues)) 复制代码
  我自己在本地测试,只要超过10s,permitsName就不一样,这就导致了这部分数据是不能移除的,就产生了冗余数据,从前面的截图也可以看出,是新生成了一个zset数据结构。
  相当于直接走到了这一步:------------------------- 更新zset结构  redis.call("zadd", permitsName, ARGV[2], struct.pack("fI", ARGV[3], ARGV[1])) 复制代码
  至于为什么会产生这样的结果,会的小伙伴可以留言,或者过段时间我提个issue。
  及时当勉励 岁月不待人
  能看到这里的人呀,都是菁英。❤❤️❤️❤️❤
  非常感谢

中年女人为什么劝你少穿牛仔裤,多穿烟管裤?看陈数你就明白了确实,中年女性的穿搭和年轻女孩总是有风格上面的区别,改变你的穿搭套路,会影响你的气质和风格,而到中年阶段的女性在穿裤方面好像都是很随意的,这种情况下建议大家还是可以大胆地尝试一些更李小萌二胎后更丰满,与老公高调合体秀恩爱,穿半透亮片裙有韵味王雷的演技还是很不错的,这么多年来观众缘也很好,与妻子李小萌也是琴瑟和鸣,二人经常同框高调秀恩爱。虽然李小萌的出镜率要低于丈夫王雷,但是她每次出场穿搭都不失女明星风范,很有自己独特一个人,瞧不起你时,会对你做三事作者闻秋声原创文章,抄袭必究01引言人际关系,就像是一张蜘蛛网,复杂又容易破裂。曾国藩说一生成败,皆关乎朋友之贤否,不可不慎。好的人际关系,能带给人舒适感,给人带来温暖和动力。不好静静让我再说点啥,我试试赶鸭子上架,其实眼睛都睁不开了我在头条对她说患癌已经整整六年了,我的这条脆弱的生命依旧在这五彩斑斓的人间游离,真是任上天千万遍的呼唤,我就是不走,就是不走。对人间的眷恋是那么地深情,是那么不舍。而我自己,虽然如乱笔文邪恶龙骑士山水间,笔墨画,此时似乎在画山水画,只是比附混乱无章的暗示,已打乱了节奏,下一秒的乱笔,却又如此凄凉无法忘却的回忆,放不下的誓言。消失的那人,今日可好?山水下,水云间。晚安年末了,致我亲爱的朋友今日推荐阅读不知不觉,2022年已经到了尾声。回首这一年,我们经历过告别和失去,也面临过考验和挑战。但无论经历什么,总有朋友一直支持陪伴着我们。这些话,我想说给亲爱的朋友听。1hr历史上那些和亲的女人们(连载)二汉代和亲(二)汉武帝元封六年(公元前105年),乌孙使臣看到汉朝地域广大,回国后向其国王报告,乌孙于是更加重视与汉朝的关系。匈奴听说乌孙与汉朝建立联系,感到愤怒,准备出兵攻打乌孙新版7岁以下儿童生长标准实施哪些因素影响身高?医生提醒近日,我国新版7岁以下儿童生长标准开始实施,其中明确规定了不同年龄儿童生长发育的各项指标。新标准对于孩子的身高是如何规定的?如何判断孩子是否属于发育迟缓?春天真是孩子长高的黄金期吗古有狡兔死,良狗烹,今有网友质疑老干部,忘恩负义!前言当今社会和平年代,人们忧心更多的是如何增加家庭收入提高幸福度等问题。比起战争年代,如今的人们无疑是幸运的。而我国也是经历过战乱年代的,南京大屠杀惨绝人寰万里长征艰难曲折是先烈们2023华蓝杯八人制足球邀请赛开赛,12支队伍66场比赛角逐冠军广西新闻网南宁3月11日讯(记者胡戴炜)3月11日,由华蓝集团股份公司南宁市青秀区足球协会主办的2023华蓝杯八人制足球邀请赛在南宁市柳沙体育公园正式开赛,来自自治区广播电视局广西葡超王牌即将驰援上港踢亚冠,已得到名记确认,曾带队在联赛夺冠日前,根据上港跟队记者刘闻超透露,球队王牌外援小保利尼奥即将到位。小保利尼奥确定新赛季驰援上港队,至于他是否可以在比赛中出场踢主力,就要看这位球员届时的状态。小保利尼奥是一位非常有
OPPOK10x评测真香定价的日常机,也可以很硬核在今年的4月,OPPO为我们带来了K10系列新机,依旧主打高能硬核的产品理念,让这款产品拥有了不错的市场反馈。似乎也是在遵循惯例,在K10系列发布几个月后,我们也迎来了这款带上了字特斯拉CEO马斯克称汽油车就是下一个蒸汽机!不久之后,我们将以看待蒸汽机的方式看待汽油车今天买的汽油车残值会比人们想象的要低很多。特斯拉CEO埃隆马斯克在社交媒体上如是说道!不出所料的是,他这一番言论又喜提热搜,在推特上更是iPhone14系列拆解报告双基带芯片国产ROM芯片入局散热有加强文小伊评科技随着iPhone14Pro首批评测视频解禁,关于iPhone14Pro拆解视频也已经出现在各大视频平台,同时也有很多很有意思的信息被泄露得出来,接下来我们就来做一个详细全新跃级标杆荣耀X40亮相,档位首款OLED硬核曲屏超乎预期9月15日,荣耀举行秋季新品发布会,荣耀亿万口碑的X系列手机,带来了全新的九年里程碑之作荣耀X40,售价1499元起。作为档位首款采用OLED硬核曲屏并兼顾轻薄长续航的跃级体验标杆三宝住两房危险重重!42岁孕妈顺利生下三胞胎咱先来看一组数据妈妈们如果想通过自然怀孕怀上三胞胎几率约为1100000也就是说大约10万个孕妇只有1个可能怀是三胞胎再来看一组数据40岁以上的产妇自然怀孕几率约为1020也就是说总演美人,却让人get不到美貌的6位女星,谁的审美出问题?娱乐圈里美女众多,可是至少要有八九分长相或者脱胎换骨般的演技才能演出倾城绝色类角色,所以这类女星并不多。经常演大美人类角色又被观众信服的有比如蒋勤勤,她饰演的西施,端庄温婉,简直就莱后欧洲王室最会穿,却输在身高上?毕竟她的王室同事都是巨人啊西班牙王后莱蒂齐亚很会穿是不争的事实,不仅会穿人还很挺拔不过莱后的气场在她身高快两米的老公面前被大大削弱,毕竟莱后身高才165cm左右,脚踩恨天高也小鸟依人连两个闺女身高都超了17邯郸保卫战是怎么做到战国四公子有其三的经历过长平惨败后,赵国能抗住秦军的长期围攻,这是相当不容易的邯郸保卫战,是秦国许久没有经历过的大败魏楚联军收复了很多失地,秦国几乎退出河东道此后,秦国虽然重新夺取了很多土地,却也降英国王室态度180度大转弯!哈里王子将突破禁令着军装参加守灵据每日邮报报道,英国王子哈里和威廉预定于当地时间17日在伦敦威斯敏斯特大厅加入女王其他6名孙子女的行列,出席祖母的特别守灵仪式。虽然先前哈里因为淡出王室,被撤销军衔,禁止在王室任何西班牙王后莱蒂齐亚身穿16岁女儿莱昂诺公主的蕾丝上衣出席活动西班牙王后莱蒂齐亚似乎还没有准备好穿她的秋装,因为她穿着据说是她女儿的白色上衣走出来。西班牙王后莱蒂齐亚早些时候被拍到出席瓜达拉哈拉大学医院扩建的落成典礼。在这个场合,西班牙王后穿人生下半场,稳住自己喜欢这么一段话当你平静地把该做的事做好,生活自然会把该给你的东西,在合适的时候,一样一样都给你,好的人生,不慌不忙。日常琐碎人际交错生计奔波,是每天都在上演的现实扎心剧。作为成年人