前言:
不要一看到“分布式”三个字就就得有多难,多高大上,其实简单的很。先从官方的角度解释一下,什么叫“分布式锁”:
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
听懂了吗?没听懂举个实际中很容易碰到的例子:
Quartz定时任务应该用过吧,那么问题来了,基于分布式,假如一个应用部署在3台服务器上,同一时间点,定时任务会同时执行3次,怎么办?
有人说很简单,写入数据库之前查一下就行了。别忘了,你怎么能保证,在你查的瞬间,其他服务器的线程执行到哪里了呢?再回顾一下这个问题的关键信息:同一时间,不同服务器的不同线程,同时并行执行同一任务。此时,你就需要用到“分布式锁”了。用Redis做分布式锁只是其中的一种实现方式,相对来说,比较简单。
在此之前,先介绍几个Redis的几个小命令:
①【setnx命令】是 SET if Not eXists的简写。将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。返回整数: 1-设置成功 ;0-设置失败。
②【ttl命令】用于获取键到期的剩余时间(秒)。返回-1代表key没有设置超时时间。
③【watch命令】可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null 。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
④【multi命令】标志着一个事务块的开始。
⑤【transaction.exec()】事务开始执行。
⑥【unwatch命令】结束监听。
前面的铺垫说完了,直接上代码:
package com.***.cache;
import java.util.List;
import java.util.UUID;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;
public class JedisManager {
private JedisPool jedisPool;
public JedisPool getJedisPool() {
return jedisPool;
}
public void setJedisPool(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public Jedis getJedis() {
Jedis jedis = null;
try {
jedis = getJedisPool().getResource();
} catch (Exception e) {
throw new JedisConnectionException(e);
}
return jedis;
}
public void returnResource(Jedis jedis, boolean isBroken) {
if (jedis == null)
return;
jedis.close();
}
/**
获取Redis分布式锁
Title: lockWithTimeout
@author Liyan
@date 2017年10月26日 下午3:27:53
@param locaName 锁的key
@param acquireTimeout 获取锁的超时时间,毫秒
@param timeout 锁本身的超时时间,毫秒
@return 锁标识
*/
public String getLock(String locaName, long acquireTimeout, long timeout) {
String retIdentifier = null;
try {
//获取连接
jedis = jedisPool.getResource();
//随机生成一个value
String identifier = UUID.randomUUID().toString();
//锁名,即key值
String lockKey = “lock:” + locaName;
//超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int)(timeout / 1000);
//获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
//【SETNX命令】是 SET if Not eXists的简写。
//将 key的值设为 value,当且仅当 key不存在。若给定的 key已经存在,则 SETNX不做任何动作。
//返回整数: 1-设置成功 ;0-设置失败.
Long setnx = jedis.setnx(lockKey, identifier);
if (setnx == 1) {
//设置锁的有效时间
jedis.expire(lockKey, lockExpire);
//返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
//【TTL命令】用于获取键到期的剩余时间(秒)。
//返回-1代表key没有设置超时时间,为key设置一个超时时间
if (jedis.ttl(lockKey) == -1) {
}
try {
//防止分布式线程并发
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (jedis != null) {
}
}
}
/**
释放锁
Title: releaseLock
@date 2017年10月26日 下午3:40:25
@param lockName 锁的key
@param identifier 释放锁的标识
@return boolean 是否释放成功
*/
public boolean releaseLock(String lockName, String identifier) {
String lockKey = “lock:” + lockName;
boolean retFlag = false;
try {
while (true) {
//对lockKey开始监控
//【watch命令】可用于提供CAS(check-and-set)功能。
//假设我们通过WATCH命令在事务执行之前监控了多个Keys,
//倘若在WATCH之后有任何Key的值发生了变化,
//EXEC命令执行的事务都将被放弃,同时返回Null
jedis.watch(lockKey);
//通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
if (identifier.equals(jedis.get(lockKey))) {
//开启一个事务
//【multi命令】标志着一个事务块的开始。
Transaction transaction = jedis.multi();
//删除lockKey
transaction.del(lockKey);
//执行事务,进行删除
List results = transaction.exec();
if (results == null) {
continue;
}
//标记成功结果
retFlag = true;
}
//结束监听
jedis.unwatch();
break;
}
} finally {
if (jedis != null) {
}
}
return retFlag;
}
}
最后还给程序员们提供一张分布式学习路线的思维导图
喜欢这篇文章的话,可以给作者点个喜欢,点下关注,每天都会分享Java相关文章!