點評項目——分布式鎖

2023.12.10

集群模式下的并發安全問題及解決

????????隨著現在分布式系統越來越普及,一個應用往往會部署在多臺機器上(多節點),通過加鎖可以解決在單機情況下的一人一單安全問題,但是在集群模式下就不行了。見下圖:

? ? ? ? 多臺服務器會對應多個jvm,?synchronized鎖可以鎖住單臺服務器的多線程,多臺服務器就鎖不住了,所以我們需要有一個多服務器共享的鎖監視器,這里就需要使用到分布式鎖了,這里我們使用redis的SETNX這個方法來實現。? 流程圖如下:

? ? ? ? 首先定義一個鎖的接口,并實現它:

public interface ILock {/*** 嘗試獲取鎖* @param timeoutSec 鎖持有的超時時間,過期后自動釋放* @return true代表獲取鎖成功; false代表獲取鎖失敗*/boolean tryLock(long timeoutSec);/*** 釋放鎖*/void unlock();
}
public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock:";@Overridepublic boolean tryLock(long timeoutSec) {//獲取線程標識long threadId = Thread.currentThread().getId();//獲取鎖Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,threadId + "",timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//防止拆箱的時候出現空指針異常}@Overridepublic void unlock() {//釋放鎖stringRedisTemplate.delete(KEY_PREFIX + name);}
}

再修改業務代碼:

