上篇文章 http://u6.gg/dCVQx 介绍了命令的执行流程, 对 redis 如何执行命令也有了初步的了解, 通过实现一个 redis 命令来再次加深印象.
笔者平时主要语言是 PHP, 有些功能 PHP 无法满足就会用到 PHP 的扩展, 比如 swoole. 因此, 就想到 redis 可不可以以做扩展? 为了满足一些特殊的需求, 可不可以为 redis 开发一个命令?
前期准备
因为 redis 是用 C 开发的, 为了能开发 redis 命令, 首先也是必须的是, 你要懂一点 C 语言基础, 另一个就是, 需要了解一下 redis 命令是如何执行的, 知道 redis 执行命令大概的流程, 最简单的一个流程描述就是:
读取命令 -> 解析命令 -> 调用命令函数 -> 返回执行结果
或者再读一次 上篇文章 http://u6.gg/dCVQx .
我们要做的就是, 确保 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 脚本的形式来实现, 实现方法是多样的, 具体的技术选型需要根据业务的场景来选择, 如果你有更好的方案, 欢迎评论留下你的方案.
原创文章, 文笔有限, 才疏学浅, 文中若有不正之处, 万望告知.
来源: http://www.tuicool.com/articles/A7B7bua