本文節(jié)選自即將出版的《可伸縮服務(wù)架構(gòu):框架與中間件》一書,作者:李艷鵬、楊彪、李海亮、賈博巖、劉淏。
- 點(diǎn)擊文末原文鏈接可以直達(dá)《可伸縮服務(wù)架構(gòu):框架與中間件》書籍主頁。
在使用Redis緩存的業(yè)務(wù)場(chǎng)景時(shí)經(jīng)常會(huì)有這樣的需求:要求遞減一個(gè)變量,如果遞減后變量小于等于0,則返回一個(gè)標(biāo)志;如果成功,則返回剩余的值,類似于數(shù)據(jù)庫事務(wù)的實(shí)現(xiàn)。
在實(shí)現(xiàn)中需要注意服務(wù)器端的多線程問題及客戶端的多線程問題。在服務(wù)器端可以利用服務(wù)器單線程執(zhí)行LUA腳本來保證,或者通過WATCH、EXEC、DISCARD、EXEC來保證。
在Redis中支持LUA腳本,由于Redis使用單線程實(shí)現(xiàn),因此我們首先給出LUA腳本的實(shí)現(xiàn)方案。在如下代碼中,我們看到變量被遞減,并判斷是否將小于0的操作放到LUA腳本里,利用Redis的單線程執(zhí)行的特性完成這個(gè)原子遞減的操作:
/**
* Implemented by LUA. Minus a key by a value, then return the left value.
* If the left value is less than 0, return -1; if error, return -1.
*
* @param key
* the key of the redis variable.
* @param value
* the value to minus off.
* @return the value left after minus. If it is less than 0, return -1; if
* error, return -1.
*/
public long decrByUntil0Lua(String key, long value) {
// If any error, return -1.
if (value <=>=>0)
return -1;
// The logic is implemented in LUA script which is run in server thread,
// which is single thread in one server.
String script = ' local leftvalue = redis.call('get', KEYS[1]); '
+ ' if ARGV[1] - leftvalue > 0 then return nil; else '
+ ' return redis.call('decrby', KEYS[1], ARGV[1]); end; ';
Long leftValue = (Long) jedis.eval(script, 1, key, '' + value);
// If the left value is less than 0, return -1.
if (leftValue == null)
return -1;
return leftValue;
}
還可以通過Redis對(duì)事務(wù)的支持方法watch和multi來實(shí)現(xiàn),類似于一個(gè)CAS方法的實(shí)現(xiàn),如果對(duì)熱數(shù)據(jù)有競(jìng)爭(zhēng),則會(huì)返回失敗,然后重試直到成功:
/**
* Implemented by CAS. Minus a key by a value, then return the left value.
* If the left value is less than 0, return -1; if error, return -1.
*
* No synchronization, because redis client is not shared among multiple
* threads.
*
* @param key
* the key of the redis variable.
* @param value
* the value to minus off.
* @return the value left after minus. If it is less than 0, return -1; if
* error, return -1.
*/
public long decrByUntil0Cas(String key, long value) {
// If any error, return -1.
if (value <=>=>0)
return -1;
// Start the CAS operations.
jedis.watch(key);
// Start the transation.
Transaction tx = jedis.multi();
// Decide if the left value is less than 0, if no, terminate the
// transation, return -1;
String curr = tx.get(key).get();
if (Long.valueOf(curr) - value <>0) {
tx.discard();
return -1;
}
// Minus the key by the value
tx.decrBy(key, value);
// Execute the transaction and then handle the result
List
END
聯(lián)系客服