Redis 扩展模块
Redis 模块概述
模块化架构
Redis 从 4.0 版本(2017 年发布)开始引入模块系统(Modules API),允许开发者使用 C 语言编写动态链接库(.so 文件)来扩展 Redis 的功能。模块系统以插件形式运行在 Redis 服务器进程中,能够:
- 注册新的命令,与原生命令享有同等性能
- 操作 Redis 底层数据结构(如跳表、压缩列表、字典)
- 订阅和响应键空间通知
- 实现自定义数据类型和持久化逻辑
- 调用 Redis 内置函数的完整 C API
模块系统采用阻塞与非阻塞两种模式。阻塞模块(如 RediSearch 的索引构建)会在后台线程中执行计算密集型任务,避免阻塞主事件循环;非阻塞模块则在主线程中同步执行。Redis 模块 API 提供了 RedisModule_Init、RedisModule_CreateCommand 等核心宏和函数,任何模块的入口函数必须是 RedisModule_Init。
Redis Stack
Redis Stack 是 Redis 官方发布的一体化发行版,将多个官方维护的模块与 Redis 核心捆绑打包。用户无需手动下载和编译模块,只需运行 Redis Stack 镜像即可同时获得以下能力:
| 模块 | 功能领域 | 引入版本 |
|---|---|---|
| RediSearch | 全文搜索与索引 | 社区模块 1.0 / Redis Stack 6.2 |
| RedisJSON | JSON 原生存储 | 社区模块 1.0 / Redis Stack 6.2 |
| RedisBloom | 概率数据结构 | 社区模块 2.0 / Redis Stack 6.2 |
| RedisTimeSeries | 时序数据 | 社区模块 1.0 / Redis Stack 6.2 |
| RedisGraph | 图数据库 | 已停维,逐步移除 |
注意:RedisGraph 已于 2024 年被 Redis 公司宣布停止维护,在最新的 Redis Stack 中不再默认包含。
模块的加载方式
模块可在 Redis 启动时通过配置文件加载,或在运行时通过命令动态加载:
# redis.conf 中配置
loadmodule /path/to/module.so [module_options]
# 运行时加载
127.0.0.1:6379> MODULE LOAD /path/to/redisearch.so
OK
# 查看已加载模块
127.0.0.1:6379> MODULE LIST
1) 1) "name"
2) "search"
3) "ver"
4) (integer) 20816RediSearch
全文索引 vs 传统搜索
传统 Redis 只能通过 KEYS(全量扫描)或 SCAN(游标遍历)来搜索字符串键名,值内容的搜索则完全无法实现。即便使用 SET 配合 SINTER 做标签检索,也无法支持分词、排名、模糊匹配等能力。
RediSearch 是构建在 Redis 之上的二级索引引擎,它维护独立于键空间的倒排索引(Inverted Index)。与 Elasticsearch 等传统搜索系统相比:
| 对比维度 | RediSearch | Elasticsearch |
|---|---|---|
| 数据存储 | 内存 + 可选持久化 | 磁盘(LSM-Tree) |
| 延迟 | 亚毫秒级 | 毫秒级 |
| 索引结构 | 紧凑倒排索引 + 跳表 | 倒排索引 + BKD 树 |
| 集群模式 | Redis 集群分片 | 原生分布式 |
| 部署成本 | 极低(随 Redis 部署) | 较高(独立的 Java 堆栈) |
| 中文分词 | 需内置或加载分词词典 | 内置 ICU/IK 分词器 |
核心原理
RediSearch 的核心数据结构包括:
倒排索引(Inverted Index):记录每个词项(Term)出现在哪些文档中,以及出现的位置(Term Frequency)。搜索时通过词项直接定位文档集合,时间复杂度为 O(1) 到 O(log N)。
跳表(Skip List):用于存储排序字段(Sortable Fields)的索引,支持按数值或时间戳高效排序和范围过滤。
向量索引(Vector Index):从 RediSearch 2.4 开始支持 KNN(K-最近邻)向量搜索,使用 HNSW(Hierarchical Navigable Small World)或 FLAT(暴力扫描)算法,适用于 AI 嵌入向量的语义搜索。
创建索引
使用 FT.CREATE 命令定义索引的 schema:
# 创建全文索引
FT.CREATE idx:articles ON HASH PREFIX 1 "article:"
SCHEMA title TEXT WEIGHT 5.0
body TEXT WEIGHT 1.0
author TAG
created_at NUMERIC SORTABLE
price NUMERIC参数说明:
ON HASH:索引的数据类型(支持 Hash 和 JSON)PREFIX 1 "article:":自动索引所有以article:开头的键TEXT:全文检索类型,支持分词和词频排序TAG:精确匹配类型,不分词NUMERIC:数值类型,支持范围过滤SORTABLE:允许按此字段排序
搜索与聚合
FT.SEARCH — 全文检索:
# 基本搜索
> FT.SEARCH idx:articles "Redis搜索引擎"
1) (integer) 2
2) "article:1001"
3) 1) "title"
2) "Redis搜索引擎实战"
3) "score"
4) "5.0"
# 带过滤条件
> FT.SEARCH idx:articles "搜索引擎" FILTER created_at 20230101 20240101 SORTBY created_at DESC LIMIT 0 10
# 高亮结果
> FT.SEARCH idx:articles "Redis" HIGHLIGHT HIGHLIGHT TAGS <b> </b>FT.AGGREGATE — 聚合管道,类似 SQL 的 GROUP BY:
# 按作者分组统计文章数量
> FT.AGGREGATE idx:articles "*"
GROUPBY 1 @author
REDUCE COUNT 0 AS num_articles
SORTBY 2 @num_articles DESC
LIMIT 0 5
# 结果示例
1) (integer) 3
2) 1) "author"
2) "张三"
3) "num_articles"
4) "42"中文分词
RediSearch 默认使用基于空格和标点的分词器(FULLTEXT),对中文不友好。需要启用 Friso 或 jieba 等中文分词支持:
# 使用中文分词器创建索引
FT.CREATE idx:cn ON HASH PREFIX 1 "doc:"
SCHEMA title TEXT WITHSCORES
LANGUAGE chinese在编译 RediSearch 时指定中文分词支持:
# 编译时启用中文分词
make BUILD_WITH_CHINESE=1加载后,"中文搜索" 会被正确切分为 中文 和 搜索 两个词项。
Spring Data Redis 集成
以下为 Spring Boot 应用中使用 RediSearch 的示例:
// 1. 添加依赖(pom.xml)
// <dependency>
// <groupId>com.redis</groupId>
// <artifactId>redisearch-jdbc</artifactId>
// <version>2.6.0</version>
// </dependency>
// 2. 定义文档实体
@Document(indexName = "idx:articles")
public class Article {
@Id
private String id;
@Searchable(weight = 5.0)
private String title;
@Searchable
private String body;
private String author;
private Long createdAt;
}
// 3. 使用 Repository
@Repository
public interface ArticleRepository extends RedisSearchRepository<Article, String> {
// 自动实现搜索方法
}
// 4. 服务层使用
@Service
public class ArticleService {
@Autowired
private ArticleRepository repository;
public SearchResults<Article> search(String keyword) {
Query query = new Query(keyword)
.setSortBy(SortField.desc("created_at"))
.limit(0, 20);
return repository.search(query);
}
}Spring Boot 配置:
spring:
data:
redis:
host: localhost
port: 6379
# RediSearch 客户端配置
repositories:
enabled: true
redisearch:
index-prefix: "idx:"RedisJSON
JSON 数据原生存储
传统 Redis 存储 JSON 数据时,通常的做法是将整个 JSON 序列化为字符串存入 String 类型。这种方式存在明显缺陷:读取或修改某个字段时必须反序列化整个对象,修改后再序列化写回(即 "read-whole-modify-whole" 模式),当 JSON 对象较大时,网络传输和 CPU 开销都非常高。
RedisJSON 模块实现了 JSON 数据的原生存储,在 Redis 内部维护了一棵 JSON 抽象语法树(AST),每个 JSON 节点在内存中以特定的编码方式存储:
- 字符串 → SDS(Simple Dynamic String)
- 数字 → 64 位整数或双精度浮点数
- 数组 → 压缩数组或快速列表
- 对象 → 哈希表或紧凑字典
这种树状结构允许通过 JSONPath 表达式直接定位和操作任意节点,无需反序列化整个对象。
核心命令
JSON.SET — 写入 JSON 数据:
# 存储一个完整的 JSON 文档
> JSON.SET user:1001 $ '{"name":"张三","age":30,"address":{"city":"北京","district":"海淀"},"skills":["Java","Redis","Python"]}'
OK
# 使用 JSONPath 设置特定字段
> JSON.SET user:1001 $.address.city '"上海"'
OKJSON.GET — 读取数据:
# 读取整个文档
> JSON.GET user:1001
"{\"name\":\"张三\",\"age\":30,\"address\":{\"city\":\"上海\",\"district\":\"海淀\"},\"skills\":[\"Java\",\"Redis\",\"Python\"]}"
# 读取特定字段
> JSON.GET user:1001 $.name $.address.city
"{\"$.name\":[\"张三\"],\"$.address.city\":[\"上海\"]}"
# 读取数组元素
> JSON.GET user:1001 $.skills[0]
"{\"$.skills[0]\":[\"Java\"]}"JSON.ARRAPPEND — 数组操作:
# 向 skills 数组追加元素
> JSON.ARRAPPEND user:1001 $.skills '"Go"' '"Rust"'
(integer) 5
# 查看结果
> JSON.GET user:1001 $.skills
"{\"$.skills\":[\"Java\",\"Redis\",\"Python\",\"Go\",\"Rust\"]}"其他常用命令:
# 删除字段
> JSON.DEL user:1001 $.address.district
(integer) 1
# 数值自增
> JSON.NUMINCRBY user:1001 $.age 1
"31"
# 判断字段是否存在
> JSON.TYPE user:1001 $.name
1) "string"
# 获取数组长度
> JSON.ARRLEN user:1001 $.skills
(integer) 5JSONPath 支持
RedisJSON 2.0+ 支持 JSONPath(RFC 9535 子集),提供了丰富的路径表达式:
| 表达式 | 含义 | 示例 |
|---|---|---|
$ | 根元素 | $ 表示整个文档 |
$.key | 子元素 | $.address.city |
$..key | 递归查找 | $..city 查找所有层级中的 city |
$[0] | 数组索引 | $.skills[0] |
$[*] | 数组全部元素 | $.skills[*] |
$[start:end] | 数组切片 | $.skills[0:2] |
$[?(condition)] | 过滤表达式 | $[?(@.age > 18)] |
# 过滤表达式示例
> JSON.SET users:all $ '[{"name":"Alice","age":25},{"name":"Bob","age":17}]'
OK
> JSON.GET users:all '$[?(@.age >= 18)].name'
"{\"$[?(@.age >= 18)].name\":[\"Alice\"]}"与传统 Hash 对比
| 对比维度 | Hash 存储 | RedisJSON |
|---|---|---|
| 数据结构 | 平坦的键值对 | 嵌套的树形结构 |
| 字段读写 | 仅支持顶层字段 | 支持任意深度路径 |
| 数组操作 | 不支持 | 支持索引/追加/弹出 |
| 序列化开销 | 无(原生类型) | 访问路径节点时才解码 |
| 内存效率 | 较高(扁平结构) | 中等(额外存储路径元信息) |
| 适用场景 | 用户信息、配置项 | 复杂嵌套对象、API 响应缓存 |
选择建议:
- 如果数据是平坦的键值对(如
field:value),优先使用 Hash,内存效率更高 - 如果数据包含嵌套对象或数组,或者需要频繁修改深层字段,使用 RedisJSON
RedisBloom
布隆过滤器原理
布隆过滤器(Bloom Filter) 是一种空间效率极高的概率性数据结构,用于判断一个元素 是否可能存在于集合中。它的核心原理如下:
数据结构
布隆过滤器由一个长度为 m 的 bit 数组(初始全为 0)和 k 个相互独立的哈希函数组成。
添加元素过程
- 对待添加元素应用 k 个哈希函数,得到 k 个哈希值
h1, h2, ..., hk - 将每个哈希值对 m 取模,得到 k 个数组下标
- 将 bit 数组的这 k 个位置全部置为 1
添加 "redis":
hash1("redis") % m = 2 → bit[2] = 1
hash2("redis") % m = 7 → bit[7] = 1
hash3("redis") % m = 15 → bit[15] = 1查询元素过程
- 对目标元素应用同样的 k 个哈希函数
- 检查对应的 k 个 bit 位置是否全部为 1
- 如果任意一个位置为 0 → 元素一定不存在
- 如果全部为 1 → 元素可能存在(存在误判可能)
误判率(False Positive Rate)
误判率公式为:
P ≈ (1 - e^(-kn/m))^k其中:
- n = 已插入元素数量
- m = bit 数组长度
- k = 哈希函数数量
当 k = (m/n) * ln(2) 时误判率最低。例如,当 m/n = 10 时,最佳 k ≈ 7,误判率约 0.8%。
布隆过滤器的特性:
- 不会漏判:如果返回"不存在",则一定不存在
- 可能误判:如果返回"存在",可能有小概率误判
- 无法删除:标准布隆过滤器不能删除元素(每个 bit 可能被多个元素共享)
核心命令
BF.ADD — 添加元素:
# 添加单个元素
> BF.ADD bloom:users "user:1001"
(integer) 1
# 添加多个元素
> BF.MADD bloom:users "user:1002" "user:1003" "user:1004"
1) (integer) 1
2) (integer) 1
3) (integer) 1BF.EXISTS — 检查元素是否存在:
> BF.EXISTS bloom:users "user:1001"
(integer) 1 # 可能存在
> BF.EXISTS bloom:users "user:9999"
(integer) 0 # 一定不存在BF.RESERVE — 显式创建并配置过滤器:
# 创建布隆过滤器,指定容量和误判率
# BF.RESERVE <key> <error_rate> <capacity> [EXPANSION expansion]
> BF.RESERVE bloom:users 0.01 1000000
OK参数说明:
error_rate:期望误判率,例如 0.01 表示 1%capacity:期望存储的元素数量EXPANSION:当容量耗尽时自动扩展的倍数(默认 2)
应用场景
1. 缓存穿透防护
// 缓存穿透:使用布隆过滤器拦截不存在的数据
public class CacheService {
private final RedisTemplate<String, String> redis;
private final BloomFilterHelper bloomFilter;
public String getUser(String userId) {
// 1. 布隆过滤器前置检查
if (!bfExists("bloom:users", userId)) {
return null; // 一定不存在,直接返回
}
// 2. 查询缓存
String user = redis.opsForValue().get("user:" + userId);
if (user != null) return user;
// 3. 查询数据库(此时才有可能真正穿透)
user = db.queryUser(userId);
if (user != null) {
redis.opsForValue().set("user:" + userId, user, 1, TimeUnit.HOURS);
}
return user;
}
private boolean bfExists(String key, String value) {
return (boolean) redis.execute(
(RedisCallback<Boolean>) conn -> conn.execute("BF.EXISTS", key, value)
);
}
}2. 爬虫 URL 去重
# 爬虫启动时初始化
> BF.RESERVE bloom:urls 0.001 10000000
OK
# 每爬取一个 URL 前检查
> BF.EXISTS bloom:urls "https://example.com/page/1"
(integer) 0 # 未爬取,继续
# 爬取后记录
> BF.ADD bloom:urls "https://example.com/page/1"
(integer) 13. 推荐系统已读过滤
# 使用 BF.ADD 记录用户已读内容
> BF.ADD bloom:rec:user1001 "article:5001"
> BF.ADD bloom:rec:user1001 "article:5002"
# 推荐候选内容前过滤
> BF.EXISTS bloom:rec:user1001 "article:5001"
(integer) 1 # 已读,跳过推荐RedisTimeSeries
时序数据模型
RedisTimeSeries 是专为时序数据(Time-Series Data)设计的模块,每条数据记录包含一个时间戳和一个值,并按时间顺序存储。它的数据模型包含以下核心概念:
- 时间序列(Time Series):一个键对应一个时序数据流,包含若干数据点
- 数据点(Sample):由
(timestamp, value)组成,timestamp 为毫秒或微秒精度 - 标签(Labels):键值对形式的元数据,用于跨序列查询和聚合
- 保留策略(Retention Policy):自动淘汰超过指定时间的历史数据
- 压缩策略(Compaction / Downsampling):将细粒度数据聚合为粗粒度数据(如 1 秒 → 1 分钟)
内部存储结构采用紧凑的列式布局,每个时序数据点只占用 16 字节(8 字节时间戳 + 8 字节双精度值),比纯 Redis String 或 Hash 存储节省约 70% 的内存。
核心命令
TS.CREATE — 创建时间序列:
# 创建基本时间序列
> TS.CREATE ts:cpu:server1
OK
# 创建带标签、保留策略和压缩规则的时间序列
> TS.CREATE ts:cpu:server2
RETENTION 86400000 # 保留最近 24 小时的数据(毫秒)
LABELS host "server2" dc "北京"
CHUNK_SIZE 4096 # 每个数据块大小(字节)
DUPLICATE_POLICY LAST # 重复时间戳的处理策略:保留最后一个值
OKTS.ADD — 添加数据点:
# 自动使用当前时间戳
> TS.ADD ts:cpu:server1 42.5
(integer) 1650000000000
# 指定时间戳
> TS.ADD ts:cpu:server1 * 43.2
(integer) 1650000001000
# 添加数据时自动创建序列(AUTO-CREATE)
> TS.ADD ts:mem:server1 * 8192 RETENTION 3600000 LABELS type "memory" host "server1"TS.RANGE — 范围查询:
# 查询最近 1 小时的数据
> TS.RANGE ts:cpu:server1 1650000000000 1650003600000
1) 1) (integer) 1650000000000
2) 42.5
2) 1) (integer) 1650000001000
2) 43.2
# 带聚合的查询(按 1 分钟窗口求平均值)
> TS.RANGE ts:cpu:server1 1650000000000 1650003600000
AGGREGATION avg 60000
1) 1) (integer) 1650000000000
2) 42.85
2) 1) (integer) 1650000060000
2) 43.10TS.MRANGE — 多序列范围查询(按标签过滤):
# 查询所有 host=server1 的时序序列在指定范围内的平均值
> TS.MRANGE 1650000000000 1650003600000
FILTER host="server1"
AGGREGATION avg 60000
WITHLABELS压缩策略(Downsampling / Compaction)
压缩规则通过 TS.CREATE 的 RETENTION 配合规则链实现,或者使用 TS.CREATERULE 创建独立的压缩序列:
# 1. 创建原始精度序列(1 秒级)
> TS.CREATE ts:sensor:raw LABELS type "temperature" sensor "t1"
OK
# 2. 创建压缩目标序列(1 分钟级)
> TS.CREATE ts:sensor:1min LABELS type "temperature_avg" sensor "t1"
OK
# 3. 创建压缩规则:每 60 秒触发一次,计算平均值
> TS.CREATERULE ts:sensor:raw ts:sensor:1min AGGREGATION avg 60000
OK
# 4. 也可以创建多级压缩(1 小时级)
> TS.CREATE ts:sensor:1hour LABELS type "temperature_hourly" sensor "t1"
> TS.CREATERULE ts:sensor:1min ts:sensor:1hour AGGREGATION avg 3600000
OK支持的聚合函数:
| 聚合函数 | 含义 |
|---|---|
avg | 平均值 |
sum | 总和 |
min | 最小值 |
max | 最大值 |
range | 值域(最大值 - 最小值) |
count | 数据点数量 |
first | 窗口内第一个值 |
last | 窗口内最后一个值 |
std.p | 总体标准差 |
var.p | 总体方差 |
twa | 时间加权平均值 |
保留策略(Retention Policy)
保留策略在创建序列时通过 RETENTION 参数指定,单位为毫秒:
# 保留最近 7 天
> TS.CREATE ts:metrics:7d RETENTION 604800000
# 修改已存在序列的保留策略
> TS.ALTER ts:metrics:7d RETENTION 1209600000 # 改为 14 天
# 不设保留限制(永久存储)
> TS.CREATE ts:metrics:forever RETENTION 0当超出保留期限时,RedisTimeSeries 会在后台异步淘汰过期数据块,不会阻塞主线程。
应用示例:系统监控
# 采集 CPU 使用率(每 10 秒写入)
> TS.ADD ts:cpu:host1 * 45.2 LABELS type "cpu" host "host1"
> TS.ADD ts:cpu:host2 * 67.8 LABELS type "cpu" host "host2"
# 采集内存使用
> TS.ADD ts:mem:host1 * 15678 LABELS type "memory" host "host1"
# 查询过去 1 小时内所有主机的 CPU 均值(5 分钟粒度)
> TS.MRANGE -1h +
FILTER type="cpu"
AGGREGATION avg 300000
WITHLABELS
# 计算过去 24 小时的 P99 延迟
> TS.RANGE ts:latency:api * *
AGGREGATION max 86400000RedisGraph
⚠️ 注意:RedisGraph 已于 2024 年被 Redis 公司宣布停止维护,不再包含在最新的 Redis Stack 中。建议新项目考虑使用 Neo4j、TigerGraph 等替代方案。
图数据库模型
RedisGraph 是一个基于 属性图模型(Property Graph Model) 的内存图数据库。它使用 邻接矩阵(Adjacency Matrix) 作为底层存储结构,并利用 稀疏矩阵乘法 来执行图遍历和查询操作。
核心概念:
- 节点(Node):表示实体,每个节点可以有若干属性(键值对)和标签(Label)
- 关系(Relationship):表示节点之间的连接,每个关系有方向、类型和属性
- 属性(Property):节点或关系的键值对属性
与传统的关系型数据库相比,RedisGraph 在社交网络、推荐系统、路径查找等场景下,查询性能可以快数个数量级——尤其是在多跳遍历场景中,因为邻接矩阵乘法比关系表的 JOIN 操作更高效。
Cypher 查询语言
RedisGraph 使用 Cypher(OpenCypher 实现)作为查询语言。Cypher 是一种声明式的图查询语言,最初由 Neo4j 设计。
基本 CRUD 操作:
# 创建节点
> GRAPH.QUERY social "CREATE (:Person {name:'张三', age:30})"
1) (empty array)
2) 1) "Labels added: 1"
2) "Nodes created: 1"
3) "Properties set: 2"
4) "Query internal execution time: 0.384000 ms"
# 创建带关系的节点
> GRAPH.QUERY social "
CREATE (a:Person {name:'李四', age:25}),
(b:Person {name:'王五', age:28}),
(a)-[:FRIENDS_WITH {since:2020}]->(b)"查询操作:
# 查找张三的朋友
> GRAPH.QUERY social "
MATCH (a:Person {name:'张三'})-[:FRIENDS_WITH]->(friend)
RETURN friend.name, friend.age"
1) 1) "friend.name"
2) "friend.age"
2) 1) 1) "李四"
2) "25"
2) 1) "王五"
2) "28"
# 查找朋友的朋友(2 度关系)
> GRAPH.QUERY social "
MATCH (a:Person {name:'张三'})-[:FRIENDS_WITH*2]->(fof)
RETURN DISTINCT fof.name"
# 路径查询
> GRAPH.QUERY social "
MATCH path = shortestPath(
(a:Person {name:'张三'})-[:FRIENDS_WITH*]->(b:Person {name:'赵六'})
)
RETURN [node IN nodes(path) | node.name] AS names,
[rel IN relationships(path) | rel.since] AS years"聚合与过滤:
# 按年龄分组统计好友数量
> GRAPH.QUERY social "
MATCH (a:Person)-[:FRIENDS_WITH]->(b:Person)
RETURN a.name, count(b) AS friend_count
ORDER BY friend_count DESC
LIMIT 10"
# 查找年龄大于 20 且住在北京的人
> GRAPH.QUERY social "
MATCH (p:Person)
WHERE p.age > 20 AND p.city = '北京'
RETURN p.name, p.age
ORDER BY p.age"邻接矩阵原理
RedisGraph 在底层将每个标签(Label)和关系类型映射为稀疏矩阵。例如,在社交网络图中:
- 矩阵 A:表示所有 Person 节点(行/列索引为节点 ID)
- 矩阵 F:表示 FRIENDS_WITH 关系,
F[i][j] = 1表示节点 i 到 j 存在朋友关系 - 查询
(a)-[:FRIENDS_WITH]->(b)等价于稀疏矩阵乘法v × F,其中 v 是起始节点的向量表示
这种基于矩阵的表示使得图遍历操作可以充分利用现代 CPU 的 SIMD 指令和 GPU 加速。
RedisGears
函数式数据处理与触发
RedisGears 是一个面向 Redis 的函数式数据处理引擎,它允许用户编写 Python(或 C)函数,将函数自动部署到 Redis 集群中的所有分片,并对数据进行实时处理和响应。RedisGears 的核心理念是 "Move the computation to the data"(将计算移至数据所在处),避免在集群环境中大量搬运数据。
编程模型
RedisGears 提供了四种执行模式:
| 模式 | 描述 | 类比 |
|---|---|---|
| 原子执行 | 在单分片上运行,保证原子性 | 类似 Lua 脚本 |
| 集群广播 | 函数在所有分片上执行,结果合并 | MapReduce |
| 键空间触发 | 监听键事件(SET/EXPIRE/DEL 等)自动触发 | 触发器 |
| 流式处理 | 持续处理 Redis Stream 中的数据 | 流计算 |
核心示例
基本操作(Python API):
from redisgears import execute, register
# 1. 原子执行:读写数据
def hello():
execute("SET", "greeting", "Hello from RedisGears!")
return execute("GET", "greeting")
result = execute("RG.PYEXECUTE", hello)
print(result) # "Hello from RedisGears!"
# 2. 过滤与映射
def process_users():
keys = execute("KEYS", "user:*")
result = []
for key in keys:
val = execute("HGETALL", key)
if val.get("age") and int(val["age"]) > 18:
result.append(key)
return result
execute("RG.PYEXECUTE", process_users)键空间触发(Key-Space Notification):
# 监听所有以 "order:" 开头的键,当它们过期时触发回调
def order_expired_callback(x):
order_id = execute("GET", f"notification:{x['key']}")
execute("XADD", "stream:expired_orders", "*",
"order_id", order_id,
"expired_at", str(time.time()))
# 注册触发器
register(prefix="order:", event="expired", mode="sync")(order_expired_callback)流式处理(Stream Processing):
# 持续处理订单流,每批最多处理 100 条
def process_orders(batch):
pipeline = execute("pipeline_open")
for record in batch:
order_id = record["value"]["order_id"]
amount = float(record["value"]["amount"])
if amount > 1000:
pipeline.execute("XADD", "stream:high_value_orders", "*",
"order_id", order_id)
pipeline.execute("pipeline_break")
register(
prefix="stream:orders",
reader="StreamReader",
batch_size=100,
duration=1000,
onFailedPolicy="retry"
)(process_orders)与 Redis Stream 结合
RedisGears 与 Redis Stream 深度集成,可以实现:
- 事件驱动的 ETL 管道:监听 Stream,经过转换后写入另一个 Stream 或数据库
- 实时分析:对 Stream 中的数据进行窗口聚合(滑动窗口、跳跃窗口)
- 数据同步:将 Redis 中的数据变更实时同步到外部系统(如 Elasticsearch、MySQL)
from redisgears import execute, register
import json
# 实时数据同步到 Elasticsearch
def sync_to_es(records):
for r in records:
data = r["value"]
# 调用外部 HTTP API 同步
execute("XADD", "stream:sync_log", "*",
"target", "elasticsearch",
"payload", json.dumps(data))
register(
prefix="stream:app_events",
reader="StreamReader",
batch_size=50,
duration=500,
onFailedPolicy="discard"
)(sync_to_es)注意:RedisGears 的 Python 环境需要额外安装 Jython 解释器或 GraalVM,在 Redis Stack 中已默认包含。RedisGears 的未来版本(2.0+)正在从 Python 向 Rust 迁移,以提升性能和安全性。
模块部署
Docker 部署 Redis Stack
使用 Docker 是部署 Redis Stack 最便捷的方式:
# 拉取 Redis Stack 镜像(包含所有官方模块)
docker pull redis/redis-stack:latest
# 启动 Redis Stack 服务器
docker run -d \
--name redis-stack \
-p 6379:6379 \
-p 8001:8001 \
-v /data/redis:/data \
redis/redis-stack:latest6379:Redis 标准端口8001:RedisInsight Web 管理界面端口/data:数据持久化目录
自定义 Docker Compose 配置:
version: "3.8"
services:
redis-stack:
image: redis/redis-stack:latest
container_name: redis-stack
ports:
- "6379:6379" # Redis
- "8001:8001" # RedisInsight
volumes:
- ./redis-data:/data
- ./redis.conf:/redis-stack.conf
environment:
- REDIS_ARGS="--requirepass mypassword"
restart: unless-stopped编译加载模块
如果使用自定义 Redis 服务器,可以单独编译和加载模块:
# 1. 编译 RediSearch
git clone https://github.com/RediSearch/RediSearch.git
cd RediSearch
make BUILD_WITH_CHINESE=1
# 生成 bin/linux-x64-release/search/redisearch.so
# 2. 编译 RedisJSON
git clone https://github.com/RedisJSON/RedisJSON.git
cd RedisJSON
make
# 生成 bin/linux-x64-release/rejson.so
# 3. 编译 RedisBloom
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
make
# 生成 redisbloom.so配置文件自动加载:
# /etc/redis/redis.conf
loadmodule /opt/redis-modules/redisearch.so MAXSEARCHRESULTS 10000
loadmodule /opt/redis-modules/rejson.so
loadmodule /opt/redis-modules/redisbloom.so
loadmodule /opt/redis-modules/redistimeseries.so RETENTION_POLICY 86400000运行时动态加载:
# 连接到 Redis 后执行
127.0.0.1:6379> MODULE LOAD /opt/redis-modules/redisearch.so MAXSEARCHRESULTS 10000
OK
# 查看已加载模块
127.0.0.1:6379> MODULE LIST
# 卸载模块
127.0.0.1:6379> MODULE UNLOAD search
OK生产注意事项
1. 内存管理
模块会显著增加 Redis 的内存消耗,需要合理估算和限制:
# 设置最大内存
maxmemory 8gb
maxmemory-policy allkeys-lru- RediSearch 的倒排索引可能占用大量内存,建议为索引数据单独部署实例
- RedisTimeSeries 的保留策略和压缩规则应尽早规划,避免无限增长
- RedisBloom 的容量(capacity)需根据数据量提前设置,过度扩展会影响误判率
2. 持久化
模块数据是否支持持久化取决于模块实现:
| 模块 | RDB 支持 | AOF 支持 | 集群支持 |
|---|---|---|---|
| RediSearch | ✅ | ✅ | ✅ |
| RedisJSON | ✅ | ✅ | ✅(2.0+) |
| RedisBloom | ✅ | ✅ | ✅ |
| RedisTimeSeries | ✅ | ✅ | ✅ |
| RedisGraph | ✅ | ✅ | ❌(仅单机) |
注意:使用 AOF 时,模块命令会被逐条记录。加载模块后如果 AOF 文件中包含模块命令,重启时必须在加载 AOF 前先加载模块,否则 Redis 会因无法识别模块命令而启动失败。
3. 高可用与集群
- Redis Sentinel:模块数据在故障转移后正常可用,前提是各节点加载了相同的模块
- Redis Cluster:大部分模块支持集群模式,但需要注意:
- RediSearch 的索引数据按
HASH TAG确定分片位置,跨分片聚合查询会产生额外网络开销 - RedisGraph 不支持集群模式,只能在单机上运行
- 集群中所有节点必须加载相同版本的模块
- RediSearch 的索引数据按
4. 安全配置
# redis.conf 安全配置
rename-command MODULE "" # 禁用运行时 MODULE 命令(防止恶意加载)
# 或限制命令
rename-command MODULE LOAD "" # 仅禁用 MODULE LOAD
rename-command MODULE UNLOAD "" # 仅禁用 MODULE UNLOAD
# 设置访问密码
requirepass your-strong-password
# ACL 控制(Redis 6.0+)
acl setuser admin on >admin-password ~* &* +@all
acl setuser readonly on >read-password ~* &* -@all +@read +@search5. 版本兼容性
生产环境升级 Redis 版本时,必须同步检查模块的兼容性:
- Redis 7.x 对应 RediSearch 2.8+、RedisJSON 2.6+、RedisBloom 2.6+、RedisTimeSeries 1.10+
- Redis 6.x 对应 RediSearch 2.0–2.6、RedisJSON 2.0–2.4、RedisBloom 2.2–2.4、RedisTimeSeries 1.4–1.8
- 不同版本的模块不能混用,否则可能导致数据格式不兼容
最佳实践是在独立的测试环境中验证模块与 Redis 版本的兼容性后再上线。