redis樂觀鎖與悲觀鎖的實(shí)戰(zhàn)?
概念
Redis是一個(gè)內(nèi)存中的鍵值存儲(chǔ)系統(tǒng),支持多種數(shù)據(jù)結(jié)構(gòu),如字符串、哈希、列表等。Redis提供了兩種鎖機(jī)制,即樂觀鎖和悲觀鎖。
樂觀鎖
樂觀鎖是一種樂觀的并發(fā)控制策略,它認(rèn)為數(shù)據(jù)在大多數(shù)情況下不會(huì)被其他線程占用,因此每次需要修改數(shù)據(jù)時(shí),都不會(huì)獲取鎖,而是直接進(jìn)行修改。在Redis中,可以通過WATCH和CAS命令來實(shí)現(xiàn)樂觀鎖,WATCH命令用于監(jiān)視一個(gè)或多個(gè)鍵,CAS命令用于檢查并更新鍵的值。
例如,假設(shè)有一個(gè)計(jì)數(shù)器鍵counter,多個(gè)客戶端都需要對(duì)其進(jìn)行操作。使用樂觀鎖的方式,可以在每個(gè)客戶端執(zhí)行操作之前,先通過WATCH命令監(jiān)視counter鍵:
WATCH counter current_count = GET counter new_count = current_count + 1 MULTI SET counter new_count EXEC
然后,在EXEC命令執(zhí)行之前,使用GET命令再次獲取counter鍵的值,并將其與之前獲取的值進(jìn)行比較。如果值相等,就說明期間沒有其他客戶端對(duì)counter鍵進(jìn)行了修改,此時(shí)可以使用CAS命令將新值設(shè)置到counter鍵中。如果值不相等,則說明期間有其他客戶端對(duì)counter鍵進(jìn)行了修改,需要重新執(zhí)行操作。
GET counter
悲觀鎖
悲觀鎖是一種悲觀的并發(fā)控制策略,它認(rèn)為數(shù)據(jù)在大多數(shù)情況下都會(huì)被其他線程占用,因此每次需要修改數(shù)據(jù)時(shí),都會(huì)先獲取鎖,確保在修改期間沒有其他線程可以訪問該數(shù)據(jù)。在Redis中,可以通過WATCH命令來實(shí)現(xiàn)悲觀鎖,該命令可以監(jiān)視一個(gè)或多個(gè)鍵,如果在事務(wù)執(zhí)行期間有任何被監(jiān)視鍵的值發(fā)生了變化,整個(gè)事務(wù)會(huì)被回滾。
還是上文的例子
WATCH counter current_count = GET counter new_count = current_count + 1 MULTI SET counter new_count EXEC
如果在執(zhí)行事務(wù)期間,有其他客戶端修改了counter鍵,那么整個(gè)事務(wù)會(huì)被回滾,需要重新執(zhí)行。
悲觀鎖的優(yōu)點(diǎn)在于它可以確保數(shù)據(jù)的一致性,但缺點(diǎn)在于它需要獲取鎖,可能會(huì)引起線程的阻塞,影響并發(fā)性能。
樂觀鎖示例
假設(shè)有一個(gè)電商平臺(tái),用戶可以在平臺(tái)上購買商品。為了保證數(shù)據(jù)的一致性,我們可以使用Redis的樂觀鎖來實(shí)現(xiàn)商品庫存的扣減。
首先,我們需要在Redis中保存每個(gè)商品的庫存信息,使用hash數(shù)據(jù)結(jié)構(gòu)來保存,例如:
然后,在業(yè)務(wù)邏輯中,當(dāng)用戶購買一個(gè)商品時(shí),需要執(zhí)行以下步驟:
- 使用WATCH命令監(jiān)視商品庫存鍵,例如stock:sku001;
- 使用GET命令獲取當(dāng)前商品庫存數(shù)量;
- 檢查商品庫存是否足夠,如果不足,直接返回錯(cuò)誤信息;
- 計(jì)算新的庫存數(shù)量,并使用MULTI命令開啟一個(gè)事務(wù);
- 使用HSET命令將新的庫存數(shù)量保存到Redis中;
- 執(zhí)行事務(wù),如果在執(zhí)行期間有其他客戶端修改了商品庫存,會(huì)回滾事務(wù),需要重新執(zhí)行。
下面是使用Spring Boot實(shí)現(xiàn)的示例代碼:
@Service public class OrderService { ? ? private final RedisTemplate<String, Integer> redisTemplate; ? ? @Autowired ? ? public OrderService(RedisTemplate<String, Integer> redisTemplate) { ? ? ? ? this.redisTemplate = redisTemplate; ? ? } ? ? public void placeOrder(String sku, int quantity) { ? ? ? ? String stockKey = "stock:" + sku; ? ? ? ? while (true) { ? ? ? ? ? ? // 監(jiān)視商品庫存鍵,以便在事務(wù)開始前檢測是否有其他客戶端修改了庫存 ? ? ? ? ? ? redisTemplate.watch(stockKey); ? ? ? ? ? ? // 獲取當(dāng)前庫存數(shù)量 ? ? ? ? ? ? int currentStock = redisTemplate.opsForHash().get(stockKey, sku); ? ? ? ? ? ? // 檢查庫存是否足夠 ? ? ? ? ? ? if (currentStock < quantity) { ? ? ? ? ? ? ? ? // 庫存不足,放棄事務(wù)并拋出異常 ? ? ? ? ? ? ? ? redisTemplate.unwatch(); ? ? ? ? ? ? ? ? throw new RuntimeException("Out of stock"); ? ? ? ? ? ? } ? ? ? ? ? ? // 計(jì)算新的庫存數(shù)量 ? ? ? ? ? ? int newStock = currentStock - quantity; ? ? ? ? ? ? // 開始事務(wù) ? ? ? ? ? ? redisTemplate.multi(); ? ? ? ? ? ? // 更新庫存數(shù)量 ? ? ? ? ? ? redisTemplate.opsForHash().put(stockKey, sku, newStock); ? ? ? ? ? ? // 提交事務(wù) ? ? ? ? ? ? List<Object> results = redisTemplate.exec(); ? ? ? ? ? ? // 如果事務(wù)執(zhí)行成功,則退出循環(huán) ? ? ? ? ? ? if (results != null) { ? ? ? ? ? ? ? ? break; ? ? ? ? ? ? } ? ? ? ? ? ? // 如果事務(wù)執(zhí)行失敗,則重試 ? ? ? ? } ? ? } }
在上面的代碼中,我們使用RedisTemplate來操作Redis,其中watch方法用于監(jiān)視商品庫存鍵,opsForHash方法用于獲取和修改商品庫存的值,multi和exec方法用于開啟和提交事務(wù)。
悲觀鎖示例
除了樂觀鎖,Redis還支持悲觀鎖,可以通過設(shè)置NX(Not Exist)或XX(Exist)標(biāo)志來實(shí)現(xiàn)。例如,當(dāng)NX標(biāo)志設(shè)置為true時(shí),如果鎖不存在,會(huì)返回OK,并創(chuàng)建一個(gè)鎖;如果鎖已經(jīng)存在,會(huì)返回null,表示獲取鎖失敗。反之,當(dāng)XX標(biāo)志設(shè)置為true時(shí),如果鎖已經(jīng)存在,會(huì)返回OK,表示獲取鎖成功;如果鎖不存在,會(huì)返回null,表示獲取鎖失敗。
下面是使用Spring Boot實(shí)現(xiàn)的悲觀鎖示例代碼:
@Service public class OrderService { ? ? private final RedisTemplate<String, String> redisTemplate; ? ? @Autowired ? ? public OrderService(RedisTemplate<String, String> redisTemplate) { ? ? ? ? this.redisTemplate = redisTemplate; ? ? } ? ? public void placeOrder(String sku, int quantity) { ? ? ? ? String lockKey = "lock:" + sku; ? ? ? ? // 嘗試獲取鎖,如果鎖已經(jīng)存在,說明有其他線程正在執(zhí)行相關(guān)操作 ? ? ? ? Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked"); ? ? ? ? if (!locked) { ? ? ? ? ? ? // 獲取鎖失敗,拋出異常 ? ? ? ? ? ? throw new RuntimeException("Unable to acquire lock"); ? ? ? ? } ? ? ? ? // 設(shè)置鎖的過期時(shí)間,防止鎖被一直占用 ? ? ? ? redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS); ? ? ? ? try { ? ? ? ? ? ? // 執(zhí)行訂單創(chuàng)建、扣減庫存等操作 ? ? ? ? } finally { ? ? ? ? ? ? // 釋放鎖 ? ? ? ? ? ? redisTemplate.delete(lockKey); ? ? ? ? } ? ? } }
在上面的代碼中,我們使用setIfAbsent方法來嘗試獲取鎖,如果鎖已經(jīng)存在,說明其他線程正在執(zhí)行相關(guān)操作,此時(shí)會(huì)返回false,表示獲取鎖失敗;否則,會(huì)返回true,表示獲取鎖成功。如果獲取鎖成功,我們會(huì)設(shè)置鎖的過期時(shí)間,并執(zhí)行相關(guān)操作,最后釋放鎖。
總結(jié)提升
需要注意的是,悲觀鎖一般適用于并發(fā)量不大的場景,如果并發(fā)量較高,容易導(dǎo)致性能問題。因此,在實(shí)際應(yīng)用中,需要根據(jù)具體情況選擇合適的鎖策略。
到此這篇關(guān)于redis樂觀鎖與悲觀鎖的實(shí)戰(zhàn) 的文章就介紹到這了,更多相關(guān)redis樂觀鎖與悲觀鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows操作系統(tǒng)下Redis服務(wù)安裝圖文教程
這篇文章主要介紹了Windows操作系統(tǒng)下Redis服務(wù)安裝圖文教程,文中給大家提供了redis的下載地址,安裝程序步驟,需要的朋友可以參考下2018-03-03CentOS系統(tǒng)下Redis安裝和自啟動(dòng)配置的步驟
相信大家都知道Redis是一個(gè)C實(shí)現(xiàn)的基于內(nèi)存、可持久化的鍵值對(duì)數(shù)據(jù)庫,在分布式服務(wù)中常作為緩存服務(wù)。所以這篇文章將詳細(xì)介紹在CentOS系統(tǒng)下如何從零開始安裝到配置啟動(dòng)服務(wù)。有需要的可以參考借鑒。2016-09-09銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法
這篇文章主要介紹了銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01window下創(chuàng)建redis出現(xiàn)問題小結(jié)
這篇文章主要介紹了window下創(chuàng)建redis出現(xiàn)問題總結(jié),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10redis?設(shè)置生存和過期時(shí)間的原理分析
這篇文章主要介紹了redis?設(shè)置生存和過期時(shí)間的原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08使用Redis實(shí)現(xiàn)點(diǎn)贊取消點(diǎn)贊的詳細(xì)代碼
這篇文章主要介紹了Redis實(shí)現(xiàn)點(diǎn)贊取消點(diǎn)贊的詳細(xì)代碼,通過查詢某實(shí)體(帖子、評(píng)論等)點(diǎn)贊數(shù)量,需要用到事務(wù)相關(guān)知識(shí),結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03