Redis【有与无】【API-1】模块: API 介绍

本文基于Redis 6.0.9版本,前提至少 Redis 3.0或更高版本。

目录

1.模块: API 介绍

1.1.加载模块(Loading modules)

1.2.你可以编写的最简单的模块

1.3.模块初始化

1.4.模块清理

1.5.Redis 模块的设置和依赖关系

1.6.将配置参数传递给 Redis 模块

1.7.使用 RedisModuleString 对象

1.8.从数字创建字符串或将字符串解析为数字

1.9.从模块访问 Redis 键

1.10.调用 Redis 命令

1.11.使用 RedisModuleCallReply 对象

1.12.释放呼叫回复对象

1.13.从Redis命令返回值

1.14.返回具有动态长度的数组

1.15.检查字体大小和类型

1.16.低级访问键

1.17.获取键类型

1.18.创建新键

1.19.删除键

1.20.管理键过期(TTL)

1.21.获取值的长度

1.22.String类型API

1.23.List类型API

1.24.Set类型API

1.25.Sorted set类型API

1.26.Hash类型API

1.26.迭代汇总

2.复制(Replicating)命令

3.自动内存管理

4.将内存分配到模块中

5.池分配器

6.编写与Redis Cluster兼容的命令


1.模块: API 介绍

模块文件由以下页面组成:

  • Redis 模块介绍。Redis 模块系统与 API 综述。从这里开始阅读是个好主意
  • 实现本机数据类型涵盖了将本机数据类型实现为模块
  • 阻塞操作 演示如何编写阻塞命令,这些命令不会立即响应,但会阻塞客户端,而不会阻塞 Redis 服务器,并且只要有可能就会提供响应
  • Redis模块 API 参考 是从RedisModule函数的module.c顶部注释生成的。 这是了解每个功能如何工作的很好参考。

Redis 模块使得使用外部模块扩展 Redis 功能成为可能,以一定的速度实现新的 Redis 命令,其特性类似于内核本身。

Redis 模块是动态库,可以在启动时或使用 MODULE LOAD 命令加载到 Redis 中。Redis 以一个名为 redismodule.h 的 c 头文件的形式导出一个 C API。模块是用 C 编写的,但是也可以使用 C + + 或者其他具有 C 绑定功能的语言。

模块的设计是为了加载到不同版本的 Redis,所以给定的模块不需要进行设计或重新编译,就可以运行特定版本的 Redis。因此,模块将使用特定的 API 版本注册到 Redis 核心。目前的 API 版本是“1”

本文档是关于 Redis 模块的 alpha 版本。 API、功能和其他细节将来可能会改变。

1.1.加载模块(Loading modules)

为了测试你正在开发的模块,你可以使用以下 redis.conf 配置指令来加载模块:

loadmodule /path/to/mymodule.so

也可以在运行时使用以下命令加载模块:

MODULE LOAD /path/to/mymodule.so

为了列出所有加载的模块,使用:

MODULE LIST

最后,您可以使用以下命令卸载(如果愿意,以后可以重新加载)一个模块:

MODULE UNLOAD mymodule

请注意,上面的mymodule不是没有.so后缀的文件名,而是用于将自身注册到Redis核心的模块的名称。

可以使用“MODULE LIST”获得名称。 但是,最好的做法是动态库的文件名与模块用于将自身注册到Redis核心的名称相同。

1.2.你可以编写的最简单的模块

为了展示模块的不同部分,这里我们将展示一个非常简单的模块,它实现一个输出随机数的命令。

#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, "fast random",
        0, 0, 0) == REDISMODULE_ERR)
        return REDISMODULE_ERR;

    return REDISMODULE_OK;
}

示例模块有两个功能。一个实现名为 HELLOWORLD.RAND 的命令。这个函数是该模块特有的。但是,在每个 Redis 模块中都必须包含另一个名为 RedisModule_OnLoad() 的函数。它是要初始化模块、注册其命令以及它使用的其他私有数据结构的入口点。

请注意,模块调用命令时,最好使用模块的名称后面跟一个点,最后使用命令名,就像 HELLOWORLD.RAND 的情况一样。这样就减少了碰撞的可能性。

