成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

繼續項目實戰,集成Redis分布式鎖

存儲 存儲軟件 分布式 Redis
今天我們就來把基于Redis實現的分布式鎖,集成到我們的項目中,分布式鎖歷來都受到大家的關注。不管是工作中、面試中,分布式鎖永遠是個不老的話題,也希望大家能掌握此技能,便于大家日后能"升官發財"。

[[386548]]

今天我們就來把基于Redis實現的分布式鎖,集成到我們的項目中,分布式鎖歷來都受到大家的關注。不管是工作中、面試中,分布式鎖永遠是個不老的話題,也希望大家能掌握此技能,便于大家日后能"升官發財"。

分布式鎖

背景

為什么要有分布式鎖呢?不是已經有synchronized、ReantrantLock等相關鎖了嗎?

是的,我們在開發應用的時候,如果需要對某一個共享變量進行多線程同步訪問的時候,可以使用我們學到的鎖進行處理,并且可以完美的運行,毫無Bug!

注意:這是單機應用,后來業務發展,需要做集群,一個應用需要部署到幾臺機器上然后做負載均衡 :

 

上圖可以看到,變量A存在三個服務器內存中(這個變量A主要體現是在一個類中的一個成員變量,是一個有狀態的對象),如果不加任何控制的話,變量A同時都會在分配一塊內存,三個請求發過來同時對這個變量操作,顯然結果是不對的!即使不是同時發過來,三個請求分別操作三個不同內存區域的數據,變量A之間不存在共享,也不具有可見性,處理的結果也是不對的!

如果我們業務中確實存在這個場景的話,我們就需要一種方法解決這個問題!

為了保證一個方法或屬性在高并發情況下的同一時間只能被同一個線程執行,在傳統單體應用單機部署的情況下,可以使用并發處理相關的功能進行互斥控制。但是,隨著業務發展的需要,原單體單機部署的系統被演化成分布式集群系統后,由于分布式系統多線程、多進程并且分布在不同機器上,這將使原單機部署情況下的并發控制鎖策略失效,單純的應用并不能提供分布式鎖的能力。為了解決這個問題就需要一種跨機器的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題!

分布鎖的基本原理

分布式環境下,多臺機器上多個進程對同一個共享資源(數據、文件等)進行操作,如果不做互斥,就有可能出現“余額扣成負數”,或者“商品超賣”的情況。

為了解決這個問題,需要分布式鎖服務。首先,來看一下分布式鎖應該具備哪些條件。

  • 互斥性:在任意時刻,對于同一個鎖,只有一個客戶端能持有,從而保證一個共享資源同一時間只能被一個客戶端操作;
  • 安全性:即不會形成死鎖,當一個客戶端在持有鎖的期間崩潰而沒有主動解鎖的情況下,其持有的鎖也能夠被正確釋放,并保證后續其它客戶端能加鎖;
  • 可用性:當提供鎖服務的節點發生宕機等不可恢復性故障時,“熱備” 節點能夠接替故障的節點繼續提供服務,并保證自身持有的數據與故障節點一致。
  • 對稱性:對于任意一個鎖,其加鎖和解鎖必須是同一個客戶端,即客戶端 A 不能把客戶端 B 加的鎖給解了。

目前市面上,分布式鎖的實現方案大致有三種:

  1. 數據庫樂觀鎖;
  2. 基于分布式緩存實現的鎖服務,典型代表有 Redis 和基于 Redis 的 RedLock;
  3. 基于分布式一致性算法實現的鎖服務,典型代表有 ZooKeeper、Chubby 和 ETCD。

項目中,使用的最多的是后兩種,其實每種方案都各有利弊。

預防死鎖

我們看下面這個典型死鎖場景。

一個客戶端獲取鎖成功,但是在釋放鎖之前崩潰了,此時該客戶端實際上已經失去了對公共資源的操作權,但卻沒有辦法請求解鎖(刪除 Key-Value 鍵值對),那么,它就會一直持有這個鎖,而其它客戶端永遠無法獲得鎖。

