1. 程式人生 > >Redis入門(七):Redis分散式鎖(單機模式/叢集模式)

Redis入門(七):Redis分散式鎖(單機模式/叢集模式)

Redis 實現分散式鎖

單機模式的Redis分散式鎖

  • 優缺點
    • 實現比較輕,大多數時候能滿足需求;因為是單機單例項部署,如果redis服務宕機,那麼所有需要獲取分散式鎖的地方均無法獲取鎖,將全部阻塞,需要做好降級處理。
    • 當鎖過期後,執行任務的程序還沒有執行完,但是鎖因為自動過期已經解鎖,可能被其它程序重新加鎖,這就造成多個程序同時獲取到了鎖,這需要額外的方案來解決這種問題。
  • 實現程式碼
      import redis.clients.jedis.Jedis;
      import java.util.Collections;
    
      public class JedisDistributedLock
    { private static final String LOCK_SUCCESS = "OK"; private static final Long RELEASE_SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; // 獲取鎖,不設定超時時間 public static boolean getLock(Jedis jedis,
    String lockKey, String requestId){ String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST); if(LOCK_SUCCESS.equals(result)){ return true; } return false; } // 獲取鎖, 設定超時時間,單位為毫秒 public static boolean getLock(Jedis jedis, String lockKey,
    String requestId, Long expireTime){ /** * jedis.set(key, value, nxxx, expx, time) * * Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1 * GB). * @param key * @param value * @param NXXX NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key if it already exist. * @param EXPX EX|PX, expire time units: EX = seconds; PX = milliseconds * * @return Status code reply set成功,返回 OK */ String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if(LOCK_SUCCESS.equals(result)){ return true; } return false; } //釋放鎖 public static boolean releaseLock(Jedis jedis, String lockKey, String requestId){ // Lua指令碼 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(RELEASE_SUCCESS.equals(result)){ return true; } return false; } }

叢集模式的Redis分散式鎖 Redlock

  • 優缺點

    • Redlock是Redis的作者antirez給出的叢集模式的Redis分散式鎖,它基於N個完全獨立的Redis節點
    • 部分節點宕機,依然可以保證鎖的可用性
    • 當某個節點宕機後,又立即重啟了,可能會出現兩個客戶端同時持有同一把鎖,如果節點設定了持久化,出現這種情況的機率會降低
    • 和單機模式Redis鎖相比,實現難度要大一些
  • 實現程式碼

    • 搭建redis叢集
    # 安裝指定版本的redis
    cd /opt
    wget http://download.redis.io/releases/redis-3.2.8.tar.gz
    tar xzf redis-3.2.8.tar.gz && rm redis-3.2.8.tar.gz
    cd redis-3.2.8
    make
    
    # 構建redis叢集
    cd
    mkdir cluster-test
    cd cluster-test
    mkdir conf logs bin
    # 拷貝客戶端伺服器檔案
    rsync -avp /opt/redis-3.2.8/src/redis-server bin/
    rsync -avp /opt/redis-3.2.8/src/redis-cli bin/
    # 配置檔案
    for i in 7000 7001 7002 7003 7004 7005; do
    cat << EOF > conf/${i}.conf
    port ${i}
    cluster-enabled yes
    cluster-config-file nodes-${i}.conf
    cluster-node-timeout 5000
    appendonly yes
    logfile "logs/${i}.log"
    appendfilename "appendonly-${i}.aof"
    EOF
    done
    # 啟動
    cd ${HOME}/cluster-test
    for i in 7000 7001 7002 7003 7004 7005; do
    ./bin/redis-server ./conf/${i}.conf &
    done
    # 建立叢集
    sudo apt-get install -y ruby rubygems
    gem install redis
    cd /opt/redis-3.2.8/src/
    ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
    # 測試
    cd ${HOME}/cluster-test/
    ./bin/redis-cli -c -p 7000 cluser nodes
    # 停止
    #cd ${HOME}/cluster-test
    #for i in 7000 7001 7002 7003 7004 7005; do
    #./bin/redis-cli -p ${i} shutdown && echo "redis ${i} 已停止"
    #done
    

    指令碼執行後,出現如下日誌,說明叢集搭建成功

    >>> Creating cluster
    >>> Performing hash slots allocation on 6 nodes...
    Using 3 masters:
    127.0.0.1:7000
    127.0.0.1:7001
    127.0.0.1:7002
    Adding replica 127.0.0.1:7003 to 127.0.0.1:7000
    Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
    Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
    M: d0e3588845a839052d9e611853740edd3b348966 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    M: 4f5518e78c1ea1c25bab0d0147f12c0281fd5f96 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
    M: 5724fb03c4527389be0d556c5dc5419a12d367c5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    S: 1206f7a76b8050b10e2e602fad422faee95efdb5 127.0.0.1:7003
    replicates d0e3588845a839052d9e611853740edd3b348966
    S: 8f421f529c86cefbf033e1e3a1687a9767802bd2 127.0.0.1:7004
    replicates 4f5518e78c1ea1c25bab0d0147f12c0281fd5f96
    S: d94710251235efde7da4073cc4ff104d9298bd0f 127.0.0.1:7005
    replicates 5724fb03c4527389be0d556c5dc5419a12d367c5
    Can I set the above configuration? (type 'yes' to accept): yes
    >>> Nodes configuration updated
    >>> Assign a different config epoch to each node
    >>> Sending CLUSTER MEET messages to join the cluster
    Waiting for the cluster to join...
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    M: d0e3588845a839052d9e611853740edd3b348966 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
    1 additional replica(s)
    S: 8f421f529c86cefbf033e1e3a1687a9767802bd2 127.0.0.1:7004
    slots: (0 slots) slave
    replicates 4f5518e78c1ea1c25bab0d0147f12c0281fd5f96
    S: 1206f7a76b8050b10e2e602fad422faee95efdb5 127.0.0.1:7003
    slots: (0 slots) slave
    replicates d0e3588845a839052d9e611853740edd3b348966
    M: 4f5518e78c1ea1c25bab0d0147f12c0281fd5f96 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
    1 additional replica(s)
    S: d94710251235efde7da4073cc4ff104d9298bd0f 127.0.0.1:7005
    slots: (0 slots) slave
    replicates 5724fb03c4527389be0d556c5dc5419a12d367c5
    M: 5724fb03c4527389be0d556c5dc5419a12d367c5 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
    1 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    
    • redis-cluster簡單叢集示例圖 Redis rdbs
    • 使用Redission構建redLock

           Redis叢集使用分片的方式儲存鍵值對,redis-cluster採用slot(槽)的概念,一共16384個槽位,分佈在叢集中的所有master例項上。儲存資料時,直接對key值做CRC16校驗後得到的校驗值對16384取模,將鍵值對儲存到對應的槽位所在的例項上。

    import org.redisson.Redisson;
    import org.redisson.api.RAtomicLong;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    
    /**
    * Created by Haiyoung on 2018/8/11.
    */
    public class RedissonManager {
    
        private static final String RAtomicName = "genId_";
    
        private static Config config = new Config();
    
        private static RedissonClient redisson = null;
    
        public static void init(){
            try{
                config.useClusterServers()
                        .setScanInterval(200000)//設定叢集狀態掃描間隔
                        .setMasterConnectionPoolSize(10000)//設定對於master節點的連線池中連線數最大為10000
                        .setSlaveConnectionPoolSize(10000)//設定對於slave節點的連線池中連線數最大