以往我们想给 Redis 加个功能或类似事务的东西只能用 Lua 脚本,这个东西没有实现真正的原子性,另外也无法使用底层的 API ,实质上比单纯的命令脚本提升有限。
Redis 4.0 终于加入了模块,暴露了必要的 API,并且有自动内存管理(大大减轻编写负担),基于 C99(C++ 或者其它语言的 C 绑定接口当然也可以)。
这东西有多灵活呢?不知道作者是不是为了突出这一点,直接编写了一个神经网络模块。
注意:
redis对布隆过滤器的支持就是通过第三方module来实现的,安装,使用都很简单
可以参考笔者的这篇博客:https://blog.csdn.net/yzf279533105/article/details/110873427
模块 Module 可以动态的载入和卸载,可以实现底层的数据结构也可以调用高层的指令,这一切都只需要包含头文件 redismodule.h ,和 Redis 本身一样简洁优雅。
基本入门
Hello World
// redis 4.0 以上源码可以找到头文件
#include "redismodule.h"
#include <stdlib.h>
int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_ReplyWithLongLong(ctx,rand());
return REDISMODULE_OK;
}
// 必须的函数
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 最先调用初始化
if (RedisModule_Init(ctx,"helloworld",1,REDISMODULE_APIVER_1)
== REDISMODULE_ERR) return REDISMODULE_ERR;
// 注册命令
if (RedisModule_CreateCommand(ctx,"helloworld.rand",
HelloworldRand_RedisCommand) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
编译命令
g++ -fPIC -c mycal.cpp -o mycal.xo
ld -o mycal.so mycal.xo -shared -lc
一般我更喜欢用 cmake,管理起来更容易
cmake_minimum_required(VERSION 2.8.11)
project(mycal)
set(mycal mycal.so)
add_library(${mycal} SHARED mycal.cpp)
set_target_properties(${mycal} PROPERTIES PREFIX "" SUFFIX "")
加载命令
MODULE LOAD /path/to/mymodule.so # 在 redis-cli 中执行,注意这里 mymodule.so 是文件名
执行命令
helloworld.rand # 在 redis-cli 中执行
基础 API 一览
#include "redismodule.h" // 这个头文件在 > 4.0 的redis 源代码可以找到
#include <stdlib.h>
// redis API 和 argv 参数 都是 RedisModuleString 类型的
int HelloworldRand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 自动内存管理
// 不需要 close keys/ free replies / free RedisModuleString
// 注意为了及时处理内存,使用大内存应该手动 free (用redis对应函数)
RedisModule_AutoMemory(ctx);
// 错误调用可以使得 redis 直接崩溃,需要检查参数
if (argc != 3) return RedisModule_WrongArity(ctx);
// 从 redis 字符串获取数据
const char *s = RedisModule_StringPtrLen(argv[0], NULL);
// 字符串到数字
long long myval;
if (RedisModule_StringToLongLong(argv[1],&myval) == REDISMODULE_OK) {
/* Do something with 'myval' */
}
// 数字到字符串
RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx, 10);
// 使用如下的函数而不是 malloc(对Redis透明,无法控制),保证和 redis 的内存分配器一致,默认是 jemalloc
// void *RedisModule_Alloc(size_t bytes);
// void* RedisModule_Realloc(void *ptr, size_t bytes);
// void RedisModule_Free(void *ptr);
// void RedisModule_Calloc(size_t nmemb, size_t size);
// char *RedisModule_Strdup(const char *str);
// 操作 Redis 原生类型底层API,通常是 RedisModule_TypeOperation 的格式命名函数
RedisModuleKey *key;
key = (RedisModuleKey*)RedisModule_OpenKey(ctx,argv[1],REDISMODULE_WRITE);
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
RedisModule_StringSet(key,argv[2]);
}
// 高层 API 用 RedisModule_Call
RedisModuleCallReply *reply;
// sc 是格式化标志位,表示命令的多个参数各是什么类型,s 代表 argv[1] 是一个 RedisModuleString
// c 代表 “10” 是以 \0 结尾的 C 字符串
reply = RedisModule_Call(ctx,"INCR","sc",argv[1],"10");
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
long long myval = RedisModule_CallReplyInteger(reply);
/* Do something with myval. */
}
// 返回一个数值给调用者
RedisModule_ReplyWithLongLong(ctx, rand());
return REDISMODULE_OK;
}
// 必须的函数
extern "C" int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
// 模块的名称在这里注册,可以跟编译后 so 名字不一样,推荐一样
// RedisModule_Init 必须在调用其它API前最先被调用
if (RedisModule_Init(ctx, "mycal", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
// 与其它模块命名冲突会导致命令注册失败,推荐采用 模块名.命令名 方式
if (RedisModule_CreateCommand(ctx, "mycal", HelloworldRand_RedisCommand, "write", 1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
}
模块必须的函数只有 RedisModule_OnLoad,它是 redis 调用模块的入口函数。
上面的语言是 C++ ,为了编译成动态时函数的可见性加了 extern "C" 前缀。
用 C 语言开发是最简单的,但事实上任何语言都可以开发 Redis 模块。
在配置文件 redis.conf
增加: loadmoudule /path/module.so [argv0] [argv1] # 配置文件,后面可以加参数,这种方式需要重启redis
客户端工具 redis-cli
module load /path/module.so [argv0] [argv1] # 客户端指令,加载模块,不需要重启redis,但是如果redis关闭后这个module需要重新加载
module list # 列出所有模块
module unload module # 卸载模块,模块名是函数中注册的名称,不是文件名
API
大致可以分为三类:直接操作底层数据;redis 命令操作;内存分配、集群支持等
注意
模块如果有没有捕获或内存泄露出错等问题 Redis 本身是无法处理的,会导致程序整个挂掉,甚至很可能连日志都没有需要非常小心。