注意,如果不同的模块有相互冲突的命令,它们将不能同时在 Redis 工作,因为 RedisModule_CreateCommand  函数将在其中一个模块中失败,所以模块加载将中止返回错误条件。

1.3.模块初始化

上面的示例显示了函数 RedisModule_Init() 的用法。它应该是模块 OnLoad 函数调用的第一个函数。以下是函数原型:

int RedisModule_Init(RedisModuleCtx *ctx, const char *modulename,
                     int module_version, int api_version);

Init 函数会通知Redis内核该模块具有给定名称,其版本(由MODULE LIST报告),并愿意使用特定版本的API。

如果API版本错误,名称已被使用或存在其他类似错误,则该函数将返回REDISMODULE_ERR,并且模块OnLoad函数应尽快返回一个错误。

在调用 Init 函数之前,不能调用其他 API 函数,否则模块将分割错误,Redis 实例将崩溃(crash)。

第二个函数 RedisModule_CreateCommand 用于将命令注册到 Redis 核心。以下是原型:

int RedisModule_CreateCommand(RedisModuleCtx *ctx, const char *name,
                              RedisModuleCmdFunc cmdfunc, const char *strflags,
                              int firstkey, int lastkey, int keystep);

正如您所看到的,大多数 Redis 模块 API 调用都以模块的上下文作为第一个参数,这样它们就有一个对调用它的模块的引用,对执行给定命令的命令和客户端的引用,等等。

要创建一个新命令,上面的函数需要上下文、命令的名称、实现命令的函数的指针、命令的标志以及命令参数中键名的位置。

执行该命令的函数必须具有以下原型:

int mycommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);

命令函数参数只是上下文,它将被传递给所有其他 API 调用、命令参数向量和用户传递的参数总数。

如您所见,参数是作为指向特定数据类型 RedisModuleString 的指针提供的。这是一个不透明的数据类型,你有 API 函数访问和使用,直接访问它的字段是永远不需要的。

放大到示例命令实现,我们可以找到另一个调用:

int RedisModule_ReplyWithLongLong(RedisModuleCtx *ctx, long long integer);

这个函数向调用命令的客户机返回一个整数,就像其他 Redis 命令一样,比如  INCR 或 SCARD

1.4.模块清理

在大多数情况下,没有必要进行特殊的清理。当模块被卸载时,Redis 将自动取消注册命令并取消通知订阅。但是,在模块包含一些持久内存或配置的情况下,模块可能包含一个可选的 RedisModule _ onunload 函数。如果一个模块提供了这个函数,它将在模块卸载过程中被调用。以下是函数原型:

int RedisModule_OnUnload(RedisModuleCtx *ctx);

OnUnload 函数可以通过返回 REDISMODULE_ERR 来阻止模块卸载。否则,应该返回 REDISMODULE_OK。

1.5.Redis 模块的设置和依赖关系

Redis 模块不依赖于 Redis 或其他库,也不需要使用特定的 redismodule.h 文件进行编译。为了创建一个新的模块,只需在源代码树中复制 RedisModule.h 的最新版本,链接所有需要的库,并创建一个导出 RedisModule_OnLoad() 函数符号的动态库。

该模块将能够加载到不同版本的 Redis。

1.6.将配置参数传递给 Redis 模块

当模块使用 MODULE LOAD 命令加载时,或者使用 redis.conf 文件中的 loadmodule 指令时,用户可以通过在模块文件名后面添加参数将配置参数传递给模块:

loadmodule mymodule.so foo bar 1234

在上面的示例中,字符串 foo、 bar 和123将作为 RedisModuleString 指针数组传递给 argv 参数中的模块 OnLoad() 函数。传入 argc 的参数数量。

访问这些字符串的方法将在本文的其余部分中解释。通常,模块将模块配置参数存储在一些静态全局变量中,这些全局变量可以在模块范围内访问,因此配置可以更改不同命令的行为。

1.7.使用 RedisModuleString 对象

通常直接将模块字符串传递给其他 API 调用,但有时可能需要直接访问字符串对象。

