概述
恍然之间惊觉Redis已经发展到8.4版本:

而我对Redis的大部分认知(知识点)可能还停留在3.0或以前的版本。本文试图学习Redis各个版本新特性。参考官方文档
| Redis版本 | 发布日期 | 核心新特性 | 当前应用与影响 |
|---|---|---|---|
| 1.0 | 2009年12月 | 基础数据结构(字符串、哈希、列表)、持久化 | 基石功能 |
| 2.6 | 2012年7月 | Lua脚本支持、自动故障恢复(复制) | 实现复杂操作的原子性,是功能扩展的关键 |
| 3.0 | 2015年3月 | 原生Redis Cluster、Redis Sentinel | 里程碑版本,提供分布式和官方高可用解决方案 |
| 4.0 | 2017年11月 | 模块系统、混合持久化、LFU缓存策略、异步命令 | 从数据库变为平台,可通过模块扩展新数据类型(如搜索、图) |
| 5.0 | 2018年10月 | Stream数据类型、改进集群管理 | 提供消息队列能力 |
| 6.0 | 2020年4月 | ACL访问控制、客户端缓存、多线程I/O(仅网络处理)、RESP3协议 | 重大安全升级,支持多用户与精细化权限;性能与协议得到增强 |
| 7.0 | 2022年4月 | Redis Functions、分片Pub/Sub、多部分AOF | 函数可存储在服务端,是Lua脚本的演进;分片Pub/Sub优化集群下的发布订阅 |
| 8.0 | 向量集合 |
模块系统
Redis的扩展模块,设计目的:无需重新编译模块即可在Redis 4.0以上版本运行。允许用户自定义扩展模块,在Redis内部实现新的数据类型和功能,使用统一的调用方式和传输协议格式扩展Redis的能力。
基于动态链接库,可在启动时加载,也可在运行时加载(MODULE LOAD);Linux下可通过dlopen,dlsym等实现动态加载库。通过RedisModule_Init方法进行注册模块,RedisModule_CreateCommand注册自定义方法。Redis导出redismodule.h头文件,通过实现该头文件相关API函数,然后编译为so动态库即可,可在配置文件中使用loadmodule指明,也可在运行时使用命令动态加载。
Redis模块列表
| 名称 | 简介 | GitHub | Star | Fork |
|---|---|---|---|---|
| Disque | 基于内存的分布式任务队列 | https://github.com/antirez/disque | 8.1K | 536 |
| RediSearch | 基于Redis的全文搜索 | https://github.com/RediSearch/RediSearch | 6K | 567 |
| RedisJSON | JSON支持 | https://github.com/RedisJSON/RedisJSON | 3.9K | 339 |
| neural-redis | 在线可训练神经网络作为 | https://github.com/antirez/neural-redis | 2.2K | 101 |
| RedisGraph | 基于Cypher查询语言和稀疏邻接矩阵的图数据库 | https://github.com/RedisGraph/RedisGraph | 2K | 232 |
| RedisBloom | 可扩展布隆过滤器 | https://github.com/RedisBloom/RedisBloom | 1.8K | 262 |
| rediSQL | A redis module that provides a full SQL capabilities embedding SQLite | https://github.com/RedBeardLab/rediSQL,已归档 | 1.5K | 150 |
| redis-cell | A Redis module that provides rate limiting in Redis as a single command. | https://github.com/brandur/redis-cell | 1.2K | 73 |
| RedisAI | A Redis module for serving tensors and executing deep learning graphs | https://github.com/RedisAI/redis-inference-optimization | 841 | 106 |
| RedisTimeSeries | Time-series data structure for redis | https://github.com/RedisTimeSeries/RedisTimeSeries | 1.1K | 146 |
| cthulhu | Extend Redis with JavaScript modules | https://github.com/sklivvz/cthulhu | 156 | 9 |
| redis-roaring | Uses the CRoaring library to implement roaring bitmap commands for Redis. | https://github.com/aviggiano/redis-roaring | 372 | 58 |
| redis-cuckoofilter | Hashing-function agnostic Cuckoo filters. | https://github.com/kristoff-it/redis-cuckoofilter | 233 | 22 |
| RedisGears | Dynamic execution framework for your Redis data. | https://github.com/RedisGears/RedisGears | 383 | 67 |
| redis-protobuf | 读写Protobuf消息 | https://github.com/sewenew/redis-protobuf | 208 | 23 |
| smartcache | 直通缓存的一种实现 | https://github.com/iazaran/smart-cache | 196 | 7 |
Streams
5.0引入,内存版Kafka,也有Consumer Groups概念。其底层的数据结构是radix tree:
c
typedef struct stream {
rax *rax; /* The radix tree holding the stream. */
uint64_t length; /* Number of elements inside this stream. */
streamID last_id; /* Zero if there are yet no items. */
rax *cgroups; /* Consumer groups dictionary: name -> streamCG */
} stream;
Radix Tree,Radix树,基数树,类似于二叉树。仅仅是在寻找方式上,以一个unsigned int类型数为例,利用这个树的每个比特位作为树节点的推断。能够这样说,比方一个数10001010101010110101010,依照Radix树的插入就是在根节点,假设遇到0,就指向左节点,假设遇到1就指向右节点,在插入过程中构造树节点,在删除过程中删除树节点。
一个保存7个单词的Radix树:

