简单聊聊分布式锁,以及如何实现
在25号的软件架构师考试里面,就提到了关于分布式锁的实现以及中间存在的问题,这里简单做一个学习。
题目的大概得意思就是说,小王要做一个秒杀系统,在实现上,小王采用数据库来实现分布式锁;小李觉得数据库来实现的话,会有很大的风险,小李觉的使用redis+过期时间的方式来实现。
假设有一个stock的资源,初始值为1。
public static Long stock 1L;
如果说我们不加锁的话,在单线程的情况下是没有问题的,当判断stock的值是否等于1,如果等于1的话,stock-1,表示秒杀成功;否则秒杀失败。
但是在多线程的情况下,就不能保证stock不超卖,像下面这样。
public static void main(String[] args) throws Exception{
for(int i=0;i<3;i++){
new Thread(
()->{
try{
// 调用秒杀的方法
}catch(Exception e){
e.printStackTrace();
}
}
).start();
}
}
如果想要在多线程的情况下,保证数据的一致性,我们就需要对资源进行加锁,我们可以用java的synchronized
实现。
public static void placeOrder() throws Exception{
// 加上同步锁
synchronized(stock){
if(stock>0){
// 业务操作stock
stock--;
}
}
}
这样子能保证一个在一个数据库里面stock是一致性的,但是说如果是分布式数据库的话,synchronized就没有办法了,因为他是jvm级的。
这个时候就会想到使用redis的setnx来实现,往redis里面set一个key,如果这个key有值的话,就表示这个资源正在被占用,其他请求需要等待资源的释放,但是这里存在一个问题,就是说服务器 挂掉的话,这个redis里面的值就没有办法的到释放。
所以需要加一个超时时间。
这也有一个问题,就是说,如果业务的处理时间大于超时时间的话,业务还没有执行完,资源就释放了,导致其他线程访问资源,导致不一致性,这里就需要加一个超时时间。
public class V3{
@Autowired
RedisTemplate redisTemplate;
public static Long stock =1L;
public static final String LOCK_KEY = "Lock::productId";
public void placeOrder(){
Boolean flag = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "1", 10, TimeUnit.SECONDS);
try{
if(flag){
if(stock>0){
// 处理
stock--;
}
}
}catch(Exception e){
e.printStackTrace()
}finally{
redisTemplate.delete(LOCK_KEY);
}
}
}
因为redis在主从集群里面的话,是将设置的值保存到一个redis服务里面,然后返回成功或者失败,然后在同步到从服务器,如果在设置的时候主服务器宕机了,那么就会收不到;这里redis提供了一个redisson,主服务器接受到消息之后设置成功之后不会立马返回true或者false,会先把主服务器的数据同步到从服务器,如果都成功了,才会返回true,否则返回false。
public class V4{
@Autowired
RedissonClient redissonClient;
public static final String LOCK_KEY = "Lock::productID";
public void placeOrder(){
RLock lock = redissonCLient.getLock(LOCK_KEY);
lock.lock();
try{
// 执行的业务
stock--;
} catch(Exception ex){
ex.printStackTrace();
} finally{
lock.unlock();
}
}
}
import org.junit.jupiter.api.Test;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.spring.starter.RedissonAutoConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.concurrent.TimeUnit;
/***
*
* redLock 实现分高可用布式锁
*/
@SpringJUnitConfig(classes ={RedissonAutoConfiguration.class,RedisConfig.class} )
public class V5 {
public static Long stock=1L;
public static final String LOCK_KEY="lock::productId";
@Autowired
RedissonClient redisson;
/**
* 下单
*/
public void placeOrder() {
// 这里需要不同的redssion客户端,配置连接到不同的redis服务器
RLock lock = redisson.getLock(LOCK_KEY);
RLock lock2 = redisson.getLock(LOCK_KEY);
RLock lock3 = redisson.getLock(LOCK_KEY);
//
RedissonRedLock redissonRedLock = new RedissonRedLock(lock,lock2,lock3);
redissonRedLock.lock(30,TimeUnit.SECONDS);
try {
if (Thread.currentThread().getName().equals("Thread-1")) {
throw new RuntimeException("人为异常!");
}
if (stock > 0) {
Thread.sleep(100); //模拟执行业务...
stock--;
System.out.println(Thread.currentThread().getName() + "秒杀成功");
} else {
System.out.println(Thread.currentThread().getName() + "秒杀失败!库存不足");
}
} catch (Exception ex) {
System.out.println(Thread.currentThread().getName() + "异常:");
ex.printStackTrace();
}
finally {
lock.unlock();
System.out.println(stock);
}
}
@Test
public void main() throws InterruptedException {
for (int i=0;i<3;i++){
Thread thread = new Thread(() -> {
placeOrder();
});
thread.start();
thread.join();
}
}
}