1. 程式人生 > >關於redis實現分布式鎖

關於redis實現分布式鎖

測試 key maven 休眠 long intval 滿足 mil 情況下

前言

分布式鎖一般有三種實現方式:1. 數據庫樂觀鎖;2. 基於Redis的分布式鎖;3. 基於ZooKeeper的分布式鎖。本篇博客將介紹第二種方式,基於Redis實現分布式鎖。雖然網上已經有各種介紹Redis分布式鎖實現的博客,然而他們的實現卻有著各種各樣的問題,為了避免誤人子弟,本篇博客將詳細介紹如何正確地實現Redis分布式鎖。

可靠性

首先,為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:

  1. 互斥性。在任意時刻,只有一個客戶端能持有鎖。
  2. 不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。
  3. 具有容錯性。只要大部分的Redis節點正常運行,客戶端就可以加鎖和解鎖。
  4. 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。

代碼實現

組件依賴

首先我們要通過Maven引入Jedis開源組件,在pom.xml文件加入下面的代碼:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

加鎖代碼

 1 package cn.hjf.redis.rediDemo.lock;
 2 
 3 import java.util.UUID;
 4 
 5 import redis.clients.jedis.Jedis;
 6 import redis.clients.jedis.Transaction;
 7 /**
 8  * redis setnx 實現分布式鎖
 9  * 真實環境下應該是在分布式多進程的情況下
10  * @author hjf
11  */
12 public class SimpleLock
13 {
14     Jedis conn = new
Jedis("127.0.0.1", 6379); 15 16 private final static String LOCK_NAME = "lock"; 17 18 // 獲得鎖 重入鎖和非重入鎖 19 // 設置超時時間 20 public String accequireLock(int timeOut){ 21 // 隨機生成一個uuid 22 String uuid = UUID.randomUUID().toString(); 23 // 結束時間 24 long end = System.currentTimeMillis() + timeOut; 25 // 設置成功返回1 失敗則返回0 26 // 當且僅當key不存在時將key設為value 27 // 若給定的key已經存在 那麽setnx不會做任何操作 28 while(System.currentTimeMillis() < end){ 29 // setnx()和expire()加起來不是一個原子操作 30 if(conn.setnx(LOCK_NAME, uuid).intValue() == 1){ 31 // 增加redis的超時機制 一旦出現異常等情況可以自動去釋放鎖 32 conn.expire(LOCK_NAME, timeOut); 33 return uuid; 34 } 35 36 // 檢查是否設置超時機制(保證原子性) 37 if(conn.ttl(LOCK_NAME) == -1){ 38 conn.expire(LOCK_NAME, timeOut); 39 } 40 41 try 42 { 43 // 未獲得鎖時 休眠一段時間 44 Thread.sleep(1000); 45 } catch (InterruptedException e) 46 { 47 e.printStackTrace(); 48 } 49 } 50 51 return null; 52 } 53 54 // 釋放鎖 55 public boolean releaseLock(String uuid){ 56 while(true){ 57 // 添加監聽 一旦LOCK_NAME發生變化 58 // 那麽下面的事務有效 59 conn.watch(LOCK_NAME); 60 if(uuid.equals(conn.get(LOCK_NAME))){ 61 // 通過事務來操作 62 Transaction transaction = conn.multi(); 63 transaction.del(LOCK_NAME); 64 // 執行失敗的時候會返回null 65 if(transaction.exec() == null){ 66 continue; 67 } 68 // 執行成功 69 return true; 70 } 71 // 取消監聽 72 conn.unwatch(); 73 break; 74 } 75 76 return false; 77 } 78 79 public static void main(String[] args) 80 { 81 // 單機測試環境下 可以采用手動去刪除redis庫中對應的LOCK_NAME 82 // 以便accequireLock可以獲取到鎖 83 SimpleLock simpleLock = new SimpleLock(); 84 String uuid = simpleLock.accequireLock(10000); 85 if(null != uuid){ 86 System.out.println("獲取鎖成功"); 87 }else{ 88 System.out.println("獲取鎖失敗"); 89 } 90 } 91 }

setnx()方法作用就是SET IF NOT EXIST,expire()方法就是給鎖加一個過期時間。乍一看好像和前面的set()方法結果一樣,然而由於這是兩條Redis命令,不具有原子性,如果程序在執行完setnx()之後突然崩潰,導致鎖沒有設置過期時間。那麽將會發生死鎖。網上之所以有人這樣實現,是因為低版本的jedis並不支持多參數的set()方法。

可以在本地安裝redis環境的前提下,進行測試。

關於redis實現分布式鎖