使用redis 实现分布式锁
在有些需要高可用的场景中,保证并发量的情况下需要使用分布式锁来做控制,保证应用的可靠性。我们知道jdk提供了一些常用的锁比如ReentrantLock,ReentrantReadWriteLock,synchronized。对于这些锁的实现这里就不详细介绍了,在使用过程中这些锁锁的是对象,在单服务器的时候有用。但是现在基本都不是单一服务器了,一般都会有多台服务器。对于一些要求高的业务,而且耗时长的任务,比如当客户端一个请求到A服务器时,A服务器还没处理完,此时客户端又请求B服务器,对于一些非幂等操作,是不允许这样操作的,比如请求第三方下单的时候,就必须保证只有第一次请求有结果后,才能进行第二次请求,否则可能存在重复下单的过程。这个时候就可以对整个下单过程加锁,使用分布式锁。
redis可以实现分布式锁主要是Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。
使用redis实现分布式锁主要使用的几个命令:
SETNX
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
GETSET命令
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。
GET命令
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。
DEL命令
删除给定的一个或多个 key ,不存在的 key 会被忽略。
其中setNX和GetSet可以理解为原子操作,是同步的。
package com.xfl.boot.common.utils.concurrent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* 分布式锁
*/
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private StringRedisTemplate redisTemplate;
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
private String lockKey;
/**
* 锁超时时间,防止线程在入锁以后,无限的执行等待
*/
private int expireMsecs = 60 * 1000;
/**
* 锁等待时间,防止线程饥饿
*/
private int timeoutMsecs = 10 * 1000;
private volatile boolean locked = false;
public RedisLock(StringRedisTemplate redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
public RedisLock(StringRedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
public RedisLock(StringRedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
public String getLockKey() {
return lockKey;
}
public String get(final String key) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] data = connection.get(serializer.serialize(key));
connection.close();
if (data == null) {
return null;
}
return serializer.deserialize(data);
}
});
} catch (Exception e) {
logger.error("get redis error, key : {}", key);
}
return obj != null ? obj.toString() : null;
}
public boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
}
});
} catch (Exception e) {
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (Boolean) obj : false;
}
private String getSet(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
connection.close();
return serializer.deserialize(ret);
}
});
} catch (Exception e) {
logger.error("getSet redis error, key : {}", key);
}
return obj != null ? (String) obj : null;
}
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); // 锁到期时间
if (this.setNX(lockKey, expiresStr)) {
// lock acquired
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); // redis里的时间
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
String oldValueStr = this.getSet(lockKey, expiresStr);
// 获取上一个锁到期时间,并设置现在的锁到期时间,
// 只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
// 延迟100 毫秒
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
public synchronized void unlock() {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
}
String key = "122456";
RedisLock lock = new RedisLock(stringRedisTemplate, key.toString(),
1000, 2000);
try {
if (lock.lock()) {
System.out.println("get lock success");
Thread.sleep(1000);
} else {
System.out.println("can not get lock");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}