实现分布式锁之前先看两个 Redis 命令:
-
SETNX
将
key
设置值为value
,如果key
不存在,这种情况下等同SET命令。 当key
存在时,什么也不做。SETNX
是”SETif Not eXists”的简写。返回值Integer reply, 特定值:
-
1
如果key被设置了 -
0
如果key没有被设置
-
例子
redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>
GETSET
自动将key对应到value并且返回原来key对应的value。如果key存在但是对应的value不是字符串,就返回错误。
设计模式
GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现:
INCR mycounter
GETSET mycounter "0"
GET mycounter
返回值
bulk-string-reply: 返回之前的旧值,如果之前Key
不存在将返回nil
。
例子
redis> INCR mycounter
(integer) 1
redis> GETSET mycounter "0"
"1"
redis> GET mycounter
"0"
redis>
这两个命令在 java 中对应为 setIfAbsent
和 getAndSet
分布式锁的实现:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
@Component
@Slf4j
public class RedisLock {
@Autowired
StringRedisTemplate redisTemplate;
/**
* 加锁
* @param key
* @param value 当前时间 + 超时时间
* @return
*/
public boolean lock(String key, String value){
if (redisTemplate.opsForValue().setIfAbsent(key, value)){
return true;
}
//解决死锁,且当多个线程同时来时,只会让一个线程拿到锁
String currentValue = redisTemplate.opsForValue().get(key);
//如果过期
if (!StringUtils.isEmpty(currentValue) &&
Long.parseLong(currentValue) < System.currentTimeMillis()){
//获取上一个锁的时间
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){
return true;
}
}
return false;
}
/**
* 解锁
* @param key
* @param value
*/
public void unlock(String key, String value){
try {
String currentValue = redisTemplate.opsForValue().get(key);
redisTemplate.opsForValue().getOperations().delete(key);
}catch (Exception e){
log.error("【redis锁】解锁失败, {}", e);
}
}
}
使用:
@SpringBootTest
@RunWith(SpringRunner.class)
public class LockTest {
/**
* 模拟秒杀
*/
@Autowired
RedisLock redisLock;
//超时时间10s
private static final int TIMEOUT = 10 * 1000;
@Test
public void secKill(){
String productId="1";
long time = System.currentTimeMillis() + TIMEOUT;
//加锁
if (!redisLock.lock(productId, String.valueOf(time))){
throw new RuntimeException("人太多了,等会儿再试吧~");
}
//具体的秒杀逻辑
System.out.println("秒杀的业务逻辑");
//解锁
redisLock.unlock(productId, String.valueOf(time));
}
}