sql >> データベース >  >> NoSQL >> Redis

ロック付きのRedis分散増分

    実際、「get」の条件がまだ適用されていることを確認せずに「get」(レイテンシーと思考)、「set」を実行しているため、コードはロールオーバー境界の周りで安全ではありません。サーバーがアイテム1000の前後でビジー状態の場合、次のようなものを含む、あらゆる種類のクレイジーな出力を取得する可能性があります。

    1
    2
    ...
    999
    1000 // when "get" returns 998, so you do an incr
    1001 // ditto
    1002 // ditto
    0 // when "get" returns 999 or above, so you do a set
    0 // ditto
    0 // ditto
    1
    

    オプション:

    1. トランザクションAPIと制約APIを使用して、ロジックの同時実行性を安全にします
    2. ScriptEvaluateを使用して、ロジックをLuaスクリプトとして書き直します

    現在、redisトランザクション(オプション1ごと)は困難です。個人的には、「2」を使用します。コーディングとデバッグが簡単であることに加えて、「get、watch、get、multi、incr / set、exec /」とは対照的に、ラウンドトリップと操作が1つしかないことを意味します。破棄」、および「開始から再試行」ループを実行して、中止シナリオを説明します。よろしければ、Luaとして書いてみることもできます。約4行にする必要があります。

    Luaの実装は次のとおりです:

    string key = ...
    for(int i = 0; i < 2000; i++) // just a test loop for me; you'd only do it once etc
    {
        int result = (int) db.ScriptEvaluate(@"
    local result = redis.call('incr', KEYS[1])
    if result > 999 then
        result = 0
        redis.call('set', KEYS[1], result)
    end
    return result", new RedisKey[] { key });
        Console.WriteLine(result);
    }
    

    注:最大値をパラメーター化する必要がある場合は、次を使用します:

    if result > tonumber(ARGV[1]) then
    

    および:

    int result = (int)db.ScriptEvaluate(...,
        new RedisKey[] { key }, new RedisValue[] { max });
    

    (つまり、ARGV[1] maxから値を取得します )

    evalを理解する必要があります / evalsha (これがScriptEvaluate 呼び出し)他のサーバーリクエストと競合していません 、したがって、incr間で何も変更されません そして可能なset 。これは、複雑なwatchが必要ないことを意味します などのロジック。

    トランザクション/制約APIを介したものも同じです(私は思います!):

    static int IncrementAndLoopToZero(IDatabase db, RedisKey key, int max)
    {
        int result;
        bool success;
        do
        {
            RedisValue current = db.StringGet(key);
            var tran = db.CreateTransaction();
            // assert hasn't changed - note this handles "not exists" correctly
            tran.AddCondition(Condition.StringEqual(key, current));
            if(((int)current) > max)
            {
                result = 0;
                tran.StringSetAsync(key, result, flags: CommandFlags.FireAndForget);
            }
            else
            {
                result = ((int)current) + 1;
                tran.StringIncrementAsync(key, flags: CommandFlags.FireAndForget);
            }
            success = tran.Execute(); // if assertion fails, returns false and aborts
        } while (!success); // and if it aborts, we need to redo
        return result;
    }
    

    複雑ですね単純な成功事例 ここにあります:

    GET {key}    # get the current value
    WATCH {key}  # assertion stating that {key} should be guarded
    GET {key}    # used by the assertion to check the value
    MULTI        # begin a block
    INCR {key}   # increment {key}
    EXEC         # execute the block *if WATCH is happy*
    

    これは...かなりの作業であり、マルチプレクサのパイプラインストールが含まれます。より複雑なケース(アサーションの失敗、ウォッチの失敗、ラップアラウンド)の出力はわずかに異なりますが、機能するはずです。



    1. ClouderaSearchとHBaseを使用したメールのインデックス作成

    2. SpringRedisエラーハンドル

    3. ElasticBeanstalk EC2からRedis(ElastiCache)への接続が失敗する

    4. MongoDBでの遅いクエリの分析