有一些函数可以处理字符串对象:

const char *RedisModule_StringPtrLen(RedisModuleString *string, size_t *len);

上面的函数通过返回字符串的指针并在 len 中设置它的长度来访问字符串。永远不要写入字符串对象指针,从常量指针限定符中可以看到这一点。

然而,如果你愿意,你可以使用下面的 API 创建新的字符串对象:

RedisModuleString *RedisModule_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len);

以上命令返回的字符串必须通过相应的调用 RedisModule_FreeString() 来释放:

void RedisModule_FreeString(RedisModuleString *str);

但是,如果您想避免释放字符串,那么本文后面介绍的自动内存管理就是一个很好的替代方案,它可以帮助您完成这项工作。

注意,通过参数向量 argv 提供的字符串永远不需要被释放。您只需释放您创建的新字符串或其他 api 返回的新字符串,其中指定必须释放返回的字符串。

1.8.从数字创建字符串或将字符串解析为数字

从一个整数创建一个新字符串是一个非常常见的操作,所以有一个函数可以做到这一点:

RedisModuleString *mystr = RedisModule_CreateStringFromLongLong(ctx,10);

类似地,为了将字符串解析为数字:

long long myval;
if (RedisModule_StringToLongLong(ctx,argv[1],&myval) == REDISMODULE_OK) {
    /* Do something with 'myval' */
}

1.9.从模块访问 Redis 键

大多数 Redis 模块为了有用,必须与 Redis 数据空间交互(这并不总是正确的,例如 ID 生成器可能永远不会触及 Redis 键)。为了访问 Redis 数据空间,Redis 模块有两个不同的 API,一个是提供非常快速访问的低级 API,另一个是操作 Redis 数据结构的函数集。另一个 API 级别更高,允许调用 Redis 命令并获取结果,类似于 Lua 脚本访问 Redis 的方式。

高级 API 对于访问不能作为 API 使用的 Redis 功能也很有用。

在一般模块中,开发人员应该更喜欢底层 API,因为使用底层 API 实现的命令的运行速度与本地 Redis 命令的速度相当。然而,对于更高级别的 API 肯定有用例。例如,通常瓶颈可能是处理数据而不访问数据。

还要注意,有时使用低级别 API 并不比使用高级别 API 更困难

1.10.调用 Redis 命令

访问 Redis 的高级 API 是 RedisModule_Call() 函数的总和,以及访问 Call ()返回的应答对象所需的函数。

RedisModule_Call 使用一个特殊的调用约定,其中包含一个格式说明符,用于指定将哪种对象作为参数传递给函数。

仅使用命令名称和参数列表来调用Redis命令。 但是,在调用命令时,参数可能源自不同类型的字符串:以null终止的C字符串,从命令实现中的argv参数接收的RedisModuleString对象,具有指针和长度的二进制安全C缓冲区等等。

例如,如果我想使用第一个参数(键)调用INCRBY ,则是在参数向量argv中接收的字符串,它是RedisModuleString对象指针的数组,而C字符串则表示数字“10”作为第二个参数(增量 ),我将使用以下函数调用:

RedisModuleCallReply *reply;
reply = RedisModule_Call(ctx,"INCRBY","sc",argv[1],"10");

第一个参数是上下文,第二个参数始终是带有命令名称的以null 结尾的C字符串。 第三个参数是格式说明符,其中每个字符都对应于后面的参数类型。 在上述情况下,“ sc”表示RedisModuleString对象和以N结尾的C字符串。 其他参数只是指定的两个参数。 实际上,argv [1]是RedisModuleString,而“10”是一个以null结尾的C字符串。

下面是格式说明符的完整列表:

 

该函数成功时返回 RedisModuleCallReply 对象,错误 时返回NULL。

 

  • c -- 以空字符串结尾的 C 字符串指针
  • b -- C 缓冲区,需要两个参数: C 字符串指针和 size_t  长度
  • s -- 在 argv 或其他 Redis 模块 api 中接收到的 RedisModuleString,返回 RedisModuleString 对象
  • l -- 长整数
  • v -- RedisModuleString 对象的数组
  • ! -- 该修饰符仅告诉函数将命令复制到副本和AOF。 从参数解析的角度来看,它将被忽略。
  • A -- 这个修饰符,当!被给出,告诉抑制 AOF 传播: 该命令将只传播到副本
  • R -- 这个修饰符,当!被赋予,告诉抑制复制传播: 如果启用,该命令将仅传播到 AOF

