- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
redis4.0以上版本,用户可以在redis定义自己的扩展module了,可以快速实现新的命令和功能;
redis模块使用动态库,可以在redis启动的时候加载或者启动后使用命令加载,API定义在了一个叫redismodule.h的头文件里,可以使用c++或者其他c语言绑定的程序编写模块;
配置文件里配置loadmodule /path/to/my_module.so
MODULE LIST 展示当前加载的所有模块;
MODULE LOAD my_module.so 加载模块;(so文件)
MODULE UNLOAD modulename 卸载模块;(模块名字,MODULE LIST展示的那个名字,非文件名,通常文件名和模块名同名)
编写模块不需要依赖redis或者其他库,只需要#include "redismodule.h"这个文件就可以编写了(该头文件里定义了一些常量以及可以使用的API),该文件在下载解压好的src目录下,复制出来就可以在模块里用了,模块可以在不同版本的redis里使用,另外api如果在当前redis版本里没有实现会被赋值为NULL,可以使用NULL或者使用RMAPI_FUNC_SUPPORTED(func)判断该api可不可用,不过前提是存在该api:
api官网:Modules API reference – Redis
模块里必须有一个RedisModule_OnLoad()函数,是模块入口点,可以在函数里定义模块名、新命令、新数据类型等等,函数原型:
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
参数1是上下文(通常函数第一个参数都是ctx),参数2是传入参数,参数3是传入参数的数量;参数2可以通过配置文件里loadmodule /path/to/my_module.so arg1 arg2或者使用命令MODULE LOAD my_module.so arg1 arg2传入,所以可以根据传入的参数使用不同的新命令;
OnLoad函数里最先被调用的函数应该是Init函数,Init函数用来注册模块名(MODULE LIST展示的模块名,卸载时使用的名),函数原型:
int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename, int module_version, int api_version);
参数1是上下文,参数2是模块名,参数3是模块版本号,参数4是api版本号(好像一直是REDISMODULE_APIVER_1变量);模块名不能跟已有的有冲突;
注册新命令使用CreateCommand函数,必须在OnLoad函数里使用,函数原型:
int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep);
参数1是上下文,参数2是命令名称,参数3是实现该命令的函数,参数4是命令标志(只读,写等等,多个标志中间用空格分开),参数5是第一个参数索引(索引是从1开始的,0的位置是命令本身,0表示命令无key),参数6是最后一个参数索引(负数表示从后开始,-1表示提供的最后一个参数,0表示命令无key),参数7是第一个参数和最后一个参数之间的距离(0表示命令无key);(上面的索引设定只用来查找索引处的key,对于复杂参数可以传递0,0,0,并使用RedisModule_SetCommandInfo设置更详细的参数)
实现命令的函数原型:
int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
参数1是上下文,参数2是用户传入的参数,参数3是用户传入参数的数量;(argv[1]才是用户传入的第一个参数,argv[0]是命令名,命令无参数的时候argc为1,所以argc通常>=1)
(通常参数数量错误的时候使用return RedisModule_WrongArity(ctx);直接返回错误,h头文件里定义了这个错误用于说明参数数量错误)
实现命令的函数里给用户返回数据可以使用以RedisModule_ReplyWith开头的api函数返回不同类型的数据,这些api定义在h头文件里:
模块可以被卸载,大多数情况不需要特殊处理,卸载模块时redis会自动注销命令和取消订阅消息,有时候命令里有一些持久内存或者配置,可以使用OnUnload函数进行处理,在模块卸载的时候将被自动调用,函数原型:
int RedisModule_OnUnload(RedisModuleCtx *ctx);
OnUnload函数正常应该返回REDISMODULE_OK,但是返回REDISMODULE_ERR可以阻止模块被卸载;
另外,有个一叫自动内存管理的函数,在自己函数的开始的地方调用该函数即可,因为通常c程序员需要自己管理内存,所以模块api有释放字符串、关闭开启的键、释放回复等功能,redis也提供了自动内存管理(会消耗一些性能),函数原型:
RedisModule_AutoMemory(ctx);
使用自动内存管理后,不需要关闭打开的键,不需要释放回复,不需要释放RedisModuleString对象;当然启用自动内存管理时,也支持手动释放,尤其分配了大量字符串需要尽快释放的时候可以使用手动加快释放;
编写一个小Demo:
test.c文件(将传入的数字乘以2后返回)
#include "redismodule.h"
int MyCmd(RedisModuleCtx *ctx, RedisModuleString ** argv, int argc)
{
if (argc != 2)
{
return RedisModule_WrongArity(ctx);
}
RedisModule_AutoMemory(ctx);
long long myparam;
if (RedisModule_StringToLongLong(argv[1], &myparam) == REDISMODULE_OK)
{
RedisModule_ReplyWithLongLong(ctx, myparam * 2);
}
else
{
RedisModule_ReplyWithError(ctx, "ERROR wrong type of arguments");
}
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv,int argc)
{
if (RedisModule_Init(ctx, "mymodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "mycmd", MyCmd, "readonly", 0, 0, 0) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
return REDISMODULE_OK;
}
编译成so:执行gcc test.c -fPIC -shared -o test.so
使用redis-cli登录redis,加载so文件并测试新命令:
调用api传递参数、返回值,用到的基本都是RedisModuleString对象,很有必要了解RedisModuleString的相关操作;
const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len);
上面这个函数可以将RedisModuleString对象转换成char和len,但是返回的char是const的;
也可以直接使用char指针和长度参数创建RedisModuleString对象:
RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);
但是直接创建的RedisModuleString对象必须使用RedisModule_FreeString()函数释放(在没有使用自动内存管理的时候):
void RedisModule_FreeString(RedisModuleString *str);
另外,参数argv的字符串对象永远不需要释放,其他api返回的字符串需要释放的情况会有明确说明;
将数字转换成字符串对象可以使用:
RedisModuleString *str= RedisModule_CreateStringFromLongLong(ctx,12345);
将字符串转换成数字可以使用:
long long x;
if (RedisModule_StringToLongLong(str, &x) == REDISMODULE_OK) {
}
可以使用高级API或者低级API两种方式操作redis,低级API速度可以媲美原生redis命令,但是大部分执行时间可能是用在处理数据而不是操作redis数据,所以高级API也并不是多余的;(高级API不会被单独做成模块的API,可以通过模块提供的一个RedisModule_Call()
函数去使用高级API,就像Lua脚本那样)
高级API使用的函数:
RedisModuleCallReply *RedisModule_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
参数1是上下文,参数2是需要调用的redis命令,参数3是对后面参数的格式说明(每个字符分别对应后面的参数,格式!、A、3、R是没有对应参数),后面参数为提供给命令的参数;
格式说明符:
命令执行成功返回一个RedisModuleCallReply对象,失败返回NULL并且将errno设置为以下值:
RedisModuleCallReply对象可以使用以RedisModule_CallReply为前缀的函数访问;
可以使用RedisModule_CallReplyType()函数查看reply对象的类型:
int RedisModule_CallReplyType(RedisModuleCallReply *reply);
函数返回的reply对象的类型有:(api官网定义了好多,h头文件只定义了6个,下划线那些)
REDISMODULE_REPLY_UNKNOWN
REDISMODULE_REPLY_STRING
REDISMODULE_REPLY_ERROR
REDISMODULE_REPLY_INTEGER
REDISMODULE_REPLY_ARRAY
REDISMODULE_REPLY_NULL
REDISMODULE_REPLY_MAP
REDISMODULE_REPLY_SET
REDISMODULE_REPLY_BOOL
REDISMODULE_REPLY_DOUBLE
REDISMODULE_REPLY_BIG_NUMBER
REDISMODULE_REPLY_VERBATIM_STRING
REDISMODULE_REPLY_ATTRIBUTE
可以使用RedisModule_CallReplyLength()函数计算reply对象的长度:
size_t RedisModule_CallReplyLength(RedisModuleCallReply *reply);
对于字符串或者错误类型计算的是字符串的长度,对于数组计算的是数组里元素的数量;
获取整数reply的值可以使用RedisModule_CallReplyInteger()函数:
long long RedisModule_CallReplyInteger(RedisModuleCallReply *reply);
使用错误类型的reply对象调用上面的函数总是返回LLONG_MIN值;
访问数组元素可使用RedisModule_CallReplyArrayElement()函数:
RedisModuleCallReply *RedisModule_CallReplyArrayElement(RedisModuleCallReply *reply, size_t idx);
访问数组元素的时候如果越界则返回NULL;
对于string或者error类型的reply可以使用以下函数转换:
const char *RedisModule_CallReplyStringPtr(RedisModuleCallReply *reply, size_t *len);
len需要先定义用来接收转换成char的长度,另外char是const修饰的,如果reply不是string或者error类型的则返回NULL;
还可以通过RedisModule_CreateStringFromCallReply()函数直接使用reply创建RedisModuleString对象(通常用于将字符串或者整数的reply传递给其他api使用):
RedisModuleString *RedisModule_CreateStringFromCallReply(RedisModuleCallReply *reply);
该函数可以处理string、error、integer的reply,如果reply类型错误则返回NULL,如果不使用自动内存管理,则需要RedisModule_FreeString()函数释放创建的字符串;
注意:reply对象必须使用void RedisModule_FreeCallReply(RedisModuleCallReply *reply);函数释放;对于数组类型的reply只需要释放最上层的reply即可,不需要释放嵌套的reply;如果使用自动内存管理则可以不用释放reply(为了尽快释放内存也可以主动释放reply);
模块实现的新命令必须能够返回值给调用者;(错误可以与任何错误字符串和错误码返回,错误码是错误消息中开头的大写单词)
向客户端发送应答的函数都是以RedisModule_ReplyWith开头的函数,都在h头文件里:
REDISMODULE_API int (*RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR;
REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR;
另外h头文件里只定义了一个参数类型错误的错误类型:
返回数组需要先使用RedisModule_ReplyWithArray()函数设定一个数组长度,然后再返回每个元素即可:
返回嵌套数组可以这样:
关于创建数组时未知大小的动态数组,可以先通过RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN)函数创建数组(REDISMODULE_POSTPONED_ARRAY_LEN为特殊参数,h头文件里定义为-1),然后设置数组的值,最后再根据设定的值使用RedisModule_ReplySetArrayLength()函数指定数组长度即可,就像这样:
嵌套的动态数组:
使用低级api可以直接操作跟键相关的值对象,执行速度类似于redis内部命令的速度;
低级api使用过程:通过打开一个键获得键指针,使用键指针进行低级api调用,最后关闭键;
由于低级api是非常快的,所以不能执行过多耗时操作,需要遵循一些规则:
可以使用RedisModule_OpenKey()函数打开一个键获得键指针:
void *RedisModule_OpenKey(RedisModuleCtx *ctx, robj *keyname, int mode);
参数2是键名,必须是RedisModuleString对象,参数3是打开键的模式REDISMODULE_READ和REDISMODULE_WRITE(可以使用“|”符号连接起来同时使用两种模式,虽然写模式默认包含读模式)
对于不存在的键仍然可以打开,使用写模式打开会新建键并返回键指针,使用读模式打开只会返回NULL;(在NULL值上调用RedisModule_CloseKey()和RedisModule_KeyType()是安全的)
打开的键必须进行关闭,使用关闭函数:
void RedisModule_CloseKey(RedisModuleKey *key);
如果启用了自动内存管理,可以不需要手动关闭键,redis会小心的自动关闭仍然打开的键;
获得键的类型可以通过RedisModule_KeyType()函数:
int RedisModule_KeyType(RedisModuleKey *key);
如果参数是一个不存在的NULL键指针,则会返回REDISMODULE_KEYTYPE_EMPTY;
头文件里定义的键类型:
#define REDISMODULE_KEYTYPE_EMPTY 0
#define REDISMODULE_KEYTYPE_STRING 1
#define REDISMODULE_KEYTYPE_LIST 2
#define REDISMODULE_KEYTYPE_HASH 3
#define REDISMODULE_KEYTYPE_SET 4
#define REDISMODULE_KEYTYPE_ZSET 5
#define REDISMODULE_KEYTYPE_MODULE 6
#define REDISMODULE_KEYTYPE_STREAM 7
创建键,只需要以写模式打开一个不存在的键,然后使用一个键写入函数即可;
删除键可使用删除函数:
int RedisModule_DeleteKey(RedisModuleKey *key);
如果以写模式打开了键,则会删除该键并返回REDISMODULE_OK,否则返回REDISMODULE_ERR(包括读模式打开),键被删除后变成空键,使用键写入后会变成写入的那种类型;
查询键过期时间函数:
mstime_t RedisModule_GetExpire(RedisModuleKey *key);
返回键过期剩余的毫秒值,没有设置过期或者键不存在返回REDISMODULE_NO_EXPIRE(可以判断键类型REDISMODULE_KEYTYPE_EMPTY区分);
设置键的过期时间函数:
int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);
设置键的过期时间(单位毫秒),过期时间需要是一个正整数,键要以写模式打开,成功返回REDISMODULE_OK,对不存在的键设置时返回REDISMODULE_ERR,如果当前键还未过期则设置一个新的过期时间,如果已过期则替换为新值;可以设置一个特殊值REDISMODULE_NO_EXPIRE来取消过期时间(相当于PERSIST命令);
另外6.2.6版本新增了两个函数,使用Unix时间戳作为单位的函数:
mstime_t RedisModule_GetAbsExpire(RedisModuleKey *key);
int RedisModule_SetAbsExpire(RedisModuleKey *key, mstime_t expire);
计算键的值的长度使用函数:
size_t RedisModule_ValueLength(RedisModuleKey *key);
对于字符串返回字符串的长度,其他类型则返回元素的数量(仅计算散列的键值),键不存在返回0;
高级API可以使用RedisModule_Call()函数,格式说明符里使用“!”“A”“R”控制复制到副本或者AOF里;
低级API可以使用以下函数复制命令到slave和aof里:
int RedisModule_ReplicateVerbatim(RedisModuleCtx *ctx);
还可以使用专用函数复制:
int RedisModule_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...);
该函数的参数跟RedisModule_Call()的参数一致,也需要格式说明符,可以使用“A”或“R”控制在副本或者AOF中传播;
c语言提供了malloc()和free()来申请和释放内存,虽然redis没有禁止c语言的内存管理,但是redis模块也提供了代替他们的内存管理的函数,c语言的内存分配对于redis是透明的,另外从rdb反序列化加载自定义本地类型的模块数据时可以直接填充使用RedisModule_Alloc()分配的内存空间,而不需要复制到数据结构中:
void *RedisModule_Alloc(size_t bytes);
该函数就像malloc()函数一样,但是这个函数分配的内存会在redis的INFO内存信息中体现出来,会被认为是redis占用的内存,也会根据最大内存设置影响键的驱逐;
void *RedisModule_Calloc(size_t nmemb, size_t size);
该函数类似calloc()函数,跟上一个函数基本一样;
void* RedisModule_Realloc(void *ptr, size_t bytes);
类似realloc(),也是使用RedisModule_Alloc()重新分配内存;
void RedisModule_Free(void *ptr);
类似free(),释放RedisModule_Alloc()和RedisModule_Realloc()申请的内存,永远不要用来释放malloc()申请的内存;
char *RedisModule_Strdup(const char *str);
类似strdup(),复制字符串,内部使用RedisModule_Alloc()分配的内存;
对于短生命周期的小内存的申请可以使用池分配器分配:
void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);
该函数返回堆分配的内存,在模块回调函数返回的时候将自动释放内存,并且可以自动字节对齐,如果bytes为0则返回NULL;
本地类型由以下几块内容构成:(源码redis-6.2.6\src\modules\hellotype.c是一个很好的例子)
注册新的本地类型首先需要定义一个RedisModuleType的全局指针变量,用于保存注册数据类型后的引用(本地类型的结构体定义在注册这里暂时用不到,在需要实现的方法里会用到);
然后需要在RedisModule_OnLoad()函数里实例化一个RedisModuleTypeMethods对象,该变量已在头文件里定义:
api官网的例子:(多了几个扩展项)
其中version、rdb_load、rdb_save、aof_rewrite、free是通常需要设置的变量和实现的方法,其他为可选;
这四个函数在头文件里的定义:
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
typedef void (*RedisModuleTypeFreeFunc)(void *value);
然后调用RedisModule_CreateDataType()函数创建本地类型:
moduleType *RedisModule_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver, void *typemethods_ptr);
参数1是上下文,参数2是9个字符的自定义唯一类型名(使用A-Z、a-z、0-9、_和-字符),参数3是持久化版本号(encver必须为0 ~ 1023之间的正值,rdb_load函数可以根据该值使用不同版本的加载方法),参数4是上面的RedisModuleTypeMethods对象;返回值使用最开始定义的RedisModuleType全局变量指针保存即可,如果名字重复或者encver无效则会返回NULL;另外注意“AAAAAAAAA”类型名被保留并产生一个错误;
关于类型名必须为9个字符的原因:
持久化RDB文件的时候使用的是键值对,格式:[1 byte type] [key] [a type specific value],其中1个byte存储字符串、列表、集合等类型,模块数据为一个特定值,无法表示某个具体数据类型只能说明为模块数据,所以在type specific value中加入一个64位的整数前缀解释具体的数据类型,类型名由A-Za-z0-9下划线和减号组成(64个符号的字符集),6个bit刚好能保存一个字符,9个字符需要54个bit,剩下10个bit保存持久化版本号encver(这也说明了encver范围是0-1023,2^10一共能保存1024个数),最后这个64位的整数格式:6|6|6|6|6|6|6|6|6|10;可以直接通过这个整数获得类型名和版本号,出错的时候也可以直接将这个类型名返回给客户端,另外使用TYPE命令的时候返回的也是这个类型名;
另外注册了本地类型的模块无法卸载:
redis提供了高级API用于存取rdb数据,可以方便的存取以下数据类型:
用于保存这些数据类型的函数:
void RedisModule_SaveUnsigned(RedisModuleIO *io, uint64_t value);
void RedisModule_SaveSigned(RedisModuleIO *io, int64_t value);
void RedisModule_SaveString(RedisModuleIO *io, RedisModuleString *s);
void RedisModule_SaveStringBuffer(RedisModuleIO *io, const char *str, size_t len);
void RedisModule_SaveDouble(RedisModuleIO *io, double value);
void RedisModule_SaveFloat(RedisModuleIO *io, float value);
void RedisModule_SaveLongDouble(RedisModuleIO *io, long double value);
对应的用于获取这些数据类型的函数:
uint64_t RedisModule_LoadUnsigned(RedisModuleIO *io);
int64_t RedisModule_LoadSigned(RedisModuleIO *io);
RedisModuleString *RedisModule_LoadString(RedisModuleIO *io);
char *RedisModule_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr);
double RedisModule_LoadDouble(RedisModuleIO *io);
float RedisModule_LoadFloat(RedisModuleIO *io);
long double RedisModule_LoadLongDouble(RedisModuleIO *io);
这些读写API不需要模块进行错误检查,模块总是认为成功;rdb_load函数是用来将储存在RBD文件中的数据重新构建出来,虽然API上没有错误处理,如果读取的内容有问题rdb_load函数在出错时仍可以返回NULL,redis仅仅是不知道发生了什么;
另外还有一个检测错误的函数:
int RedisModule_IsIOError(RedisModuleIO *io);
如果之前调用的任何IO API失败,该函数会返回true,对于RedisModule_Load*的api,需要先通过RedisModule_SetModuleOptions()函数设置REDISMODULE_OPTIONS_HANDLE_IO_ERRORS标志;
对于aof重写只有一个函数:
void RedisModule_EmitAOF(RedisModuleIO *io, const char *cmdname, const char *fmt, ...);
用于在AOF重写过程中向AOF发出命令。这个函数只在模块自定义的数据类型的aof_rewrite函数中被调用。工作方式和RedisModule_Call()一样,参数传递方式也一样,但该函数无返回值;
当redis需要释放一个保存了本地类型的键时,需要模块提供一个释放函数来帮助处理内存;
如果本地数据类型只是一个简单的分配内存组成,可以直接在free函数里使用一个释放函数:
void RedisModule_Free(void *ptr);
通常数据类型更复杂,需要将free函数里的void指针参数转换成对应数据类型,然后释放结构内的其他所有内存资源;
构造好了本地数据类型,需要提供各种使用方法,类似get、set等方法,通常使用低级api操作存取数据;
保存本地类型数据使用RedisModule_ModuleTypeSetValue()函数:
int RedisModule_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value);
参数1是以写模式打开的key指针,参数2是本地类型的引用(注册时获得的保存在全局里的RedisModuleType指针),参数3是包含具体本地数据的指针(自定义的struct结构体数据);
如果有旧值则被覆盖,成功返回REDISMODULE_OK,否则返回REDISMODULE_ERR;
读取本地类型数据使用RedisModule_ModuleTypeGetValue()函数:
void *RedisModule_ModuleTypeGetValue(RedisModuleKey *key);
参数1是打开的key指针;
如果该key通过RedisModule_KeyType(key)函数返回类型是REDISMODULE_KEYTYPE_MODULE,则会按照RedisModule_ModuleTypeSetValue()时传入的结构体返回结构体指针,如果key为NULL或者类型不是模块类型或者是空的,则该函数返回NULL;
另外还可以通过RedisModule_ModuleTypeGetType()函数获得模块具体类型:(RedisModule_KeyType()函数只会返回REDISMODULE_KEYTYPE_MODULE)
moduleType *RedisModule_ModuleTypeGetType(RedisModuleKey *key);
返回值指的是全局定义的那个RedisModuleType指针变量(注册时保存了类型引用);
官网的惯用检查代码展示:
模块数据类型应该使用RedisModule_Alloc()函数家族来分配内存和释放内存,使用redis提供的内存分配函数有许多好处:
如果模块里使用了libc分配内存或者使用了libc分配内存的库,可以使用简单的宏来将libc替换成redis的api:
#define malloc RedisModule_Alloc
#define realloc RedisModule_Realloc
#define free RedisModule_Free
#define strdup RedisModule_Strdup
但是应注意libc和redis api不能混用,否则会导致redis异常或者崩溃,例如不能使用RedisModule_Free()释放libc分配的内存;
一个本地数据类型的小demo:
test.c代码:
#include "redismodule.h"
//全局的RedisModuleType指针
static RedisModuleType *Mytype = NULL;
/*
int MyCmd(RedisModuleCtx *ctx, RedisModuleString ** argv, int argc)
{
RedisModule_ReplyWithSimpleString(ctx, "TEST_OK");
return REDISMODULE_OK;
}
*/
//定义具体类型的结构体
typedef struct{
long long id;
RedisModuleString *name;
} MYTYPE;
//从rdb加载数据
void *MytypeLoad(RedisModuleIO *io, int encver)
{
long long id = RedisModule_LoadSigned(io);
if (RedisModule_IsIOError(io)) return NULL;
RedisModuleString *name = RedisModule_LoadString(io);
if (RedisModule_IsIOError(io)) return NULL;
MYTYPE *mt = (MYTYPE *)RedisModule_Alloc(sizeof(MYTYPE));
mt->id = id;
mt->name = name;
return mt;
}
//保存数据到rdb文件
void MytypeSave(RedisModuleIO *io, void *value)
{
MYTYPE *mt = (MYTYPE *)value;
RedisModule_SaveSigned(io, mt->id);
RedisModule_SaveString(io, mt->name);
}
//aof rewrite的时候调用
void MytypeRewrite(RedisModuleIO *aof, RedisModuleString *key, void *value)
{
//未生效,未知原因
MYTYPE *mt = (MYTYPE *)value;
if (mt)
{
RedisModule_EmitAOF(aof, "mtset", "sls", key, mt->id, mt->name);
}
}
//删除键的时候释放资源
void MytypeFree(void *value)
{
if (value)
{
MYTYPE * mt = (MYTYPE *)value;
if (mt->name) RedisModule_FreeString(NULL, mt->name);
RedisModule_Free(mt);
}
}
//保存数据
int MytypeSet(RedisModuleCtx *ctx, RedisModuleString ** argv, int argc)
{
if (argc != 4)
{
return RedisModule_WrongArity(ctx);
}
RedisModule_AutoMemory(ctx);
//判断参数
long long argid;
if (RedisModule_StringToLongLong(argv[2], &argid) != REDISMODULE_OK)
{
return RedisModule_ReplyWithError(ctx, "ERROR 2nd param is not a Integer");
}
//判断key
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ|REDISMODULE_WRITE);
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != Mytype)
{
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
}
//保存数据和返回结果
MYTYPE *data = RedisModule_Calloc(sizeof(MYTYPE), 1);
data->id = argid;
data->name = argv[3];
RedisModule_RetainString(ctx, data->name);
//保存
RedisModule_ModuleTypeSetValue(key, Mytype, data);
RedisModule_CloseKey(key);
RedisModule_ReplyWithSimpleString(ctx, "OK");
return REDISMODULE_OK;
}
//读取数据
int MytypeGet(RedisModuleCtx *ctx, RedisModuleString ** argv, int argc)
{
if (argc != 2)
{
return RedisModule_WrongArity(ctx);
}
RedisModule_AutoMemory(ctx);
RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_READ|REDISMODULE_WRITE);
int type = RedisModule_KeyType(key);
if (type != REDISMODULE_KEYTYPE_EMPTY && RedisModule_ModuleTypeGetType(key) != Mytype)
{
return RedisModule_ReplyWithError(ctx, REDISMODULE_ERRORMSG_WRONGTYPE);
}
//读取
MYTYPE *data = RedisModule_ModuleTypeGetValue(key);
RedisModule_CloseKey(key);
if (!data)
{
RedisModule_ReplyWithNullArray(ctx);
//RedisModule_ReplyWithNull(ctx);
}
else
{
//使用数组返回自定义数据
RedisModule_ReplyWithArray(ctx, 2);
RedisModule_ReplyWithLongLong(ctx, data->id);
RedisModule_ReplyWithString(ctx, data->name);
//简单构造一个json字符串返回
//size_t len;
//const char *n = RedisModule_StringPtrLen(data->name, &len);
//RedisModuleString * jsonstr = RedisModule_CreateStringPrintf(ctx, "{'id':%d, 'name':'%s'}", data->id, n);
//RedisModule_ReplyWithString(ctx, jsonstr);
}
return REDISMODULE_OK;
}
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv,int argc)
{
if (RedisModule_Init(ctx, "mymodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
//为RedisModule_Load*一类的函数启用RedisModule_IsIOError()
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
//指定类型必需的方法
RedisModuleTypeMethods tm = {
.version = REDISMODULE_TYPE_METHOD_VERSION,
.rdb_load = MytypeLoad,
.rdb_save = MytypeSave,
.aof_rewrite = MytypeRewrite,
.free = MytypeFree,
};
//创建自定义类型,类型名字必须9个字符
Mytype = RedisModule_CreateDataType(ctx, "Mytype789", 1, &tm);
if (Mytype == NULL)
{
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "mtset", MytypeSet, "write", 1, 1, 1) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
if (RedisModule_CreateCommand(ctx, "mtget", MytypeGet, "readonly", 1, 1, 1) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
/*
if (RedisModule_CreateCommand(ctx, "mycmd", MyCmd, "readonly", 0, 0, 0) == REDISMODULE_ERR)
{
return REDISMODULE_ERR;
}
*/
return REDISMODULE_OK;
}
生成一个test.so文件:
另外修改一下redis.conf配置,使redis启动的时候就加载模块并开启rdb持久化用来测试:
loadmodule test.so
save 5 1
启动redis服务:
已经loaded模块了,使用redis-cli连接,并存取几个数据:
关闭redis自动保存rdb,然后重新启动redis加载rdb文件测试:
重新连接redis,读取数据:
可以看到rdb里也保存了自定义的本地数据类型;
另外aof_rewrite不知道为啥不好用,aof持久化没鼓捣好;
=。=写完博客一刷新官网,官网居然换新版了,左右两侧还多了目录,网址也换新了:
实验性的阻塞命令以后有时间再研究;
参考:Redis Modules: an introduction to the API – Redis
redis 4.0以上的module (一)_gochenguowei的博客-CSDN博客_redismodule
我最近在我的机器上安装了 cx_Oracle 模块,以便连接到远程 Oracle 数据库服务器。 (我身边没有 Oracle 客户端)。 Python:版本 2.7 x86 Oracle:版本 11.
我想从 python timeit 模块检查打印以下内容需要多少时间,如何打印, import timeit x = [x for x in range(10000)] timeit.timeit("
我盯着 vs 代码编辑器上的 java 脚本编码,当我尝试将外部模块包含到我的项目中时,代码编辑器提出了这样的建议 -->(文件是 CommonJS 模块;它可能会转换为 ES6 模块。 )..有什么
我有一个 Node 应用程序,我想在标准 ES6 模块格式中使用(即 "type": "module" in the package.json ,并始终使用 import 和 export)而不转译为
我正在学习将 BlueprintJS 合并到我的 React 网络应用程序中,并且在加载某些 CSS 模块时遇到了很多麻烦。 我已经安装了 npm install @blueprintjs/core和
我需要重构一堆具有这样的调用的文件 define(['module1','module2','module3' etc...], function(a, b, c etc...) { //bun
我是 Angular 的新手,正在学习各种教程(Codecademy、thinkster.io 等),并且已经看到了声明应用程序容器的两种方法。首先: var app = angular.module
我正在尝试将 OUnit 与 OCaml 一起使用。 单元代码源码(unit.ml)如下: open OUnit let empty_list = [] let list_a = [1;2;3] le
我在 Angular 1.x 应用程序中使用 webpack 和 ES6 模块。在我设置的 webpack.config 中: resolve: { alias: { 'angular':
internal/modules/cjs/loader.js:750 return process.dlopen(module, path.toNamespacedPath(filename));
在本教程中,您将借助示例了解 JavaScript 中的模块。 随着我们的程序变得越来越大,它可能包含许多行代码。您可以使用模块根据功能将代码分隔在单独的文件中,而不是将所有内容都放在一个文件
我想知道是否可以将此代码更改为仅调用 MyModule.RED 而不是 MyModule.COLORS.RED。我尝试将 mod 设置为变量来存储颜色,但似乎不起作用。难道是我方法不对? (funct
我有以下代码。它是一个 JavaScript 模块。 (function() { // Object var Cahootsy; Cahootsy = { hello:
关闭。这个问题是 opinion-based 。它目前不接受答案。 想要改进这个问题?更新问题,以便 editing this post 可以用事实和引文来回答它。 关闭 2 年前。 Improve
从用户的角度来看,一个模块能够通过 require 加载并返回一个 table,模块导出的接口都被定义在此 table 中(此 table 被作为一个 namespace)。所有的标准库都是模块。标
Ruby的模块非常类似类,除了: 模块不可以有实体 模块不可以有子类 模块由module...end定义. 实际上...模块的'模块类'是'类的类'这个类的父类.搞懂了吗?不懂?让我们继续看
我有一个脚本,它从 CLI 获取 3 个输入变量并将其分别插入到 3 个变量: GetOptions("old_path=s" => \$old_path, "var=s" =
我有一个简单的 python 包,其目录结构如下: wibble | |-----foo | |----ping.py | |-----bar | |----pong.py 简单的
这种语法会非常有用——这不起作用有什么原因吗?谢谢! module Foo = { let bar: string = "bar" }; let bar = Foo.bar; /* works *
我想运行一个命令: - name: install pip shell: "python {"changed": true, "cmd": "python <(curl https://boot
我是一名优秀的程序员,十分优秀!