Redis分布式鎖實(shí)例分析講解
1 一人一單并發(fā)安全問(wèn)題
之前一人一單的業(yè)務(wù)使用的悲觀鎖,在分布式系統(tǒng)下,是無(wú)法生效的。
理想的情況下是這樣的:一個(gè)線(xiàn)程成功獲取互斥鎖,并對(duì)查詢(xún)訂單并創(chuàng)建訂單,其他線(xiàn)程無(wú)法干預(yù)。它的原理是會(huì)有一個(gè)鎖監(jiān)視器,來(lái)監(jiān)聽(tīng)是誰(shuí)獲得了鎖。

但是問(wèn)題就出現(xiàn)在:
分布式系統(tǒng)下,有多個(gè)不同的JVM,不同的JVM的環(huán)境下,鎖監(jiān)聽(tīng)器是有多個(gè)的,就會(huì)出現(xiàn)有的線(xiàn)程在別的線(xiàn)程已經(jīng)拿到鎖的情況下,仍然可以獲取的到鎖。

這個(gè)時(shí)候,普通的JVM中的鎖就已經(jīng)不管用了,就需要我們利用分布式鎖 。
2 分布式鎖的原理和實(shí)現(xiàn)
2.1 什么是分布式鎖
就是可以滿(mǎn)足分布式系統(tǒng)或集群模式下多進(jìn)程可見(jiàn)并且互斥的鎖。
它的實(shí)現(xiàn)原理就是,不同的JVM環(huán)境,都來(lái)共用一個(gè)鎖監(jiān)視器。這樣就不會(huì)導(dǎo)致出現(xiàn)多個(gè)線(xiàn)程用多把鎖的情況了。

特點(diǎn):

2.2 分布式鎖的實(shí)現(xiàn)
主要有三種實(shí)現(xiàn)方法,我們可以都來(lái)進(jìn)行一個(gè)對(duì)比。
如下圖:

這里主要講基于Redis的分布式鎖的實(shí)現(xiàn) 。
實(shí)現(xiàn)Reids分布式鎖的方法主要就下面兩個(gè)步驟:
1. 獲取鎖
獲取鎖的方法已經(jīng)是老朋友了,就是使用Redis String類(lèi)型方法中的setnx方法(互斥性)。但是,為了預(yù)防redis服務(wù)器宕機(jī)的問(wèn)題,我們要給鎖設(shè)置一個(gè)超時(shí)時(shí)間,避免出現(xiàn)死鎖。(非阻塞)
所以,獲取鎖的方式可以使用如下代碼
SET lock thread1 nx ex 10
lock是鎖的key,thread1 是value,nx就是setnx方法,ex就是設(shè)置超時(shí)時(shí)間
2. 釋放鎖
釋放鎖就簡(jiǎn)單了,刪除即可。
del lock
代碼實(shí)現(xiàn):
需求:定義一個(gè)接口,利用Redis實(shí)現(xiàn)分布式鎖的功能。

