1.lua脚本(集成实现了乐观锁,hmset ,expire等)
local key=KEYS[1]; local oldVerion=tonumber(ARGV[1]); local seconds=ARGV[2]; local fieldLen = table.getn(ARGV)-2; local idx=1; local argvIdx=1; local version = redis.call('HINCRBY',key,'version','0'); if(version~=oldVerion) then return 0; end for idx=1,fieldLen,2 do argvIdx=idx+2; redis.call('HSet',key,ARGV[argvIdx],ARGV[argvIdx+1]); end version =redis.call('HINCRBY',key,'version','1'); redis.call('EXPIRE',key,seconds); return version;
2.eval直接调用测试
传入参数
keysCount: 1
key: key11
version: 0
ttl: 6000
field1: icbc
field2:wh
eval "local key=KEYS[1];local oldVerion=tonumber(ARGV[1]);local seconds=ARGV[2];local fieldLen = table.getn(ARGV)-2;local idx=1;local argvIdx=1;local version = redis.call('HINCRBY',key,'version','0');if(version~=oldVerion) then return 0; end for idx=1,fieldLen,2 do argvIdx=(idx-1)+1+2; redis.call('HSet',key,ARGV[argvIdx],ARGV[argvIdx+1]); end version =redis.call('HINCRBY',key,'version','1');redis.call('EXPIRE',key,seconds);return version;" 1 key11 0 6000 field1 icbc field2 wh
3.java代码
@Autowired private StringRedisTemplate redisTemplate; private static final String LUA_SCRIPT_HMSETBYVERSION = "local key=KEYS[1]; " + "local oldVerion=tonumber(ARGV[1]); " + "local seconds=ARGV[2]; " + "local fieldLen = table.getn(ARGV)-2; " + "local idx=1; " + "local argvIdx=1; " + "local version = redis.call('HINCRBY',key,'version','0'); " + "if(version~=oldVerion) then " + " return 0; " + "end " + "for idx=1,fieldLen,2 do " + "argvIdx=idx+2; " + "redis.call('HSet',key,ARGV[argvIdx],ARGV[argvIdx+1]); " + "end " + "version =redis.call('HINCRBY',key,'version','1'); " + "redis.call('EXPIRE',key,seconds); " + "return version;"; private static final String LUA_SCRIPT_HMSETBYVERSION_SHA1 = SHA1.encode(LUA_SCRIPT_HMSETBYVERSION);
public long compareAndHMset(String key, int version, Map<String, String> values, int seconds) { if (CollectionUtils.isEmpty(values)) { return 0; } List<String> keys = Collections.singletonList(key); List<String> argvs = new ArrayList<>(values.size() * 3); argvs.add(Integer.toString(version)); argvs.add(Integer.toString(seconds)); for (Map.Entry<String, String> item : values.entrySet()) { argvs.add(item.getKey()); argvs.add(Strings.isNullOrEmpty(item.getValue()) ? "" : item.getValue()); } Long result = redisTemplate.execute(new RedisCallback<Long>() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { Object nativeConnection = connection.getNativeConnection(); // 集群模式和单点模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行 // 集群 if (nativeConnection instanceof JedisCluster) { try { return (Long) ((JedisCluster) nativeConnection).evalsha(LUA_SCRIPT_HMSETBYVERSION_SHA1, keys, argvs); } catch (redis.clients.jedis.exceptions.JedisNoScriptException ex) { return (Long) ((JedisCluster) nativeConnection).eval(LUA_SCRIPT_HMSETBYVERSION, keys, argvs); } catch (Exception ex) { return 0L; } } else { // 单点 或 哨兵 try { return (Long) ((Jedis) nativeConnection).evalsha(LUA_SCRIPT_HMSETBYVERSION_SHA1, keys, argvs); } catch (redis.clients.jedis.exceptions.JedisNoScriptException ex) { return (Long) ((Jedis) nativeConnection).eval(LUA_SCRIPT_HMSETBYVERSION, keys, argvs); } catch (Exception ex) { return 0L; } } } }); return result; }
4.调用
return 0 < compareAndHMset("hashkey11", 1, ImmutableMap.of("field1", "icbc2"), 6000 );