示例1(存在问题)
import redis.clients.jedis.Jedis;
import java.util.UUID;
/**
* @author lee
* @date 2020/8/26 22:01
*/
public class RedisLock {
/**
* 上锁
*
* @param key
* @return
*/
public static String lock(String key) {
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
String value = fetchLockValue();
if ("ok".equals(jedis.set(key, value))) {
jedis.expire(key, 10);
return value;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return null;
}
/**
* 解锁
*
* @param key
* @param value
* @return
*/
public static boolean unLock(String key, String value) {
if (key == null || value == null) {
return true;
}
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
if (jedis.exists(key) && jedis.get(key).equals(value)) {
jedis.del(key);
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
/**
* 获取唯一value
*
* @return
*/
public static String fetchLockValue() {
return UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
}
public static void main(String[] args) {
String key = "订单号";
String value = lock(key);
try {
//to do something
} catch (Exception e) {
e.printStackTrace();
} finally {
unLock(key, value);
}
}
}
上述代码存在以下问题:
在上锁时,如果成功了,但是在执行expire时系统崩溃了,那么这个锁将不会被释放掉
在解锁时,如果一个客户端执行完“判断是否自己持有锁”步骤后,得出自己持有锁的结论,此时锁的过期时间到了,自动被 redis 释放了,同时另一个客户端又基于这个 key 加锁成功,如果第一个客户端还继续执行删除 key-value的操作,就将不属于自己的锁给释放了。
示例2(正确的写法)
package com.chenguangli.spring.redis;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.UUID;
/**
* @author lee
* @date 2020/8/27 22:53
*/
public class RedisLock2 {
/**
* 上锁
*
* @param key
* @return
*/
public static String lock(String key) {
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
String value = fetchLockValue();
if ("ok".equals(jedis.set(key, value, "NX", "EX", 10))) {
return value;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return null;
}
/**
* 解锁
*
* @param key
* @param value
* @return
*/
public static boolean unLock(String key, String value) {
if (key == null || value == null) {
return true;
}
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
String command = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
if (jedis.eval(command, Collections.singletonList(key),Collections.singletonList(value)).equals(1L)) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return false;
}
/**
* 获取唯一value
*
* @return
*/
public static String fetchLockValue() {
return UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
}
public static void main(String[] args) {
String key = "订单号";
String value = lock(key);
try {
//to do something
} catch (Exception e) {
e.printStackTrace();
} finally {
unLock(key, value);
}
}
}
上锁时使用jedis.set(key, value, "NX", "EX", 10)),保证set key 和设置过期时间是原子性的
解锁时使用eval脚本保证自己持有锁的机制是用加锁的时候的 key-value 来判断当前的 key 的值是否等于自己持有锁时获得的值。