redis采用lua脚本执行业务逻辑,以及在redis中对lua脚本进行debug调试

本文介绍了如何利用Lua脚本来确保Redis操作的原子性,详细阐述了Lua脚本的基础语法、调试方法及Redis的相关命令。通过一个具体的lua脚本实例,展示了如何更新会话信息并确保其逻辑的正确执行。同时,提供了Java代码示例,展示如何将脚本加载到Redis缓存以及通过SHA1校验码执行lua脚本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 背景

今天需要对一批redis操作保证原子性,故采用lua脚本进行实现。

2.核心

1.lua脚本基础语法:可以查看菜鸟教程
2.对lua脚本进行debug,验证脚本准确性,执行命令如下

 1、debug 模式
 redis-cli -h ip -p port --ldb --eval F:/c.lua chat.u.conversation.uid:7:aid:11171385790976491520001000000000:h , 1 2 3 4 5 6 7 8 9 10
 step 进行下一步
 
 2、正常执行模式
 redis-cli -h ip -p port --eval F:/c.lua chat.u.conversation.uid:7:aid:11171385790976491520001000000000:h , 1 2 3 4 5 6 7 8 9 10

3.redis脚本命令(命令参考:https://www.runoob.com/redis/redis-scripting.html)
3.1执行lua脚本命令
EVAL script numkeys key [key …] arg [arg …]使用 Lua 解释器执行脚本,但是这个命令每次都要对脚本进行编译传输,效率低于EVALSHA sha1 numkeys key [key …] arg [arg …],根据给定的 sha1 校验码,执行缓存在服务器中的脚本。
3.2将脚本缓存到redis
SCRIPT LOAD script:将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。

3.注意点

1、服务端为windows时不支持DEBUG
2、DEBUG模式不真正修改数据,只模拟过程,确保脚本能正确执行以及验证业务逻辑。
3、服务端存在密码时不支持DEBUG
4、jedis无法在管道中执行evalSha,需要通过Lettuce执行

4.代码实例

4.1lua脚本内容

-- 1、获取会话hash是否存在
-- 2、存在时判断传进来的最大消息ID是否大于当前存储的最大消息ID
-- 2.1 大于赋值
-- 2.2 小于结束
-- 3、不存在时结束

--[[
存在的坑
  1、服务端为windows时不支持DEBUG
  2、DEBUG模式不真正修改数据
  3、服务端存在密码时不支持

执行命令
 1、debug 模式
 redis-cli -h xxx -p 6380 --ldb --eval F:/c.lua chat.u.conversation.uid:7:aid:11171385790976491520001000000000:h , 1 2 3 4 5 6 7 8 9 10
 step 进行下一步
 
 2、执行模式
 redis-cli -h xxx -p 6380 --eval F:/c.lua chat.u.conversation.uid:7:aid:11171385790976491520001000000000:h , 1 2 3 4 5 6 7 8 9 10

 redis 127.0.0.1:6379> EVALSHA sha1 numkeys key [key ...] arg [arg ...] 	
 EVALSHA 6bc281f8c8a794dc689a06a1136f09a1ca13063f 1 chat.u.conversation.uid:7:aid:11171385790976491520001000000000:h 1 2 3 4 5 6 7 8 9 10
 	
--]]

local result = 1;
if redis.call('EXISTS',KEYS[1]) == 1 then
	 -- 获取当前存储的最后一条消息ID
     local storeVal = redis.call('hget',KEYS[1],'lastMessageId');
	 -- 比较传进来的消息ID是否大于存储的消息ID,小于时不执行
	 if tonumber(ARGV[1]) <= tonumber(storeVal) then
		result = 0
	 end
end

if result == 1 then
	redis.call('hmset',KEYS[1],
		'lastMessageId',ARGV[1],
		'userId',ARGV[2],
		'anotherId',ARGV[3],
		'type',ARGV[4],
		'createBy',ARGV[5],
		'updateBy',ARGV[6],
		'updateDatetime',ARGV[7],
		'tenantId',ARGV[8]
	)
	
	local lastReadMessageId = ARGV[9];
	if (lastReadMessageId ~= nil and lastReadMessageId ~= '' and tonumber(lastReadMessageId) ~= 0) then
		redis.call('hset',KEYS[1],'lastReadMessageId',lastReadMessageId)		
	end
	
	redis.call('hsetnx',KEYS[1],'createDatetime',ARGV[10])
	redis.call('hsetnx',KEYS[1],'topFlag',0)
	redis.call('hsetnx',KEYS[1],'topDatetime',0)
	redis.call('hsetnx',KEYS[1],'disturbFlag',0)
	redis.call('hsetnx',KEYS[1],'startMessageId',0)
	redis.call('hsetnx',KEYS[1],'deleteFlag',0)
end

return result

4.2将脚本加载缓存至redis

 public void start(Application application) {
        RedisCacheTemplate redisCacheTemplate = ApplicationContextUtil.getBean(RedisCacheTemplate.class);
        String luaScriptConversationUpdate = IoUtil.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("lua-tpl/conversation-update.lua"), GlobalConst.CharSet);

        redisCacheTemplate.getRedisTemplate().execute(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                String luaScriptConversationUpdateSha = connection.scriptLoad(luaScriptConversationUpdate.getBytes());
                log.info("luaScriptConversationUpdate.SHA = {}",luaScriptConversationUpdateSha);
                ChatLuaEnum.addLua(ChatLuaEnum.ConversationUpdate,luaScriptConversationUpdateSha);
                return null;
            }
        });
    }

4.3java中通过sha1校验码执行lua脚本

public void addConversationInfoBatchByLua(List<ImConversationRPO> imConversationRPOList) {
		//jedis无法在管道中执行evalSha,所以通过Lettuce执行
        redisCacheTemplate.getLettuceRedisTemplate().executePipelined(new RedisCallback<Object>() {
            @Override
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.openPipeline();
                for (ImConversationRPO imConversationRPO : imConversationRPOList) {
                    String keyName = conversationInfoKey.getName(imConversationRPO.getUserId(), imConversationRPO.getAnotherId());
                    String luaSha = ChatLuaEnum.getLuaSha(ChatLuaEnum.ConversationUpdate);
                    if (Objects.isNull(imConversationRPO.getLastReadMessageId())) {
                        imConversationRPO.setLastReadMessageId(0L);
                    }
                    redisConnection.evalSha(luaSha, ReturnType.INTEGER, 1,
                            keyName.getBytes(),
                            (imConversationRPO.getLastMessageId() + "").getBytes(),
                            imConversationRPO.getUserId().getBytes(),
                            imConversationRPO.getAnotherId().getBytes(),
                            (imConversationRPO.getType() + "").getBytes(),
                            imConversationRPO.getCreateBy().getBytes(),
                            imConversationRPO.getUpdateBy().getBytes(),
                            (imConversationRPO.getUpdateDatetime() + "").getBytes(),
                            imConversationRPO.getTenantId().getBytes(),
                            (imConversationRPO.getLastReadMessageId() + "").getBytes(),
                            (imConversationRPO.getCreateDatetime() + "").getBytes()
                    );
                    redisConnection.pExpire(keyName.getBytes(), TimeoutUtils.toMillis(conversationInfoKey.getTimeout(), conversationInfoKey.getTimeUnit()));
                }
                return null;
            }
        });
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值