Redis采用相同的Lua解释器去运行所有命令,我们可以保证,脚本的执行是原子性的 。作用就类似于加了MULTI/EXEC 。
- Lua 脚本内多个命令以原子性的方式执行,保证了命令执行的线程安全 。
- Lua 脚本结合List命令实现定长队列,实现批量消费 。
- Lua 脚本仅支持单个key的操作,不支持多key的操作 。
LLEN key计算List的长度时间复杂度:O(1) 。LPOP key [count]从List的左侧移除元素时间复杂度:O(N),N为移除元素的个数 。RPUSH key element [element ...]从List的右侧保存元素时间复杂度:O(N),N为保存元素的个数 。
- List的基础命令包括计算List的长度,移除数据,添加数据,整体命令的复杂度都在O(N)的常量时间 。
- 整合上述三个命令,我们能保证实现固定长度的队列,通过判断队列长度是否达到定长结合新增队列元素和移除队列元素来完成 。
LRANGE key start end时间复杂度:O(S+N),S为偏移量start,N为指定区间内元素的数量 。下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推 。你也可以使用负数下标,以 -1 表示列表的最后一个元素,-2 表示列表的倒数第二个元素,以此类推 。LTRIM key start stop时间复杂度:O(N) where N is the number of elements to be removed by the operation.修剪(trim)一个已存在的 list,这样 list 就会只包含指定范围的指定元素 。
- List的基础命令包括批量返回数据和裁剪数据,整体命令的复杂度都在O(N)的常量时间 。
- 整合上述两个命令,我们能够批量消费数据并移除队列数据,通过LRANGE批量返回数据并通过LTRIM保留剩余数据 。
SADD key member [member ...]往Set集合添加数据 。时间复杂度:O(1) 。SISMEMBER key member判断Set集合是否存在元素 。时间复杂度:O(1) 。
- 通过Set集合来保证数据的唯一性,且时间复杂度可控 。
4.1 生产消息
定义LUA脚本CACHE_NPPA_EVENT_LUA ="local retVal = 0 " +"local key = KEYS[1] " +"local num = tonumber(ARGV[1]) " +"local val = ARGV[2] " +"local expire = tonumber(ARGV[3]) " +"if (redis.call('llen', key) < num) then redis.call('rpush', key, val) " +"else redis.call('lpop', key) redis.call('rpush', key, val) retVal = 1 end " +"redis.call('expire', key, expire) return retVal";执行LUA脚本String data = https://www.isolves.com/it/sjk/Redis/2022-08-05/JSON.toJSONString(nppaBehavior);Long retVal = (Long)jedisClusterTemplate.eval(CACHE_NPPA_EVENT_LUA, 1, NPPA_PREFIX + nppaBehavior.getGamePackage(), String.valueOf(MAX_GAME_EVENT_PER_GAME), data, String.valueOf(NPPA_TTL_MINUTE * 60)); 执行效果实现固长队列的数据存储并设置过期时间
- 通过整合llen+rpush+lpop三个命令实现定长队列 。
- 通过lua脚本保证上述命令的原子性执行 。
文章插图
- 整体的执行流程如上图所示,核心理念通过lua脚本的原子性保证了队列长度计算(llen)、队列数据移除(lpop)、队列数据保存(rpush)的原子性执行 。
定义LUA脚本QUERY_NPPA_EVENT_LUA ="local data = https://www.isolves.com/it/sjk/Redis/2022-08-05/{} " +"local key = KEYS[1] " +"local num = tonumber(ARGV[1]) " +"data = redis.call('lrange', key, 0, num) redis.call('ltrim', key, num+1, -1) return data"; 执行LUA脚本Integer batchSize = NppaConfigUtils.getInteger("nppa.report.batch.size", 1);Object result = jedisClusterTemplate.eval(QUERY_NPPA_EVENT_LUA, 1,NPPA_PREFIX + gamePackage, String.valueOf(batchSize)); 执行效果取固定数量的对象,然后保留队列的剩余的消息对象 。
- 通过整合lrange+ltrim两个命令实现消息的批量消费 。
- 通过lua脚本保证上述命令的原子性执行 。
文章插图