? ? ? ? 在平常的開發工作中,我們經常會用到鎖,那么鎖有什么用呢?鎖主要是控制對共享資源的訪問順序,防止多個線程并發操作導致數據不一致的問題。經常可能會聽到樂觀鎖、悲觀鎖、分布式鎖、行鎖、表鎖等等,那么我們今天總結下分布式鎖的實現方式之Redisson。
? ? ?一:概述
? ? ? ? 提到分布式鎖,我們先說說什么是鎖?鎖就是為了避免多個線程同時訪問某個共享資源時,修改了數據,導致出現數據一致性。鎖好比一個房間只能進去一個人辦理事情,得等里面的人出來,其他人才能進去。那么什么是分布式鎖呢?分布式鎖就是在分布式項目中使用的鎖。說到這兒,那就說說什么是分布式。分布式系統是指由多個節點、通過網絡連接,共同完成任務的系統。如一個系統由用戶服務、訂單服務、支付服務、倉儲服務等組成,之間通過網絡通信調用,那么就是分布式系統。
? ? ?二:分布式鎖特點
? ? ? ? 1:互斥性:同一時刻只有一個客戶端(進程或線程)能夠持有鎖。
? ? ? ? 2:可重入性:同一個客戶端在已經持有鎖的情況下,可以再次獲取該鎖而不會被阻塞。
? ? ? ? 3:容錯性:如客戶端崩潰、網絡中斷等。當客戶端在持有鎖的過程中出現異常崩潰時,鎖能夠自動釋放,避免出現死鎖的情況。
? ? ? ? 4:高可用:有良好的獲取鎖與釋放鎖的功能,避免分布式鎖失效。
? ? 三:分布式鎖應用場景
? ? ? ?1:電商系統中秒殺、庫存扣減,防止超賣。
???????2:分布式任務執行,先獲取鎖,再執行任務,保證數據一致性。
???????3:分布式系統冪等,如支付回調、消息消費等。
? ? ?四:代碼實現
? ? ?1:引入依賴
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.18.0</version>
</dependency>
? ? ?2:application.yml? redis 配置
data:redis:# 連接地址host: 127.0.0.1# 端口port: 6379# 數據庫database: 0# 用戶名# username: redis# 密碼password: 123456# 連接超時connect-timeout: 5s# 讀超時timeout: 30s
? ? ? 3:RedissonConfig
????????RedissonConfig 主要作用是對 Redisson 客戶端進行配置和初始化。
import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Value;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Value("${spring.data.redis.host}")private String redisHost;@Value("${spring.data.redis.port}")private int redisPort;@Value("${spring.data.redis.password}")private String password;@Value("${spring.data.redis.database}")private int database;@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort).setDatabase(database).setPassword(password);return Redisson.create(config);}
? ? ? 4:鎖實現
import cn.hutool.extra.spring.SpringUtil;
import com.ruoyi.system.service.ILockforService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** 商品實現類*/
@Service
@Slf4j
public class SysLockforServiceImpl implements ILockforService{@Resourceprivate RedissonClient redissonClient;@Overridepublic void addGoods() {// 定義鎖的鍵String lockKey = "addGoodsLock";// 獲取分布式鎖實例// 如果在靜態方法中,無法注入,通過如下獲取 bean//RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);RLock lock = redissonClient.getLock(lockKey);try {// 嘗試獲取鎖,等待 1 秒,鎖的過期時間為 1 秒boolean isLocked1 = lock.tryLock(1, 5, TimeUnit.SECONDS);// 嘗試獲取鎖,等待 0 秒,即沒有獲取到會立即返回,沒有設置鎖的過期時間,則不會自動釋放鎖,釋放鎖需要手動控制// boolean isLocked2 = lock.tryLock(0, TimeUnit.SECONDS);if (!isLocked1) {log.info("未獲取到鎖,請稍后再試!");}// 模擬業務邏輯log.info("開始處理業務");log.info("處理業務結束");} catch (Exception e) {log.error("新增商品異常", e);} finally {// 釋放鎖lock.unlock();}}
? ? ? 5:鎖的過期時間
? ? ? ? 如果設置了過期時間,在到達過期時間后,鎖會釋放。如果未設置鎖的過期時間,則需要在代碼中手動釋放鎖。
????????如果持有鎖的客戶端在獲取鎖之后發生異常關閉(例如程序崩潰、服務器宕機等),由于 Redisson 底層是基于 Redis 實現的,Redis 中存儲的鎖鍵值對可能會一直存在,導致其他客戶端無法獲取該鎖,形成死鎖。不過,Redisson 為了避免這種情況,有一個看門狗(Watchdog)機制。
????????看門狗機制會在客戶端獲取鎖時自動為鎖設置一個默認的過期時間(默認是 30 秒),并且會在后臺啟動一個定時任務,不斷地延長鎖的過期時間,只要客戶端還持有鎖且沒有手動釋放。但如果客戶端異常關閉,定時任務無法繼續執行,鎖會在過期時間到達后自動釋放。
????????正常情況下使用 tryLock(0, TimeUnit.SECONDS) 需要手動釋放鎖,但在客戶端異常關閉的情況下,看門狗機制會保證鎖最終能夠自動釋放。
? ? ?五:總結
? ? ? ? 實現 Redisson 分布式鎖,主要是引入依賴,在 application.yml 中配置 redis 連接信息,配置 RedissonConfig,實現分布式鎖。
? ? ? ? 可能遇到的問題,如果未配置 RedissonConfig,則會注入異常,如下:
? ? ? ? 以上為 Redisson 實現分布式鎖的主要步驟。