実際、「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
オプション:
- トランザクションAPIと制約APIを使用して、ロジックの同時実行性を安全にします
-
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*
これは...かなりの作業であり、マルチプレクサのパイプラインストールが含まれます。より複雑なケース(アサーションの失敗、ウォッチの失敗、ラップアラウンド)の出力はわずかに異なりますが、機能するはずです。