Redis使用SETNX命令實現(xiàn)分布式鎖
什么是分布式鎖
分布式鎖是一種用于在分布式系統(tǒng)中控制多個節(jié)點對共享資源進行訪問的機制。在分布式系統(tǒng)中,由于多個節(jié)點可能同時訪問和修改同一個資源,因此需要一種方法來確保在任意時刻只有一個節(jié)點能夠?qū)Y源進行操作,以避免數(shù)據(jù)不一致或沖突。分布式鎖就是用來實現(xiàn)這種互斥訪問的工具。
為什么 Redis 的 SETNX 可以實現(xiàn)分布式鎖
Redis 的 SETNX
命令(即 SET if Not eXists
)可以用來實現(xiàn)分布式鎖,原因如下:
- 原子性:
SETNX
是一個原子操作,這意味著在同一時間只有一個客戶端能夠成功設(shè)置鍵值對。如果鍵已經(jīng)存在,SETNX
將不會執(zhí)行任何操作。這種原子性確保了鎖的獲取和釋放是線程安全的。 - 唯一性:
SETNX
確保了鎖的唯一性。只有第一個嘗試設(shè)置鍵的客戶端能夠成功,其他客戶端在嘗試設(shè)置相同的鍵時會失敗。這模擬了鎖的“獲取”和“釋放”行為。 - 過期時間:通過結(jié)合
EXPIRE
命令或使用SET
命令的EX
選項,可以為鎖設(shè)置一個過期時間。這防止了鎖被永久占用,即使客戶端在持有鎖期間崩潰或未能正確釋放鎖。 - 分布式環(huán)境:Redis 是一個分布式內(nèi)存數(shù)據(jù)庫,可以在多個節(jié)點之間共享數(shù)據(jù)。因此,使用 Redis 實現(xiàn)的鎖可以在分布式系統(tǒng)中的多個節(jié)點之間共享,從而實現(xiàn)分布式鎖。
- 高性能:Redis 是一個高性能的數(shù)據(jù)庫,能夠處理大量的并發(fā)請求。這使得 Redis 非常適合作為分布式鎖的實現(xiàn)基礎(chǔ)。
準(zhǔn)備工作
創(chuàng)建一個Spring Boot項目,并引入相關(guān)依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在application.yml
文件中配置 Redis 連接信息:
server: port: 8080 spring: redis: host: xxx.xxx.xxx.xxx port: 6379 password: xxxxxx
具體實現(xiàn)
創(chuàng)建分布式鎖工具類
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; @Component public class DistributedLock { private final StringRedisTemplate redisTemplate; // 通過構(gòu)造函數(shù)注入 StringRedisTemplate public DistributedLock(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * 嘗試獲取分布式鎖 * * @param lockKey 鎖的鍵 * @param requestId 請求標(biāo)識,用于區(qū)分不同的鎖持有者 * @param expireTime 鎖的過期時間,單位為毫秒 * @return 如果成功獲取鎖,返回 true;否則返回 false */ public boolean acquireLock(String lockKey, String requestId, long expireTime) { // 使用 setIfAbsent 方法嘗試設(shè)置鍵值對,如果鍵不存在則設(shè)置成功并返回 true,否則返回 false Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS); return result != null && result; } /** * 釋放分布式鎖 * * @param lockKey 鎖的鍵 * @param requestId 請求標(biāo)識,用于確保只有鎖的持有者才能釋放鎖 * @return 如果成功釋放鎖,返回 true;否則返回 false */ public boolean releaseLock(String lockKey, String requestId) { // 獲取當(dāng)前鎖的值 String currentValue = redisTemplate.opsForValue().get(lockKey); // 檢查當(dāng)前鎖的值是否等于請求標(biāo)識,確保只有鎖的持有者才能釋放鎖 if (currentValue != null && currentValue.equals(requestId)) { // 刪除鎖鍵 return redisTemplate.delete(lockKey); } return false; } }
創(chuàng)建業(yè)務(wù)類用來測試
import com.wh.demo01.demos.web.utils.DistributedLock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Date; @Service public class MyRedisService { private final DistributedLock distributedLock; // 通過構(gòu)造函數(shù)注入 DistributedLock @Autowired public MyRedisService(DistributedLock distributedLock) { this.distributedLock = distributedLock; } /** * 模擬需要同步執(zhí)行的方法 */ public void someMethod() { String lockKey = "myLockKey"; String requestId = "uniqueRequestId"; long expireTime = 10000; // 10 seconds try { // 嘗試獲取鎖 if (distributedLock.acquireLock(lockKey, requestId, expireTime)) { // 獲取到鎖,執(zhí)行需要同步的操作 System.out.println(new Date() + "獲取鎖成功"); // 模擬業(yè)務(wù)操作 Thread.sleep(5000); } else { System.out.println(new Date() + "獲取鎖失敗"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 確保鎖在操作完成后被釋放 distributedLock.releaseLock(lockKey, requestId); } } }
創(chuàng)建Controller
import com.wh.demo01.demos.web.service.MyRedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/redis") public class RedisController { @Autowired private MyRedisService service; @GetMapping("/test") public void TestRedis(){ service.someMethod(); } }
整個項目結(jié)構(gòu)如下:
使用idea的復(fù)制配置功能將該服務(wù)復(fù)制一份,并指定端口為8081
,模擬分布式服務(wù):
使用接口調(diào)試工具分別向8080
和8081
端口發(fā)送請求:
結(jié)果如下:
可見在分布式鎖的影響下,someMethod
方法在10秒內(nèi)只能被調(diào)用一次。
到此這篇關(guān)于Redis使用SETNX命令實現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)Redis SETNX實現(xiàn)分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redisson分布式限流器RRateLimiter的使用及原理小結(jié)
本文主要介紹了Redisson分布式限流器RRateLimiter的使用及原理小結(jié),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06Redis數(shù)據(jù)結(jié)構(gòu)原理淺析
這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)原理淺析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02Redis數(shù)據(jù)過期策略的實現(xiàn)詳解
最近項目當(dāng)中遇到一個需求場景,需要清空一些存放在Redis的數(shù)據(jù),本文對Redis的過期機制簡單的講解一下,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09Redis中統(tǒng)計各種數(shù)據(jù)大小的方法
這篇文章主要介紹了Redis中統(tǒng)計各種數(shù)據(jù)大小的方法,本文使用PHP實現(xiàn)統(tǒng)計Redis內(nèi)存占用比較大的鍵,需要的朋友可以參考下2015-03-03Redis遍歷海量數(shù)據(jù)的實現(xiàn)示例
本文主要介紹了 Redis遍歷海量數(shù)據(jù)的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04