分布式鎖+分布式下的緩存問題
- 當微服務模式時,多個相同服務之間對于資料庫需要保持資料一緻性.此時需要從本地鎖 演變為 分布式鎖.
- 本博文通過進階的形式 不斷提出問題以及解決思路,一步一步完善代碼,實作具有高可靠性的分布式鎖功能.

使用redis的set指令帶NX(not exist)參數實作分布式鎖
NX:隻有當不存在時,才可以set;成功set會傳回OK,不成功傳回null
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加鎖成功 執行業務
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除鎖
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
階段二 獨立加上分布式鎖的過期時間
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加鎖成功 執行業務
//2、設定過期時間
stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除鎖
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
階段三 原子占鎖和設定過期時間
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑 并設定過期時間 必須是同步的 原子的
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock",30,TimeUnit.SECONDS);
if(aBoolean){
//加鎖成功 執行業務
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除鎖
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
階段四 删鎖進行權限uuid比對
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑 并設定過期時間 必須是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加鎖成功 執行業務
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
String lock = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lock)){
//删除自己的鎖
stringRedisTemplate.delete("lock");
}
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
階段五 lua腳本 删鎖原子操作
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑 并設定過期時間 必須是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加鎖成功 執行業務
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//擷取值 + 對比 + 删除 必須是原子操作 lua腳本解鎖
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
階段六 最終結果
不論業務是否正确完成都删除自己建立的鎖
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式鎖。去redis占坑 并設定過期時間 必須是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if(aBoolean){
//加鎖成功 執行業務
Map<String, List<Catelog2Vo>> dataFromDB = null;
try {
dataFromDB = this.getDataFromDB();
}finally {
//擷取值 + 對比 + 删除 必須是原子操作 lua腳本解鎖
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDB;
}else {
//加鎖失敗 重試 自旋
//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDBWithRedisLock();
}
}
使用Redisson架構實作分布式鎖
如果手動使用redis實作分布式鎖,在每一次業務處理前都需要編寫原子上鎖和lua腳本原子解鎖語句,過于繁瑣,使用Redisson分布式鎖的架構能更加友善實作.Reddison底層實作了JUC包下的方法,是以可用無縫對接JUC的使用.
引入依賴
<!--引入redis的分布式鎖 分布式對象架構redison-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
配置redissonClient用戶端
@Configuration
public class MyRedissonConfig {
/**
* 所有對Redisson的使用 都是通過RedissonClient對象
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() throws IOException{
//1、建立配置 redisson包下的config
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.231.1:6379");
//2、根據config建立出RedissonClient示例
return Redisson.create(config);
}
}
使用redisson鎖實作分布式鎖
//分布式鎖
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedissonLock() {
//1、占分布式鎖。redisson
/**
* 鎖的名字 決定了鎖的粒度,越細越快
* 鎖的粒度約定: 具體緩存的是某個資料 例如 11号商品 product-11-lock
*/
RLock lock = redissonClient.getLock("catalogJson-lock");
lock.lock();
Map<String, List<Catelog2Vo>> dataFromDB = null;
try{
//加鎖成功 執行業務
dataFromDB = this.getDataFromDB();
}finally{
lock.unlock();
}
return dataFromDB;
}