Redis系列(二)--分布式鎖、分布式ID簡單實現及思路


分布式鎖:

  Redis可以實現分布式鎖,只是討論Redis的實現思路,相對來說,Zookeeper實現分布式鎖可能更加可靠

為什么使用分布式鎖:

  單機環境下只存在多線程,通過同步操作就可以實現對並發環境的安全操作,但是多機環境就變成多進程、多線程,這時候同步、加鎖已經無

法保證原子性 

實現分布式可靠性的條件:

  1、互斥性。在任意時刻,只有一個客戶端能持有鎖

  2、不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖

  3、具有容錯性。只要大部分的Redis節點正常運行,客戶端就可以加鎖和解鎖

  4、加鎖和解鎖必須是同一個客戶端

實現分布式鎖的方式:

  1、基於DB的唯一索引。

  2、基於ZK的臨時有序節點。

  3、基於Redis的NX、EX參數。

代碼實現:

public static final String LOCK_SUCCESS = "OK";//加鎖成功

public static final String SET_IF_NOT_EXIST = "NX";

public static final String SET_WITH_EXPIRE_TIME = "PX";

public static final Long RELEASE_SUCCESS = 1L;
public class RedisUtils {

    @Autowired
    JedisPool jedisPool;

    /**
     * 嘗試獲取分布式鎖
     * @param lockKey
     * @param requestId
     * @param expireTime
     * @return
     */
    public boolean tryGetDistributedLock(String lockKey, String requestId, int expireTime) {
        Jedis jedis = jedisPool.getResource();
        String result = jedis.set(lockKey, requestId, RedisConstant.SET_IF_NOT_EXIST, RedisConstant.SET_WITH_EXPIRE_TIME, expireTime);
        if (StringUtils.equals(result, RedisConstant.LOCK_SUCCESS))
            return true;
        return false;
    }

    /**
     * 釋放分布式鎖
     * @param jedis
     * @param lockKey
     * @param requestId
     * @return
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        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(requestId));

        if (RedisConstant.RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
}

加鎖:

  lockKey:唯一的key

  requestId:每個客戶端的唯一ID

  NX:保證key不存在才會set

  PX:key具有過期時間

  expireTime:key的具體過期時間

解鎖:
  通過lua代碼傳到jedis.eval()方法里,並使參數KEYS[1]賦值為lockKey,ARGV[1]賦值為requestId。eval()方法是將Lua代碼交給Redis服務

端執行。

  首先獲取鎖對應的value值,檢查是否與requestId相等,如果相等則刪除鎖(解鎖)。那么為什么要使用Lua語言來實現呢?因為要確保上述

操作是原子性的。

  以上只是針對單機部署Redis,如果Redis是多機部署的,可以采用Redisson實現分布式鎖

PS:上面的set方法需要RedisV2.6+支持

無法避免的問題:

  如在 key 超時之后業務並沒有執行完畢但卻自動釋放鎖了,這樣就會導致並發問題。

  就算 Redis 是集群部署的,如果每個節點都只是 master 沒有 slave,那么 master 宕機時該節點上的所有 key 在那一時刻都相當於是釋放

鎖了,這樣也會出現並發問題。就算是有 slave 節點,但如果在數據同步到 salve 之前 master 宕機也是會出現上面的問題。

 

  Redis分布式鎖內容參考:https://xiaozhuanlan.com/topic/4672859130https://redis.io/topics/distlock

基於Redis實現分布式ID:

  因為Redis是單線程的,所以可以用來生成全部唯一ID,通過incr、incrby實現

  生產環境可能是Redis集群,假如有5個Redis實例,每個Redis的初始值是1,2,3,4,5,然后增長都是5

各個Redis生成的ID為:

A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25

這樣的話,無論請求打到那個Redis上面,都可以獲得不同的ID

優點:

  1、不依賴於數據庫,靈活方便,且性能優於數據庫。

  2、數字ID天然排序,對分頁或者需要排序的結果很有幫助。

缺點:

  1、如果系統里沒有Redis,就比較操蛋了

  2、編碼、配置工作量大一點

 

分布式ID推薦一篇文章:https://blog.csdn.net/hengyunabc/article/details/44244951

流水號:

  Redis同樣可以生成每天的流水號,日期+自增長序號,進行incr

面試題:如何從Redis查詢出前綴為id的key?

  首先這個問題應該要明確數據量,如果數據量很小,可以直接使用keys id*,keys命令直接返回所有的key,如果是海量數據,keys命令肯定

不行了,所以要跟面試官明確這個問題。海量數據環境下,例如1億條數據,可以使用scan命令

scan是基於游標的迭代器,每次使用都要基於上一次的游標延續之前的迭代過程

格式:scan cursor [MATCH pattern] [COUNT count]

cursor以0開始,到0結束,scan 0 match id* count 10,從0開始,匹配以id開頭的key,每次返回10條

返回結果有兩部分:

1) "0"
2) 1) "id1"
   2) "id2"
    .......

  1)為返回的游標,返回0證明迭代結束。這里希望返回10條,並不是一定返回10條,可能只是返回5條數據(一次返回的數量不可控,大概率符合

count),如果返回cursor不是0,證明迭代沒有結束,可以繼續查詢,知道返回cursor為0,效率低於keys,但是不會阻塞Redis

PS:scan返回的游標可能后一次比前一次更小,所以可能會出現重復數據,需要外部程序進行去重

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM