Redis分布式ロックの原理と実現

4956 ワード

ps:このブログカタログは【Redis分散ロック使用】のコードに瑕疵があるので、誤った使用を避けるために、移動してくださいhttps://blog.csdn.net/She_lock/article/details/88894096、興味のある人は傷がどこにあるかを見ることができます
前言
問題を解決します:複数のプロセスの複数の機械、1つのデータに対する操作の反発.たとえば、受注と在庫控除の操作は、2つの操作が一貫している必要があります.1つのスレッドがこの2つの操作を実行した後、次のスレッドが介入して実行することができます.同時に実行すると、「マルチ販売」の現象が発生する可能性があります.
解決方法:
1、sqlレベル:
  • は、SELECT FOR UPDATEライン・レベル・ロックを使用して実現することができる.

  • 2、コードレベル:
  • synchronizedキーワードは、メソッドにロックを追加することで、同時問題を解決することができますが、キューの実行速度が遅く、高同時性の場合は適切ではありません.
  • Redis分散ロックは、主にSETNXコマンドとGETSETコマンドを利用する.高同時性を解決します.

  • 以上の3つの方法はいずれも問題を解決することができ,今日議論するのは3つ目の------Redis分布式ロックである.
    Redis分散ロックベース命令
    SETNX
    構文:
    SETNX key value
    
    keyvalueに設定し、keyが存在しない場合はSETコマンドに等しい.keyが存在する場合、何もしません.SETNX”SET if Not eXists”の略語です.
    戻り値:
  • 1 keyが
  • に設定されている場合
  • 0 keyが
  • に設定されていない場合
    例:
    redis> SETNX mykey "Hello"
    (integer) 1
    redis> SETNX mykey "World"
    (integer) 0
    redis> GET mykey
    "Hello"
    redis> 
    

    用途:足かせに使用できます.例えば商品idに鍵をかける.
    詳細コマンドの説明は、「SETNX key value」を参照してください.
    GETSET
    構文:
    GETSET key value
    

    keyをvalueに自動的に対応させ、元のkeyに対応するvalueを返します.keyが存在しますが、対応するvalueが文字列ではない場合は、エラーが返されます.
    この解釈は少し拗ねていますが、実は分解して見ることができます.まずGET、最後にSET、前の古い値を返します.前のKeyが存在しなければnilを返します.
    例:
    redis> INCR mycounter
    (integer) 1
    redis> GETSET mycounter "0"
    "1"
    redis> GET mycounter
    "0"
    redis>
    

    用途:デッドロックを解決します.原理は、元のロックを新しいロックに置き換えることです.
    詳細コマンドの説明は、「GETSET key value」を参照してください.
    Redis分散ロックの使用
    次のツールクラスRedisLock:
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    /**
     * Redis    
     */
    @Component
    @Slf4j
    public class RedisLock {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         *   
         * @param key
         * @param value     +    
         * @return
         */
        public boolean lock(String key, String value) {
        
            if(redisTemplate.opsForValue().setIfAbsent(key, value)) {//   SETNX  ,setIfAbsent       true,      false
                return true;
            }
            //  currentValue=A                value  B           ,             (            ),             ,          “  ”   
            String currentValue = redisTemplate.opsForValue().get(key);
            //           
            if (!StringUtils.isEmpty(currentValue)
                    && Long.parseLong(currentValue) < System.currentTimeMillis()) {
                //         ,    ,GETSET          
                String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
                if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                    return true;
                }
            }
    
            return false;//          ,          ,                
        }
    
        /**
         *   
         * @param key
         * @param value
         */
        public void unlock(String key, String value) {
            try {
                String currentValue = redisTemplate.opsForValue().get(key);
                if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
                    redisTemplate.opsForValue().getOperations().delete(key);
                }
            }catch (Exception e) {
                log.error("【redis    】    , {}", e);
            }
        }
    
    }
    

    ソースコードから分かるように、setIfAbsentはロック機能を実現し、一部のネットワークやioなどの原因で異常にデッドロックが発生した場合、現在のロック操作が期限切れになった後、getAndSetは新しいロックで元の古いロックを置き換え、デッドロック問題の解決を実現する.
    運用:
    
    	private static final int TIMEOUT = 10 * 1000; //     10s
    
    	@Autowired
        private RedisLock redisLock;
    
        public void orderProductMockDiffUser(String productId)
        {
            //  
    		long time = System.currentTimeMillis() + TIMEOUT ;
    		if(!redisLock.lock(productId,String.valueOf(time))){ //    
    			throw SellException(101,"      ,       !"); 
    		}
    	
    		/*****    ,            *******/
    		
            //1.       , 0     。
            int stockNum = stock.get(productId);
            if(stockNum == 0) {
                throw new SellException(100,"    ");
            }else {
                //2.  (      openid  )
                orders.put(KeyUtil.genUniqueKey(),productId);
                //3.   
                stockNum =stockNum-1;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stock.put(productId,stockNum);
            }
    
            //  
    		redisLock.unlock(productId,String.valueOf(time));
        }