ACL
Access Control List,访问控制列表。Redis 6.0开始支持用户,可给每个用户分配不同的权限。
官方引入此功能特性主要是出于安全考虑,限制对命令和密钥的访问权限:
- 不受信任的客户端无法访问;
- 屏蔽部分命令如
flushall。
早期版本中有auth命令:auth <password>,新版本拓展命令:auth <username> <password>,于是auth <password>相当于使用默认用户default,实现对低版本的兼容。
acl list输出:user default on nopass sanitize-payload ~* &* +@all,解读:
default:默认用户;on:用户启用,设置为off,则用户禁用;nopass:没有密码;~*:表示可访问的Key(正则匹配),*表示所有键;&*:没有pub,sub限制。频道(channel)使用&;allchannels表示可访问所有Pub/Sub频道的权限,resetchannels表示没有任何Pub/Sub频道的访问权限;+@:表示用户权限,+表示授权权限,有权限操作或访问,-表示没有权限;@为权限分类,可通过acl cat查询支持的分类。+@all表示所有权限。
权限对Key的类型和命令的类型进行分类,如对数据类型进行分类:string、hash、list等,对命令类型进行分类:connection、admin、dangerous。
命令acl cat可查看支持的权限分类列表,V7.4版本里总共21个:keyspace、read、write、set、sortedset、list、hash、string、bitmap、hyperloglog、geo、stream、pubsub、admin、fast、slow、blocking、dangerous、connection、transaction、scripting。

以String为例,acl cat string返回指定类别中的命令,String有22个子命令:

