Redis Module 通过使用外部模块和自定义的 Redis 命令来扩展 Redis 的能力,在 Redis 4.0 版本引入。

Reids Module 是一种动态库,可以在启动时通过配置文件或者命令行参数获取路径进行加载,也可以在 Redis 运行后通过命令加载。它本身的设计目的就是在不同版本的 Redis 中运行,因此无需重新编译模块即可与特定版本 ( Redis > 4.0 )一起运行。

使用模块

下载动态库,并在配置文件中加入

loadmodule /path/to/mymodule.so

或者在命令行启动时,加入参数

MODULE LOAD /path/to/mymodule.so

即可在 Redis 中运行模块。 我们可以通过命令

module list

来查看加载的模块。如果想要卸载已经加载的模块,可以使用命令

module unload mymodule

但是如果一个模块实现了新的数据结构,那么它不能被这个命令卸载,只能通过重启 Redis 并修改配置文件的方式来卸载这个模块

实现模块

想要实现一个 Redis Module 很简单,首先需要拷贝 redismodule.h,然后实现两个函数 _RedisCommand 和 _OnLoad 即可。前者用于命令的具体逻辑,后者用于挂载前者函数的指针到 Redis 的命令字典中。这样就可通过命令调用来进行相应的功能处理。实现 Redis Module 的语言并不一定要求是 C 语言,可以是 C++/Cython 等可以与 C 语言绑定的语言。

#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;
}

上面是一个最简单的模块的实现,它的功能是产生一个随机数。我们将该文件命名为 helloModule.c, 并将 redismodule.h 拷贝到相同的文件路径下,使用命令

gcc helloModule.c -shared -fPIC -o helloModule.so

编译,可以得到 helloModule.so 动态库。在一个运行的 redis-cli 中使用命令

module load /path/to/helloModule.so

即可使用该命令。这里我们没有对参数进行任何使用,也没有参数个数的限制。感兴趣的读者可以通过修改 HelloworldRand_RedisCommand 的逻辑来进行修改。

127.0.0.1:6379> helloworld.rand
(integer) 14866126
127.0.0.1:6379> helloworld.rand 
(integer) 746876630
127.0.0.1:6379> helloworld.rand 1
(integer) 713603695
127.0.0.1:6379> helloworld.rand 1 2
(integer) 1988617017
127.0.0.1:6379> helloworld.rand 1 2 3
(integer) 1398206458
127.0.0.1:6379> helloworld.rand a
(integer) 1889874132

模块内部访问 Redis 的数据

Redis Module 提供了两种 API 对 Redis 内部的数据进行访问。

高级 API 访问 Redis 使用 RedisModule_Call() 函数,它的原理和 Redis 内部的 lua 脚本一样,通过调用 Redis 原生的命令来进行处理。这也意味着,你可以封装一组类似的 lua 命令到 Redis Module中。

低级 API 可以直接操作 key 和与 key 相关的结构,这使得它的处理速度会和原生的 Redis 命令速度相当,比高级 API 有更多的性能提升。

模块生成新的数据类型

使用 API 访问 Redis 内部数据结构,只能组合 Redis 本身的功能,并不能生成新的数据结构。因此, Redis 官方在 Redis Module 增加了实现新的数据结构的能力,并与原生支持的类型相同。

要在模块中实现一个原生类型,需要实现以下几个条件:

  • 实现新的数据结构和与之配套的命令
  • 一组回调函数( rdb 加载,rdb 存储,重写 aof,释放内存,计算内存占用,计算哈希值)
  • 类型名称(需要全局唯一的9位字符)
  • 编码版本

这里你可能要问为什么要一个全局唯一的9位字符?

这是因为 Redis 用了一个64位的整数来保存模块实现的数据类型,其中前54位用来表示名称,后10位用来保存版本号。当 RDB 文件加载时,先会读取这个值的前54位,并且在模块类型的缓存中查找一个可以匹配上的模块。
当找到一个匹配项时,载入 RDB 文件值的方法就被调用。后10位的编码版本作为该方法的参数,以便如果支持多个版本,模块能够了解要加载的数据布局的版本。
如果因为没有被加载的模块,而模块不能被解决,可以将64位值转换回9个字符的名字,并且将一个包括模块类型名称的错误打印给用户,让用户意识到错误。

部分模块介绍

在 Redis 官网上有很多功能强大模块,可以通过 Redis Module查看,这里介绍一些我们平常比较常见的模块。

模块名称

主要功能

RediSearch

基于 Redis 实现的全文搜索

neural-redis

在线神经网络训练

RedisJSON

在 Redis 实现的 json 数据类型

RedisGraph

基于 Redis 实现的图数据库

RedisBloom

布隆过滤器

redis-cell

限速器

RedisTimeSeries

基于 Redis 实现的时序缓存

redis-roaring

咆哮位图

TairHash

支持 hash filed 过期

TairString

乐观锁

redlock

分布式锁

TairZset

对有序集合内的元素进行多重分值和多重排序

redis-protobuf

用于读写 protobuf

后记

本文主要来自于官方文档,有兴趣的读者可以通过查看官方文档进一步了解。也可以下载一下 Redis 源代码,在自己的电脑上运行一下 HelloModule 的源代码;秒杀场景是一个非常常见的demo,通常情况下使用 lua 脚本来防止超卖,你可以实现一个模块来进行原子性的秒杀库存。

读到这里,你是不是对 Redis Module 有了浓厚的兴趣,如果有任何意见和建议,欢迎在下方留言。下一篇文章,将介绍布隆过滤器模块的相关知识。