Memang, kode Anda tidak aman di sekitar batas rollover, karena Anda melakukan "get", (latensi dan pemikiran), "set" - tanpa memeriksa apakah kondisi di "get" Anda masih berlaku. Jika server sibuk di sekitar item 1000, akan mungkin untuk mendapatkan segala macam output gila, termasuk hal-hal seperti:
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
Opsi:
- gunakan API transaksi dan batasan untuk membuat logika Anda aman-konkurensi
- tulis ulang logika Anda sebagai skrip Lua melalui
ScriptEvaluate
Sekarang, transaksi redis (per opsi 1) sulit dilakukan. Secara pribadi, saya akan menggunakan "2" - selain lebih sederhana untuk kode dan debug, itu berarti Anda hanya memiliki 1 perjalanan pulang pergi dan operasi, sebagai lawan dari "dapatkan, tonton, dapatkan, multi, incr/set, exec/ buang", dan "coba lagi dari awal" untuk memperhitungkan skenario pembatalan. Saya dapat mencoba menulisnya sebagai Lua untuk Anda jika Anda mau - harus sekitar 4 baris.
Berikut implementasi 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);
}
Catatan:jika Anda perlu membuat parameter maks, Anda akan menggunakan:
if result > tonumber(ARGV[1]) then
dan:
int result = (int)db.ScriptEvaluate(...,
new RedisKey[] { key }, new RedisValue[] { max });
(jadi ARGV[1]
mengambil nilai dari max
)
Perlu dipahami bahwa eval
/evalsha
(yang merupakan ScriptEvaluate
panggilan) tidak bersaing dengan permintaan server lain , jadi tidak ada yang berubah di antara incr
dan kemungkinan set
. Ini berarti kita tidak memerlukan watch
yang rumit dll logika.
Ini sama (saya pikir!) melalui API transaksi / kendala:
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;
}
Rumit, ya? kasus sukses sederhana ini dia:
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*
yang... sedikit pekerjaan, dan melibatkan saluran pipa pada multiplexer. Kasus yang lebih rumit (kegagalan pernyataan, kegagalan arloji, penyelesaian) akan memiliki keluaran yang sedikit berbeda, tetapi seharusnya berhasil.