RESP
REdis Serialization Protocol,Redis 1.2引入的Client/Server网络通信的序列化文本协议,Redis 2.0演变为RESP2,Redis 6引入RESP3。
RESP能序列化不同的数据类型,请求从客户端以字符串数组的形式发送到Redis服务器,服务端用特定于命令的数据类型回复。RESP是二进制安全的,并且不需要处理从一个进程发到另外一个进程的批量数据,因为它使用前缀长度来传输批量数据。Redis集群使用不同的二进制协议在节点之间交换消息。
相比于通用的序列化协议如BSON、MessagePack、ProtoBuffer,RESP的主要目标:
- 易于实现(Simple to implement)
- 快速解析(Fast to parse)
- 内容可读(Human readable)
RESP2
以\r\n作为分隔符,类型支持:
- 整数:以
:开头,格式为:[<+|->]<value>\r\n,如:1234\r\n表示整数1234。 - 简单(单行)字符串:以
+开头,格式为+Simple String\r\n,如+OK\r\n表示OK。 - 复杂(多行)字符串(Bulk strings):以
$开头,格式为$<length>\r\n<data>\r\n,如$5\r\nhello\r\n表示hello。 - 简单错误(Simple Error):以
-开头,格式为-Error message\r\n。格式类似简单字符串,无法表示带有二进制的错误信息。 - 数组:以
*开头,格式为*<number-of-elements>\r\n<element-1>...<element-n>。数组[1,"foo"]表示为:*2\r\n+1\r\n$3\r\nfoo\r\n - 空(null):在RESP2里面null有两种表示,
$-1\r\n表示空字符串null,*-1\r\n表示数组null。
问题
RESP2存在的问题:
- 错误信息只能是简单文本,无法携带二进制内容。
- null有两种形式,客户端需要按照命令来解释。
- 缺少准确的数据类型,如SET和Map类型导致只能通过数组来表示,客户端解析时需要知道当前是什么命令才能将返回解析成正确的数据类型:
HGETALL命令,则将响应结果解析为MapSMEMBERS命令,则将响应结果解析为SetLRANGE命令,则将响应结果解析为Array
如果能够返回精确的类型,那么客户端在解析的时候则可不需要关心当前命令,根据返回数据类型即可对应进行转换。类似的,目前浮点类型使用字符串表示,而布尔类型整数0|1来表示。
RESP3
RESP3引入以下几个新类型:
- 布尔类型:以
#开头,格式为#<t|f>\r\n,其中t表示true,f表示false - 空(null):以
_开头,格式固定为_\r\n - 浮点数:以
,开头,格式为,[<+|->]<integral>[.<fractional>][<E|e>[sign]<exponent>]\r\n。如,+1.23\r\n表示浮点数1.23。而在RESP2则只能使用字符串$4\r\n1.23\r\n来表示浮点数1.23。 - 大数类型(Big Numbers):以
(开头,格式为([+|-]<number>\r\n。若客户端不支持Big Number类型则应该转换为字符串类型。 - 复杂错误类型(Bulk errors):以
!开头,格式为!<length>\r\n<error>\r\n。形式上和复杂字符串是一致,只是开头字符不一样。 - 字典(Maps):以
%开头,格式为%<number-of-entries>\r\n<key-1><value-1>...<key-n><value-n>。 - 集合(Sets):以
~开头,格式为~<number-of-elements>\r\n<element-1>...<element-n>。 - 推送(Push):以
>开头,格式为><number-of-elements>\r\n<element-1>...<element-n>。用于Redis Server主动推送数据给客户端,Client-side Caching实现需依赖Push协议来将更新数据推送给客户端。 - 逐字字符串(Verbatim strings):以
=开头,格式为=<length>\r\n<encoding>:<data>\r\n,其中encoding使用固定3个字符表示编码方式,紧接着:用来分割编码和内容。纯文本为=15\r\ntxt:Some string\r\n。除纯文本是txt外,mkd表示Markdown字符串,客户端可以据此决定是否进行渲染。
Redis请求/响应
对Redis服务来说,请求统一都使用字符串数组(bulk strings array),第一个参数是命令,其他为命令参数,而整数、错误、简单字符串、null,只会在响应里面使用。以SET hello johnny命令为例,变成Redis请求:
text
*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$6\r\njohnny\r\n
协议解读:
- 第一个字符遇到
*则可知当前是一个数组,解析数组长度直到分隔符,数组长度是3,接着读取数组元素; - 解析数组第一个元素,看到
$则开始进入解析字符串长度直到分隔符,读取到一个元素长度为3,接着读取到内容SET\r\n,长度符合预期,则继续解析下一个元素,否则抛错;以同样方式继续解析数组后面的两个元素; - Redis处理请求,请求如果正确被处理则返回
+OK\r\n,出错-ERR {message}\r\n。
分片Pub/Sub
多部分AOF
RedisFunctions
Redis提供编程接口(programming interface),可在Redis服务器端使用eval命令执行客户Lua脚本。
eval命令的问题:在Redis服务器端执行SCRIPT FLUSH命令,或服务器重启,或主节点执行主备切换,则存在于服务器端的脚本将会丢失,于是客户端的应用程序需要重新将整个脚本再次发送到服务器。
Redis Functions可解决上述问题。
提供如下指令:
- Function Load:加载函数
- FCALL:执行函数
- FCALL_RO:只读模式调用函数(在函数执行时,无法写入Redis数据,但可读取)。分布式Redis针对这种模式会有优化;
- Function DELETE:删除加载的Lua模块(模块维度删除);
- Function DUMP:序列化已加载的函数,返回byte串;
- Function RESTORE:通过byte串恢复加载函数,有3种模式:
- FLUSH:清空已加载的再恢复
- APPEND:新增,同名的不再加载
- REPLACE:新增,并替代同名
- Function FLUSH:清空已加载函数;
- Function KILL:终止正在执行的只读函数;
- Function LIST:返回已加载的模块、函数列表;
- Function STATS:获取正在执行的函数的状态(函数名、参数信息、已经执行多久)。因为有些函数耗时太久,会导致Redis单线程一直卡着。通过查询状态,用户可决策是否通过Function KILL终止它。
示例test.lua脚本:
lua
#!lua name=mylib
redis.register_function('myfunc', function(keys, args) return args[1] end)
执行命令cat test.lua | redis-cli -x function load,或function load "#!lua name=mylib \n redis.register_function('myfunc', function(keys, args) return args[1] end)"。

大小写不敏感。
特性:
- Lua代码需要编译,在Function Load后会自动编译,后续调用时无需再次编译,调用速度更快(相比于eval);
- 支持模块级别的重新加载。参数REPLACE,可用新模块替换同名旧模块,若该模块是第一次加载,则直接加载;
- 内存数据可持久化保存。加载函数后,关闭Redis时,注册的函数也会被持久化到硬盘。重启Redis时自动重新加载之前加载过的函数;
- Redis Function中执行代码是原子操作,执行过程中不会被打断。
向量
有必要单独再写一篇。