如何實(shí)現(xiàn)分布式鎖?
什么是分布式鎖?
既然是是分布式鎖,那么肯定是用在多個(gè)進(jìn)程之間甚至是多個(gè)物理機(jī)之間,因?yàn)樵诤芏鄨鼍爸形覀円WC這個(gè)數(shù)據(jù)的一致性,也就是說這么多的進(jìn)程,我必須保證在同一時(shí)刻只能有一個(gè)線程在執(zhí)行,這樣的情景在一個(gè)進(jìn)程內(nèi)很好解決,我們可以使用Java給我們提供的各種鎖、比如說,Synchronized、ReentrantLock、ReadWriteLock等,但是在多進(jìn)程條件下這些鎖就不太適用了,比如說在電商系統(tǒng)的訂單業(yè)務(wù)模塊這個(gè)業(yè)務(wù)模塊某一項(xiàng)功能是定時(shí)的將沒有支付的訂單給取消掉,同時(shí)商品的庫存加上訂單里面該商品的數(shù)量,加入這個(gè)模塊是集群部署的,那么該如何保證某一時(shí)間只有一個(gè)在執(zhí)行呢?負(fù)責(zé)就會出現(xiàn)數(shù)據(jù)錯(cuò)亂的傾情況,這我們就必須借助于其他第三方工具比如Redis、Zookeeper、mysql,因?yàn)檫@些東西都是各個(gè)進(jìn)程共同要訪問的。
分布式鎖的實(shí)現(xiàn)方式
1、基于Redis的分布式鎖
redis里面有一個(gè)方法叫做setnx ,如果設(shè)置成功則返回1,否則返回0,同時(shí)在設(shè)置的時(shí)候我們可以同時(shí)設(shè)置一個(gè)最大超時(shí)時(shí)間,這個(gè)是為了防止死鎖的發(fā)生,接下來執(zhí)行我們的業(yè)務(wù)邏輯,當(dāng)執(zhí)行完畢后執(zhí)行del這個(gè)命令,刪除這個(gè)鎖,從某種意義上說就是一個(gè)標(biāo)志位,在這種場合下稱之為鎖。代碼描述:
Long result = jedis.setnx("lock","value")//步驟一,
if(result!=null && result.intValue==1){
jedis.expire("lock",50) //步驟二,設(shè)置有效期,防止死鎖,
.
. 業(yè)務(wù)邏輯部分,在執(zhí)行業(yè)務(wù)邏輯期間,其他進(jìn)程通過setnx("lock","value"),返回0
.
jedis.del("lock")//釋放這把鎖
}else{
System.out.pringln("獲取鎖失敗")
}
但是這時(shí)候表面上看似乎沒什么問題,但是如果在步驟一和步驟二之間出現(xiàn)了異常怎末辦也就是有效期沒有設(shè)置上而且也沒有刪除,此時(shí)lock這把鎖就一直存在Redis里面了,其他進(jìn)程想設(shè)置但始終會0因此又會導(dǎo)致死鎖出現(xiàn)的可能性
2、基于Redis分布式鎖的升級版
接著上面的問題來看,當(dāng)其它進(jìn)程想要獲取這這把鎖的時(shí)候那么這個(gè)進(jìn)程可以通過getset命令進(jìn)行重新設(shè)置的,但是我要知道到這個(gè)key的最大有效時(shí)間,我也不能每次都通過getset來覆蓋你啊,萬一其他線程正在使用呢?因此我們把方法一中的value進(jìn)行修改(當(dāng)前時(shí)間+最大有效期),通過時(shí)間進(jìn)行判斷。
int lockTimeout = 50; //假設(shè)最大超時(shí)時(shí)間是50秒
Long result = jedis.setnx("lock",String.valueOf(System.currentTimeMillis()+lockTimeout))//步驟一,
if(result!=null && result.intValue==1){
jedis.expire("lock",50) //步驟二,設(shè)置有效期,防止死鎖,
//業(yè)務(wù)邏輯部分.........在執(zhí)行業(yè)務(wù)邏輯期間,其他進(jìn)程通過setnx("lock","value"),返回0
jedis.del("lock")//釋放這把鎖
}else{
String lockValueStr = RedisPoolUtil.get("lock"); //獲取其他線程設(shè)置的超時(shí)時(shí)間
if(lockValueStr!=null && System.currentTimeMillis()>Long.parseLong(lockValueStr)){ //如果當(dāng)前時(shí)間大于其他線程設(shè)置的超時(shí)時(shí)間
String ret = RedisPoolUtil.getSet("lock",String.valueOf(System.currentTimeMillis()+lockTimeout));
if(ret==null||lockValueStr.equals(ret)){ //再次進(jìn)行判斷
jedis.expire("lock",50)
//執(zhí)行業(yè)務(wù)邏輯............
jedis.del("lock")//釋放這把鎖
}else{
System.out.println("未能夠獲得分布式鎖");
}
}else{
System.out.println("未能夠獲取分布式鎖");
}
}
3、基于Redisson實(shí)現(xiàn)的分布式鎖
Redisson為開發(fā)者提供了一系列具有分布式特性的常用工具類,接下來看看如何使用
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)){
//業(yè)務(wù)邏輯處理.....
}else{
System.out.print("獲取分布式鎖失敗")
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(!getLock){
return;
}
lock.unlock(); //解鎖
log.info("Redisson分布式鎖釋放鎖");
}
}
使用Redison實(shí)現(xiàn)的分布式鎖,相對于我們自己來寫要非常簡單,當(dāng)然Redison的功能可不止這么多

浙公網(wǎng)安備 33010602011771號