概述
分布式锁有很多种实现方式,比如数据库,zk,Redis等。本文主要讲解Redis方式。
实现
Redis分布式锁使用 SET k v NX EX max-lock-time 实现
该方案在 Redis 官方 SET 命令页有详细介绍。
http://doc.redisfans.com/string/set.html
在介绍该分布式锁设计之前,我们先来看一下在从 Redis 2.6.12 开始 SET 提供的新特性,
命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中:
- EX seconds — 以秒为单位设置 key 的过期时间;
- PX milliseconds — 以毫秒为单位设置 key 的过期时间;
- NX — set key value,当且仅当key不存在才set成功,等效于 SETNX。
- XX — set key value,当且仅当key存在才set成功,等效于 SETEX。
命令 SET k v NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
客户端执行以上的命令:
如果服务器返回 OK ,那么这个客户端获得锁。(RedisTemplate封装为 true)
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。(RedisTemplate封装为 false)
@Slf4j
@Component
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 解锁的lua脚本
*/
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();
}
final Random random = new Random();
/**
* @param millis 毫秒
* @param nanos 纳秒
* @Title: seleep
* @Description: 线程等待时间
* @author yuhao.wang
*/
private void sleep(long millis, int nanos) {
try {
Thread.sleep(millis, random.nextInt(nanos));
} catch (InterruptedException e) {
logger.info("获取分布式锁休眠被中断:", e);
}
}
/**
* 尝试加锁,非阻塞
*/
public boolean tryLock(String key, String value, long expireSeconds) {
final String lockKey = new StringBuilder(RedisConsts.LOCK_KEY_PRE).append(key).toString();
Boolean setBool = redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
byte[] keyByte = stringRedisSerializer.serialize(lockKey);
//springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包
RedisSerializer<String> valueSerializer = (RedisSerializer<String>) redisTemplate.getValueSerializer();
byte[] valueBytes = valueSerializer.serialize(value);
Boolean setBool = connection.set(keyByte, valueBytes, Expiration.seconds(expireSeconds), RedisStringCommands.SetOption.SET_IF_ABSENT);
return setBool;
}
});
if (setBool == null) {
return false;
}
return setBool;
}
/**
* 解锁
*
* @param lockKey
* @param lockValue
* @return
*/
public Boolean unlock(String lockKey, String lockValue) {
lockKey = new StringBuilder(RedisConsts.LOCK_KEY_PRE).append(lockKey).toString();
//注意是Long类型,而不是Integer
RedisScript redisScript = RedisScript.of(UNLOCK_LUA, Long.class);
Object exeResult = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
if (exeResult == null) {
return false;
}
return NumberUtils.LONG_ONE.equals(exeResult);
}
}