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;
}
});
}