MemcachedとRedis分散ロックスキーム

6354 ワード

分散キャッシュにより、1台のサーバメモリが無限に拡張できないボトルネックを解決できます.分散キャッシュのアプリケーションでは、複数のクライアントが同時に競合する問題が発生します.この場合、分散ロックを使用して、ロックを取得したクライアントに操作権限があります.
MemcachedおよびRedisは一般的な分散キャッシュ構築スキームであり、以下にMemcachedおよびRedis分散ロックに基づく実装方法を列挙する.
Memcached分散ロック
Memcachedはaddコマンドを使用できます.このコマンドはKEYが存在しない場合にのみ追加されるか、処理されません.Memcachedのすべてのコマンドは原子的で、addと同じKEYを発行して、1つだけ成功します.
この原理を利用して,ロックロックロックKEYを定義することができ,addはロックを得ることに成功したと考えられる.また[期限切れタイムアウト]時間を設定し、ダウンタイム後もデッドロックしないことを保証します.
具体的な操作が完了した後、今回の操作がタイムアウトしたかどうかを判断します.タイムアウトした場合はロックは削除されず、タイムアウトしない場合はロックは削除されます.
疑似コード:
 1          if (mc.Add("LockKey", "Value", expiredtime))

 2             {

 3                 //   

 4                 try

 5                 {

 6                     //do business  function

 7 

 8                     //    

 9                     if (!CheckedTimeOut())

10                     {

11                         mc.Delete("LockKey");

12                     }

13                 }

14                 catch (Exception e)

15                 {

16                     mc.Delete("LockKey");

17                 }

18                

19             }

 
Redis分散ロック
Redisにはaddコマンドはありませんが、SETNX(SET if Not eXists)があり、与えられたkeyが既に存在する場合、SETNXは何もしません.設定に成功し、1を返します.設定に失敗し、0を返します.
SETNXコマンドでは有効期限を設定できません.EXPIREコマンドで有効期限を設定する必要があります.
疑似コード:
            int lockResult = rd.SETNX("LockKey", "Value");

            if (lockResult == 1)

            {

                //[1]   



                //[2]        

                rd.EXPIRE("LockKey", expiredtime);



                try

                {

                    //do business  function



                    //    

                    if (!CheckedTimeOut())

                    {

                        rd.DEL("LockKey");

                    }

                }

                catch (Exception e)

                {

                    rd.DEL("LockKey");

                }



            }


このようなやり方には、大きな潜在的なリスクがあります.[1]ロックを取得してから[2]を実行して有効期限を設定します.この期間中にダウンタイムが発生した場合、有効期限が設定されていません.Redisのデフォルトキャッシュの期限切れポリシーを押すと、このロックは解放されず、デッドロックが発生します.
このような方法では、ロックのタイムアウト期限切れポリシーを他の方法で実現することは推奨されません.
1:SETNX value値=現在時間+期限切れタイムアウト時間、1を返すとロックが取得され、0を返すとロックが取得されません.回転2.
2:GETはvalueの値を取得します.ロックが期限切れになったかどうかを判断します.タイムアウトした場合は、3を回します.
3:GETSET(所与のkeyの値をvalueとし、keyの旧値を返す)、GETSET value値=現在時間+期限切れタイムアウト時間であり、判定されたvalueがタイムアウトであれば、ロックが得られることを示し、そうでなければロックが得られない.
2から3への同時進行操作では,タイムアウト時間は複数回書き換えられるが,これはあまり影響しない.
疑似コード:
 
            string expiredtime = DateTime.Now.AddMinutes(LockTimeoutMinutes).ToString();

            int lockResult = rd.SETNX("LockKey", expiredtime);

            bool getLock = false;

            if (lockResult == 1)

            {

                //   

                getLock = true;

            }

            else

            {

                string curExpiredtime = rd.GET("LockKey");



                //     

                if (CheckedLockTimeOut(expiredtime))

                {

                    expiredtime = DateTime.Now.AddMinutes(LockTimeoutMinutes).ToString();

                    string newExpiredTime = GETSET(expiredtime);

                    if (CheckedLockTimeOut(newExpiredTime))

                    {

                        //   

                        getLock = true;

                    }

                }

            }

            if (getLock)

            {

                try

                {

                    //do business  function



                    //    

                    if (!CheckedTimeOut())

                    {

                        rd.DEL("LockKey");

                    }

                }

                catch (Exception e)

                {

                    rd.DEL("LockKey");

                }

            }


個人的には、このようなやり方は、まだ完璧ではないと思います. 
ZooKeeperの分布式ロックについて,次編で検討する.
 
参照先:
鄭昀,
電子商取引課題V:分散ロック
jeffkit, 
Redisによる分散ロック