Redis分布式鎖實(shí)例分析講解
1 一人一單并發(fā)安全問(wèn)題
之前一人一單的業(yè)務(wù)使用的悲觀鎖,在分布式系統(tǒng)下,是無(wú)法生效的。
理想的情況下是這樣的:一個(gè)線程成功獲取互斥鎖,并對(duì)查詢訂單并創(chuàng)建訂單,其他線程無(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)有的線程在別的線程已經(jīng)拿到鎖的情況下,仍然可以獲取的到鎖。
這個(gè)時(shí)候,普通的JVM中的鎖就已經(jīng)不管用了,就需要我們利用分布式鎖 。
2 分布式鎖的原理和實(shí)現(xiàn)
2.1 什么是分布式鎖
就是可以滿足分布式系統(tǒng)或集群模式下多進(jìn)程可見(jiàn)并且互斥的鎖。
它的實(shí)現(xiàn)原理就是,不同的JVM環(huán)境,都來(lái)共用一個(gè)鎖監(jiān)視器。這樣就不會(huì)導(dǎo)致出現(xiàn)多個(gè)線程用多把鎖的情況了。
特點(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類型方法中的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)類:
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ù)名稱,也就是鎖的名稱 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) { //獲取線程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)類 * </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è)用戶只能下一單!"); } 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和用戶id判斷訂單是否已經(jīng)存在 //如果存在,則返回錯(cuò)誤信息 int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count(); if (count > 0) { return Result.fail("用戶已經(jīng)購(gòu)買!"); } //7. 創(chuàng)建訂單 VoucherOrder voucherOrder = new VoucherOrder(); //7.1添加訂單id Long orderId = redisIdWorker.nextId("order"); voucherOrder.setId(orderId); //7.2添加用戶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.clients.jedis.exceptions.JedisDataException:?NOAUTH?
本文主要介紹了redis.clients.jedis.exceptions.JedisDataException:?NOAUTH?Authentication?required數(shù)據(jù)操作異常的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05redis5.0以上基于密碼認(rèn)證的集群cluster方式
這篇文章主要介紹了redis5.0以上基于密碼認(rèn)證的集群cluster方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Caffeine實(shí)現(xiàn)類似redis的動(dòng)態(tài)過(guò)期時(shí)間設(shè)置示例
這篇文章主要為大家介紹了Caffeine實(shí)現(xiàn)類似redis的動(dòng)態(tài)過(guò)期時(shí)間示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08redis哈希類型_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis哈希類型的常用方法及原理淺析,感興趣的朋友一起看看吧2017-08-08window手動(dòng)操作清理redis緩存的技巧總結(jié)
在本篇文章中小編給大家分享了關(guān)于window環(huán)境手動(dòng)操作清理redis緩存的方法和技巧,有興趣的朋友們可以跟著學(xué)習(xí)下。2019-07-07