वास्तव में, आपका कोड रोलओवर सीमा के आसपास सुरक्षित नहीं है, क्योंकि आप "प्राप्त", (विलंबता और सोच), "सेट" कर रहे हैं - यह जांचे बिना कि आपके "प्राप्त" में शर्तें अभी भी लागू होती हैं। यदि सर्वर आइटम 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 का उपयोग करें
- अपने तर्क को
ScriptEvaluate
. के माध्यम से Lua स्क्रिप्ट के रूप में फिर से लिखें
अब, रेडिस लेनदेन (प्रति विकल्प 1) कठिन हैं। व्यक्तिगत रूप से, मैं "2" का उपयोग करता हूं - कोड और डीबग के लिए सरल होने के अलावा, इसका मतलब है कि आपके पास केवल 1 राउंड-ट्रिप और ऑपरेशन है, जैसा कि "प्राप्त करें, देखें, प्राप्त करें, बहु, incr/set, exec/ त्यागें", और निरस्त परिदृश्य के लिए खाते में "शुरू से पुनः प्रयास करें" लूप। यदि आप चाहें तो मैं इसे आपके लिए लुआ के रूप में लिखने की कोशिश कर सकता हूं - यह लगभग 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]
max
. से मान लेता है )
यह समझना आवश्यक है कि eval
/evalsha
(जो कि ScriptEvaluate
है कॉल) अन्य सर्वर अनुरोधों के साथ प्रतिस्पर्धा नहीं कर रहे हैं , इसलिए incr
. के बीच कुछ भी नहीं बदलता है और संभव set
. इसका मतलब है कि हमें जटिल watch
की आवश्यकता नहीं है आदि तर्क।
लेन-देन/बाधा एपीआई के माध्यम से यहां वही है (मुझे लगता है!):
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*
जो... काफी काम का है, और इसमें मल्टीप्लेक्सर पर एक पाइपलाइन स्टॉल शामिल है। अधिक जटिल मामलों (अभिकथन विफलताओं, घड़ी की विफलता, रैप-अराउंड) का आउटपुट थोड़ा अलग होगा, लेकिन काम करना चाहिए।