jedis分布式锁实现

  • Post author:
  • Post category:其他




方案1

利用setNX若存在则不插入,不存在则插入成功,同时value为时间戳,没拿到锁则判断时间是否过期,get拿到时间戳,过期则用,getset方式赋值,在比较时间戳是否过期,过期则拿到锁;

@Component
public class RedisLock {
 
    @Autowired
    StringRedisTemplate stringRedisTemplate;
 
    public boolean addLock(String key, long expireTime) throws InterruptedException {
        boolean lock = true;
        long now = System.currentTimeMillis();
        //setNx
        while (!stringRedisTemplate.opsForValue().setIfAbsent(key, expireTime + "")) {
            String lastTime = stringRedisTemplate.opsForValue().get(key);
            if (StringUtils.isNotEmpty(lastTime) && Long.parseLong(lastTime) < now) {
                // getset
                String recentTime = stringRedisTemplate.opsForValue().getAndSet(key, expireTime + "");
                //必须判断上一次时间和当前取出来的时间一致才行
                if (StringUtils.isNotEmpty(recentTime) && recentTime.equals(lastTime)) {
                    lock = true;
                    break;
                }
            }
            System.out.println("等待...");
            Thread.sleep(1000);
        }
        return lock;
    }
 
 
    public void unLock(String key, long nowTime) {
        //加锁解锁同一个人
        if (nowTime == Long.parseLong(stringRedisTemplate.opsForValue().get(key))) {
            stringRedisTemplate.delete(key);
        }
    }
}



方案2:

加锁:set(key,value,‘NX’,‘EX’,expireTime)

解锁:eval(lua语句,key1,value)

  • lua脚本

    if redis.call("get",KEYS[1]) == ARGV[1]
    then
        return redis.call("del",KEYS[1])
    else
        return 0
    end
    
    @Component
    public class RedisFlusterLock {
     
        @Autowired
        StringRedisTemplate stringRedisTemplate;
     
        private static final String LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1]  then "
                + "    return redis.call(\"del\",KEYS[1]) " + "else " + "    return 0 " + "end ";
     
        public void tryLock(String key,String value, Long expireTime) {
            while (1 == 1) {
                String setResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> {
                    Object nativeConnection = connection.getNativeConnection();
                    String result = null;
                    if (nativeConnection instanceof JedisCluster) {
                        result = ((JedisCluster) nativeConnection).set(key, value, "NX", "EX", expireTime);
                    }
                    if (nativeConnection instanceof Jedis) {
                        result = ((Jedis) nativeConnection).set(key, value, "NX", "EX", expireTime);
                    }
                    return result;
                });
                if ("ok".equalsIgnoreCase(setResult)) {
                    break;
                } else {
                    try {
                        System.out.println("等待...");
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
     
     
        public boolean unLock(String key, String value) {
            if (stringRedisTemplate.opsForValue().get(key).equals(value)) {
                Boolean delResult = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                    Object nativeConnection = connection.getNativeConnection();
                    long result = 0L;
                    List<String> keys = new ArrayList<>();
                    keys.add(key);
                    List<String> values = new ArrayList<>();
                    values.add(value);
                    if (nativeConnection instanceof JedisCluster) {
                        result = (Long) ((JedisCluster) nativeConnection).eval(LUA, keys, values);
                    }
                    if (nativeConnection instanceof Jedis) {
                        result = (Long) ((Jedis) nativeConnection).eval(LUA, keys, values);
                    }
                    return result == 1L;
                });
                return delResult;
            } else {
                return false;
            }
        }
    }
    


redis官网




其他参考

基于Redis命令:

SET key valueNX EX max-lock-time  

适用于redis单机和redis集群模式

  1. SET命令是原子性操作,NX指令保证只要当key不存在时才会设置value
  2. 设置的value要有唯一性,来确保锁不会被误删(value=系统时间戳+UUID)
  3. 当上述命令执行返回OK时,客户端获取锁成功,否则失败
  4. 客户端可以通过redis释放脚本来释放锁
  5. 如果锁到达了最大生存时间将会自动释放

只有当前key的value和传入的value相同才会执行DEL命令

if redis.call("get",KEYS[1]) == ARGV[1]  
then 
	return redis.call("del",KEYS[1]) 
else 
	 return 0
end 
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
 
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
 
/**
 * redis锁工具类
 */
public class RedisLock {
    public static final String OK = "OK";
    public static final String UNLOCK_LUA = "if redis.call(\"get\",KEYS[1]) == ARGV[1] " + "then "
            + "    return redis.call(\"del\",KEYS[1]) " + "else " + "    return 0 " + "end ";
    /**
     * 单机和集群redis分布式锁
     * 
     * 参考:https://redis.io/commands/set
     * 
     * 版本:Redis Version >= 2.6.12
     * 
     * 命令:SET key-name uuid NX EX max-lock-time
     * 
     * @param keyName redis key name
     * @param stringRedisTemplate stringRedisTemplate
     * @param expireSeconds 锁定的最大时长
     * @return 锁定结果
     */
    public static LockRes tryLock(String keyName, StringRedisTemplate stringRedisTemplate, Integer expireSeconds) {
 
        // 将value设置为当前时间戳+随机数
        String lockValue = System.currentTimeMillis() + UUID.randomUUID().toString();
 
        String redisLockResult = stringRedisTemplate.execute((RedisCallback<String>) connection -> {
            Object nativeConnection = connection.getNativeConnection();
            String result = null;
            // 集群
            if (nativeConnection instanceof JedisCluster) {
                result = ((JedisCluster) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
            }
            // 单机
            if (nativeConnection instanceof Jedis) {
                result = ((Jedis) nativeConnection).set(keyName, lockValue, "NX", "EX", expireSeconds);
            }
            return result;
        });
 
        if (OK.equalsIgnoreCase(redisLockResult)) {
            return new LockRes(true, keyName, lockValue);
        } else {
            return new LockRes(false, keyName, null);
        }
    }
 
    public static Boolean unlock(LockRes lockRes, StringRedisTemplate stringRedisTemplate) {
        if (lockRes.isFlag()) {
            return stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                Long result = 0L;
 
                List<String> keys = new ArrayList<>();
                keys.add(lockRes.getKey());
                List<String> values = new ArrayList<>();
                values.add(lockRes.getValue());
 
                // 集群
                if (nativeConnection instanceof JedisCluster) {
                    result = (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, values);
                }
 
                // 单机
                if (nativeConnection instanceof Jedis) {
                    result = (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, values);
                }
 
                return result == 1L;
            });
        } else {
            return true;
        }
    }
}
public class LockRes {
    // 是否拿到锁,false:没拿到,true:拿到
    private boolean flag;
    // 缓存的键
    private String key;
    // 缓存的值
    private String value;
    
    public LockRes(boolean flag, String key, String value) {
        super();
        this.flag = flag;
        this.key = key;
        this.value = value;
    }
    public boolean isFlag() {
        return flag;
    }
 
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
 
    public String getKey() {
        return key;
    }
 
    public void setKey(String key) {
        this.key = key;
    }
 
    public String getValue() {
        return value;
    }
 
    public void setValue(String value) {
        this.value = value;
    }
}



版权声明:本文为ximaiyao1984原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。