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

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。
  及时当勉励 岁月不待人
  能看到这里的人呀,都是菁英。❤❤️❤️❤️❤
  非常感谢

离谱!霍华德搞大台妹肚子??今早闹得沸沸扬扬的霍华德搞大台妹肚子这事儿,大家伙儿都听说了吧?在这里强调一下,目前并未有明确新闻表示该消息为真,因此该新闻真实性有待商榷。消息来源于某知名篮球评论员的电台节目,来国青队时隔9年打入亚青赛八强能否重回世青赛怀抱?3月9日,中国国青队球员在比赛后庆祝晋级。新华社发随着小组末轮以11战平吉尔吉斯斯坦队,中国国青队时隔9年再度打入亚青赛(U20亚洲杯)八强。按照赛事规则,获得亚青赛前四名的球队就象棋古谱金鹏十八变全局篇第53局让左马得先顺手炮局新豪华新智享在高低手对弈中,高手往往采取让一子或双子的方式。在子力弱势的情况下,高手主要利用出子较快的优势来争先取势,以求战略平衡。其间运子占位等战术,值得借鉴。金鹏十八变的让子类1980年,农村娃考上北大,全村凑路费,38年后每户获赠一栋别墅2018年6月,广东湛江市官湖村,一场搬迁宴上,村民们个个红光满面,举杯换盏,场面好不热闹。一栋栋三层高的红顶别墅,便是村民们免费得到的新居,曾经那个十里八乡出名的省级贫困村,如今山西省委巨虎落马受贿9541万判无期,因包养女明星导致被查他原本是一名普通的电话接线员,但凭借察言观色的能力和足够的努力,实现了人生的飞跃,成为一名高官。但是这位高官,从表面上来看,为国家的发展做出了一些贡献。但实际上,这都是一种假象。当全国人大代表金晶科技董事长王刚推动新能源科学有序发展当前,我国新能源产业发展如火如荼,众多行业深度参与其中,金晶科技也加快从建筑玻璃向光伏玻璃产业转型的速度。今年两会,针对山东新能源产业发展中所遇的问题,全国人大代表金晶科技董事长王凭新鲜再度获奖,左庭右院鲜牛肉火锅受邀参加高质量发展盛会阳春三月,万物生长,到处洋溢着生机勃勃的喜悦。继斩获2021年度质量口碑典范企业之后,鲜牛肉火锅的领军品牌左庭右院带着无限殊荣,在春暖花开之际,应邀出席第五届高质量发展交流会暨质量3月15日凌晨,中国传来34个新消息,个人认为非常给力34为什么很多人反对取消教师编制?对此,你们大家怎么看?网友1教师职业无非就两个吸引人的地方1寒暑假,2编制。但教师(中小学教师)又是个比较低端的行业,但凡好点大学毕业的人,都不会这次真是要拼了!再不拼来不及了经济连续下滑,人口持续减少,暗示着香港似乎在走下坡路,但是任何一座伟大的城市都有可能衰落,也有机会被重建。从吸引顶尖人才到吸收全球资本再到减税降费,香港政府正在努力为这座城市注入新向中国芯宣战!高通突然宣布,外媒华为还是输了文90科技大家都知道,老美如今对我国的半导体行业发展制裁套路,和上个世纪对日本的打压是一样的。先发布一系列禁令,堵死各个渠道,再对相关企业进行多番制裁如孟晚舟事件台积电断供,等等,做足农特产文章中国电信奏响乡村振兴春之和鸣(图片来源摄图网)(记者陈锦锋)政府工作报告指出,今年工作重点之一,稳定粮食生产和推进乡村振兴发展乡村特色产业,拓宽农民增收致富渠道。在数字中国的乐章中,三农是最重要的一部分,中国
寻找好赛道何为好赛道?思维力中说到,选择大于努力,思路决定出路。刚进入社会的人儿,都应该听过一句话,男怕入错行,女怕嫁错郎。其实两者都适应。一个好的职业方向决定着一个人的未来和成长空间,一个好的伴侣可以特步国际研究报告卡位跑鞋赛道,品牌蓄势待发(报告出品方作者财通证券,于健,李跃博)1特步国际国内第三大运动品牌1。1品牌历经二十余载,战略变革焕发生机公司成立于1987年,依靠OEM代工起家,2001年特步品牌正式成立,2京东物流90亿收购德邦获提升,三季度收入增长39,净利4。5亿京东90亿收购德邦2022年7月28日晚间,德邦股份公告,京东物流旗下京东卓风于7月26日实现对德邦控股的控制,从而间接控制德邦控股所持有的公司66。4965的股份,合计约89。7中国男篮换帅,周鹏亮明态度!山东官宣新外援,霍华德顶不住了北京时间11月22日,虽然说CBA第二阶段还有2周左右的时间,但是从中国男篮换帅的层面,杜锋下课之后,乔尔杰维奇上任,依旧是吸引众多球迷和媒体的关注。不久前,周鹏接受于嘉的采访,谈山东农村流水席太霸气,百人同吃一锅饭,凉菜端盘用手抓,羡慕了山东农村流水席太霸气,百人同吃一锅饭,凉菜端盘用手抓,羡慕了时代步伐的加快,人们的生活压力也随之越来越大,有时候甚至忙的一天连饭都顾不上吃,属于工作的时间,越来越多,属于自己的时间浙江省十大药膳(点心)状元笔放眼全国,以状元二字命名的小吃不仅多而且名扬四海,多数与金榜题名的状元故事有关。比如浙江嘉兴的状元糕,与一位会做糕点的状元有关南京夫子庙的状元豆是老母亲给状元儿做的健脑养生零食安徽重返辽篮无望!山东冠军中锋申请离队王晗对其不待见或加盟四川山东队在CBA第一阶段的比赛当中取得了六胜三负的战绩,排名联盟第四位,这样的表现完全能够达到预期。虽然王晗每场比赛只启用了十名球员出战,但是结果让人满意,即便在人员使用当中存在很多山东高速悍将正式归队,王晗再添得力帮手,强势冲击CBA总冠军山东高速悍将正式归队,王晗再添得力帮手,强势冲击CBA总冠军。CBA仍旧处于窗口期,不过再有一周的时间各支球队就要出发前往诸暨赛区,迎来十二月初举办的第二阶段的比赛。据悉山东高速传山东名帅离开国家男篮!有望执教山东男篮?最近,中国男篮官宣了乔尔杰维奇将出任中国男篮主教练的消息。随之而来的,就是教练团队的重组。主教练杜锋下课后,他的教练团队也大部分会离开,其中就包括曾经是山东男篮五虎将之一的杨文海。三大股指集体跳空低开,中药旅游白酒跌幅靠前受利空消息的影响,A股三大股指集体跳空低开。其中,沪指低开0。62,深证成指低开0。84,创指低开0。84。从盘面上看,中药旅游白酒煤炭跌幅靠前,web3。0教育题材回调。Wind又有创新产品来了!八家巨头率先出手中国基金报记者李树超在北交所开市满一周年之际,11月21日,北证50成份指数(简称北证50)正式发布实时行情,跟踪该指数的公募基金也来了。当日,易方达华夏广发富国招商汇添富南方嘉实