如何实现分布式锁?
什么是分布式锁?
既然是是分布式锁,那么肯定是用在多个进程之间甚至是多个物理机之间,因为在很多场景中我们要保证这个数据的一致性,也就是说这么多的进程,我必须保证在同一时刻只能有一个线程在执行,这样的情景在一个进程内很好解决,我们可以使用Java给我们提供的各种锁、比如说,Synchronized、ReentrantLock、ReadWriteLock等,但是在多进程条件下这些锁就不太适用了,比如说在电商系统的订单业务模块这个业务模块某一项功能是定时的将没有支付的订单给取消掉,同时商品的库存加上订单里面该商品的数量,加入这个模块是集群部署的,那么该如何保证某一时间只有一个在执行呢?负责就会出现数据错乱的倾情况,这我们就必须借助于其他第三方工具比如Redis、Zookeeper、mysql,因为这些东西都是各个进程共同要访问的。
分布式锁的实现方式
1、基于Redis的分布式锁
redis里面有一个方法叫做setnx ,如果设置成功则返回1,否则返回0,同时在设置的时候我们可以同时设置一个最大超时时间,这个是为了防止死锁的发生,接下来执行我们的业务逻辑,当执行完毕后执行del这个命令,删除这个锁,从某种意义上说就是一个标志位,在这种场合下称之为锁。代码描述:
Long result = jedis.setnx("lock","value")//步骤一,
if(result!=null && result.intValue==1){
jedis.expire("lock",50) //步骤二,设置有效期,防止死锁,
.
. 业务逻辑部分,在执行业务逻辑期间,其他进程通过setnx("lock","value"),返回0
.
jedis.del("lock")//释放这把锁
}else{
System.out.pringln("获取锁失败")
}
但是这时候表面上看似乎没什么问题,但是如果在步骤一和步骤二之间出现了异常怎末办也就是有效期没有设置上而且也没有删除,此时lock这把锁就一直存在Redis里面了,其他进程想设置但始终会0因此又会导致死锁出现的可能性
2、基于Redis分布式锁的升级版
接着上面的问题来看,当其它进程想要获取这这把锁的时候那么这个进程可以通过getset命令进行重新设置的,但是我要知道到这个key的最大有效时间,我也不能每次都通过getset来覆盖你啊,万一其他线程正在使用呢?因此我们把方法一中的value进行修改(当前时间+最大有效期),通过时间进行判断。
int lockTimeout = 50; //假设最大超时时间是50秒
Long result = jedis.setnx("lock",String.valueOf(System.currentTimeMillis()+lockTimeout))//步骤一,
if(result!=null && result.intValue==1){
jedis.expire("lock",50) //步骤二,设置有效期,防止死锁,
//业务逻辑部分.........在执行业务逻辑期间,其他进程通过setnx("lock","value"),返回0
jedis.del("lock")//释放这把锁
}else{
String lockValueStr = RedisPoolUtil.get("lock"); //获取其他线程设置的超时时间
if(lockValueStr!=null && System.currentTimeMillis()>Long.parseLong(lockValueStr)){ //如果当前时间大于其他线程设置的超时时间
String ret = RedisPoolUtil.getSet("lock",String.valueOf(System.currentTimeMillis()+lockTimeout));
if(ret==null||lockValueStr.equals(ret)){ //再次进行判断
jedis.expire("lock",50)
//执行业务逻辑............
jedis.del("lock")//释放这把锁
}else{
System.out.println("未能够获得分布式锁");
}
}else{
System.out.println("未能够获取分布式锁");
}
}
3、基于Redisson实现的分布式锁
Redisson为开发者提供了一系列具有分布式特性的常用工具类,接下来看看如何使用
1、引入jar包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-avro</artifactId>
<version>2.9.0</version>
</dependency>
2、初始化配置
public class RedissonManager {
private Config config = new Config();
private Redisson redisson = null;
public Redisson getRedisson() {
return redisson;
}
private static String redisIp = "127。0.0.1";
private static Integer redisPort = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
@PostConstruct
private void init(){
try {
config.useSingleServer().setAddress(new StringBuffer().append(redisIp).append(":").append(redisPort).toString());
redisson = (Redisson) Redisson.create(config);
log.info("init redisson finished");
}catch (Exception e){
}
}
}
3、使用
public void closeTask3(){
RLock lock = redissonManager.getRedisson().getLock("lock");
boolean getLock = false;
try {
if(getLock=lock.tryLock(0,50, TimeUnit.SECONDS)){
//业务逻辑处理.....
}else{
System.out.print("获取分布式锁失败")
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(!getLock){
return;
}
lock.unlock(); //解锁
log.info("Redisson分布式锁释放锁");
}
}
使用Redison实现的分布式锁,相对于我们自己来写要非常简单,当然Redison的功能可不止这么多