我們的解決方案是:在加鎖時為鎖設置過期時間,當過期時間到達,Redis 會自動刪除對應的 Key-Value,從而避免死鎖。需要注意的是,這個過期時間需要結合具體業務綜合評估設置,以保證鎖的持有者能夠在過期時間之內執行完相關操作并釋放鎖。

另外,前面已經說了,在實際項目中我們都是使用后兩種方案,所以我們重點在后兩種方案上。說明 此文章是基于前面我們搞的項目繼續開展,同時把Redis已經集成到項目中了,所以此文中分布式鎖是基于Redis的實現。

基于Redis實現分布式鎖

先說一下使用Redis實現方案的思路:

  • setnx +expire+delete
  • setnx+lua
  • set key value px milliseconds nx

簡單版

創建分布鎖

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3. import org.springframework.data.redis.core.RedisTemplate; 
  4. import org.springframework.stereotype.Component; 
  5.  
  6. import javax.annotation.Resource; 
  7. import java.util.UUID; 
  8. import java.util.concurrent.TimeUnit; 
  9.  
  10. @Component 
  11. public class DistributedLockV1 { 
  12.  
  13.     private Logger logger = LoggerFactory.getLogger(DistributedLockV1.class); 
  14.  
  15.     @Resource 
  16.     private RedisTemplate redisTemplate; 
  17.  
  18.     public boolean lock(String businessKey) { 
  19.         boolean result = false
  20.         String uniqueValue = UUID.randomUUID().toString(); 
  21.         try { 
  22.             //@see <a href="http://redis.io/commands/setnx">Redis Documentation: SETNX</a> 
  23.             result = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue); 
  24.             if (!result) { 
  25.                 return false
  26.             } 
  27.             //設置key的有效期 
  28.             redisTemplate.expire(businessKey, 10, TimeUnit.SECONDS); 
  29.             return result; 
  30.         } catch (Exception ex) { 
  31.             logger.error("獲取鎖失敗", ex); 
  32.         } 
  33.         return result; 
  34.     } 
  35.  
  36.     public void unlock(String businessKey) { 
  37.         try { 
  38.             //delete 
  39.             redisTemplate.delete(businessKey); 
  40.         } catch (Exception ex) { 
  41.             logger.error("釋放鎖失敗", ex); 
  42.         } 
  43.     } 

就這樣,一個簡單的分布式鎖就實現了,但是這里會存在問題,問題也不是一定會出現,在特定的時刻還是會出現的。

下面,我們就來把這個分布式鎖應用到用戶賬戶余額扣減的功能中。

