# demo **Repository Path**: Rbang/demo ## Basic Information - **Project Name**: demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-05-13 - **Last Updated**: 2022-08-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # demo #### 介绍 sudo docker build -t demo:v1 . sudo docker run -d -p 10010:10010 demo:v1 --name demo1 9.4.1 分布式令牌桶限流Lua脚本 分布式令牌桶限流Lua脚本的核心逻辑和Java令牌桶的执行逻辑类 似,只是限流计算相关的统计和时间数据存放于Redis中。 这里将限流的脚本命名为rate_limiter.lua,该脚本既使用Redis 存储令牌桶信息,自身又执行于Redis中,所以笔者将该脚本放置于 base-redis基础模块中,它的代码如下: ---此脚本的环境:redis内部,不是运行在Nginx内部 ---方法:申请令牌 ----1:failed ---1:success ---@param key:key限流关键字 ---@param apply:申请的令牌数量 local function acquire(key, apply) local times = redis.call('TIME'); --times[1] 秒数 --times[2] 微秒数 local curr_mill_second = times[1] *1000000 + times[2]; curr_mill_second = curr_mill_second / 1000; local cacheInfo = redis.pcall("HMGET", key, "last_mill_second", "curr_permits", "max_permits", "rate") ---局部变量:上次申请的时间 local last_mill_second = cacheInfo[1]; ---局部变量:之前的令牌数 local curr_permits = tonumber(cacheInfo[2]); ---局部变量:桶的容量 local max_permits = tonumber(cacheInfo[3]); ---局部变量:令牌的发放速率 local rate = cacheInfo[4]; ---局部变量:本次的令牌数 更多IT书籍请关注:www.cmsblogs.cn local local_curr_permits = max_permits; if (type(last_mill_second) ~= 'boolean' and last_mill_second ~= nil) then --计算时间段内的令牌数 local reverse_permits = math.floor(((curr_mill_second - last_mill_second) / 1000) *rate); --令牌总数 local expect_curr_permits = reverse_permits + curr_permits; --可以申请的令牌总数 local_curr_permits = math.min(expect_curr_permits, max_permits); else --第一次获取令牌 redis.pcall("HSET", key, "last_mill_second", curr_mill_second) end local result = -1; --有足够的令牌可以申请 if (local_curr_permits - apply >= 0) then --保存剩余的令牌 redis.pcall("HSET", key, "curr_permits", local_curr_permits - apply); --保存时间,下次令牌获取时使用 redis.pcall("HSET", key, "last_mill_second", curr_mill_second) --返回令牌获取成功 result = 1; else --保存令牌总数 redis.pcall("HSET", key, "curr_permits", local_curr_permits); --返回令牌获取失败 result = -1; end return result end ---方法:初始化限流器 ---1 success ---@param key key ---@param max_permits 桶的容量 更多IT书籍请关注:www.cmsblogs.cn ---@param rate 令牌的发放速率 local function init(key, max_permits, rate) local rate_limit_info = redis.pcall("HMGET", key, "last_mill_second", "curr_permits", "max_permits", "rate") local org_max_permits = tonumber(rate_limit_info[3]) local org_rate = rate_limit_info[4] if (org_max_permits == nil) or (rate ~= org_rate or max_permits ~= org_max_permits) then redis.pcall("HMSET", key, "max_permits", max_permits, "rate", rate, "curr_permits", max_permits) end return 1; end ---方法:删除限流Key local function delete(key) redis.pcall("DEL", key) return 1; end local key = KEYS[1] local method = ARGV[1] if method == 'acquire' then return acquire(key, ARGV[2], ARGV[3]) elseif method == 'init' then return init(key, ARGV[2], ARGV[3]) elseif method == 'delete' then return delete(key) else --ignore end 该脚本有3个方法,其中两个方法比较重要,分别说明如下: (1)限流器初始化方法init(key,max_permits,rate),此方 法在限流开始时被调用。 更多IT书籍请关注:www.cmsblogs.cn (2)限流检测的方法acquire(key,apply),此方法在请求到 可以向自己发送文件或转发消息​​​ 10.3.7 秒杀的Lua脚本设计 前面讲到,在seckill.lua脚本中完成设置令牌和令牌检查的工作 有两大优势:一是在Redis内部执行Lua脚本天然具备分布式锁的特 点;二是能减少网络传输次数,提高性能。 在seckill.lua脚本中定义了两个方法:setToken令牌设置方法和 checkToken令牌检查方法。其中,setToken令牌设置方法的执行流程 如下: (1)检查token秒杀令牌是否存在,如果存在,就返回标志5,表 明排队过了。 (2)检查以JSON格式缓存的秒杀商品的库存是否足够,如果库存 不够,就返回标志4,表明库存不足。 (3)为秒杀商品减少一个库存,并编码成JSON格式,再一次缓存 起来。 (4)使用hset命令将用户的秒杀令牌保存在Redis哈希表结构 中,其hash key为用户的userId。 (5)最终返回标志1,表明排队成功。 更多IT书籍请关注:www.cmsblogs.cn checkToken令牌检查方法的执行流程如下: (1)使用hget命令从保存秒杀令牌的Redis哈希表结构中,以用 户的userId作为hash key,取出之前缓存的秒杀令牌。 (2)如果令牌获取成功,就返回标志5,表明排队成功。 (3)如果令牌不存在,就返回标志-1,表明没有排队。 seckill.lua脚本的源码如下: -- 返回值说明 -- 1 排队成功 -- 2 排队商品没有找到 -- 3 人数超过限制 -- 4 库存不足 -- 5 排队过了 -- 6 秒杀过了 -- -2 Lua方法不存在 local function setToken(goodId, userId, token) --检查token秒杀令牌是否存在 local oldToken = redis.call("hget", "seckill:queue:" .. goodId, userId); if oldToken then return 5; --返回 5 之前已经排队过了 end --获取商品缓存次数 local goodJson = redis.call("get", "seckill:goods:" .. goodId); if not goodJson then --redis.debug("秒杀商品没有找到") return 2; --返回2秒杀商品没有找到 end --redis.log(redis.LOG_NOTICE, goodJson) 更多IT书籍请关注:www.cmsblogs.cn local goodDto = cjson.decode(goodJson); --redis.log(redis.LOG_NOTICE, "good title=" .. goodDto.title) local stockCount = tonumber(goodDto.stockCount); --redis.log(redis.LOG_NOTICE, "stockCount=" .. stockCount) if stockCount <= 0 then return 4; --返回4库存不足 end stockCount = stockCount - 1; goodDto.stockCount = stockCount; redis.call("set", "seckill:goods:" .. goodId, cjson.encode(goodDto)); redis.call("hset", "seckill:queue:" .. goodId, userId, token); return 1; --返回1排队成功 end -- 返回值说明 -- 5 排队过了 -- -1 没有排队 local function checkToken(goodId, userId, token) --检查token是否存在 local oldToken = redis.call("hget", "seckill:queue:" .. goodId, userId); if oldToken and (token == oldToken) then --return 1 ; return 5; --5 排队过了 end return -1; ---1 没有排队 end local method = KEYS[1] local goodId = ARGV[1] local userId = ARGV[2] local token = ARGV[3] --执行lua脚本时传入的key1 --执行lua脚本时传入的value1 --执行lua脚本时传入的value2 --执行lua脚本时传入的value3 if method == 'setToken' then return setToken(goodId, userId, token) elseif method == 'checkToken' then return checkToken(goodId, userId, token) 更多IT书籍请关注:www.cmsblogs.cn else return -2; --Lua方法不存在 end 以上seckill.lua脚本在Java中可以通过spring-data-redis包的 以下方法来执行: RedisTemplate.execute(RedisScript script, List keys, Object,..., args) 在开发脚本的过程中往往需要进行脚本调试,可以通过Shell指令 redis-cli--eval直接执行seckill.lua脚本,具体的调试执行过程可 查看疯狂创客圈社群的秒杀练习演示视频。 可以向自己发送文件或转发消息​​​