参考:
Redis命令
Redis命令
1、分布式锁应满足的四个条件
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
2、Redis流程图
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/15b9c7bbeaa8d46987ff7a266dd362eb.png)
3、pom依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
4、lua脚本示例
1)获取锁
local key = KEYS[1]
local value = ARGV[1]
local expireTime = ARGV[2]
if (redis.call('setnx',key,value) == 1) then
redis.call('pexpire' , key , expireTime)
return 'true'
else
return 'false'
end
2)延长锁
local key = KEYS[1]
local value = ARGV[1]
local newExpireTime = ARGV[2]
if (redis.call('get',key) == value) then
redis.call('pexpire' , key ,newExpireTime)
return 'true'
else
return 'false'
end
3)释放锁
local key = KEYS[1]
local value = ARGV[1]
if (redis.call('get',key) == value) then
redis.call('del' , key )
return 'true'
else
return 'false'
end
5、java示例
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
@Slf4j
@Data
@Accessors(chain = true)
public class RedisLock {
private StringRedisTemplate redisTemplate;
public static final String NX = "NX";
public static final String EX = "EX";
public static final String OK = "OK";
private static final long TIME_OUT = 100;
public static final int EXPIRE = 60;
public static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
private String lockKey;
private String lockKeyLog = "";
private String lockValue;
private int expireTime = EXPIRE;
private long timeOut = TIME_OUT;
private volatile boolean locked = false;
final Random random = new Random();
public RedisLock(StringRedisTemplate redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
public RedisLock(StringRedisTemplate redisTemplate, String lockKey, int expireTime) {
this(redisTemplate, lockKey);
this.expireTime = expireTime;
}
public RedisLock(StringRedisTemplate redisTemplate, String lockKey, long timeOut) {
this(redisTemplate, lockKey);
this.timeOut = timeOut;
}
public RedisLock(StringRedisTemplate redisTemplate, String lockKey, int expireTime, long timeOut) {
this(redisTemplate, lockKey, expireTime);
this.timeOut = timeOut;
}
public boolean tryLock() {
lockValue = UUID.randomUUID().toString();
long timeout = timeOut * 1000000;
long nowTime = System.nanoTime();
while ((System.nanoTime() - nowTime) < timeout) {
if (OK.equalsIgnoreCase(this.set(lockKey, lockValue, expireTime))) {
locked = true;
return true;
}
seleep(10, 50000);
}
return locked;
}
public boolean lock() {
lockValue = UUID.randomUUID().toString();
String result = set(lockKey, lockValue, expireTime);
locked = OK.equalsIgnoreCase(result);
return locked;
}
public boolean lockBlock() {
lockValue = UUID.randomUUID().toString();
while (true) {
String result = set(lockKey, lockValue, expireTime);
if (OK.equalsIgnoreCase(result)) {
locked = true;
return locked;
}
seleep(10, 50000);
}
}
public Boolean unlock() {
if (locked) {
try {
return redisTemplate.execute((RedisConnection connection) -> {
Object nativeConnection = connection.getNativeConnection();
Long result = 0L;
List<String> keys = new ArrayList<>();
keys.add(lockKey);
List<String> values = new ArrayList<>();
values.add(lockValue);
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);
}
if (result == 0 && !StringUtils.isEmpty(lockKeyLog)) {
log.debug("Redis分布式锁,解锁{}失败!解锁时间:{}", lockKeyLog, System.currentTimeMillis());
}
locked = result == 0;
return result == 1;
});
} catch (Throwable e) {
log.warn("Redis不支持EVAL命令,使用降级方式解锁:{}", e.getMessage());
String value = this.get(lockKey, String.class);
if (lockValue.equals(value)) {
redisTemplate.delete(lockKey);
return true;
}
return false;
}
}
return true;
}
public boolean isLock() {
return locked;
}
private String set(final String key, final String value, final long seconds) {
Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
return redisTemplate.execute((RedisConnection connection) -> {
Object nativeConnection = connection.getNativeConnection();
String result = null;
if (nativeConnection instanceof JedisCommands) {
result = ((JedisCommands) nativeConnection).set(key, value, SetParams.setParams().nx().ex((int) seconds));
}
if (nativeConnection instanceof JedisCluster) {
result = ((JedisCluster) nativeConnection).set(key, value, SetParams.setParams().nx().ex((int) seconds));
} else {
log.error("nativeConnection error");
}
if (!StringUtils.isEmpty(lockKeyLog) && !StringUtils.isEmpty(result)) {
log.info("获取锁{}的时间:{}", lockKeyLog, System.currentTimeMillis());
}
return result;
});
}
private <T> T get(final String key, Class<T> aClass) {
Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
return redisTemplate.execute((RedisConnection connection) -> {
Object nativeConnection = connection.getNativeConnection();
Object result = null;
if (nativeConnection instanceof JedisCommands) {
result = ((JedisCommands) nativeConnection).get(key);
}
if (nativeConnection instanceof JedisCluster) {
result = ((JedisCluster) nativeConnection).get(key);
}
return (T) result;
});
}
private void seleep(long millis, int nanos) {
try {
Thread.sleep(millis, random.nextInt(nanos));
} catch (InterruptedException e) {
log.info("获取分布式锁休眠被中断:", e);
}
}
}