@Overridepublic Result seckillVoucher(Long voucherId) {//1.查詢優惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//2.判斷秒殺活動是否開始if(voucher.getBeginTime().isAfter(LocalDateTime.now())){//尚未開始return Result.fail("秒殺尚未開始!");}//3.判斷秒殺活動是否已經結束if(voucher.getEndTime().isBefore(LocalDateTime.now())){//尚未開始return Result.fail("秒殺已經結束!");}//4.判斷庫存是否充足if(voucher.getStock() < 1){return Result.fail("庫存不足");}//此處需要將整個函數鎖起來Long userId = UserHolder.getUser().getId();//創建鎖對象SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);//獲取鎖boolean isLock = lock.tryLock(1200);//判斷是否獲取鎖成功if(!isLock){//獲取鎖失敗,不能讓黃牛不斷重復,所以直接返回失敗return Result.fail("不允許重復下單!");}//獲取鎖成功try {//獲取代理對象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//釋放鎖lock.unlock();}

????????再使用jmeter+多臺服務器進行測試,集群模式下的并發安全問題得到解決。

redis分布式鎖誤刪問題及解決

? ? ? ? 考慮一種情況:假設線程1獲取了分布式鎖,然后業務阻塞了,阻塞的時間超過了redis中鎖的超時時間,redis將鎖釋放了。這時線程2就順利獲取了該鎖,并執行它的業務。此時線程1蘇醒了并執行完自己的業務,于是釋放鎖,此時釋放的鎖是線程2剛剛獲取的鎖,意味著此時其他線程也可以獲取鎖進來了,這就又出現了并發安全問題了。 核心原因就在于:線程1在釋放鎖之前沒有判斷一下這把鎖是不是自己之前獲取的鎖,導致誤刪了其他線程的鎖。

????????解決辦法就是:在獲取鎖的時候存入線程標識(用UUID標識,在一個JVM中,ThreadId一般不會重復,但是我們現在是集群模式,有多個JVM,多個JVM之間可能會出現ThreadId重復的情況),在釋放鎖的時候先獲取鎖的線程標識,判斷是否與當前線程標識一致:如果一致則允許釋放。

? ? ? ? 流程圖改為:

? ? ? ? 需要修改SimpleRedisLock.java代碼:

public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock:";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";@Overridepublic boolean tryLock(long timeoutSec) {//獲取線程標識String threadId = ID_PREFIX + Thread.currentThread().getId();//獲取鎖Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name,threadId,timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//防止拆箱的時候出現空指針異常}@Overridepublic void unlock() {//獲取線程標示String threadId = ID_PREFIX + Thread.currentThread().getId();//獲取鎖中的標示String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);//判斷標示是否一致if(threadId.equals(id)) {//釋放鎖stringRedisTemplate.delete(KEY_PREFIX + name);}}
}

分布式鎖的原子性問題及解決

? ? ? ? 然而上述解決方案在極端情況下還有問題:當線程1在判斷完鎖的標示之后,準備釋放鎖之前如果出現阻塞的話(由于jvm的垃圾回收機制等原因),redis的超時時間到了,將鎖釋放掉,其他線程又可以獲取鎖了,則又會出現和上述一樣的情況:線程1會將其他線程的鎖誤釋放掉。 產生此問題的核心原因就在于:判斷鎖標示和釋放鎖這兩個操作不具有原子性。?導致在這期間又有可能出現并發安全問題。

? ? ? ? 這里我們使用Lua腳本解決多條命令原子性問題。Redis提供了Lua腳本功能,在一個腳本中編寫多條Redis命令,確保多條命令執行時的原子性。

? ? ? ? 編寫lua腳本:

--比較線程標示與鎖中的標示是否一致
if(redis.call('get',KEYS[1]) == ARGY[1]) then--釋放鎖return redis.call('del',KEYS[1])
end
return 0

? ? ? ? 調用lua腳本:

    @Overridepublic void unlock() {//調用lua腳本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}

? ? ? ? 這樣判斷和釋放操作就具有原子性了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/213577.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/213577.shtml
英文地址,請注明出處:http://en.pswp.cn/news/213577.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在 Android WebView 中實現和 JavaScript 的互操作

前言 在 APP 中內嵌一個 H5 來實現特定的業務功能已經是非常成熟且常用的方案了。 雖然 H5 已經能夠實現大多數的需求&#xff0c;但是對于某些需求還是得依靠原生代碼來實現然后與 JavaScript 進行交互&#xff0c;例如我目前所負責的項目就是一個 “智能硬件” 設備&#x…

【PyTorch】卷積神經網絡

文章目錄 1. 理論介紹1.1. 從全連接層到卷積層1.1.1. 背景1.1.2. 從全連接層推導出卷積層 1.2. 卷積層1.2.1. 圖像卷積1.2.2. 填充和步幅1.2.3. 多通道 1.3. 池化層&#xff08;又稱匯聚層&#xff09;1.3.1. 背景1.3.2. 池化運算1.3.3. 填充和步幅1.3.4. 多通道 1.4. 卷積神經…

實現React18加TS,解決通用后臺管理系統,實戰方案落地有效實踐經驗

隨著前端技術的不斷發展和更新&#xff0c;使用React 18結合TypeScript&#xff08;TS&#xff09;來構建通用后臺管理系統已成為一種常見的選擇。本文將介紹如何在項目中應用React 18和TS&#xff0c;并分享一些實戰方案的有效實踐經驗。 一、搭建React 18 TS項目 首先&…

12.2每日一題(1無窮型冪指函數:二倍角公式+三部曲+等價無窮小代換(只有整體的因子不為0才能先算出來))

注意&#xff1a;求極限不能想先算哪里就先算哪里&#xff0c;只有整體的因子不為0才能先算出來&#xff0c;部分不為0不可以先算

外貿老業務也棘手的一個問題

這幾天有2個老業務都被一個類同的問題纏住了。 客戶定購了三臺車&#xff0c;由于是非常規要求所以我建議收取全款或者最少收50%的定金。但是業務員為了當月業績或者為了拿到就收了客戶20% 或者30% &#xff0c;定金收到了&#xff0c;我也不好再逼著業務員去加收定金。 訂單就…

記錄 | ubuntu上安裝fzf

在 ubuntu 上采用命令行安裝 fzf 的方式行不通 指的是采用下面的方式行不通&#xff1a; sudo apt install fzf # 行不通 sudo snap install fzf --classic # 行不通正確的安裝方式是&#xff1a; ● 到 fzf 的 git 倉庫&#xff1a;https://github.com/junegunn/fzf/re…

在高數據量中如何優化MySQL的Group by語句?

在實際開發環境中&#xff0c;MySQL的GROUP BY操作的優化需要結合具體的業務場景和數據特點。以下是一些建議&#xff0c;可以幫助你在實際開發中優化GROUP BY查詢&#xff1a; 使用合適的索引&#xff1a; 確保GROUP BY和ORDER BY中的列上存在索引。這有助于加速分組和排序操作…

計算機畢業設計 基于SpringBoot的電動車租賃系統的設計與實現 Java實戰項目 附源碼+文檔+視頻講解

博主介紹&#xff1a;?從事軟件開發10年之余&#xff0c;專注于Java技術領域、Python人工智能及數據挖掘、小程序項目開發和Android項目開發等。CSDN、掘金、華為云、InfoQ、阿里云等平臺優質作者? &#x1f345;文末獲取源碼聯系&#x1f345; &#x1f447;&#x1f3fb; 精…

場景示例:有贊商城 × 微盛企微管家,助力零售企業,實現私域運營自動化

1 場景描述 在零售行業內&#xff0c;線上渠道已經是零售行業的主要銷售渠道&#xff0c;大多數零售企業都會將產品上架到有贊商城&#xff0c;并使用微盛企微管家系統進行客戶管理和服務&#xff0c;希望能對客戶畫像進行精細化管理&#xff0c;以提升銷售和服務效率。 然而&a…

2023年最新prometheus + grafana搭建和使用+gmail郵箱告警配置

一、安裝prometheus 1.1 安裝 prometheus官網下載地址 sudo -i mkdir -p /opt/prometheus #移動解壓后的文件名到/opt/,并改名prometheus mv prometheus-2.45 /opt/prometheus/ #創建一個專門的prometheus用戶&#xff1a; -M 不創建家目錄&#xff0c; -s 不讓登錄 useradd…

女士內衣市場分析:預計2028年將達到643.08億美元

內衣 (英文名:Underwear)&#xff0c;是指貼身穿的衣物。內衣有保暖及污穢的危害作用&#xff0c;有時會被視為性征。女士內衣行業生產的主要原料是各類織布或無紡布&#xff0c;成分有海綿、邊、定型紗、骨膠、肩帶等&#xff0c;布面料在內衣企業的生產成本中所占比重較大。女…

Python基礎(四、探索迷宮游戲)

Python基礎&#xff08;四、探索迷宮游戲&#xff09; 游戲介紹游戲說明 游戲介紹 在這個游戲中&#xff0c;你將扮演一個勇敢的冒險者&#xff0c;進入了一個神秘的迷宮。你的任務是探索迷宮的每個房間&#xff0c;并最終找到隱藏在其中的寶藏。 游戲通過命令行界面進行交互…

web 前端之標簽練習+知識點

目錄 實現過程&#xff1a; 結果顯示 1、HTML語法 2、注釋標簽 3、常用標簽 4、新標簽 5、特殊標簽 6、在網頁中使用視頻和音頻、圖片 7、表格標簽 8、超鏈接標簽 使用HTML語言來實現該頁面 實現過程&#xff1a; <!DOCTYPE html> <html><head>…

泡沫包裝市場分析:預計2029年將達到659億元

泡沫包裝&#xff0c;簡單地講&#xff0c;就是用數學方法對無線電測量或光學測量所獲得的彈道數據進行檢驗、整理、校正、計算&#xff0c;減小或消除數據的誤差&#xff0c;得出反映運載火箭運動軌跡的精確彈道參數。通常所說的泡沫包裝&#xff0c;主要是指由可發性聚苯乙烯…

面試操作系統八股文五問五答第二期

面試操作系統八股文五問五答第二期 作者&#xff1a;程序員小白條&#xff0c;個人博客 相信看了本文后&#xff0c;對你的面試是有一定幫助的&#xff01; ?點贊?收藏?不迷路&#xff01;? 1.怎么解決死鎖&#xff1f; 1、預防死鎖&#xff1a;通過設置一些限制條件&am…

JAVA面試題8

1.Java中的線程是什么&#xff1f; 它有什么作用&#xff1f; 答案&#xff1a;線程是程序執行流的最小單位&#xff0c;用于實現多任務并發執行。Java中的線程可以實現并發編程&#xff0c;提高程序的性能和響應性。 2.什么是Java中的同步&#xff08;Synchronization&#x…

超靜音的兩相步進電機驅動芯片GC6609,GC6610的性能分析

兩相步進電機驅動芯片GC6609&#xff0c;GC6610它們是一款超靜音的兩相步進電機驅動芯片&#xff0c;內置最大 256 細分的步進驅動模式&#xff0c; 超靜音&#xff0c;低振動。芯片可以工作在 4~36V 的寬工作電壓范圍內&#xff0c;平均工作電流可以達到 2A和2.5A &#xff0c…

大數據機器學習算法項目——基于Django/協同過濾算法的房源可視化分析推薦系統的設計與實現

大數據機器學習算法項目——基于Django/協同過濾算法的房源可視化分析推薦系統的設計與實現 技術棧&#xff1a;大數據爬蟲/機器學習學習算法/數據分析與挖掘/大數據可視化/Django框架/Mysql數據庫 本項目基于 Django框架開發的房屋可視化分析推薦系統。這個系統結合了大數據…

STM32-01-認識單片機

文章目錄 一、單片機簡介二、Cortex-M系列介紹三、初識STM32四、STM32原理圖設計五、搭建開發環境六、STM32初體驗七、MDK5使用技巧 一、單片機簡介 單片機是什么&#xff1f; 單片機&#xff1a;Single-Chip Microcomputer&#xff0c;單片微型計算機&#xff0c;是一種集成電…

python獲得曲線峰值的個數

import numpy as np from scipy.signal import find_peaks import matplotlib.pyplot as plt# 生成示例數據 x np.linspace(0, 10, 100) y np.sin(x)# 查找峰值 peaks, _ find_peaks(y)# 繪制曲線和峰值點 plt.plot(x, y) plt.plot(x[peaks], y[peaks], ro)# 顯示峰值個數 n…