Redis基数树:rax前缀树如何提升键值存储效率

Redis基数树:rax前缀树如何提升键值存储效率

【免费下载链接】redis Redis 是一个高性能的键值对数据库,通常用作数据库、缓存和消息代理。* 缓存数据,减轻数据库压力;会话存储;发布订阅模式。* 特点:支持多种数据结构,如字符串、列表、集合、散列、有序集等;支持持久化存储;基于内存,性能高。 【免费下载链接】redis 项目地址: https://gitcode.com/GitHub_Trending/re/redis

为什么Redis需要特殊的数据结构?

当你在Redis中执行KEYS user:*命令时,是否好奇它如何高效地查找所有匹配前缀的键?这背后正是Redis基数树(Radix Tree)——一种被称为rax的特殊前缀树结构在发挥作用。不同于普通树结构,基数树特别适合处理大量字符串键的存储与检索,这也是Redis能在百万级键值对中快速定位数据的关键原因之一。

什么是rax基数树?

rax(Redis Radix Tree)是Redis自主实现的压缩前缀树,它结合了前缀树(Trie)和基数树的优点,通过路径压缩技术减少内存占用,同时保持高效的查找性能。在src/rax.h中定义了其核心结构:

typedef struct raxNode {
    uint32_t iskey:1;     /* 节点是否包含键 */
    uint32_t isnull:1;    /* 值是否为NULL */
    uint32_t iscompr:1;   /* 节点是否被压缩 */
    uint32_t size:29;     /* 子节点数量或压缩字符串长度 */
    unsigned char data[]; /* 柔性数组存储字符和子节点指针 */
} raxNode;

typedef struct rax {
    raxNode *head;        /* 根节点 */
    uint64_t numele;      /* 元素总数 */
    uint64_t numnodes;    /* 节点总数 */
    void *metadata[];     /* 元数据 */
} rax;

核心特性:压缩与高效并存

1. 路径压缩技术

普通前缀树在存储类似"user:1000"、"user:1001"这样的键时会产生大量冗余节点。rax通过压缩连续单分支路径解决这一问题。例如,存储"foo"、"foobar"和"footer"时,rax会将公共前缀"foo"压缩为单个节点:

["foo"] ""
   |
[t   b] "foo"
 /     \
"er"    "ar"
 |       |
[]      []
footer  foobar

这种结构在src/rax.h的注释中有详细说明,通过iscompr标记和size字段控制压缩状态。

2. 混合节点类型

rax节点分为两种类型:

  • 压缩节点iscompr=1):存储连续字符序列,如上述示例中的"foo"
  • 非压缩节点iscompr=0):存储分支字符集合,如上述示例中的[t,b]

这种混合结构在src/rax.craxLowWalk函数中得到体现,通过不同的遍历逻辑处理两种节点:

if (h->iscompr) {
    // 处理压缩节点:遍历连续字符
    for (j = 0; j < h->size && i < len; j++, i++) {
        if (v[j] != s[i]) break;
    }
} else {
    // 处理非压缩节点:查找分支字符
    for (j = 0; j < h->size; j++) {
        if (v[j] == s[i]) break;
    }
}

实战应用:Redis中的rax场景

1. 键空间遍历

当执行KEYS patternSCAN命令时,Redis使用rax的迭代器功能。在src/defrag.c中可以看到遍历实现:

raxIterator ri;
raxStart(&ri, s->rax);
raxSeek(&ri, "^", NULL, 0);
while (raxNext(&ri)) {
    // 处理每个键值对
    raxSetData(ri.node, ri.data = newdata);
}
raxStop(&ri);

2. 流数据结构(Stream)

Redis Stream使用rax存储消费者组信息。在src/defrag.c中,defragStreamConsumerGroup函数展示了如何操作流中的rax结构:

void* defragStreamConsumerGroup(raxIterator *ri, void *privdata) {
    streamCG *cg = ri->data;
    // 递归处理消费者组的pel(挂起条目列表)
    defragRadixTree(&cg->pel, 1, defragStreamConsumerPendingEntry, NULL);
    return cg;
}

3. 模块系统支持

Redis模块可以通过API使用rax结构。src/module.c中的RedisModule_Dict实现就基于rax:

d->rax = raxNew();  // 创建新的rax
raxInsert(d->rax, key, keylen, ptr, NULL);  // 插入元素
raxFind(d->rax, key, keylen, &res);  // 查找元素

性能对比:为什么选择rax而非其他结构?

数据结构查找复杂度内存效率前缀匹配Redis应用场景
哈希表O(1)低(无压缩)不支持主键空间
普通前缀树O(k)低(冗余节点)支持-
rax基数树O(k)高(路径压缩)支持键扫描、Stream、模块

(k为键的长度)

源码探索:核心操作实现

1. 插入操作

raxInsert函数(src/rax.c)处理节点分裂与压缩逻辑:

int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) {
    size_t i = raxLowWalk(rax, s, len, &h, &parentlink, &j, NULL);
    // 处理分裂压缩节点的复杂逻辑
    // ...
    rax->numele++;
    return 1;
}

2. 迭代器实现

rax提供了安全迭代器,支持在遍历过程中修改树结构。src/rax.h定义了迭代器结构:

typedef struct raxIterator {
    int flags;          /* 迭代器状态标志 */
    rax *rt;            /* 关联的rax树 */
    unsigned char *key; /* 当前键 */
    void *data;         /* 当前数据 */
    // ... 其他字段
} raxIterator;

使用示例:

raxIterator iter;
raxStart(&iter, rax);          // 初始化迭代器
raxSeek(&iter, "^", NULL, 0);  // 定位到起始位置
while (raxNext(&iter)) {       // 遍历所有元素
    printf("key: %.*s\n", (int)iter.key_len, iter.key);
}
raxStop(&iter);                // 释放迭代器

总结:rax如何赋能Redis

rax基数树通过路径压缩混合节点类型高效迭代三大特性,为Redis提供了兼顾内存效率和查询性能的数据结构。它不仅优化了KEYSSCAN等命令的实现,还为Stream、模块系统等高级特性提供了底层支持。

要深入了解rax的实现细节,可以阅读以下源码文件:

rax的设计展示了Redis在追求极致性能道路上的匠心独运,也为我们在处理字符串集合问题时提供了优秀的参考范例。

【免费下载链接】redis Redis 是一个高性能的键值对数据库,通常用作数据库、缓存和消息代理。* 缓存数据,减轻数据库压力;会话存储;发布订阅模式。* 特点:支持多种数据结构,如字符串、列表、集合、散列、有序集等;支持持久化存储;基于内存,性能高。 【免费下载链接】redis 项目地址: https://gitcode.com/GitHub_Trending/re/redis

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值