如果命令名无效、格式说明符使用无法识别的字符或者命令调用时参数数目错误,则返回 NULL。在上面的例子中,errno 变量被设置为 EINVAL。如果在启用了集群的实例中,目标键是关于非本地散列槽的,那么也会返回 NULL。在这种情况下,errno 被设置为 EPERM。

1.11.使用 RedisModuleCallReply 对象

RedisModuleCall 返回可以使用 RedisModule_CallReply* 函数家族访问的应答对象。

为了获取类型或应答(对应于 Redis 协议支持的数据类型之一) ,使用 RedisModule_CallReplyType() 函数:

reply = RedisModule_Call(ctx,"INCRBY","sc",argv[1],"10");
if (RedisModule_CallReplyType(reply) == REDISMODULE_REPLY_INTEGER) {
    long long myval = RedisModule_CallReplyInteger(reply);
    /* Do something with myval. */
}

有效的回复类型如下:

  • REDISMODULE_REPLY_STRING 批量字符串或状态回复
  • REDISMODULE_REPLY_ERROR Errors 错误
  • REDISMODULE_REPLY_INTEGER 有符号64位整数
  • REDISMODULE_REPLY_ARRAY 回复数组
  • REDISMODULE_REPLY_NULL NULL答复

字符串、错误和数组具有相关联的长度。对于字符串和错误,长度对应于字符串的长度。对于数组,长度是元素的数量。要获得回复长度,使用以下函数:

size_t reply_len = RedisModule_CallReplyLength(reply);

为了获得整数回复的值,使用了以下函数,如上面的示例所示:

long long reply_integer_val = RedisModule_CallReplyInteger(reply);

用错误类型的回复对象调用时,上述函数始终返回LLONG_MIN。

数组回复的子元素可以通过以下方式访问:

RedisModuleCallReply *subreply;
subreply = RedisModule_CallReplyArrayElement(reply,idx);

如果您尝试访问超出范围的元素,则上述函数将返回NULL。

可以通过以下方式访问字符串和错误(类似于字符串,但具有不同的类型),并确保永远不要写入结果指针(作为const指针返回,因此滥用必须非常明确):

size_t len;
char *ptr = RedisModule_CallReplyStringPtr(reply,&len);

如果回复类型不是字符串或错误,则返回NULL。

RedisCallReply对象与模块字符串对象(RedisModuleString类型)不同。 但是,有时你可能需要将字符串或整数类型的答复传递给需要模块字符串的API函数。

在这种情况下,您可能需要评估使用低级API是否可能是实现命令的更简单方法,或者可以使用以下函数从string类型的调用应答中创建新的string对象, 错误或整数:

RedisModuleString *mystr = RedisModule_CreateStringFromCallReply(myreply);

如果回复的类型不正确,则返回NULL。 通常,应该使用 RedisModule_FreeString() 或通过启用自动内存管理来释放返回的字符串对象(请参见相应部分)。

1.12.释放呼叫回复对象

必须使用RedisModule_FreeCallReply释放回复对象。 对于数组,您只需要释放顶级回复,而不需要嵌套回复。 当前,模块实现提供了一种保护,以避免在释放嵌套的回复对象以防止错误时崩溃,但是此功能不能保证永远存在,因此不应将其视为API的一部分。

如果您使用自动内存管理(在本文档的后面部分进行说明),则不需要释放答复(但如果希望尽快释放内存,则仍然可以)。

1.13.从Redis命令返回值

像普通的Redis命令一样,通过模块实现的新命令必须能够将值返回给调用方。 API会为此目的导出一组函数,以便返回Redis协议的常用类型,以及此类元素的数组。 错误也可以使用任何错误字符串和代码返回(错误代码是错误消息中的初始大写字母,例如“BUSY the sever is busy”错误消息中的“BUSY”字符串)。

