1、Jedis
/**
* 获取分布式锁,成功返回锁标识
* @param lockName 竞争获取锁key
* @param acquireTimeoutInMS 获取锁超时时间
* @param lockTimeOut 锁的超时时间(秒)
* @return 锁标识
*/
public String acquireLockWithTimeout(String lockName, long acquireTimeoutInMS, int lockTimeOut) {
Jedis conn = null;
// 锁标识
String retIdentifier = null;
try {
conn = jedisPool.getResource();
// 锁的值
String identifier = UUID.randomUUID().toString();
// 锁的key
String lockKey = "lock:" + lockName;
// 获取锁超时时间
long end = System.currentTimeMillis() + acquireTimeoutInMS;
while (System.currentTimeMillis() < end) {
// key存在的情况下,不操作,返回0,不存在的情况set identifier 返回1
if (conn.setnx(lockKey, identifier) == 1) {
// 设置 lockKey 生存时间,当 lockKey 过期时,它会被自动删除
conn.expire(lockKey, lockTimeOut);
// 返回锁标识
retIdentifier = identifier;
// 获取到锁直接跳出循环,返回
break;
}
try {
// 轮训睡眠10毫秒
Thread.sleep(10);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
}
/**
* 获取分布式锁,成功则返回锁标识,失败返回空
* @param lockName 竞争获取锁key
* @param lockTimeOut 锁的超时时间(秒)
* @return 锁标识
*/
public String acquireLock(String lockName, int lockTimeOut) {
Jedis conn = null;
// 锁标识
String retIdentifier = null;
try {
conn = jedisPool.getResource();
String identifier = UUID.randomUUID().toString();
String lockKey = "lock:" + lockName;
// key存在的情况下,不操作,返回0,不存在的情况set identifier 返回1
if (conn.setnx(lockKey, identifier) == 1) {
// 设置 lockKey 生存时间,当 lockKey 过期时,它会被自动删除
conn.expire(lockKey, lockTimeOut);
// 返回锁标识
retIdentifier = identifier;
// 获取到锁直接跳出循环,返回
}
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
}
/**
* 释放锁
* @param lockName 竞争获取锁key
* @param identifier 释放锁标识
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis conn = null;
// 锁key
String lockKey = "lock:" + lockName;
// 返回状态
boolean retFlag = false;
try {
conn = jedisPool.getResource();
while (true) {
// 监视lock,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
conn.watch(lockKey);
if (identifier.equals(conn.get(lockKey))) {
//开启事务
Transaction trans = conn.multi();
// 删除key
trans.del(lockKey);
// 执行所有事务块内的命令 EXEC 命令原子性
List<Object> results = trans.exec();
if (results == null) {
continue;
}
retFlag = true;
}
// 取消 WATCH 命令对所有 key 的监视
conn.unwatch();
break;
}
} finally {
if (conn != null) {
conn.close();
}
}
return retFlag;
}
2、RedisTemplate
/**
* 原子性插入(互斥),成功返回true
* @param key
* @param value
* @param timeout-seconds
* @return
*/
public boolean setNX(String key,String value,int timeout) {
return redisTemplate.opsForValue().setIfAbsent(key, value,timeout,TimeUnit.SECONDS);
}
3、Redission
<!-- 分布式redis锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
注入bean,仅供参考,真是环境中参数可以配的优雅一点
@Bean
public RedissonClient redisson(){
// 单机模式
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6380").setDatabase(1);
return Redisson.create(config);
}
java示例
@RequestMapping(value = "/stock")
public String deductStock(){
// 加密的key
String lockKey = "product:001";
// 1.获取锁对象
RLock redissonLock = redisson.getLock(lockKey);
try{
// 2.加锁 等价于 setIfAbsent,默认key过期时间30s
redissonLock.lock();
// 从redis 中拿当前库存的值
int stock = Integer.parseInt(redisUtil.get("stock"));
if(stock > 0){
int realStock = stock - 1;
boolean stock1 = redisUtil.set("stock", realStock+"");
System.out.println("扣减成功,剩余库存:" + realStock);
}else{
System.out.println("扣减失败,库存不足");
}
}finally {
// 3.释放锁
redissonLock.unlock();
}
return"end";
}
Redission中锁底层使用的是lua脚本的方式实现的
- exists 判断 key 是否存在
- 当判断不存在则以hash的方式设置 key
- 然后给设置的key追加过期时间
- 唤起一个后台运行的定时任务,每过设置锁过期时间的1/3时执行一次,给锁进行延期处理
释放锁的时候同样lua脚本的方式
4、分布式锁技巧
场景1:
- 当redis实现抢购单一商品或者多个商品时(redis集群结构),商品分布式锁定义key=product:001时,锁会占用所有节点,如果
key=product:001-100:001;key=product:101-200:101
的方式将锁分散,这样加锁可以使锁落在不同的节点上,充分利用集群多节点方式,实现锁的水平扩容,提升锁的性能