Redisマルチコンカレント問題

3530 ワード

具体的な問題
キーがあります.名前がmyNumで、アラビア数字が保存されていると仮定し、現在値が1であると仮定し、複数の接続がmyNumを操作している場合があります.この場合、同時発生する問題があります.2つの接続linkAとlinkBがあると仮定します.この2つの接続は次の操作を実行し、mynumの値を取り出し、+1して保存し、次のインタラクションを見てみましょう.
for (int i = 0; i < 50000; i++)
{                
     int result = rds.Get("mykey1");
     rds.Set("mykey1", result + 1);                
}
Console.WriteLine("    ,  " + rds.Get("mykey1"));

私たちの単一スレッドと、50000を2つ走ると、100000が出力されます.現在は2つの同時スレッドが同時実行されています.同時実行によるデータの結果は、私たちが望んでいないことが多いです.
発生原因
redisは、key-valueに基づいてデータを永続化する単一スレッドメカニズムのnosqlデータベースです.単一スレッドのためredis自体にロックの概念はなく,複数のクライアント接続に競合関係はないがjedisなどのクライアントを用いてredisに同時アクセスすると問題が発生する.接続タイムアウト、データ変換エラー、ブロック、クライアントの接続停止などの問題が発生しました.これらの問題は、クライアント接続の混乱によるものです.
解決策
メソッド1(データベースの読み書き操作にロックをかける)
読み書きの方法をカプセル化し、ロックを追加
type Accout struct {
    flag sync.Mutex            //sync.Mutex  
}
//      
func (a *Accout) Reed(n int) {  // 
    a.flag.Lock()            //  
    defer a.flag.Unlock()    //          
}
//      
func (a *Accout) Write(n int) {  // 
    a.flag.Lock()            //  
    defer a.flag.Unlock()    //          
}

メソッド2(setnxによるロック)
SETNXコマンド(SET if Not eXists)
構文:SETNX key value
機能:keyの値をvalueに設定し、keyが存在しない場合のみ;与えられたkeyが既に存在する場合、SETNXは何もしない.
モード:SETNXをロックに使用する
キーワードfooにロックをかける場合は、クライアントは次の方法を試します.
SETNX lock.foo
SETNXが1を返すと、クライアントがロックを取得したことを示し、keyが設定したunix時間はロックが無効になった時間を指定します.その後、クライアントはDEL lockを通過することができる.fooはロックを解除します.
SETNXが0を返すと、keyが他のクライアントにロックされていることを示します.ロックが非ブロック(non blocking lock)である場合、ロックまたは再試行タイムアウトが正常に取得されるまで、コールを返すか、再試行サイクルに入るかを選択できます.
しかし、SETNXロックのみを使用すると競合条件があり、特定の場合にエラーが発生することが確認されています.例えばデッドロックが発生します.
デッドロックの処理
上のロックアルゴリズムには、クライアントが失敗したり、クラッシュしたり、他の理由でロックを解放できなかったりしたらどうしますか?
この状況は、ロックされたkeyがunixタイムスタンプを保存しているため、key値のタイムスタンプが現在のタイムスタンプより小さい場合、ロックが有効ではないことを示すことを検出することによって発見することができる.
しかし、複数のクライアントが同時に1つのロックが期限切れであるかどうかを検出し、それを解放しようとすると、デッドロックのkeyを簡単に乱暴に削除し、SETNXでロックすることはできません.このとき、競争条件(race condition)が形成されているからです.
C1   C2    lock.foo       , SETNX     0 ,       C3    ,  C3         (crashed) 。
C1   lock.foo    DEL   。
C1   lock.foo    SETNX    。
C2   lock.foo    DEL   。
C2   lock.foo    SETNX    。

エラー:競合条件の関係で、C 1とC 2の両方がロックされました.
幸いなことに、以下のアルゴリズムは以上の問題を回避することができる.スマートなC 4クライアントを見てみましょう.
C4   lock.foo    SETNX   。
       C3     lock.foo ,   Redis   C4    0 。
C4   lock.foo    GET   ,   lock.foo       。   ,   (sleep)    ,      。
    ,   lock.foo    unix           ,C4       :
GETSET lock.foo 

GETSETの役割のため、C 4はGETSETの戻り値を見てlockを決定することができる.foo以前に格納された古い値は依然としてその期限切れのタイムスタンプであり、もしそうであれば、C 4はロックを取得する.
他のクライアント、例えばC 5が、C 4よりも早くGETSET操作を実行してロックを取得した場合、C 4のGETSET操作は、期限切れのタイムスタンプ(C 5が設定したタイムスタンプ)を返す.C 4は第一歩からやり直すしかない.
注意、C 4のGETSET操作がkeyを修正しても、将来には影響しません.
ここでは,ロックキーに対応するvalueが実際のビジネス意義を持たないと仮定すると,問題があり,実際にはそのvalueもビジネスで使用すべきではない.
このロックアルゴリズムをより丈夫にするために、ロックを取得したクライアントは、DELなどのコマンドの実行によってロックが意外に解除されないように、期限切れの時間を常にチェックしなければならない.クライアントの失敗は非常に複雑で、クラッシュだけでなく、クライアントがいくつかの操作でかなり長い間ブロックされている可能性があるからだ.その後、DELコマンドが実行されます(ただし、ロックは別のクライアントの手にあります).
GETSETコマンド
構文:GETSET key value
機能:指定したkeyの値をvalueに設定し、keyの古い値(old value)を返します.keyが存在するが文字列タイプではない場合、エラーが返されます.
戻り値:指定したkeyの古い値を返します.キーに古い値がない場合、すなわちキーが存在しない場合、nilを返します.
ref by
http://blog.csdn.net/lwljava/article/details/38555911