Redis如何實現(xiàn)分布式鎖詳解
一、前言
在Java的并發(fā)編程中,我們通過鎖,來避免由于競爭而造成的數(shù)據(jù)不一致問題。通常,我們以synchronized 、Lock
來使用它。
但是Java中的鎖,只能保證在同一個JVM進程內(nèi)中執(zhí)行。如果在分布式集群環(huán)境下,就需要分布式鎖了。
通常的分布式鎖的實現(xiàn)方式有redis,zookeeper,但是一般我們的程序中都會用到redis,用redis做分布式鎖,也能夠降低成本。
二、實現(xiàn)原理
2.1 加鎖
加鎖實際上就是在redis中,給Key鍵設(shè)置一個值,為避免死鎖,并給定一個過期時間。
在Redis 2.6.12以及之前,可以通過setnx key value (key不存在才設(shè)置成功)設(shè)置值,通過expire key seconds設(shè)置過期時間。但是由于是兩個命令,不是原子的。如果在設(shè)置值之后還沒有來得及設(shè)置過期時間,程序掛掉了,那么這個key就永遠的存在redis中了。
在Redis 2.6.12之后,redis提供了set key value EX seconds NX 和set key value PX millisecond NX 來原子性的設(shè)置值和設(shè)置過期時間。
2.2 解鎖
解鎖的過程就是將Key鍵刪除。但也不能亂刪,不能說客戶端1的請求將客戶端2的鎖給刪除掉,在刪除前需要判斷是不是設(shè)置的value,如果是才刪除。
為了保證解鎖操作的原子性,我們用LUA腳本完成這一操作。先判斷當(dāng)前鎖的字符串是否與傳入的值相等,是的話就刪除Key,解鎖成功。LUA腳本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end
三、通過RedisTemplate實現(xiàn)分布式鎖
我們通過SpringBoot構(gòu)建的應(yīng)用程序一般都是用RedisTemplate來操作緩存redis。下面介紹基于RedisTemplate來實現(xiàn)分布式鎖。
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.stereotype.Service; import java.util.Arrays; import java.util.concurrent.TimeUnit; @Service @Slf4j public class RedisLockUtil { @Autowired private RedisTemplate redisTemplate; //獲取鎖超時時間 private long timeout = 60000; /** * 獲取鎖 * @param key * @param value * @param ms * @return */ public Boolean getLock(String key, String value, Long ms) { long startTime = System.currentTimeMillis(); while (true) { Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, ms, TimeUnit.MILLISECONDS); if (flag) { return true; } //避免一直無限獲取鎖 if (System.currentTimeMillis() - startTime > timeout) { return false; } try { log.info("{}重試鎖", key); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 解鎖 * @param key * @param value * @return */ public Boolean unLock(String key, String value) { String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" + " return redis.call('del',KEYS[1]) " + "else" + " return 0 " + "end"; // 構(gòu)造RedisScript并指定返回類類型 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class); // 參數(shù)一:redisScript,參數(shù)二:key列表,參數(shù)三:arg(可多個) Object result = redisTemplate.execute(redisScript, Arrays.asList(key), value); return "1".equals(result.toString()); } }
四、通過Redisson實現(xiàn)
Redisson為我們封裝了細節(jié),可以開箱即用。
引入maven依賴:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.14.0</version> </dependency>
配置類:
import lombok.Data; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * redisson 配置類 */ @Configuration @Data public class RedissonConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.password}") private String password; @Bean public RedissonClient getRedisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password); //添加主從配置 // config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""}); return Redisson.create(config); } }
redis屬性配置:
spring: redis: host: 127.0.0.1 port: 6379 password: root
封裝的加鎖解鎖工具類:
import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * redis分布式鎖幫助類 */ @Component public class RedissLockUtil { @Autowired private RedissonClient redissonClient; /** * 加鎖 * @param lockKey * @return */ public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } /** * 釋放鎖 * @param lockKey */ public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } /** * 釋放鎖 * @param lock */ public void unlock(RLock lock) { lock.unlock(); } /** * 帶超時的鎖 * @param lockKey * @param timeout 超時時間 單位:秒 */ public RLock lock(String lockKey, int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, TimeUnit.SECONDS); return lock; } /** * 帶超時的鎖 * @param lockKey * @param unit 時間單位 * @param timeout 超時時間 */ public RLock lock(String lockKey, TimeUnit unit , int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } /** * 嘗試獲取鎖 * @param lockKey * @param waitTime 最多等待時間 * @param leaseTime 上鎖后自動釋放鎖時間 * @return */ public boolean tryLock(String lockKey, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS); } catch (InterruptedException e) { return false; } } /** * 嘗試獲取鎖 * @param lockKey * @param unit 時間單位 * @param waitTime 最多等待時間 * @param leaseTime 上鎖后自動釋放鎖時間 * @return */ public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } }
到此這篇關(guān)于Redis如何實現(xiàn)分布式鎖詳解的文章就介紹到這了,更多相關(guān)Redis分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 整合 Mockito提升Java單元測試的高效實踐案例
Mockito與Spring Boot的整合為Java開發(fā)者提供了一套完整的解決方案,使得單元測試更為精準(zhǔn)、高效,從而確保了代碼質(zhì)量、降低了維護成本,并促進了項目的持續(xù)集成與交付,感興趣的朋友跟隨小編一起看看吧2024-04-04Spring boot集成swagger2生成接口文檔的全過程
這篇文章主要給大家介紹了關(guān)于Spring boot集成swagger2生成接口文檔的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Spring boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Java8中List轉(zhuǎn)Map(Collectors.toMap) 的技巧分享
在最近的工作開發(fā)之中,慢慢習(xí)慣了很多Java8中的Stream的用法,很方便而且也可以并行的去執(zhí)行這個流,這篇文章主要給大家介紹了關(guān)于Java8中List轉(zhuǎn)Map(Collectors.toMap) 的相關(guān)資料,需要的朋友可以參考下2021-07-07基于Java SWFTools實現(xiàn)把pdf轉(zhuǎn)成swf
這篇文章主要介紹了基于Java SWFTools實現(xiàn)把pdf轉(zhuǎn)成swf,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11