上篇文章介绍了命令的执行流程,对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脚本的形式来实现,实现方法是多样的,具体的技术选型须要根据业务的场景来选择,若是你有更好的方案,欢迎评论留下你的方案。
原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。
若是本文对你有帮助,请点个赞吧,谢谢^_^
更多精彩内容,请关注我的公众号。