복붙노트

[REDIS] 잠금과 분산 증가를 레디 스

REDIS

잠금과 분산 증가를 레디 스

나는 몇 가지 API 호출로 전송 될 것 카운터를 생성하기위한 요구 사항을 가지고있다. 나는 고유 카운터를 생성하고 싶었 어떻게 내 응용 프로그램 때문에 약간의 여러 노드에서 실행됩니다. 나는 다음과 같은 코드를 시도

 public static long GetTransactionCountForUser(int telcoId)
    {
        long valreturn = 0;
        string key = "TelcoId:" + telcoId + ":Sequence";
        if (Muxer != null && Muxer.IsConnected && (Muxer.GetDatabase()) != null)
        {
            IDatabase db = Muxer.GetDatabase();
            var val = db.StringGet(key);
            int maxVal = 999;
            if (Convert.ToInt32(val) < maxVal)
            {
                valreturn = db.StringIncrement(key);
            }
            else
            {
                bool isdone = db.StringSet(key, valreturn);
                //db.SetAdd(key,new RedisValue) .StringIncrement(key, Convert.ToDouble(val))
            }
        }
        return valreturn;
    }

그리고 실행은 작업 병렬 라이브러리를 통해 테스트. 내가 경계 값을 가질 때 내가 볼 것은 여러 시간 0 항목이 세트는 점이다

제가 할 필요가 무엇 보정 알려주세요

최신 정보: 내 마지막 논리는 다음과 같다

 public static long GetSequenceNumberForTelcoApiCallViaLuaScript(int telcoId)
    {
        long valreturn = 0;
        int maxIncrement = 9999;//todo via configuration
        if (true)//todo via configuration
        {
            IDatabase db;
            string key = "TelcoId:" + telcoId + ":SequenceNumber";
            if (Muxer != null && Muxer.IsConnected && (db = Muxer.GetDatabase()) != null)
            {
                valreturn = (int)db.ScriptEvaluate(@"
                    local result = redis.call('incr', KEYS[1])
                    if result > tonumber(ARGV[1]) then
                    result = 1
                    redis.call('set', KEYS[1], result)
                    end
                    return result", new RedisKey[] { key }, flags: CommandFlags.HighPriority, values: new RedisValue[] { maxIncrement });
            }
        }
        return valreturn;
    }

해결법

  1. ==============================

    1.의 조건이 "수"여전히 적용된다는 것을 확인하지 않고 - 당신이, (대기 시간과 생각) "GET", "설정"을하고 있기 때문에 실제로 코드는 롤오버 경계 주변에 안전하지 않습니다. 서버가 항목 1,000의 주위에 바쁜 경우는 같은 것들을 포함 미친 출력, 모든 종류의를 얻을 수있을 것입니다 :

    의 조건이 "수"여전히 적용된다는 것을 확인하지 않고 - 당신이, (대기 시간과 생각) "GET", "설정"을하고 있기 때문에 실제로 코드는 롤오버 경계 주변에 안전하지 않습니다. 서버가 항목 1,000의 주위에 바쁜 경우는 같은 것들을 포함 미친 출력, 모든 종류의를 얻을 수있을 것입니다 :

    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 당) 거래 레디 스. 개인적으로, 나는 "2"를 사용하십시오 - 또한 코드 및 디버그에 간단하게되고, 그것은, 간부 / 시계 "에 도착 반대 만 멀티, 증분 / 세트, 얻을, 1 왕복 및 작업을 의미 폐기 ", 그리고"를 중단 시나리오에 대한 계정에 재시도 처음부터 "루프. 이 4 선에 대해해야 - 만약 당신이 좋아하면, 나는 당신을 위해 루아로 작성하려고 할 수 있습니다.

    여기에 루아 구현입니다 :

    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]에서 최대 값을 취하므로)

    그것은 이해하는 것이 필요하다고 평가 / evalsha 증분 및 가능한 세트 사이에 아무것도 변화하므로, 다른 서버 요청과 경쟁하지 않는 (ScriptEvaluate 이른바이다). 이 방법은 우리는 등의 논리 복잡한 시계가 필요하지 않습니다.

    여기에 거래 / 제약 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*
    

    이는입니다 ... 아주 작품의 비트, 그리고 멀티플렉서에 파이프 라인 스톨을 포함한다. 더 복잡한 경우 (주장 실패, 시계 장애, 랩 - 어라운드)는 약간 다른 결과를 가지고,하지만 작동합니다.

  2. ==============================

    2.당신은 시계 명령을 사용할 수 있습니다 - 이런 식으로, 값이 변경되면, 당신은 알림을받을 수 있습니다

    당신은 시계 명령을 사용할 수 있습니다 - 이런 식으로, 값이 변경되면, 당신은 알림을받을 수 있습니다

  3. from https://stackoverflow.com/questions/34871567/redis-distributed-increment-with-locking by cc-by-sa and MIT license