redis分布式锁的实现(2)-基于setNX+lua实现分布式锁

3,322 阅读2分钟

基于setNX + Lua脚本的实现

  • Lua简介
    • 从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用 EVAL 命令对 Lua 脚本进行求值。
    • Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
  • Lua脚本配置流程
    • 1、在resource目录下面新增一个后缀名为.lua结尾的文件
    • 2、编写lua脚本
    • 3、传入lua脚本的key和arg
    • 4、调用redisTemplate.execute方法执行脚本

    jedis 加锁

public static boolean lock(String key,String lockValue,int expire){
       if(null == key){
           return false;
       }
       try {
           Jedis jedis = getJedisPool().getResource();
           String res = jedis.set(key,lockValue,"NX","EX",expire);
           jedis.close();
           return res!=null && res.equals("OK");
       } catch (Exception e) {
           return false;
       }
   }
   

lua脚本加锁

add.lua

local lockKey = KEYS[1]
local lockValue = KEYS[2]

-- setnx info
local result_1 = redis.call('SETNX', lockKey, lockValue)
if result_1 == true
then
local result_2= redis.call('SETEX', lockKey,3600, lockValue)
return result_1
else
return result_1
end
 /**
     * 获取lua结果
     * @param key
     * @param value
     * @return
     */
    public Boolean luaExpress(String key,String value) {
        DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<Boolean>();
        lockScript.setScriptSource(
                new ResourceScriptSource(new ClassPathResource("add.lua")));
        lockScript.setResultType(Boolean.class);
        // 封装参数
        List<Object> keyList = new ArrayList<Object>();
        keyList.add(key);
        keyList.add(value);
        Boolean result = (Boolean) redisTemplate.execute(lockScript, keyList);
        return result;
    }

释放锁

unlock.lua

if redis.call('get', KEYS[1]) == ARGV[1] 
   then 
       return redis.call('del', KEYS[1]) 
   else 
       return 0 
end
private static final Long lockReleaseOK = 1L;
static String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";// lua脚本,用来释放分布式锁

public static boolean releaseLock(String key ,String lockValue){
   if(key == null || lockValue == null) {
       return false;
   }
   try {
       Jedis jedis = getJedisPool().getResource();
       Object res =jedis.eval(luaScript,Collections.singletonList(key),Collections.singletonList(lockValue));
       jedis.close();
       return res!=null && res.equals(lockReleaseOK);
   } catch (Exception e) {
       return false;
   }
}

采用spring redisTemplate 实现分布式锁

spring-data-redis的版本尽量高版本,2.0以下的connection.set是没有返回值的。

    @Component
    public class RedisLock {
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
        //加锁
        public Boolean setNX(final String key, final String requestId, final long expirationTime, final TimeUnit timeUnit) {
            return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.set(key.getBytes(), (value == null ? "" : value).getBytes(),
                    Expiration.from(expirationTime, timeUnit),
                    RedisStringCommands.SetOption.ifAbsent()));
        }
        //释放锁
        public  Boolean releaseLock(String key, String requestId) {
            return (Boolean) redisTemplate.execute((RedisCallback<Boolean>) connection -> {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Boolean result = connection.eval(script.getBytes(), ReturnType.BOOLEAN, 1, key.getBytes(), requestId.getBytes());
                return result;
            });
        }
    }