上篇文章介紹了命令的執行流程,對redis如何執行命令也有了初步的瞭解,經過實現一個redis命令來再次加深印象。redis
筆者平時主要語言是PHP,有些功能PHP沒法知足就會用到PHP的擴展,好比swoole。所以,就想到redis可不能夠以作擴展?爲了知足一些特殊的需求,可不能夠爲redis開發一個命令?shell
由於redis是用C開發的,爲了能開發redis命令,首先也是必須的是,你要懂一點C語言基礎,另外一個就是,須要瞭解一下redis命令是如何執行的,知道redis執行命令大概的流程,最簡單的一個流程描述就是:swoole
讀取命令->解析命令->調用命令函數->返回執行結果
複製代碼
或者再讀一次上篇文章。函數
咱們要作的就是,確保redis能解析到新增的命令,能根據輸入的命令找到對應的方法並執行。測試
要實現一個命令,說明當前redis的命令沒法知足開發的需求。考慮這樣的一個需求,在秒殺的情景下,達到了這樣的case:商品剩下最後一件,兩個用戶同時搶購,使用業務代碼:lua
if (redis->decr(key) >= 0) {
return success
}
複製代碼
這樣作有個問題就是活動結束後,key的值可能爲-1,這樣對於最終查詢庫存時會出現負值,不利於數據對帳及統計。那麼,能不能新增一個命令,讓redis在計算時判斷key的值,若是是0就不進行扣減呢?spa
將函數命名爲nonzerodecr,開始實現。code
把函數名稱添加到命令表,參照decr命令的命令表:cdn
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0}
複製代碼
增長nonzerodecrCommand:server
{"nonzerodecr",nonzerodecrCommand,2,"wmF",0,NULL,1,1,1,0,0},
複製代碼
聲明nonzerodecr,由於新增的命令nonzerodecr只是內部增長一個非0的判斷,其他操做沒有變化,所以只須要跟decrCommand同樣的聲明便可:
void nonzerodecrCommand(client *c);
複製代碼
在實現新的函數以前,先看看decrComamnd命令的實現:
void decrCommand(client *c) {
incrDecrCommand(c, -1);
}
複製代碼
函數調用了incrDecrCommand實現自增和自減,實現以下:
/* * incr、decr具體的實現 */
void incrDecrCommand(client *c, long long incr) {
long long value, oldvalue;
robj *o, *new;
// 檢查key和value的類型
o = lookupKeyWrite(c->db,c->argv[1]);
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
// 處理執行後溢出的狀況
oldvalue = value;
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
addReplyError(c,"increment or decrement would overflow");
return;
}
value += incr;
/* * 在long範圍內,直接賦值,不然使用longlong建立字符串後再賦值 */
if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
(value < 0 || value >= OBJ_SHARED_INTEGERS) &&
value >= LONG_MIN && value <= LONG_MAX) {
new = o;
o->ptr = (void*)((long)value);
} else {
new = createStringObjectFromLongLong(value);
if (o) {
dbOverwrite(c->db,c->argv[1],new);
} else {
dbAdd(c->db,c->argv[1],new);
}
}
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReply(c,shared.colon);
addReply(c,new);
addReply(c,shared.crlf);
}
複製代碼
函數的流程圖以下:
如圖所示,要實現函數nonzerodecrCommand,只須要在進行增/減操做前增長一個大於等於0 的判斷便可,其他的邏輯不變,實現以下:
void nonzerodecrCommand(client *c) {
long long incr = -1;
long long value, oldvalue;
robj *o, *new;
// 檢查key和value的類型
o = lookupKeyWrite(c->db,c->argv[1]);
if (o != NULL && checkType(c,o,OBJ_STRING)) return;
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return;
// 處理執行後溢出的狀況
oldvalue = value;
if (incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) {
addReplyError(c,"increment or decrement would overflow");
return;
}
value += incr;
// 判斷,若是操做後結果小於0,直接返回
if (value < 0) {
addReply(c,shared.czero);
return;
}
if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT &&
(value < 0 || value >= OBJ_SHARED_INTEGERS) &&
value >= LONG_MIN && value <= LONG_MAX) {
new = o;
o->ptr = (void*)((long)value);
} else {
new = createStringObjectFromLongLong(value);
if (o) {
dbOverwrite(c->db,c->argv[1],new);
} else {
dbAdd(c->db,c->argv[1],new);
}
}
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
server.dirty++;
addReply(c,shared.colon);
addReply(c,new);
addReply(c,shared.crlf);
}
複製代碼
編寫完代碼後,對代碼進行編譯測試:
127.0.0.1:6379> set totalCount 1
OK
127.0.0.1:6379> get totalCount
"1"
127.0.0.1:6379> nonzerodecr totalCount
(integer) 0
127.0.0.1:6379> get totalCount
"0"
127.0.0.1:6379> nonzerodecr totalCount
(integer) 0
127.0.0.1:6379> get totalCount
"0"
複製代碼
結果符合最初的需求,在值等於0以後,再進行扣減,值不會變爲負數。
經過介紹實現nonzerodecr命令的過程,對如何實現一個命令有了一個初步的認識,以後若是有新的需求也能夠根據這個步驟去實現一個新的命令。
上面介紹的命令實現方式比較粗暴,可能會有隱藏的bug,但對於入門實現一個命令這個目的來講,這個代碼時能夠的,另外,一開始提到的秒殺場景除了能夠使用新命令來解決,也能夠使用redis-lua腳本的形式來實現,實現方法是多樣的,具體的技術選型須要根據業務的場景來選擇,若是你有更好的方案,歡迎評論留下你的方案。
原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。
若是本文對你有幫助,請點個贊吧,謝謝^_^
更多精彩內容,請關注我的公衆號。