redisベースの分散ロック(SETNXおよびRedisson)の実装(在庫の削減を事例とする)


一:実現原理:
redisのsetコマンドを使用して分散ロックを実現します.
Redis 2.6.12以降、setでは次のパラメータを使用できます.
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX second:キーの有効期限をsecond秒に設定します.SET key value EX second効果はSETEX key second valueと同等です.PX millisecond:キーの有効期限をmillisecondミリ秒に設定します.SET key value PX millisecond効果はPSETEX key millisecond valueと同等です.NX:キーが存在しない場合のみ、キーの設定操作を行います.SET key value NX効果はSETNX key valueに等しい.XX:キーが既に存在する場合のみ、キーを設定します.
二:SETNX
Redisは単一プロセスの単一スレッドモードであり、キューモードを使用すると同時アクセスがシリアルアクセスになり、マルチクライアントによるRedisへの接続に競合はありません.SETNXコマンドを使用してsetnx name xiaowangなどの分散ロックを実現できます.
キーが存在しない場合にのみ、キーの値をvalueに設定します.与えられたキーが既に存在する場合、SETNXは何もしない
SETNXは『SET if Not eXists』(存在しなければSET)の略である.
戻り値:設定に成功し、1を返します.設定に失敗し、0を返します.
3:次に、redisを使用して分散ロックを実装する方法を例に挙げます.
@RestController
public class IndexController {
  //  private static final Logger LOGGER= LoggerFactory.getLogger(IndexController.class);
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
        if(stock>0){
            int realStock=stock-1;
            stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
        }else{
            System.out.println("    ,    ");
        }
        return "end";
    }
}

これは古典的な在庫控除の例で、実現するたびに在庫が1つ減少します.
ここではspringboot統合redisを用いて実現し,springboot操作redistributionのテンプレートを注入した.もちろん分からなくても大丈夫です.私はすべての肝心なコードの後ろでjedisで説明しました.
例えばこれはkey、valueの操作を設定します.jedis.set(key,value);
stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)

1:次に、このコードを具体的に見てみましょう.まず、このコードに何か問題があるかどうかを見ることができますか.
コンカレントの問題であることがわかりますが、このコードを実行するスレッドが複数ある場合、多くのリクエストがあり、3人のユーザーが同時にこのコードにアクセスしている場合、50件の商品がある場合、同時に発生した場合、最後に戻る可能性があるのは49で、理屈では47で、コンカレントの問題が発生した場合、どのように解決しますか?
2:ソリューション:synchronizedロックを追加して問題を解決できますか?
単体アーキテクチャであればsynchronizedの追加は問題ありません.しかし、クラスタアーキテクチャでは、このwarパッケージを複数のtomcatの上に置くと、jdkはsynchronizedロックやlockロックなど、多くのロックを提供してくれます.例えば、synchronizedロックやlockロックはjvmプロセス上であり、1つのtomcatの上でしか役に立ちません.nginxを通じて複数のtomcatの上に配布されると、実際にはロックできません.
3:redisのSETNXで分散ロックを実現
多くのリクエストが送信されると、redisは単一スレッドアーキテクチャであるため、リクエストが実行されます.
質問1:stringRedisTemplate.delete(lockKey);//このロックを解除すると、このコードに異常が発生した場合、このロックは解除されず、他のスレッドが入ってくるとロックが取れず、デッドロック状態に陥ります.
解決方法:このコードに異常を加えて処理し、最後にこのロックを解放し、デッドロックに陥らないようにします.
質問2:まだロックが解除されていない場合、システムが突然ダウンし、finallyの後ろのコードが実行できなくなりました.どうすればいいですか.
解決策:このkeyに10秒の期限切れを設定することができ、時間が来たとき、redisは自動的にこのkeyを削除します.
問題3:超高同時シーンではkey'の有効期限を10秒に設定し、最初のスレッドが来て15秒実行されると、高同時の場合、最初のスレッドが2番目のスレッドのロックを解放し、ロックが無効になる可能性が高い.
解決策:各スレッドに重複しない文字列を生成し、ロックを解除するときに、このスレッド自体のロックかどうかを検証します.
問題4:もし私たちのコードが10秒実行されたら、このkeyはもう期限切れで、コードがまだ実行されていないのに、この時どうやって解決しますか?
解決策:私たちはこのkeyが期限切れになった後にこのkeyの生命の時間を歌って彼に命を続けることができて、私たちはredissonで実現することができます.
@Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="lockKey";
        String redisId= UUID.randomUUID().toString();
       try {  //   
           Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"lockkey",10, TimeUnit.SECONDS);//   
           if(!result){
               return "err";
           }
           int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
           if(stock>0){
               int realStock=stock-1;
               stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
           }else{
               System.out.println("    ,    ");
           }
       }finally {
           if(stringRedisTemplate.opsForValue().get(lockKey).equals(redisId)){  //   
               stringRedisTemplate.delete(lockKey);//      
           }

       }
        return "end";
    }

四:Redissonによる分散ロック
Redissonはredisのオープンソースフレームワークであり、このフレームワークは上記の問題をすべてカプセル化し、直接使用すればよい.


@RestController
public class IndexController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/deduct_stock")
    public String deductStock(){
        String lockKey="lockKey";
        RLock redisId=redisson.getLock(lockKey);//     
       try {  //   
           redisId.lock();//j  
           int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));//jedis.get("stock");
           if(stock>0){
               int realStock=stock-1;
               stringRedisTemplate.opsForValue().set("stock",realStock+"");//jedis.set(key,value)
           }else{
               System.out.println("    ,    ");
           }
       }finally {
         
          redisId.unlock();
       }
        return "end";
    }
}

Redissonの下部は基本的に上記の問題であり、コードをより簡潔にするためにパッケージ化されています.