分布式锁
目前几乎所有的大型网站及应用都是采用分布式部署的方式,分布式系统开发带来的优点很多,高可用,高并发,水平扩展,分开部署等。但分布式的开发也带来了一些新问题,有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API ,也就是我们常说的“锁”(如synchronized,lock),但是这些API在分布式场景中就无能为力了,也就是说Java没有提供分布式锁的功能。
基于分布式锁的实现有多种方案,常见的有基于数据库本身的锁来实现,或者基于zookeeper的API实现,或者是基于缓存来实现分布式锁等等,这些方案都各有可取之处,今天我们介绍的是基于redis的缓存实现分布式锁的方案。
在使用redis 实现,又两种方案。
第一种方案自己封装实现Lock和unLock函数。
第二种方案 Redisson 实现,这是一个牛逼的框架,属于二次开发,在redis的基础上开发得到的Redisson。
reids 实现分布式锁的原理
基于Redis的分布式锁,是常见的分布式锁实现方式之一。它通过以下一些机制来解决分布式锁的相关问题。
1. 通过setnx命令的排他性来达成锁的互斥。setnx命令的特性是,在插入一个key、value对时,当key不存在时,可以设置成功,返回1,当key已经存在时,会设置失败,返回0。
2. 通过锁超时来达成避免死锁的问题。当在Redis中插入key成功后,即认为获取锁成功,当锁使用完成后,需要删除相应key,即释放锁。当删除失败时,就会出现死锁问题。这时候,可以通过在set时,设置超时时间来解决。如设置超时时间为10s等。
3. 通过独立线程来完成锁续租。由于加入了锁超时机制,所以,有可能会发生任务还未完成,锁就被释放的问题。一些分布式锁的开源框架,会通过一个单独线程,来不断通过exprie命令重刷key的过期时间,来达成一个锁续租的目的。
4. 通过value来配合解决锁重入问题。在分布式锁下,value可以是当前进程或线程的一个唯一id,当获取锁时,如果发现锁已经存在,可以根据value值来判断,是否可以获取锁,来达成一个锁重入的目的。
第一种方案实现:
先在pom.xml中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
java 实现 redis如下:
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
*
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
//相当于setnx命令
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);
if (!StringUtils.isEmpty(currentValue)
&& currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("【redis分布式锁】 解锁异常,{}", e);
}
}
}
第二种实现方案
pom.xml 的引入
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
java 代码实现:
import org.junit.jupiter.api.Test;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
public class RedisTickTest {
private int count = 100;
private RLock lock =getRedissonLock();
/**
* 获取锁
* @return
*/
private RLock getRedissonLock(){
Config config = new Config();
config.useSingleServer().setAddress("redis://10.0.0.1:6379").setPassword("123456");
Redisson redisson = (Redisson) Redisson.create(config);
RLock lock = redisson.getLock("xxx");
return lock;
}
@Test
public void tickeTest() throws Exception {
RedisTickTest.TickRunnable tr = new RedisTickTest.TickRunnable();
new Thread(tr,"窗口A").start();
new Thread(tr,"窗口B").start();
new Thread(tr,"窗口c").start();
Thread.sleep(10000);
}
public class TickRunnable implements Runnable {
@Override
public void run() {
while (count>0) {
lock.lock();
try {
if(count>0){
System.out.println(Thread.currentThread().getName()+"售出:"+ count -- + " 张票");
}
}finally {
lock.unlock();
}
}
}
}
}
上面的例子相对比较简单,因为精力能力有限,楼主没法给大家展示真正的分布式锁的实现效果,但从原理上其实是一样的,都是用redis的setnx命令来加上锁,保证分布式环境下锁住的对象只能被一个线程访问,而且从实现方式上来说也比较简单 。
标签:currentValue,缓存,--,lock,redis,value,key,分布式 From: https://blog.51cto.com/u_15461374/5938160