Redis的分布与增量锁定 [英] Redis distributed increment with locking

查看:279
本文介绍了Redis的分布与增量锁定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有产生将被发送到某些API调用一个柜台的要求。我的应用程序在多个节点,所以有些我怎么想生成唯一的柜台上运行。
下面的代码我已经试过

 公共静态长GetTransactionCountForUser(INT telcoId)
{
长valreturn = 0;
字符串键=TelcoId:+ telcoId +:序列;
如果(MUXER =空&放大器;!&放大器; Muxer.IsConnected&放大器;及(Muxer.GetDatabase())!= NULL)
{
了IDatabase分贝= Muxer.GetDatabase();
VAR VAL = db.StringGet(键);
INT MAXVAL = 999;
如果(Convert.ToInt32(VAL)LT; MAXVAL)
{
valreturn = db.StringIncrement(键);
}
,否则
{
布尔isdone = db.StringSet(键,valreturn);
//db.SetAdd(key,new RedisValue).StringIncrement(键,Convert.ToDouble(VAL))
}
}
返回valreturn;
}

和通过运行任务并行libray进行了测试。当我有边界值什么,我看到的是,多个时间0项设置



请让我知道我需要做的。


$什么修正b $ b

更新:
我的最后一个逻辑如下

 公共静态长GetSequenceNumberForTelcoApiCallViaLuaScript(INT telcoId) 
{
长valreturn = 0;
INT maxIncrement = 9999;通过配置
// TODO如果(真)//通过配置
{
分贝了IDatabase待办事项;
字符串键=TelcoId:+ telcoId +:的SequenceNumber
如果(MUXER = NULL&放大器;!&安培; Muxer.IsConnected和放大器;及(DB = Muxer.GetDatabase())!= NULL)
{
valreturn =(int)的分贝。 ScriptEvaluate(@
局部结果= redis.call('增量',KEYS [1])$ ​​b $ b如果结果> tonumber(ARGV [1]),那么
结果= 1
redis.call('设置',KEYS [1],结果)

返回结果,新RedisKey [] {}键,旗:CommandFlags.HighPriority,价值观,新的RedisValue [] { maxIncrement});
}
}
返回valreturn;
}


解决方案

事实上,你的代码是不是翻转边界周围安全的,因为你正在做一个获取,(延迟和思想),集 - 不检查,在条件你的得到仍然适用。如果服务器是围绕项目1000忙着将有可能获得各种疯狂​​的输出,包括像:

  1 
2
...
999
1000 //当获取返回998,让你做一个增量
1001 //同上
1002 //同上
0 //当获取返回999或以上,让你做一套
0 //同上
0 //同上
1

选项:




  1. 使用事务,约束的API,使你的逻辑并发安全

  2. 通过 ScriptEvaluate

  3. 重写逻辑作为一个Lua脚本


现在,(每次选择1)交易的Redis的很难。就个人而言,我会使用2 - 除了作为简单的代码和调试,这意味着你只有1个往返和操作,而不是得,手表,得到的多,增量/套,EXEC /抛弃,和一个重试从开始循环账户中止的情况。我可以尝试,如果你喜欢把它写成的Lua为你 - 它应该是4行






下面是Lua的执行:

 字符串键= ... 
的for(int i = 0; I< 2000;我++) //只是一个测试循环我;你只会做一次等
{
INT结果=(int)的db.ScriptEvaluate(@
局部结果= redis.call('增量',KEYS [1])
如果结果> 999则
结果为0
redis.call('设置',KEYS [1],结果)

返回结果,新RedisKey [] {键});
Console.WriteLine(结果);
}

请注意:如果你需要参数的最大值,你可以使用:

 如果结果> tonumber(ARGV [1]),那么

  INT结果=(INT)db.ScriptEvaluate(...,
新RedisKey [] {}键,新RedisValue [] {最大值});



(所以 ARGV [1] 取从值最大



有必要了解评估 / evalsha (这是 ScriptEvaluate 电话)的未与其他服务器的请求竞争,所以没什么之间的变化增量和可能的设置。这意味着我们并不需要复杂的等逻辑。



下面是相同的(我想!)通过交易/约束API:

 静态INT IncrementAndLoopToZero(了IDatabase分贝,RedisKey键,诠释最大值)
{
INT结果;
布尔成功;

{
RedisValue电流= db.StringGet(键);
VAR TRAN = db.CreateTransaction();
//断言并没有改变 - 注意,这个处理不存在正确
tran.AddCondition(Condition.StringEqual(键,电流));
如果(((int)的电流)>最大)
{
结果为0;
tran.StringSetAsync(关键,因此,标志:CommandFlags.FireAndForget);
}
,否则
{
结果=((int)的电流)+ 1;
tran.StringIncrementAsync(键,旗:CommandFlags.FireAndForget);
}
成功= tran.Execute(); //如果断言失败,返回false并中止
},而(成功!); //如果它中止,我们需要重新
返回结果;
}



复杂的,不是吗?在简单的成功案例的位置则为:

  GET {键}#得到当前的值
WATCH {键}#断言指出{键}应谨慎
获得{键}使用的断言来检查值
多样#开始块
INCR#{键} #增量{键}
EXEC#执行块*如果手表是高兴*

这是...相当多的工作,而且涉及的多路管道失速。更复杂的情况(断言失败,看故障,回绕)将有略微不同的输出,但是应该工作。


I have a requirement for generating an counter which will be send to some api calls. My application is running on multiple node so some how I wanted to generate unique counter. I have tried following code

 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;
    }

And run tested it via Task Parallel libray. When I have boundary values what i see is that multiple time 0 entry is set

Please let me know what correction i needed to do

Update: My final logic is as following

 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;
    }

解决方案

Indeed, your code is not safe around the rollover boundary, because you are doing a "get", (latency and thinking), "set" - without checking that the conditions in your "get" still apply. If the server is busy around item 1000 it would be possible to get all sorts of crazy outputs, including things like:

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

Options:

  1. use the transaction and constraint APIs to make your logic concurrency-safe
  2. rewrite your logic as a Lua script via ScriptEvaluate

Now, redis transactions (per option 1) are hard. Personally, I'd use "2" - in addition to being simpler to code and debug, it means you only have 1 round-trip and operation, as opposed to "get, watch, get, multi, incr/set, exec/discard", and a "retry from start" loop to account for the abort scenario. I can try to write it as Lua for you if you like - it should be about 4 lines.


Here's the Lua implementation:

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);
}

Note: if you need to parameterize the max, you would use:

if result > tonumber(ARGV[1]) then

and:

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

(so ARGV[1] takes the value from max)

It is necessary to understand that eval/evalsha (which is what ScriptEvaluate calls) are not competing with other server requests, so nothing changes between the incr and the possible set. This means we don't need complex watch etc logic.

Here's the same (I think!) via the transaction / constraint 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;
}

Complicated, eh? The simple success case here is then:

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*

which is... quite a bit of work, and involves a pipeline stall on the multiplexer. The more complicated cases (assertion failures, watch failures, wrap-arounds) would have slightly different output, but should work.

这篇关于Redis的分布与增量锁定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