所有向客户端发送回复的功能都称为  RedisModule_ReplyWith<something>

要返回错误,请使用:

RedisModule_ReplyWithError(RedisModuleCtx *ctx, const char *err);

对于错误类型错误的键,有一个预定义的错误字符串:

REDISMODULE_ERRORMSG_WRONGTYPE

用法示例:

RedisModule_ReplyWithError(ctx,"ERR invalid arguments");

在上面的示例中,我们已经了解了很长的答案:

RedisModule_ReplyWithLongLong(ctx,12345);

要使用不包含二进制值或换行符的简单字符串进行回复,(因此,它适合发送诸如“OK”之类的小词),我们使用:

RedisModule_ReplyWithSimpleString(ctx,"OK");

使用两个不同的函数,可以用二进制安全的“bulk strings”进行回复:

int RedisModule_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len);

int RedisModule_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str);

第一个函数获取C指针和长度。 第二个是RedisModuleString对象。 根据您手头的来源类型使用一种或另一种。

为了对数组进行回复,您只需要使用一个函数来发出数组长度,然后对上述函数的调用就跟数组元素数一样多:

RedisModule_ReplyWithArray(ctx,2);
RedisModule_ReplyWithStringBuffer(ctx,"age",3);
RedisModule_ReplyWithLongLong(ctx,22);

要返回嵌套数组很容易,您的嵌套数组元素仅使用对 RedisModule_ReplyWithArray()的另一个调用,然后使用该调用来发出子数组元素。

1.14.返回具有动态长度的数组

有时无法事先知道数组的项目数。 例如,考虑实现一个FACTOR命令的Redis模块,该命令给定一个数字,输出素数。 与其将因数分解,将素因数存储到数组中,然后再生成命令答复,不如将一个未知长度的数组回复开始,然后再进行设置,这是一个更好的解决方案。 这可以通过RedisModule_ReplyWithArray()的特殊参数来完成:

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);

上面的调用启动了一个数组回复,因此我们可以使用其他ReplyWith调用来生成数组项。 最后,为了设置长度,请使用以下调用:

RedisModule_ReplySetArrayLength(ctx, number_of_items);

对于FACTOR命令,这将转换为类似于以下代码的代码:

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
number_of_factors = 0;
while(still_factors) {
    RedisModule_ReplyWithLongLong(ctx, some_factor);
    number_of_factors++;
}
RedisModule_ReplySetArrayLength(ctx, number_of_factors);

此功能的另一个常见用例是遍历某个集合的数组,仅返回经过某种过滤的数组。

可能有多个嵌套数组,且回复延迟。 每次调用 SetArray() 都会设置对 ReplyWithArray() 的最新相应调用的长度:

RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 100 elements ...
RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
... generate 10 elements ...
RedisModule_ReplySetArrayLength(ctx, 10);
RedisModule_ReplySetArrayLength(ctx, 100);

这将创建一个100个项目的数组,最后一个元素是10个项目的数组。

1.15.检查字体大小和类型

通常,命令需要检查参数的数量和键的类型是否正确。为了报告错误的现象,有一个名为 RedisModule_WrongArity() 的特定函数。用法很简单:

if (argc != 2) return RedisModule_WrongArity(ctx);

检查错误的类型包括打开键和检查类型:

RedisModuleKey *key = RedisModule_OpenKey(ctx,argv[1],
    REDISMODULE_READ|REDISMODULE_WRITE);

int keytype = RedisModule_KeyType(key);
if (keytype != REDISMODULE_KEYTYPE_STRING &&
    keytype != REDISMODULE_KEYTYPE_EMPTY)
{
    RedisModule_CloseKey(key);
    return RedisModule_ReplyWithError(ctx,REDISMODULE_ERRORMSG_WRONGTYPE);
}

请注意,如果键是期望的类型,或者它是空的,那么您通常都希望继续执行命令。

1.16.低级访问键

对键的低级别访问允许直接在与键关联的值对象上执行操作,其速度类似于Redis在内部用于实现内置命令的速度。

