Redis Modules及其引申问题

一、引言

我今天在逛redis官网的时候(redis.io),无意中看到r edis m odules 这个功能,于是就去了解了一下同时记录为本篇内容。本文包括两个部分:第一部分是对redis modules进行入门级介绍,回答的问题包括r edis m odules 是什么?redis modules怎么用这两个问题?同时提出了三个问题。第二部分是针对第一部分提出的三个问题进行回答,回答主要是以个人观点为主。

二、redis modules

2.1 redis modules是什么

回答redis modules是什么问题之前我们可以先看看redis modules能做什么,通过redis modules,用户可以扩展redis的功能,比如用户可以实现自定义命令, 当客户端往redis server发送这个命令的时候,redis server会执行用户实现的代码,运行起来就像redis内置命令一样。总结一下,redis modules是redis提供的一种插件化机制,他能够让用户的代码以插件(modules)的形式集成到redis服务中,如下是redis modules示意图帮助理解。

图1 redis模块系统插件化机制示意图

2.2 redis模块系统怎么玩

这个功能看起来很有意思,那么我们怎么玩呢?如果只用一句话描述的话,可以为: 引入redismodule.h头文件实现指定的接口和自定义命令,并将你的代码打包成动态链接库然后使用redis提供的命令将动态链接库集成到redis服务中就可以啦 。除了运行期动态加载链接库之外,我们还可以使用配置文件的方式(redis.conf),让redis在启动的时候加载动态链接库,这样会有一个预热的过程,下面是代表两种做法的代码,帮助理解:

 MODULE LOAD /path/to/mymodule.so 或 loadmodule /path/to/mymodule.so

1)那么,有哪些接口可以或者需要实现呢?

其实没那么多,也就只有RedisModule_OnLoad这个接口需要实现,RedisModule_OnLoad相当于一个redis提供的一个回调方法,在模块刚被加载时候,redis服务会回调这个方法,因此我们可以在这个方法中添加一些模块初始化相关的逻辑,这部分逻辑可以在module.c的moduleLoad方法中找到,redis服务分别调用dlopen方法加载动态链接库库并获得句柄,然后传给dlsym方法来调用动态链接库中的用户代码,注意用户是必须要实现RedisModule_OnLoad方法的,否则模块不会生效。


handle = dlopen(path,RTLD_NOW|RTLD_LOCAL);
//省略
onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,”RedisModule_OnLoad”);
if (onload == NULL) {
//省略
return C_ERR;
}

2)然后,怎么实现自定义命令呢?

实现起来可以认为一共分两步,第一步是实现自定义命令处理逻辑,这里提供一个简单的例子:


int HelloSimple_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_ReplyWithLongLong(ctx, 1);
return REDISMODULE_OK;
}

这个例子实现的功能就只是往客户端返回固定的数字“1”,这段代码涉及对RedisModule_ReplyWithLongLong这个方法的调用,RedisModule_ReplyWithLongLong方法是Redis Modules提供给自定义模块开发者的标准接口,换句话说就是你在实现自己的模块逻辑的时候,可以调用这样一组标准接口,来实现自己想要的功能(类系统调用),这样的标准接口还有很多很多。 第二步是注册自定义命令名与自定义命令处理逻辑,具体是在RedisModule_OnLoad方法的实现中调用 RedisModule_CreateCommand 方法注册自定义命令,假设自定义的命令名为“hello.simple”,那么注册方式如下:

RedisModule_CreateCommand(ctx,"hello.simple", HelloSimple_RedisCommand)

RedisModule_CreateCommand方法也是属于redis模块系统提供给模块实现者的标准接口。

2.3 提出引申问题

上面是基于redis modules实现一个自定义命令的完整示例,打包加载部分这里就不带着大家一起看了,接下来我提出三个引申问题:

1、从redis的Copyright看,从2006年就开始了,从代码提交看可追溯到2009年,也就是说redis至少经历了10年的迭代,功能不断增增加,代码规模不断增大,redis是如何控制代码复杂度的?

