什么是分布式鎖
分布式鎖是一種用于在分布式系統中控制多個節點對共享資源進行訪問的機制。在分布式系統中,由于多個節點可能同時訪問和修改同一個資源,因此需要一種方法來確保在任意時刻只有一個節點能夠對資源進行操作,以避免數據不一致或沖突。分布式鎖就是用來實現這種互斥訪問的工具。
為什么 Redis 的 SETNX 可以實現分布式鎖
Redis 的 SETNX
命令(即 SET if Not eXists
)可以用來實現分布式鎖,原因如下:
-
原子性:
SETNX
是一個原子操作,這意味著在同一時間只有一個客戶端能夠成功設置鍵值對。如果鍵已經存在,SETNX
將不會執行任何操作。這種原子性確保了鎖的獲取和釋放是線程安全的。 -
唯一性:
SETNX
確保了鎖的唯一性。只有第一個嘗試設置鍵的客戶端能夠成功,其他客戶端在嘗試設置相同的鍵時會失敗。這模擬了鎖的“獲取”和“釋放”行為。 -
過期時間:通過結合
EXPIRE
命令或使用SET
命令的EX
選項,可以為鎖設置一個過期時間。這防止了鎖被永久占用,即使客戶端在持有鎖期間崩潰或未能正確釋放鎖。 -
分布式環境:Redis 是一個分布式內存數據庫,可以在多個節點之間共享數據。因此,使用 Redis 實現的鎖可以在分布式系統中的多個節點之間共享,從而實現分布式鎖。
-
高性能:Redis 是一個高性能的數據庫,能夠處理大量的并發請求。這使得 Redis 非常適合作為分布式鎖的實現基礎。
準備工作
創建一個Spring Boot項目,并引入相關依賴
<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.xxxport: 6379password: xxxxxx
具體實現
創建分布式鎖工具類
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;// 通過構造函數注入 StringRedisTemplatepublic DistributedLock(StringRedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}/*** 嘗試獲取分布式鎖** @param lockKey 鎖的鍵* @param requestId 請求標識,用于區分不同的鎖持有者* @param expireTime 鎖的過期時間,單位為毫秒* @return 如果成功獲取鎖,返回 true;否則返回 false*/public boolean acquireLock(String lockKey, String requestId, long expireTime) {// 使用 setIfAbsent 方法嘗試設置鍵值對,如果鍵不存在則設置成功并返回 true,否則返回 falseBoolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);return result != null && result;}/*** 釋放分布式鎖** @param lockKey 鎖的鍵* @param requestId 請求標識,用于確保只有鎖的持有者才能釋放鎖* @return 如果成功釋放鎖,返回 true;否則返回 false*/public boolean releaseLock(String lockKey, String requestId) {// 獲取當前鎖的值String currentValue = redisTemplate.opsForValue().get(lockKey);// 檢查當前鎖的值是否等于請求標識,確保只有鎖的持有者才能釋放鎖if (currentValue != null && currentValue.equals(requestId)) {// 刪除鎖鍵return redisTemplate.delete(lockKey);}return false;}
}
創建業務類用來測試
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;// 通過構造函數注入 DistributedLock@Autowiredpublic MyRedisService(DistributedLock distributedLock) {this.distributedLock = distributedLock;}/*** 模擬需要同步執行的方法*/public void someMethod() {String lockKey = "myLockKey";String requestId = "uniqueRequestId";long expireTime = 10000; // 10 secondstry {// 嘗試獲取鎖if (distributedLock.acquireLock(lockKey, requestId, expireTime)) {// 獲取到鎖,執行需要同步的操作System.out.println(new Date() + "獲取鎖成功");// 模擬業務操作Thread.sleep(5000);} else {System.out.println(new Date() + "獲取鎖失敗");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 確保鎖在操作完成后被釋放distributedLock.releaseLock(lockKey, requestId);}}
}
創建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 {@Autowiredprivate MyRedisService service;@GetMapping("/test")public void TestRedis(){service.someMethod();}
}
整個項目結構如下:
使用idea的復制配置功能將該服務復制一份,并指定端口為8081
,模擬分布式服務:
使用接口調試工具分別向8080
和8081
端口發送請求:
結果如下:
可見在分布式鎖的影響下,someMethod
方法在10秒內只能被調用一次。