我們來創建一張用戶賬戶表,表中主要有userId和余額:

  1. CREATE TABLE `user_account` ( 
  2.   `id` bigint NOT NULL AUTO_INCREMENT, 
  3.   `user_id` bigint DEFAULT NULL
  4.   `balance` decimal(10,2) DEFAULT NULL
  5.   `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
  6.   `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
  7.   PRIMARY KEY (`id`) 
  8. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 

然后創建UserAccountRepository接口。

  1. import com.tian.user.entity.UserAccount; 
  2. import org.springframework.data.jpa.repository.JpaRepository; 
  3. import org.springframework.data.jpa.repository.Modifying; 
  4. import org.springframework.data.jpa.repository.Query; 
  5. import org.springframework.stereotype.Repository; 
  6.  
  7. import java.math.BigDecimal; 
  8.  
  9. @Repository 
  10. public interface UserAccountRepository extends JpaRepository<UserAccount, Long> { 
  11.  
  12.     UserAccount findByUserId(Long userId); 
  13.     //通過userId更新余額 
  14.     @Modifying 
  15.     @Query("update UserAccount u set u.balance=?1 where  u.userId=?2"
  16.     void updateBalanceByUserId(BigDecimal balance, Long userId); 

創建UserAccountService和實現類,并實現其扣減方法:

  1. import com.tian.user.entity.UserAccount; 
  2.  
  3. import java.math.BigDecimal; 
  4.  
  5. public interface UserAccountService {  
  6.     /** 
  7.      * 扣減余額 
  8.      * @param userId 當前用戶userId 
  9.      * @param balance 當前需要減余額 
  10.      * @return 是否扣減成功 
  11.      */ 
  12.     boolean reduceBalance(Long userId, BigDecimal balance); 
  13. import com.tian.user.entity.UserAccount; 
  14. import com.tian.user.lock.DistributedLockV1; 
  15. import com.tian.user.repository.UserAccountRepository; 
  16. import com.tian.user.service.UserAccountService; 
  17. import org.springframework.stereotype.Service; 
  18. import org.springframework.transaction.annotation.Transactional; 
  19.  
  20. import javax.annotation.Resource; 
  21. import java.math.BigDecimal; 
  22. import java.util.Date
  23.  
  24. @Service 
  25. @Transactional(rollbackFor = Exception.class) 
  26. public class UserAccountServiceImpl implements UserAccountService { 
  27.     private Logger logger= LoggerFactory.getLogger(getClass()); 
  28.     @Resource 
  29.     private UserAccountRepository userAccountRepository; 
  30.     @Resource 
  31.     private DistributedLockV1 distributedLockV1;  
  32.      
  33.     @Override 
  34.     public boolean reduceBalance(Long userId, BigDecimal balance) { 
  35.         try { 
  36.             //把該賬戶給鎖住,使用userId作為key。 
  37.             boolean lock = distributedLockV1.lock(userId.toString()); 
  38.             //獲取鎖失敗,則直接返回扣減失敗 
  39.             if (!lock) { 
  40.                 return false
  41.             } 
  42.             UserAccount userAccount = userAccountRepository.findByUserId(userId); 
  43.             BigDecimal currBalance = userAccount.getBalance(); 
  44.             //校驗余額是否足夠扣減 
  45.             if (currBalance.compareTo(balance) > 0) { 
  46.                 BigDecimal newBalance = currBalance.subtract(balance); 
  47.                 //扣減余額 
  48.                 userAccountRepository.updateBalanceByUserId(newBalance, userId); 
  49.                 return true
  50.             } 
  51.         }catch(Exception ex){ 
  52.             logger.error("余額扣減失敗", ex); 
  53.         } finally { 
  54.             //釋放鎖 
  55.             distributedLockV1.unlock(userId.toString()); 
  56.         } 
  57.         return false
  58.     } 

到此,簡單版的分布式鎖,以及如何使用,這里就已經搞完了。下面我們來理一下思路:

1.使用setnx(set not exist),就是如何set的這個key在redis不存在就返回true,否則返回false。

2.對已經set的key設置有效期,使用expire設置有效期。

3.校驗我們的可用余額是否足夠扣減,不夠就直接結束并使用delete刪除redis中的key。

4.扣減余額,更新數據庫余額值。

5.刪除key,delete redis中key。

那么,問題來了,第一步、第二步都成功了。但假如第三步查詢余額、扣減余額耗時20秒了,上面我們對Redis中key的有效期設置的10秒,也就是超時了,key過期了,并且在10秒到20秒之間又有其他線程來獲取到鎖了,然后此時把其他線程拿到的鎖給刪了,把其他線程的鎖給解了。此時,不就亂了嗎?

這也是面試中常被問使用redis做分布式鎖,業務超時了怎么辦?

升級版

我們可以把每次key對應的value返回,當釋放鎖的時候,判斷當前key對應的value是否是當前手里持有的value。

然后,我們針對上面的進行修改一版。

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3. import org.springframework.data.redis.core.RedisTemplate; 
  4. import org.springframework.stereotype.Component; 
  5.  
  6. import javax.annotation.Resource; 
  7. import java.util.UUID; 
  8. import java.util.concurrent.TimeUnit; 
  9.  
  10. @Component 
  11. public class DistributedLockV1 { 
  12.     private Logger logger = LoggerFactory.getLogger(DistributedLockV1.class); 
  13.     @Resource 
  14.     private RedisTemplate redisTemplate; 
  15.  
  16.     public String lockV2(String businessKey) { 
  17.         boolean result = false
  18.         String uniqueValue = UUID.randomUUID().toString(); 
  19.         try { 
  20.             result = redisTemplate.opsForValue().setIfAbsent(businessKey, uniqueValue); 
  21.             if (!result) { 
  22.                 return null
  23.             } 
  24.             redisTemplate.expire(businessKey, 100, TimeUnit.SECONDS); 
  25.             return uniqueValue; 
  26.         } catch (Exception ex) { 
  27.             logger.error("獲取鎖失敗", ex); 
  28.         } 
  29.         return null
  30.     } 
  31.  
  32.     public void unlockV2(String businessKey, String businessValue) { 
  33.         try { 
  34.             Object value = redisTemplate.opsForValue().get(businessKey); 
  35.             if (value == null) { 
  36.                 return false
  37.             } 
  38.             //當前key在redis中value和當前線程手里持有的是否一致 
  39.             if (!businessValue.equals(value)) { 
  40.                 //不一致,證明被其他線程獲取了 
  41.                 logger.info("key={}釋放鎖失敗嗎,該鎖已被其他線程獲取",businessKey); 
  42.                 return false
  43.             } 
  44.             redisTemplate.delete(businessKey); 
  45.             logger.info("key={}釋放鎖成功",businessKey); 
  46.         } catch (Exception ex) { 
  47.             logger.error("釋放鎖失敗", ex); 
  48.         } 
  49.     } 

這里比簡單版多了一個判斷,判斷持有鎖的線程是否為當前線程。盡管使用隨機字符串的 value來判斷是否為當前線程,但是在釋放鎖時(delete方法),還是無法做到原子操作,比如進程 A 執行完業務邏輯,在準備釋放鎖時,恰好這時候進程 A 的鎖自動過期時間到了,而另一個進程 B 獲得鎖成功,然后 B 還沒來得及執行,進程 A 就執行了 delete(key) ,釋放了進程 B 的鎖.... ,因此需要配合 Lua 腳本釋放鎖。

setnx+Lua腳本Lua 是一種輕量小巧的腳本語言,用標準 C 語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。

Lua 提供了交互式編程模式。我們可以在命令行中輸入程序并立即查看效果。

lua腳本優點:

  • 減少網絡開銷:原先多次請求的邏輯放在 redis 服務器上完成。使用腳本,減少了網絡往返時延
  • 原子操作:Redis會將整個腳本作為一個整體執行,中間不會被其他命令插入(想象為事務)
  • 復用:客戶端發送的腳本會永久存儲在Redis中,意味著其他客戶端可以復用這一腳本而不需要使用代碼完成同樣的邏輯

在resources目錄下創建一個redis-lock.lua文件。填入內容:

  1. if redis.call('get', KEYS[1]) == ARGV[1] 
  2.     then 
  3.         return redis.call('del', KEYS[1]) 
  4.     else 
  5.         return 0 
  6. end 

這段代碼的意思。就是通過key獲得其在redis中value,然后使用當前線程手里的value與之對比,一樣則刪除redis這個key。刪除返回1,否則返回0表示什么沒做。

Redis鎖代碼塊如下:

  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3. import org.springframework.core.io.ClassPathResource; 
  4. import org.springframework.data.redis.core.RedisTemplate; 
  5. import org.springframework.data.redis.core.script.DefaultRedisScript; 
  6. import org.springframework.scripting.support.ResourceScriptSource; 
  7. import org.springframework.stereotype.Component; 
  8.  
  9. import javax.annotation.PostConstruct; 
  10. import javax.annotation.Resource; 
  11. import java.util.ArrayList; 
  12. import java.util.List; 
  13. import java.util.UUID; 
  14. import java.util.concurrent.TimeUnit; 
  15.  
  16. @Component 
  17. public class DistributedLockV1 { 
  18.  
  19.     private Logger logger = LoggerFactory.getLogger(DistributedLockV1.class); 
  20.  
  21.     @Resource 
  22.     private RedisTemplate redisTemplate; 
  23.  
  24.     private DefaultRedisScript<Long> script; 
  25.  
  26.     @PostConstruct 
  27.     public void init() { 
  28.         script = new DefaultRedisScript<Long>(); 
  29.         script.setResultType(Long.class); 
  30.         script.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis-lock.lua"))); 
  31.     } 
  32.  
  33.     public String lockV3(String key) { 
  34.         String value = UUID.randomUUID().toString().replace("-"""); 
  35.  
  36.         /* 
  37.          * setIfAbsent <=> SET key value [NX] [XX] [EX <seconds>] [PX [millseconds]] 
  38.          * set expire time 5 mins 
  39.          */ 
  40.         Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, 10000, TimeUnit.MILLISECONDS); 
  41.         if (flag) { 
  42.             return value; 
  43.         } 
  44.         return null
  45.     } 
  46.  
  47.     public void unlockV3(String key, String value) { 
  48.         /** 業務邏輯處理完畢,釋放鎖 **/ 
  49.         String lockValue = (String) redisTemplate.opsForValue().get(key); 
  50.         if (lockValue != null && lockValue.equals(value)) { 
  51.             System.out.println("lockValue========:" + lockValue); 
  52.             List<String> keys = new ArrayList<>(); 
  53.             keys.add(key); 
  54.             Object execute = redisTemplate.execute(script, keys, lockValue); 
  55.             System.out.println("execute執行結果,1表示執行del,0表示未執行 ===== " + execute); 
  56.             logger.info("{} 解鎖成功,結束處理業務"key); 
  57.             return
  58.         } 
  59.         logger.info("key={}釋放鎖失敗"key); 
  60.     } 

最后我們再次執行罰款扣減,日志輸出:

  1. lockValue========:199740e62c184a6a9897f9c95e720b4d 
  2. execute執行結果,1表示執行del,0表示未執行 ===== 1 
  3. 2021-03-09 19:03:51.592  INFO 6692 --- [nio-8080-exec-4] com.tian.user.lock.DistributedLockV1     : 1 解鎖成功,結束處理業務 

到此,setnx+Lua 這種方案我們已經實現了。如果對此有懷疑的,是好事,建議創建多個線程去調用罰款扣減這個service方法,看看器是否會出現問題。

「注意」

setnx在redis較低的版本里是沒有的,后面才引入的。其實我們也可以使用set命令來解決setnx,另外還可以加過期時間,整體命令為

  1. set key value nx px xxx 

value 最好是隨機字符串,這樣可以防止業務代碼執行時間超過設置的鎖自動過期時間,而導致再次釋放鎖時出現釋放其他進程鎖的情況。

setnx 瑣最大的缺點就是它加鎖時只作用在一個 Redis 節點上,即使 Redis 通過 Sentinel(哨崗、哨兵) 保證高可用,如果這個 master 節點由于某些原因發生了主從切換,那么就會出現鎖丟失的情況,下面是個例子:

  1. 在 Redis 的 master 節點上拿到了鎖;
  2. 但是這個加鎖的 key 還沒有同步到 slave 節點;
  3. master 故障,發生故障轉移,slave 節點升級為 master節點;
  4. 上邊 master 節點上的鎖丟失。

有的時候甚至不單單是鎖丟失這么簡單,新選出來的 master 節點可以重新獲取同樣的鎖,出現一把鎖被拿兩次的場景。

由此可知,鎖被獲取兩次,肯定不能滿足安全性了。

盡管前兩種方案不是很如意,總是有些問題,但也有被部分企業采用,下面我們就來看基于Redis來實現分布式鎖更高級的版本。

高級版 Redisson + RedLock

Redisson 是 java 的 Redis 客戶端之一,是 Redis 官網推薦的 java 語言實現分布式鎖的項目。

Redisson 提供了一些 api 方便操作 Redis。因為本文主要以鎖為主,所以接下來我們主要關注鎖相關的類,以下是 Redisson 中提供的多樣化的鎖:

  • 可重入鎖(Reentrant Lock)
  • 公平鎖(Fair Lock)
  • 聯鎖(MultiLock)
  • 紅鎖(RedLock)
  • 讀寫鎖(ReadWriteLock)
  • 信號量(Semaphore) 等等

總之,管你了解不了解,反正 Redisson 就是提供了一堆鎖... 也是目前大部分公司使用 Redis 分布式鎖最常用的一種方式。

整體加鎖和解鎖的代碼結構如下:

  1. RLock lock = redissonClient.getLock("xxx"); 
  2.  
  3. lock.lock(); 
  4.  
  5. try { 
  6.     ... 
  7. } finally { 
  8.     lock.unlock(); 

其實,加鎖和解鎖的磁層也是使用Lua腳本來實現的,有興趣的朋友可以去翻看一下器底層源碼。

由于篇幅問題,還涉及到Redis集群,所以這里就給出加鎖和解鎖的流程圖,僅供參考。

「加鎖過程」

 

「解鎖過程」

 

總結

通常我們為了實現 Redis 的高可用,一般都會搭建 Redis 的集群模式,比如給 Redis 節點掛載一個或多個 slave 從節點,然后采用哨兵模式進行主從切換。但由于 Redis 的主從模式是異步的,所以可能會在數據同步過程中,master 主節點宕機,slave 從節點來不及數據同步就被選舉為 master 主節點,從而導致數據丟失,大致過程如下:

  1. 用戶在 Redis 的 master 主節點上獲取了鎖;
  2. master 主節點宕機了,存儲鎖的 key 還沒有來得及同步到 slave 從節點上;
  3. slave 從節點升級為 master 主節點;
  4. 用戶從新的 master 主節點獲取到了對應同一個資源的鎖,同把鎖獲取兩次。

ok,然后為了解決這個問題,Redis 作者提出了 RedLock 算法,步驟如下(五步):

在下面的示例中,我們假設有 5 個完全獨立的 Redis Master 節點,他們分別運行在 5 臺服務器中,可以保證他們不會同時宕機。

獲取當前 Unix 時間,以毫秒為單位。

依次嘗試從 N 個實例,使用相同的 key 和隨機值獲取鎖。在步驟 2,當向 Redis 設置鎖時,客戶端應該設置一個網絡連接和響應超時時間,這個超時時間應該小于鎖的失效時間。例如你的鎖自動失效時間為 10 秒,則超時時間應該在 5-50 毫秒之間。這樣可以避免服務器端 Redis 已經掛掉的情況下,客戶端還在死死地等待響應結果。如果服務器端沒有在規定時間內響應,客戶端應該盡快嘗試另外一個 Redis 實例。

客戶端使用當前時間減去開始獲取鎖時間(步驟 1 記錄的時間)就得到獲取鎖使用的時間。當且僅當從大多數(這里是 3 個節點)的 Redis 節點都取到鎖,并且使用的時間小于鎖失效時間時,鎖才算獲取成功。

如果取到了鎖,key 的真正有效時間等于有效時間減去獲取鎖所使用的時間(步驟 3 計算的結果)。

如果因為某些原因,獲取鎖失敗(沒有在至少 N/2+1 個Redis實例取到鎖或者取鎖時間已經超過了有效時間),客戶端應該在所有的 Redis 實例上進行解鎖(即便某些 Redis 實例根本就沒有加鎖成功)。

到這,基本看出來,只要是大多數的 Redis 節點可以正常工作,就可以保證 Redlock 的正常工作。這樣就可以解決前面單點 Redis 的情況下我們討論的節點掛掉,由于異步通信,導致鎖失效的問題。

但是細想后, Redlock 還是存在如下問題:

假設一共有5個Redis節點:A, B, C, D, E。設想發生了如下的事件序列:

  1. 客戶端1 成功鎖住了 A, B, C,獲取鎖成功(但 D 和 E 沒有鎖住)。
  2. 節點 C 崩潰重啟了,但客戶端1在 C 上加的鎖沒有持久化下來,丟失了。
  3. 節點 C 重啟后,客戶端2 鎖住了 C, D, E,獲取鎖成功。
  4. 這樣,客戶端1 和 客戶端2 同時獲得了鎖(針對同一資源)。

哎,還是不能解決故障重啟后帶來的鎖的安全性問題...

針對節點重后引發的鎖失效問題,Redis 作者又提出了 延遲重啟 的概念,大致就是說,一個節點崩潰后,不要立刻重啟他,而是等到一定的時間后再重啟,等待的時間應該大于鎖的過期時間,采用這種方式,就可以保證這個節點在重啟前所參與的鎖都過期,聽上去感覺 延遲重啟 解決了這個問題...

但是,還是有個問題,節點重啟后,在等待的時間內,這個節點對外是不工作的。那么如果大多數節點都掛了,進入了等待,就會導致系統的不可用,因為系統在過期時間內任何鎖都無法加鎖成功。

總之,在 Redis 分布式鎖的實現上還有很多問題等待解決,我們需要認識到這些問題并清楚如何正確實現一個 Redis 分布式鎖,然后在工作中合理的選擇和正確的使用分布式鎖。

但為什么又說,有一部分企業采用Redis來實現分布式鎖呢?其實實現分布式鎖,從中間件上來選,也有 Zookeeper 可選,并且 Zookeeper 可靠性比 Redis 強太多,但是效率是低了點,如果并發量不是特別大,追求可靠性,那么肯定首選 Zookeeper。

關于分布式鎖的實現方案,沒有絕對的好與壞,沒有最好的方案,只有最適合你的業務的方案。

以下兩種方案僅供參考:

  • 如果為了效率,建議使用Redis來實現;
  • 如果追求可靠性,建議使用Zookeeper來實現。

本文轉載自微信公眾號「Java后端技術全棧」,可以通過以下二維碼關注。轉載本文請聯系Java后端技術全棧公眾號。

 

責任編輯:武曉燕 來源: Java后端技術全棧
相關推薦

2019-06-19 15:40:06

分布式鎖RedisJava

2023-01-13 07:39:07

2022-01-06 10:58:07

Redis數據分布式鎖

2023-08-21 19:10:34

Redis分布式

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2020-11-16 12:55:41

Redis分布式鎖Zookeeper

2024-10-07 10:07:31

2022-09-19 08:17:09

Redis分布式

2019-07-16 09:22:10

RedisZookeeper分布式鎖

2021-06-16 07:56:21

Redis分布式

2022-06-16 08:01:24

redis分布式鎖

2024-04-01 05:10:00

Redis數據庫分布式鎖

2020-07-30 09:35:09

Redis分布式鎖數據庫

2021-07-26 11:09:46

Redis分布式技術

2021-10-26 19:37:15

RedisRedis應用篇

2020-07-15 16:50:57

Spring BootRedisJava

2023-03-01 08:07:51

2022-07-22 06:55:20

Redis分布式鎖

2018-07-17 08:14:22

分布式分布式鎖方位

2020-07-03 13:29:08

Redis集群哈希槽
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久人爽 | 亚洲精品成人 | 高清欧美性猛交 | 日韩在线看片 | 国产第一页在线播放 | 国产成人精品一区二 | 少妇特黄a一区二区三区88av | 97国产精品 | 青草青草久热精品视频在线观看 | 欧美成人免费 | 成人影音 | 欧美视频在线看 | 亚洲欧美在线观看 | 久久av一区| 影音先锋亚洲资源 | 免费观看一级毛片 | 蜜臀久久99精品久久久久野外 | 日一区二区| 在线午夜| 国产一区二区中文字幕 | 欧美一级片中文字幕 | 欧美区日韩区 | 亚洲 中文 欧美 日韩 在线观看 | 99视频在线播放 | 午夜影晥 | 国产精品夜夜春夜夜爽久久电影 | 欧美a在线 | 在线播放亚洲 | 中文字幕在线第一页 | 精品无码久久久久久国产 | 精品在线视频播放 | 国产精品久久福利 | 精品久久久久一区二区国产 | 久久亚洲一区 | 网站国产| 男女视频在线观看 | 男插女下体视频 | 精品国产一区二区三区日日嗨 | 国产视频精品在线 | 国产美女在线免费观看 | 完全免费在线视频 |