[Redis源码阅读]实现一个redis命令--nonzerodecr

上篇文章介绍了命令的执行流程,对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);
}

复制代码

函数的流程图以下:

incrDecrCommand

如图所示,要实现函数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脚本的形式来实现,实现方法是多样的,具体的技术选型须要根据业务的场景来选择,若是你有更好的方案,欢迎评论留下你的方案。

原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。

若是本文对你有帮助,请点个赞吧,谢谢^_^

更多精彩内容,请关注我的公众号。

相关文章
相关标签/搜索