打开键后,将返回一个键指针,该指针将与所有其他低级API调用一起使用,以便对键或其关联值执行操作。

因为该API的速度非常快,所以它不能执行太多的运行时检查,因此用户必须知道要遵循的某些规则:

  • 在打开至少一个实例进行写入的情况下,多次打开同一键是未定义的,并且可能导致崩溃。
  • 打开键后,只能通过低级键API对其进行访问。 例如,打开一个键,然后使用RedisModule_Call()API在同一键上调用DEL,将导致崩溃。 但是,可以安全地打开键,使用低级API执行一些操作,将其关闭,然后使用其他API管理同一键,然后再次打开它以执行更多工作。

为了打开键,使用RedisModule_OpenKey函数。 它返回一个键指针,我们将在接下来的所有调用中使用该键指针来访问和修改值:

RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_READ);

第二个参数是键名称,它必须是RedisModuleString对象。 第三个参数是模式:REDISMODULE_READ或REDISMODULE_WRITE。 可以使用| 按位或两种模式以在两种模式下打开键。 当前,也可以访问为阅读而打开的用于写入的键,但这应被视为实现细节。 正确的模式应在合理的模块中使用。

您可以打开不存在的键进行写入,因为在尝试写入键时会创建键。 但是,当打开仅用于读取的键时,如果键不存在,则RedisModule_OpenKey将返回NULL。

使用键完成后,可以使用以下命令将其关闭:

RedisModule_CloseKey(key);

请注意,如果启用了自动内存管理,则不会强制您关闭键。 当模块功能返回时,Redis将注意关闭所有仍处于打开状态的键。

1.17.获取键类型

为了获得键的值,请使用 RedisModule_KeyType() 函数:

int keytype = RedisModule_KeyType(key);

它返回以下值之一:

REDISMODULE_KEYTYPE_EMPTY
REDISMODULE_KEYTYPE_STRING
REDISMODULE_KEYTYPE_LIST
REDISMODULE_KEYTYPE_HASH
REDISMODULE_KEYTYPE_SET
REDISMODULE_KEYTYPE_ZSET

以上只是通常的Redis键类型,另外还有一个空类型,它表示该密钥指针与一个尚不存在的空键相关联。

1.18.创建新键

要创建新键,请打开它进行写入,然后使用其中一种键写入功能对其进行写入。 例:

RedisModuleKey *key;
key = RedisModule_OpenKey(ctx,argv[1],REDISMODULE_WRITE);
if (RedisModule_KeyType(key) == REDISMODULE_KEYTYPE_EMPTY) {
    RedisModule_StringSet(key,argv[2]);
}

1.19.删除键

只需使用:

RedisModule_DeleteKey(key);

如果没有打开写入键,该函数将返回REDISMODULE_ERR。 请注意,删除键后,将其设置为新键命令的目标。 例如 RedisModule_KeyType() 将返回它是一个空键,对其进行写操作将创建一个新的键,该键可能是另一种类型(取决于所使用的API)。

1.20.管理键过期(TTL)

为了控制键过期,提供了两个功能,它们可以设置,修改,获取和取消与键关联的生存时间。

使用一个函数来查询打开键的当前到期时间:

mstime_t RedisModule_GetExpire(RedisModuleKey *key);

该函数以毫秒为单位返回键的生存时间,或者返回REDISMODULE_NO_EXPIRE作为特殊值,以表示键没有关联的过期时间或根本不存在(您可以区分两种情况,检查键类型是否为REDISMODULE_KEYTYPE_EMPTY)。

为了更改键的到期时间,请使用以下功能:

int RedisModule_SetExpire(RedisModuleKey *key, mstime_t expire);

在不存在的键上调用时,将返回REDISMODULE_ERR,因为该函数只能将过期与现有的打开键相关联(不存在的打开键仅在创建具有特定于数据类型的写入操作的新值时有用)。

再次指定到期时间(以毫秒为单位)。 如果键当前没有过期,则设置一个新的过期。 如果键已经过期,则将其替换为新值。

