Redis 模块机制 - 模块编写篇
在 Redis 应用中,模块机制是提及得比较少的一个功能,主要是 Redis 的功能基本上能应付各种需求,很少需要自己编写模块来扩展功能的。但有时候我们希望为 Redis 提供一些较为高级的功能,比如提供支持回滚的事务功能,这时就需要编写模块来进行扩展。
Redis 为模块编写者提供了丰富的 API 来操纵 Redis,下面我们编写一个简单的 Redis 模块来阐明模块编写的过程。
编写一个简单的 Redis 模块
我们要编写的模块只提供一个简单的功能,就是计算一个数的平方数,命令如下
$ 127.0.0.1:6379> math.double 10
(integer) 100
我们先把模块的代码贴出来,然后再解释代码的意义:
#include "redismodule.h"
#include <stdlib.h>
int MathDouble_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
long long num;
if (argc == 2) {
RedisModule_StringToLongLong(argv[1], &num);
RedisModule_ReplyWithLongLong(ctx, num * num);
return REDISMODULE_OK;
}
return RedisModule_ReplyWithError(ctx, "ERR invalid num parameters");
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (RedisModule_Init(ctx, "math", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "math.double",
MathDouble_RedisCommand, "readonly", 1, 1, 1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
上面引入的 redismodule.h
文件是从 Redis 源码包中复制过来的,编写 Redis 模块时必须引入这个文件。
然后我们可以通过以下命令来编译这个模块:
$ gcc math.c -fPIC -shared -o math.so
上面的命令是把模块编译成 .so
文件,也就是共享库文件。然后可以通过 Redis 的 module load
命令来把模块加载到 Redis 中,如下:
$ module load ./math.so
OK
然后可以通过 module list
命令查看 Redis 已经加载的模块列表:
$ module list
1) 1) "name"
2) "math"
3) "ver"
4) (integer) 1
可以看到,我们的 math
模块已经被加载到 Redis 中。
接下来,我们就可以通过 math
模块提供的 math.double
命令来计算一个数的平方数,如下:
$ 127.0.0.1:6379> math.double 10
(integer) 100
如果看到上面的结果,那么恭喜你,因为你编写的模块已经被加入到 Redis 中。
模块代码解释
每个 Redis 模块都需要提供一个 RedisModule_OnLoad()
函数,这个函数是 Redis 加载模块时会调用的函数,也就是说,Redis 加载一个模块时,会调用这个模块的 RedisModule_OnLoad()
函数。 RedisModule_OnLoad()
函数的定义如下:
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
函数的第一个参数类型必须是 RedisModuleCtx
,表示模块的上下文,主要用来存放模块的一些基本信息。第二个参数类型为 RedisModuleString
,表示载入模块时传入的参数列表。第三个参数类型为 int
,表示参数列表的个数。
在 RedisModule_OnLoad()
函数中一般需要调用 RedisModule_Init()
函数来向 Redis 注册模块,RedisModule_Init()
函数原型如下:
int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver);
第一个参数的类型前面已经介绍过,第二个参数用于指定模块的名称,第三个参数用于指定模块的版本,而第四个参数用于指定API的版本。
注册完模块后,就可以通过 RedisModule_CreateCommand()
函数来为模块创建命令。RedisModule_CreateCommand()
函数原型如下:
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc,
const char *strflags, int firstkey, int lastkey, int keystep);
RedisModule_CreateCommand()
函数稍微有点复杂,下面介绍各个参数的作用:
-
ctx
:就是模块的上下文。 -
name
:用于指定命令的名称,比如上面我们定义的math.double
。 -
cmdfunc
:用于指定命令的功能函数,也就是当我们调用一个命令时,就会通过调用这个函数来实现具体的功能。 -
strflags
:用于指定命令的功能,比如write
表示命令对 Redis 进行写操作,而readonly
表示命令只会读取 Redis 的数据等,为什么需要指定命令的功能呢?因为如果命令会对 Redis 进行写操作,那么 Redis 就需要把命令持久化。 -
firstkey
,lastkey
,keystep
:用于指导command getkeys
命令(command getkeys
命令的使用可以参考 Redis 使用手册)获取 keys 列表时使用的,如果不提供这个功能,可以设置为0。
一个模块最重要的是命令的功能函数,如上面模块中的 MathDouble_RedisCommand()
函数,我们把 MathDouble_RedisCommand()
再展示一遍:
int MathDouble_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
long long num;
if (argc == 2) {
RedisModule_StringToLongLong(argv[1], &num);
RedisModule_ReplyWithLongLong(ctx, num * num);
return REDISMODULE_OK;
}
return RedisModule_ReplyWithError(ctx, "ERR invalid num parameters");
}
一个命令的功能函数的原型如下:
int xxx_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
函数的名称可以随便定义,不过按照规范,一般以 _RedisCommand
结尾。第一个参数就是模块上下文,而第二个参数是命令的参数列表,第三个参数是参数列表的个数。
在上面的例子中,MathDouble_RedisCommand()
函数首先判断参数个数是否正确,如果不正确就返回 invalid num parameters
的错误信息。然而,Redis 提供了很多 API 让我们使用,比如 RedisModule_StringToLongLong()
函数就是把参数转换成 long long
类型的数值。而 RedisModule_ReplyWithLongLong()
函数可以让 Redis 回复一个整型数值给客户端。
Redis 提供的 API 非常多,具体可以参考 Redis 官网的模块开发介绍或者查看 redismodule.h
文件。Redis 模块编写的介绍就到这里了,下篇会介绍 Redis 模块内部的实现原理。
参考文献:
- Redis模块开发手册:Redis Modules: an introduction to the API
- Redis模块API手册 Modules API reference
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(六)easyUI与富文本编辑器UEditor整合
- 使用shell脚本查看数据库负载情况(第二篇)(r3笔记第92天)
- tensorflow LSTM + CTC实现端到端OCR
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(七)图片上传功能
- 黑客比程序员牛在哪?
- oracle工具集初探(r4笔记第8天)
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(二)Log4j讲解与整合
- 京东JData算法大赛-高潜用户购买意向预测(github源码)
- 巧用linux命令做图片下载器(r4笔记第7天)
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(四)单元测试实例
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(五)结合MockMvc进行服务端的单元测试
- 关于order by中的数据排序(r4笔记第6天)
- 深度学习CTPN+CRNN模型实现图片内文字的定位与识别(OCR)
- Markdown语法讲解及MWeb使用教程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- PHP序列化的四种实现办法与横向对比
- php设计模式之观察者模式定义与用法经典示例
- Laravel获取所有的数据库表及结构的方法
- redis+php实现微博(二)发布与关注功能详解
- PHP实现小程序批量通知推送
- Thinkphp5.0 框架Model模型简单用法分析
- php设计模式之单例模式用法经典示例分析
- PHP实现统计代码行数小工具
- redis+php实现微博(三)微博列表功能详解
- php设计模式之工厂模式用法经典实例分析
- laravel 关联关系遍历数组的例子
- PHP开启目录引索+fancyindex漂亮目录浏览带搜索功能
- 解决Laravel 使用insert插入数据,字段created_at为0000的问题
- 关于php unset对json_encode的影响详解
- php进行md5加密简单实例方法