<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                “鎖”是我們實際工作和面試中無法避開的話題之一,正確使用鎖可以保證高并發環境下程序的正確執行,也就是說只有使用鎖才能保證多人同時訪問時程序不會出現問題。 我們本課時的面試題是,什么是分布式鎖?如何實現分布式鎖? #### 典型回答 第 06 課時講了單機鎖的一些知識,包括悲觀鎖、樂觀鎖、可重入鎖、共享鎖和獨占鎖等內容,但它們都屬于單機鎖也就是程序級別的鎖,如果在分布式環境下使用就會出現鎖不生效的問題,因此我們需要使用分布式鎖來解決這個問題。 分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。是為了解決分布式系統中,不同的系統或是同一個系統的不同主機共享同一個資源的問題,它通常會采用互斥來保證程序的一致性,這就是分布式鎖的用途以及執行原理。 分布式鎖示意圖,如下圖所示: ![](https://img.kancloud.cn/15/ca/15ca775beb5e48c3d98e520c83392097_1724x1028.png) 分布式鎖的常見實現方式有四種: * 基于 MySQL 的悲觀鎖來實現分布式鎖,這種方式使用的最少,因為這種實現方式的性能不好,且容易造成死鎖; * 基于 Memcached 實現分布式鎖,可使用 add 方法來實現,如果添加成功了則表示分布式鎖創建成功; * 基于 Redis 實現分布式鎖,這也是本課時要介紹的重點,可以使用 setnx 方法來實現; * 基于 ZooKeeper 實現分布式鎖,利用 ZooKeeper 順序臨時節點來實現。 由于 MySQL 的執行效率問題和死鎖問題,所以這種實現方式會被我們先排除掉,而 Memcached 和 Redis 的實現方式比較類似,但因為 Redis 技術比較普及,所以會優先使用 Redis 來實現分布式鎖,而 ZooKeeper 確實可以很好的實現分布式鎖。但此技術在中小型公司的普及率不高,尤其是非 Java 技術棧的公司使用的較少,如果只是為了實現分布式鎖而重新搭建一套 ZooKeeper 集群,顯然實現成本和維護成本太高,所以綜合以上因素,我們本文會采用 Redis 來實現分布式鎖。 之所以可以使用以上四種方式來實現分布式鎖,是因為以上四種方式都屬于程序調用的“外部系統”,而分布式的程序是需要共享“外部系統”的,這就是分布式鎖得以實現的基本前提。 #### 考點分析 分布式鎖的問題看似簡單,但卻有很多細節需要注意,比如,需要考慮分布式鎖的超時問題,如果不設置超時時間的話,可能會導致死鎖的產生,所以在對待這個“鎖”的問題上,一定不能馬虎。和此知識點相關的面試還有以下這些: * 單機鎖有哪些?它為什么不能在分布式環境下使用? * Redis 是如何實現分布式鎖的?可能會遇到什么問題? * 分布式鎖超時的話會有什么問題?如何解決? #### 知識擴展 * [ ] 單機鎖 程序中使用的鎖叫單機鎖,我們日常中所說的“鎖”都泛指單機鎖,其分類有很多,大體可分為以下幾類: * 悲觀鎖,是數據對外界的修改采取保守策略,它認為線程很容易把數據修改掉,因此在整個數據被修改的過程中都會采取鎖定狀態,直到一個線程使用完,其他線程才可以繼續使用,典型應用是 synchronized; * 樂觀鎖,和悲觀鎖的概念恰好相反,樂觀鎖認為一般情況下數據在修改時不會出現沖突,所以在數據訪問之前不會加鎖,只是在數據提交更改時,才會對數據進行檢測,典型應用是 ReadWriteLock 讀寫鎖; * 可重入鎖,也叫遞歸鎖,指的是同一個線程在外面的函數獲取了鎖之后,那么內層的函數也可以繼續獲得此鎖,在 Java 語言中 ReentrantLock 和 synchronized 都是可重入鎖; * 獨占鎖和共享鎖,只能被單線程持有的鎖叫做獨占鎖,可以被多線程持有的鎖叫共享鎖,獨占鎖指的是在任何時候最多只能有一個線程持有該鎖,比如 ReentrantLock 就是獨占鎖;而 ReadWriteLock 讀寫鎖允許同一時間內有多個線程進行讀操作,它就屬于共享鎖。 單機鎖之所以不能應用在分布式系統中是因為,在分布式系統中,每次請求可能會被分配在不同的服務器上,而單機鎖是在單臺服務器上生效的。如果是多臺服務器就會導致請求分發到不同的服務器,從而導致鎖代碼不能生效,因此會造成很多異常的問題,那么單機鎖就不能應用在分布式系統中了。 使用 Redis 實現分布式鎖 使用 Redis 實現分布式鎖主要需要使用 setnx 方法,也就是 set if not exists(不存在則創建),具體的實現代碼如下: ``` 127.0.0.1:6379> setnx lock true (integer) 1 #創建鎖成功 #邏輯業務處理... 127.0.0.1:6379> del lock (integer) 1 #釋放鎖 ``` 當執行 setnx 命令之后返回值為 1 的話,則表示創建鎖成功,否則就是失敗。釋放鎖使用 del 刪除即可,當其他程序 setnx 失敗時,則表示此鎖正在使用中,這樣就可以實現簡單的分布式鎖了。 但是以上代碼有一個問題,就是沒有設置鎖的超時時間,因此如果出現異常情況,會導致鎖未被釋放,而其他線程又在排隊等待此鎖就會導致程序不可用。 有人可能會想到使用 expire 來設置鍵值的過期時間來解決這個問題,例如以下代碼: ``` 127.0.0.1:6379> setnx lock true (integer) 1 #創建鎖成功 127.0.0.1:6379> expire lock 30 #設置鎖的(過期)超時時間為 30s (integer) 1 #邏輯業務處理... 127.0.0.1:6379> del lock (integer) 1 #釋放鎖 ``` 但這樣執行仍然會有問題,因為 setnx lock true 和 expire lock 30 命令是非原子的,也就是一個執行完另一個才能執行。但如果在 setnx 命令執行完之后,發生了異常情況,那么就會導致 expire 命令不會執行,因此依然沒有解決死鎖的問題。 這個問題在 Redis 2.6.12 之前一直沒有得到有效的處理,當時的解決方案是在客戶端進行原子合并操作,于是就誕生了很多客戶端類庫來解決此原子問題,不過這樣就增加了使用的成本。因為你不但要添加 Redis 的客戶端,還要為了解決鎖的超時問題,需額外的增加新的類庫,這樣就增加了使用成本,但這個問題在 Redis 2.6.12 版本中得到了有效的處理。 在 Redis 2.6.12 中我們可以使用一條 set 命令來執行鍵值存儲,并且可以判斷鍵是否存在以及設置超時時間了,如下代碼所示: ``` 127.0.0.1:6379> set lock true ex 30 nx OK #創建鎖成功 ``` 其中,ex 是用來設置超時時間的,而 nx 是 not exists 的意思,用來判斷鍵是否存在。如果返回的結果為“OK”則表示創建鎖成功,否則表示此鎖有人在使用。 * [ ] 鎖超時 從上面的內容可以看出,使用 set 命令之后好像一切問題都解決了,但在這里我要告訴你,其實并沒有。例如,我們給鎖設置了超時時間為 10s,但程序的執行需要使用 15s,那么在第 10s 時此鎖因為超時就會被釋放,這時候線程二在執行 set 命令時正常獲取到了鎖,于是在很短的時間內 2s 之后刪除了此鎖,這就造成了鎖被誤刪的情況,如下圖所示: ![](https://img.kancloud.cn/fd/22/fd22073cdaedcb970d2aeb5b5fd46bfc_1294x484.png) 鎖被誤刪的解決方案是在使用 set 命令創建鎖時,給 value 值設置一個歸屬標識。例如,在 value 中插入一個 UUID,每次在刪除之前先要判斷 UUID 是不是屬于當前的線程,如果屬于再刪除,這樣就避免了鎖被誤刪的問題。 注意:在鎖的歸屬判斷和刪除的過程中,不能先判斷鎖再刪除鎖,如下代碼所示: ``` if(uuid.equals(uuid)){ // 判斷是否是自己的鎖 del(luck); // 刪除鎖 } ``` 應該把判斷和刪除放到一個原子單元中去執行,因此需要借助 Lua 腳本來執行,在 Redis 中執行 Lua 腳本可以保證這批命令的原子性,它的實現代碼如下: ``` /** * 釋放分布式鎖 * @param jedis Redis客戶端 * @param lockKey 鎖的 key * @param flagId 鎖歸屬標識 * @return 是否釋放成功 */ public static boolean unLock(Jedis jedis, String lockKey, String flagId) { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(flagId)); if ("1L".equals(result)) { // 判斷執行結果 return true; } return false; } ``` 其中,Collections.singletonList() 方法是將 String 轉成 List,因為 jedis.eval() 最后兩個參數要求必須是 List 類型。 鎖超時可以通過兩種方案來解決: * 把執行耗時的方法從鎖中剔除,減少鎖中代碼的執行時間,保證鎖在超時之前,代碼一定可以執行完; * 把鎖的超時時間設置的長一些,正常情況下我們在使用完鎖之后,會調用刪除的方法手動刪除鎖,因此可以把超時時間設置的稍微長一些。 #### 小結 本課時我們講了分布式鎖的四種實現方式,即 MySQL、Memcached、Redis 和 ZooKeeper,因為 Redis 的普及率比較高,因此對于很多公司來說使用 Redis 實現分布式鎖是最優的選擇。本課時我們還講了使用 Redis 實現分布式鎖的具體步驟以及實現代碼,還講了在實現過程中可能會遇到的一些問題以及解決方案。 #### 課后問答 * 1、如果業務就是會出現1%的超時呢?怎么處理? 講師回復: 要看 1% 的業務超出了多少時間,如果超出的不多就增加超時時長,否則就想辦法把耗時的業務代碼拎出來。 * 2、鎖超時那里,感覺圖和描述的不是很清楚,線程1的鎖因為超時被釋放了,線程2獲取到鎖開始執行,隨后因為線程1在線程2前完成了,所以線程1會去刪除鎖,所以這里產生了線程2的鎖被線程1誤刪的問題,不知道我這樣理解的對不 講師回復: 是這個意思。 * 3、老師,redis集群下是如何實現鎖的呢 講師回復: 實現方法都是一樣的 * 4、刪除鎖時如果判斷鎖和刪除鎖兩個操作不是原子性的,可能會出現什么問題? 講師回復: 一個先執行,一個后執行,中間執行過程可能被打斷,并且有可能把自己的執行權交由另一個線程執行,就會出現一些非安全性問題。 * 5、假如某個線程獲取到鎖后,執行時間大于過期時間,是不是執行時間到了才會刪除設置的鍵?還是把超時當成異常,然后直接刪除鍵釋放鎖? 講師回復: 過期時間到了也會刪除鎖,這是 Redis 層面執行的,程序線程執行完了也會刪除鎖,會有兩次刪除。正常來說要保證鎖的執行時間要盡量的短(不要出現超時的情況),第二,如果超時了要保證線程 A,不能誤刪線程 B 的鎖。 * 6、為什么是刪除鎖,而不是釋放鎖? 講師回復: 其實是一個意思,一個是物理刪除一個是邏輯刪除,可以這樣理解。 * 7、如果占用鎖的任務執行超時,任務會怎么處理? 講師回復: 可能會出現 Redis 已經把過期的鎖給刪除了,線程 A 執行完之后又把線程 B 的鎖給誤刪了,文章有解決方案哈。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看