如果键已过期,并且特殊值REDISMODULE_NO_EXPIRE用作新的过期,则类似于Redis PERSIST命令,将删除该过期。 如果键已经永久存在,则不执行任何操作。

1.21.获取值的长度

为了检索与打开键关联的值的长度,只有一个功能。 返回的长度是特定于值的,是字符串的字符串长度,以及聚合数据类型的元素数量(列表,集合,排序集合,哈希中有多少个元素)。

size_t len = RedisModule_ValueLength(key);

如果键不存在,则该函数返回0:

1.22.String类型API

设置新的字符串值(如Redis SET命令一样)是使用以下命令执行的:

int RedisModule_StringSet(RedisModuleKey *key, RedisModuleString *str);

该函数的工作方式与Redis SET命令本身完全相同,也就是说,如果存在(任何类型的)先验值,它将被删除。

为了提高速度,使用DMA(直接内存访问)来访问现有的字符串值。 该API将返回一个指针和一个长度,以便可以访问,并在需要时直接修改字符串。

size_t len, j;
char *myptr = RedisModule_StringDMA(key,&len,REDISMODULE_WRITE);
for (j = 0; j < len; j++) myptr[j] = 'A';

在上面的示例中,我们直接在字符串上写。 请注意,如果要编写,必须确保要求使用WRITE模式。

DMA指针仅在使用指针之前,在DMA调用之后没有使用键执行其他任何操作时才有效。

有时,当我们想直接操作字符串时,我们也需要更改其大小。 对于此范围,使用RedisModule_StringTruncate函数。 例:

RedisModule_StringTruncate(mykey,1024);

该函数会根据需要截断或扩大字符串,如果先前的长度小于我们请求的新长度,则将其填充零字节。 如果由于键与一个打开的空键相关联而该字符串不存在,那么将创建一个字符串值并将其与该键相关联。

请注意,每次调用StringTruncate()时,我们都需要重新获取DMA指针,因为旧指针可能无效。

1.23.List类型API

可以从列表值中推送和弹出值:

int RedisModule_ListPush(RedisModuleKey *key, int where, RedisModuleString *ele);
RedisModuleString *RedisModule_ListPop(RedisModuleKey *key, int where);

在这两个API中,where参数使用以下宏指定是从尾部还是从头推动或弹出:

REDISMODULE_LIST_HEAD
REDISMODULE_LIST_TAIL

RedisModule_ListPop() 返回的元素类似于使用RedisModule_CreateString()创建的字符串,必须使用RedisModule_FreeString() 或通过启用自动内存管理来释放它们。

1.24.Set类型API

...

1.25.Sorted set类型API

缺少文档,请参阅module.c内的顶部注释以获取以下功能:

  • RedisModule_ZsetAdd
  • RedisModule_ZsetIncrby
  • RedisModule_ZsetScore
  • RedisModule_ZsetRem

对于排序集迭代器:

  • RedisModule_ZsetRangeStop
  • RedisModule_ZsetFirstInScoreRange
  • RedisModule_ZsetLastInScoreRange
  • RedisModule_ZsetFirstInLexRange
  • RedisModule_ZsetLastInLexRange
  • RedisModule_ZsetRangeCurrentElement
  • RedisModule_ZsetRangeNext
  • RedisModule_ZsetRangePrev
  • RedisModule_ZsetRangeEndReached

1.26.Hash类型API

缺少文档,请参阅module.c内的顶部注释以获取以下功能:

  • RedisModule_HashSet
  • RedisModule_HashGet

1.26.迭代汇总

...

2.复制(Replicating)命令

如果要在复制的Redis实例的上下文中使用与常规Redis命令完全相同的模块命令,或者使用AOF文件来保持持久性,则对于模块命令以一致的方式处理它们的复制非常重要。

使用较高级别的API调用命令时,如果使"!",则会自动进行复制。RedisModule_Call() 格式字符串中的修饰符,如以下示例所示:

reply = RedisModule_Call(ctx,"INCRBY","!sc",argv[1],"10");

如您所见,格式说明符为"!sc"。 爆炸没有被解析为格式说明符,但在内部将命令标记为“must replicate”。

