上篇文章介紹了命令的執行流程,對redis如何執行命令也有了初步的瞭解,通過實現一個redis命令來再次加深印象。
筆者平時主要語言是PHP,有些功能PHP無法滿足就會用到PHP的擴充套件,比如swoole。因此,就想到redis可不可以以做擴充套件?為了滿足一些特殊的需求,可不可以為redis開發一個命令?
前期準備
因為redis是用C開發的,為了能開發redis命令,首先也是必須的是,你要懂一點C語言基礎,另一個就是,需要了解一下redis命令是如何執行的,知道redis執行命令大概的流程,最簡單的一個流程描述就是:
讀取命令->解析命令->呼叫命令函式->返回執行結果
複製程式碼
或者再讀一次上篇文章。
我們要做的就是,確保redis能解析到新增的命令,能根據輸入的命令找到對應的方法並執行。
函式需求
要實現一個命令,說明當前redis的命令無法滿足開發的需求。考慮這樣的一個需求,在秒殺的情景下,達到了這樣的case:商品剩下最後一件,兩個使用者同時搶購,使用業務程式碼:
if (redis->decr(key) >= 0) {
return success
}
複製程式碼
這樣做有個問題就是活動結束後,key的值可能為-1,這樣對於最終查詢庫存時會出現負值,不利於資料對賬及統計。那麼,能不能新增一個命令,讓redis在計算時判斷key的值,如果是0就不進行扣減呢?
將函式命名為nonzerodecr,開始實現。
新增函式名到命令表
把函式名稱新增到命令表,參照decr命令的命令表:
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0}
複製程式碼
增加nonzerodecrCommand:
{"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指令碼的形式來實現,實現方法是多樣的,具體的技術選型需要根據業務的場景來選擇,如果你有更好的方案,歡迎評論留下你的方案。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
如果本文對你有幫助,請點個贊吧,謝謝^_^
更多精彩內容,請關注個人公眾號。