2、插件化这个思路也很常见,官网显示MODULE LOAD是从redis 4.0.0版本开始使用的,从代码提交看,redis 模块系统代码至少提交于2016年5月,实际开始编写会更早,redis为什么会提供这样的功能?

3、插件化设计的技术难点是什么?(有什么需要注意的?)

三、引申问题思考

3.1 redis是如何应分解代码的?

redis是基于c编写的,redis src文件夹下的代码行数约10W行,对于c语言编写的代码来说规模上算是比较宏大的了,我们从redis src下面的.h和.c文件可以看出一些规律,不完全枚举,大家可以感受一下:

图二 redis源码结构

可以看出, redis主要是以功能模块的方式分解代码的 ,个人认为redis源码没有给人一种非常复杂的感觉,每个模块的命名很清晰,比如zmalloc.c一看就知道是内存分配相关功能的具体实现,同时每个功能都会分成.h和.c两个文件,如果我们只想了解这个功能大概包括哪些核心功能,只需要看.h就可以了,比如下面是zmalloc.h中声明的所有方法:


void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
size_t zmalloc_get_rss(void);
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
void set_jemalloc_bg_thread(int enable);
int jemalloc_purge();
size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
size_t zmalloc_get_memory_size(void);

void zlibc_free(void *ptr);

3.2 redis为什么要引入插件化功能?

从时代背景看,redis的诞生是在PC互联网时代迁移到移动互联网时代中的十年(2000-2010),发展是在移动互联网爆发的十年(2010-2020),redis模块系统从2016年开始提交代码,我印象中2015年是互联网创业的泡沫时期,当时创业项目成千上万,是一个ppt拿融资的年代,随着移动互联网的快速发展,移动流量不断突破记录对后端高性能系统设计提出了挑战,而redis作为一个高性能的内存数据库产品恰好是这方面的优秀解决方案,互联网的发展带动了技术的发展,因此,我认为这是一部分原因。然而redis本身是一个慢项目,redis定位是基础设施层,是存储数据的地方,千万不能出现问题,核心是求稳,每个版本都要经过足够一段时间的beta才能发布,其不可能像互联网业务项目一样能够快速迭代?需求需要支撑,系统要求稳定,怎么办呢?redis想到了一个办法, 按开放式的思路设计,提供可插拔的能力,每个人都可以将自己的代码集成进来 ,以应对redis团队资源方面的问题以及各种各样业务场景中redis本身无法满足的业务需求 其实,横向看一下我们会发现无独有偶,从技术角度看,2017年阿里双十一交易系统TMP2.0也是基于同样的思路设计的,TMF是在中台化的背景下产生的,其能够支持业务代码以插件包的形式集成到交易平台上,交易平台整体框架,插件化支持业务定制。

图三 TMP 2.0

3.3 插件化系统设计的难点是什么?

“设计最难的部分在于设计什么”——《设计原本》

类似,我认为插件化系统的核心难点是插件什么?这里包括两个部分问题,一是在哪里设置插口?二是插口如何设计?从大的方向看,这部分核心依赖对业务流程的理解,对变与不变的识别,将变化的东西通过插口隔离出来。

除此之外,我认为还有一个很重要的问题是安全性问题,既然允许用户代码跑在核心系统里面,那么如何保证这部分代码执行是安全的?举个栗子,用户实现了一个死循环丢给核心系统调度会有什么后果?这个问题操作系统设计的时候也有同样的困惑,因此引入了用户态和内核态的概念。

这部分我主要是作为提问者吧,读者有答案欢迎分享。

四、总结

本文从redis模块系统出发,首先介绍了redis模块系统是什么,怎么玩,然后提出了三个引申的问题,后面针对这三个引申的问题试着解答,但答案仅仅是作者个人理解。插件化思路无论是对基础服务来说,还是对业务系统来说都是非常具有设计参考价值,希望本文能够对读者有帮助,谢谢。

五、参考文献

【1】https://www.sohu.com/a/212071978_612370

【2】https://redis.io/topics/modules-intro