如果使用上述编程风格,则不会有问题。 但是,有时候事情比这更复杂,因此您使用低级API。 在这种情况下,如果命令执行没有副作用,并且始终执行相同的工作,则可以做的是在用户执行命令时逐字复制命令。 为此,您只需要调用以下函数:

RedisModule_ReplicateVerbatim(ctx);

使用上述API时,不应保证使用其他任何复制功能,因为它们不能保证混在一起。

但是,这不是唯一的选择。 也可以使用类似于RedisModule_Call()的API确切地告诉Redis作为命令执行的结果要复制哪些命令,但不是调用命令而是将其发送到AOF / slaves 流。 例:

RedisModule_Replicate(ctx,"INCRBY","cl","foo",my_increment);

可以多次调用RedisModule_Replicate,每次都会发出一个命令。 发出的所有序列都包装在MULTI / EXEC事务之间,因此AOF和复制效果与执行单个命令相同。

注意,如果您想混合两种形式的复制,则Call()复制和Replicate()复制具有规则(如果有更简单的方法,不一定是个好主意)。 用Call()复制的命令始终是最后的MULTI / EXEC块中的第一个发出的命令,而使用Replicate()发出的所有命令都将紧随其后。

3.自动内存管理

通常,使用C语言编写程序时,程序员需要手动管理内存。 这就是Redis模块API具有释放字符串,关闭打开键,免费回复等功能的原因。

但是,鉴于命令是在封闭的环境中执行并使用一组严格的API,Redis能够以一些性能为代价(大多数情况下,非常低的成本)为模块提供自动内存管理。

启用自动内存管理后:

  • 您无需关闭打开键。
  • 您不需要发表答复。
  • 您不需要释放RedisModuleString对象。

但是,如果需要,您仍然可以这样做。 例如,自动内存管理可能处于活动状态,但是在分配大量字符串的循环中,您可能仍想释放不再使用的字符串。

为了启用自动内存管理,只需在命令实现开始时调用以下函数:

RedisModule_AutoMemory(ctx);

自动内存管理通常是要走的路,但是经验丰富的C程序员可能不使用它来获得速度和内存使用方面的好处。

4.将内存分配到模块中

普通的C程序使用malloc()free()来动态分配和释放内存。 虽然从技术上讲,在Redis模块中不禁止使用malloc,但是最好使用Redis模块特定的功能,这些功能可以精确替换 mallocfreereallocstrdup。 这些功能是:

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);

它们的工作方式与它们的libc等效调用完全相同,但是它们使用Redis所使用的分配器,并且使用这些函数分配的内存由内存部分中的INFO命令报告,在执行maxmemory 策略时考虑,通常是第一个 Redis可执行文件的公民。 相反,在libc模块内部使用 malloc()分配的方法对Redis是透明的。

使用模块函数分配内存的另一个原因是,在模块内部创建本机数据类型时,RDB加载函数可以直接将反序列化的字符串(从RDB文件中)作为RedisModule_Alloc()分配返回,因此可以直接使用它们。 在加载后填充数据结构,而不必将其复制到数据结构中。

5.池分配器

有时,在命令实现中,需要执行许多小的分配,这些分配不会在命令执行结束时保留,而只是执行命令本身的功能。

使用Redis池分配器可以更轻松地完成这项工作:

void *RedisModule_PoolAlloc(RedisModuleCtx *ctx, size_t bytes);

它的工作方式与 malloc()类似,并返回与大于或等于2 bytes 的的下一个幂次对齐的内存(最大对齐8个bytes )。 但是,它以块为单位分配内存,因此分配的开销很小,更重要的是,当命令返回时,分配的内存会自动释放。

因此,一般而言,短期分配是池分配器的不错选择。

6.编写与Redis Cluster兼容的命令

缺少文档,请检查module.c中的以下功能:

RedisModule_IsKeysPositionRequest(ctx);
RedisModule_KeyAtPos(ctx,pos);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Redis API文档。Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。redis的官网地址,非常好记,是redis.io。(域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地)目前,Vmware在资助着redis项目的开发和维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

琴 韵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值