代碼如下:
接口代碼:
package com.hmdp.utils;
public interface ILock {
/**
* 嘗試獲取鎖
* @param timeoutSec 鎖的持有時(shí)間,過(guò)期自動(dòng)釋放
* @return true代表獲取鎖成功,false代表獲取鎖失敗。
*/
boolean tryLock(long timeoutSec);
/**
* 釋放鎖
*/
void unlock();
}接口實(shí)現(xiàn)類(lèi):
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;
/**
* @Version 1.0
*/
public class SimpleRedisLock implements ILock {
//Redis
private StringRedisTemplate stringRedisTemplate;
//業(yè)務(wù)名稱(chēng),也就是鎖的名稱(chēng)
private String name;
public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
this.stringRedisTemplate = stringRedisTemplate;
this.name = name;
}
//key的前綴
private static final String KEY_PREFIX = "lock:";
@Override
public boolean tryLock(long timeoutSec) {
//獲取線(xiàn)程id,當(dāng)作set的value
long threadId = Thread.currentThread().getId();
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(KEY_PREFIX + name, threadId+"", timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
//釋放鎖
@Override
public void unlock() {
//刪除key
stringRedisTemplate.delete(KEY_PREFIX+name);
}
}業(yè)務(wù)層獲取鎖和釋放鎖(優(yōu)惠券秒殺業(yè)務(wù)修改)
package com.hmdp.service.impl;
import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.SimpleRedisLock;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.time.LocalDateTime;
/**
* <p>
* 服務(wù)實(shí)現(xiàn)類(lèi)
* </p>
*
*/
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
@Resource
private ISeckillVoucherService iSeckillVoucherService;
@Resource
private RedisIdWorker redisIdWorker;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result seckillVoucher(Long voucherId) {
//1.獲取優(yōu)惠券信息
SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);
//2.判斷是否已經(jīng)開(kāi)始
if (voucher.getBeginTime().isAfter(LocalDateTime.now())){
Result.fail("秒殺尚未開(kāi)始!");
}
//3.判斷是否已經(jīng)結(jié)束
if (voucher.getEndTime().isBefore(LocalDateTime.now())){
Result.fail("秒殺已經(jīng)結(jié)束了!");
}
//4.判斷庫(kù)存是否充足
if (voucher.getStock() < 1) {
Result.fail("庫(kù)存不充足!");
}
//5.扣減庫(kù)存
boolean success = iSeckillVoucherService.update()
.setSql("stock = stock-1").eq("voucher_id",voucherId).gt("stock",0)
.update();
if (!success){
Result.fail("庫(kù)存不充足!");
}
Long userId = UserHolder.getUser().getId();
//1.創(chuàng)建鎖對(duì)象
SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
//2.嘗試獲取鎖
boolean isLock = lock.tryLock(1200);
if (!isLock){
//獲取鎖失敗
return Result.fail("一個(gè)用戶(hù)只能下一單!");
}
try {
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
} finally {
//釋放鎖
lock.unlock();
}
}
@Transactional
public Result createVoucherOrder(Long voucherId) {
Long userId = UserHolder.getUser().getId();
//6.根據(jù)優(yōu)惠券id和用戶(hù)id判斷訂單是否已經(jīng)存在
//如果存在,則返回錯(cuò)誤信息
int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
if (count > 0) {
return Result.fail("用戶(hù)已經(jīng)購(gòu)買(mǎi)!");
}
//7. 創(chuàng)建訂單
VoucherOrder voucherOrder = new VoucherOrder();
//7.1添加訂單id
Long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
//7.2添加用戶(hù)id
voucherOrder.setUserId(userId);
//7.3添加優(yōu)惠券id
voucherOrder.setVoucherId(voucherId);
save(voucherOrder);
//8.返回訂單id
return Result.ok(orderId);
}
}到此這篇關(guān)于Redis分布式鎖實(shí)例分析講解的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis持久化方式之RDB和AOF的原理及優(yōu)缺點(diǎn)
在Redis中,數(shù)據(jù)可以分為兩類(lèi),即內(nèi)存數(shù)據(jù)和磁盤(pán)數(shù)據(jù),Redis?提供了兩種不同的持久化方式,其中?RDB?是快照備份機(jī)制,AOF?則是追加寫(xiě)操作機(jī)制,本文將詳細(xì)給大家介紹Redis?持久化方式RDB和AOF的原理及優(yōu)缺點(diǎn),感興趣的同學(xué)可以跟著小編一起來(lái)學(xué)習(xí)2023-06-06
微服務(wù)Spring Boot 整合 Redis 實(shí)現(xiàn)好友關(guān)注功能
這篇文章主要介紹了微服務(wù)Spring Boot 整合 Redis 實(shí)現(xiàn) 好友關(guān)注,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-12-12
Redis定時(shí)任務(wù)原理的實(shí)現(xiàn)
本文主要是基于?redis?6.2?源碼進(jìn)行分析定時(shí)事件的數(shù)據(jù)結(jié)構(gòu)和常見(jiàn)操作,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
利用redis實(shí)現(xiàn)聊天記錄轉(zhuǎn)存功能的全過(guò)程
社交類(lèi)軟件聊天功能必不可少,聊天記錄存儲(chǔ)的方式也比較多,比如文本,數(shù)據(jù)庫(kù),云等等,但是最好的選擇還是redis進(jìn)行存儲(chǔ),這篇文章主要給大家介紹了關(guān)于如何利用redis實(shí)現(xiàn)聊天記錄轉(zhuǎn)存功能的相關(guān)資料,需要的朋友可以參考下2021-08-08
淺析Redis中紅鎖RedLock的實(shí)現(xiàn)原理
RedLock?是一種分布式鎖的實(shí)現(xiàn)算法,由?Redis?的作者?Salvatore?Sanfilippo(也稱(chēng)為?Antirez)提出,本文主要為大家詳細(xì)介紹了紅鎖RedLock的實(shí)現(xiàn)原理,感興趣的可以了解下2024-02-02
redis分布式鎖優(yōu)化的實(shí)現(xiàn)
本文主要介紹了redis分布式鎖優(yōu)化的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09

