17

Redis Modules及其引申问题

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA4NzQ5OTkxNw%3D%3D&%3Bmid=2247483826&%3Bidx=1&%3Bsn=e16a2195f6bc330ea6f3aae0bc641033
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

一、引言

我今天在逛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示意图帮助理解。

fuEBvqy.png!web

图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文件可以看出一些规律,不完全枚举,大家可以感受一下:

QnqiMfF.png!web

图二 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是在中台化的背景下产生的,其能够支持业务代码以插件包的形式集成到交易平台上,交易平台整体框架,插件化支持业务定制。

iyAfium.jpg!web

图三 TMP 2.0

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

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

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

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

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

四、总结

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

五、参考文献

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK