Redis 数据结构底层实现:intset、ziplist、skiplist 深度剖析

作者按:本文基于 Redis 7.2 源码进行深度剖析,结合源码实例和实战代码,带您深入理解 Redis 核心数据结构的底层实现原理。

目录

前言:为什么需要理解 Redis 底层数据结构

Redis 之所以快,不仅因为它是内存数据库,更因为它对数据结构的极致优化。Redis 的作者 antirez 在设计时,针对不同场景选择了最合适的数据结构:

  • intset:当集合只包含整数且数量不多时,使用紧凑的整数数组
  • ziplist:当列表/哈希的元素较少且较小时,使用连续内存块
  • skiplist:在有序集合中提供 O(log n) 的查找性能

这些优化让 Redis 在内存占用和 CPU 性能之间找到了最佳平衡点。本文将深入源码,解析这些数据结构的实现细节。

一、intset:整数集合的紧凑实现

1.1 intset 概述

intset(integer set)是 Redis 用于存储整数值的集合抽象数据结构。它是 Redis 中 SET 数据类型的底层实现之一(当集合满足以下条件时):

  • 集合中所有元素都是整数值
  • 集合中元素数量不超过 512 个(默认配置)
// Redis 7.2 源码:src/intset.h
typedef struct intset {
    uint32_t encoding;  // 编码方式:int16_t/int32_t/int64_t
    uint32_t length;    // 元素个数
    int8_t contents[];  // 柔性数组,实际存储元素
} intset;

1.2 intset 的编码升级机制

intset 最大的特点是支持自动升级。当新元素的值超出当前编码范围时,intset 会自动升级到更大的编码类型。

graph TD
    A[插入新元素] --> B{新元素值是否超出当前编码范围?}
    B -->|否| C[直接插入]
    B -->|是| D[升级编码类型]
    D --> E[重新分配内存]
    E --> F[将旧数据转换为新编码]
    F --> G[插入新元素]
    
    style D fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#bbf,stroke:#333,stroke-width:2px

升级过程源码分析(Redis 7.2):

// src/intset.c - intsetUpgradeAndAdd 函数
static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
    uint8_t curenc = intrev32(if(is->encoding));  // 当前编码
    uint8_t newenc = _intsetValueEncoding(value);  // 新编码
    int length = intrev32(if(is->length));
    int prepend = value < 0 ? 1 : 0;  // 是否需要向前插入(负数情况)
    
    // 重新分配内存空间
    is->encoding = intrev32(newenc);
    is = intsetResize(is, intrev32(if(is->length)) + 1);
    
    // 从后向前迁移数据,防止覆盖
    while(length--)
        _intsetSet(is, length + prepend, 
                   _intsetGetEncoded(is, length, curenc));
    
    // 插入新元素
    if (prepend)
        _intsetSet(is, 0, value);
    else
        _intsetSet(is, intrev32(if(is->length)), value);
    
    is->length = intrev32(if(is->length) + 1);
    return is;
}

1.3 intset 的查找与插入

intset 使用二分查找来定位元素位置,保证 O(log n) 的查找复杂度。

// src/intset.c - intsetFind 函数
uint8_t intsetFind(intset *is, int64_t value) {
    uint8_t valenc = _intsetValueEncoding(value);
    // 编码不匹配直接返回 false
    if (valenc > intrev32(if(is->encoding))) return 0;
    
    // 二分查找
    return intsetSearch(is, value, NULL);
}

static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
    int min = 0, max = intrev32(if(is->length)) - 1;
    int64_t cur;
    
    // 空集合处理
    if (intrev32(if(is->length)) == 0) {
        if (pos) *pos = 0;
        return 0;
    }
    
    // 边界检查
    if (value > _intsetGet(is, max)) {
        if (pos) *pos = intrev32(if(is->length));
        return 0;
    }
    if (value < _intsetGet(is, 0)) {
        if (pos) *pos = 0;
        return 0;
    }
    
    // 二分查找主循环
    while(max >= min) {
        int mid = (min + max) >> 1;  // 无符号右移
        cur = _intsetGet(is, mid);
        
        if (value > cur) {
            min = mid + 1;
        } else if (value < cur) {
            max = mid - 1;
        } else {
            break;  // 找到目标值
        }
    }
    
    if (pos) *pos = min;
    return value == cur;
}

1.4 intset 的内存布局

下表展示了不同编码下 intset 的内存占用:

编码类型单个元素大小存储范围典型应用场景
INTSET_ENC_INT162 bytes-32,768 ~ 32,767小范围整数集合
INTSET_ENC_INT324 bytes-2^31 ~ 2^31-1中等范围整数集合
INTSET_ENC_INT648 bytes-2^63 ~ 2^63-1大范围整数集合

示例代码:创建并操作 intset

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

// 模拟 intset 结构
typedef struct {
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
} intset;

int main() {
    // 创建一个 intset: [1, 2, 3]
    // 初始编码为 INT16 (假设)
    size_t size = sizeof(uint32_t) * 2 + sizeof(int16_t) * 3;
    intset *is = malloc(size);
    is->encoding = 2;  // INT16
    is->length = 3;
    
    // 设置元素
    int16_t *ptr = (int16_t *)is->contents;
    ptr[0] = 1;
    ptr[1] = 2;
    ptr[2] = 3;
    
    printf("intset created: [%d, %d, %d]\n", ptr[0], ptr[1], ptr[2]);
    
    free(is);
    return 0;
}

二、ziplist:紧凑列表的极致压缩

2.1 ziplist 概述

ziplist(zippered list)是 Redis 为了节约内存而开发的顺序存储结构。它将所有数据紧凑地存储在一块连续内存中,避免了指针开销。

应用场景: - LIST 类型:当列表元素数量 < 512 且所有元素长度 < 64 字节 - HASH 类型:当哈希字段数量 < 512 且所有键值长度 < 64 字节

// Redis 7.2 源码:src/ziplist.h (简化版)
typedef struct {
    uint32_t bytes;      // ziplist 占用总字节数
    uint32_t tail_offset; // 尾部节点到起始位置的偏移量
    uint16_t length;     // 节点数量(当超过 2^14-1 时值为 2^14)
    uint8_t tail;        // 实际存储节点
} ziplist;

2.2 ziplist 的内存布局

ziplist 的内存结构如下:

graph LR
    A[zlbytes<br/>4字节] --> B[zltail<br/>4字节]
    B --> C[zllen<br/>2字节]
    C --> D[entry1]
    D --> E[entry2]
    E --> F[...]
    F --> G[entryN]
    G --> H[zlend<br/>1字节<br/>值为255]
    
    style A fill:#f96,stroke:#333
    style B fill:#6f9,stroke:#333
    style C fill:#69f,stroke:#333
    style H fill:#f66,stroke:#333

字段说明

字段大小说明
zlbytes4 bytes整个 ziplist 占用内存数
zltail4 bytes最后一个节点到起始位置的偏移(用于快速遍历)
zllen2 bytes节点数量(最大 2^14-1,超过需遍历计算)
entry变长具体节点数据
zlend1 byte结束标记,固定值 255

2.3 entry 节点结构

每个 entry 节点由三部分组成:

+----------------+---------------+----------+
| previous_entry | encoding/len  | content  |
| length (变长)   | (变长)        | (变长)   |
+----------------+---------------+----------+

previous_entry_length

  • 前一个节点的长度,用于从后向前遍历
  • 如果前一个节点长度 < 254 字节:用 1 byte 存储
  • 如果前一个节点长度 >= 254 字节:用 5 byte 存储(第一个 byte 为 254,后 4 byte 为实际长度)

encoding:编码和长度信息

| 00pppppp | - 1 byte,字符串,长度 <= 63 字节
| 01pppppp | qqqqqqqq - 2 byte,字符串,长度 <= 16383 字节
| 10______ | qqqqqqqq rrrrrrrr ... - 5 byte,字符串,长度 >= 16384 字节
| 11______ | - 整数编码
  | 11000000 | - int16_t
  | 11010000 | - int32_t
  | 11100000 | - int64_t
  | 11110000 | - 24 bit 有符号整数
  | 11111110 | - 8 bit 有符号整数
  | 1111xxxx | - 4 bit 无符号整数 (0-12)

源码示例(Redis 7.2):

// src/ziplist.c - ziplistEncodeLength 函数
unsigned int ziplistEncodeLength(unsigned char *p, unsigned int len) {
    if (len < 254) {
        p[0] = len;
        return 1;
    } else {
        p[0] = 254;
        memwrite(p+1, len, 4);  // 小端序存储
        return 5;
    }
}

// src/ziplist.c - zipEntry 函数
#define ZIP_ENTRY_PTR(zl, offset) ((zl) + (offset))

void zipEntry(unsigned char *p, zlentry *e) {
    ZIP_DECODE_PREVLEN(p, e->prevrawlensize, e->prevrawlen);
    ZIP_DECODE_LENGTH(p + e->prevrawlensize, e->encoding, e->lensize, e->len);
    e->headersize = e->prevrawlensize + e->lensize;
    e->p = p;
}

2.4 ziplist 的连锁更新问题

ziplist 最大的性能隐患是连锁更新(Cascade Update)。

场景描述: 1. 节点 e1 的长度在 250-253 字节之间(prevlen 用 1 byte 存储) 2. 在 e1 前面插入一个长度 >= 254 字节的新节点 e_new 3. e1 的 prevlen 需要扩展为 5 byte,导致 e1 的总长度增加 4. e2 的 prevlen 也需要扩展…以此类推

graph TD
    A[插入大节点 e_new] --> B[e1 的 prevlen: 1→5 bytes]
    B --> C[e1 长度增加导致 e2 的 prevlen 扩展]
    C --> D[e2 长度增加导致 e3 的 prevlen 扩展]
    D --> E[连锁更新直到列表尾部]
    
    style B fill:#f99,stroke:#333,stroke-width:2px
    style C fill:#f99,stroke:#333,stroke-width:2px
    style D fill:#f99,stroke:#333,stroke-width:2px

最坏情况时间复杂度:O(n^2)

Redis 的优化策略: - 设置阈值:list-max-ziplist-size 和 hash-max-ziplist-entries - 当触发连锁更新且达到阈值时,转换为 linkedlist 或 hashtable

2.5 ziplist 的优缺点对比

特性ziplistlinkedlist/hashtable
内存占用极低(无指针开销)较高(每个节点有指针)
插入/删除O(n)(需要移动数据)O(1)(已知位置时)
查找O(n)(顺序查找)O(1)(哈希表)
连锁更新风险
适用场景小数据量、短元素大数据量或长元素

三、skiplist:跳跃表的平衡艺术

3.1 skiplist 概述

skiplist(跳跃表)是一种随机化的数据结构,由 William Pugh 在 1990 年提出。它在 Redis 中是 ZSET(有序集合)的底层实现之一(当有序集合元素数量 >= 128 或任一元素长度 >= 64 字节时)。

为什么选择 skiplist 而不是平衡树?

  1. 实现简单:代码量远小于红黑树
  2. 范围查询高效:天然支持范围操作
  3. 内存占用低:不需要存储颜色、父指针等额外信息
  4. 并发友好:无锁并发实现更容易
// Redis 7.2 源码:src/server.h (简化版)
typedef struct zskiplistNode {
    sds ele;                      // 成员对象
    double score;                 // 分值
    struct zskiplistNode *backward;  // 后退指针
    struct zskiplistLevel {
        struct zskiplistNode *forward;  // 前进指针
        unsigned long span;             // 跨度
    } level[];                  // 层
} zskiplistNode;

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;  // 头尾节点
    unsigned long length;                 // 节点数量
    int level;                            // 最大层数
} zskiplist;

3.2 skiplist 的结构可视化

graph TD
    subgraph "Level 3"
    H3[Header] --> NULL3[NULL]
    end
    
    subgraph "Level 2"
    H2[Header] --> N2_1[Node1<br/>score:1.0]
    N2_1 --> N2_3[Node3<br/>score:3.0]
    N2_3 --> NULL2[NULL]
    end
    
    subgraph "Level 1"
    H1[Header] --> N1_1[Node1<br/>score:1.0]
    N1_1 --> N1_2[Node2<br/>score:2.0]
    N1_2 --> N1_3[Node3<br/>score:3.0]
    N1_3 --> N1_4[Node4<br/>score:4.0]
    N1_4 --> NULL1[NULL]
    end
    
    subgraph "Level 0 (最底层)"
    H0[Header] --> N0_1[Node1<br/>score:1.0]
    N0_1 --> N0_2[Node2<br/>score:2.0]
    N0_2 --> N0_3[Node3<br/>score:3.0]
    N0_3 --> N0_4[Node4<br/>score:4.0]
    N0_4 --> N0_5[Node5<br/>score:5.0]
    N0_5 --> NULL0[NULL]
    end

3.3 skiplist 的插入算法

插入过程包括三个步骤: 1. 查找插入位置:从最高层开始向下查找 2. 随机确定层数:使用概率算法决定新节点的层数 3. 调整指针:更新前驱节点的 forward 指针

// Redis 7.2 源码:src/t_zset.c - zslInsert 函数
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;
    
    x = zsl->header;
    
    // 第一步:从高到低查找插入位置
    for (i = zsl->level-1; i >= 0; i--) {
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
        
        while (x->level[i].forward &&
               (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                 sdscmp(x->level[i].forward->ele, ele) < 0)))
        {
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
        update[i] = x;
    }
    
    // 第二步:随机计算新节点的层数
    level = zslRandomLevel();
    if (level > zsl->level) {
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            update[i] = zsl->header;
            update[i]->level[i].span = zsl->length;
        }
        zsl->level = level;
    }
    
    // 第三步:创建新节点并调整指针
    x = zslCreateNode(level, score, ele);
    for (i = 0; i < level; i++) {
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;
        
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }
    
    // 处理高层节点的 span
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }
    
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    
    zsl->length++;
    return x;
}

// 随机层数算法(幂次定律)
int zslRandomLevel(void) {
    int level = 1;
    // 25% 的概率增加一层
    while ((random() & 0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

3.4 skiplist 的删除算法

graph TD
    A[开始删除] --> B[查找待删除节点]
    B --> C[记录每层的前驱节点]
    C --> D[调整前驱节点的 forward 指针]
    D --> E[释放节点内存]
    E --> F[更新空层]
    F --> G[完成]
    
    style B fill:#6f9,stroke:#333
    style D fill:#f96,stroke:#333
// Redis 7.2 源码:src/t_zset.c - zslDelete 函数
int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;
    
    x = zsl->header;
    
    // 查找待删除节点及其前驱
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward &&
               (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                 sdscmp(x->level[i].forward->ele, ele) < 0)))
        {
            x = x->level[i].forward;
        }
        update[i] = x;
    }
    
    x = x->level[0].forward;
    if (x && score == x->score && sdscmp(x->ele, ele) == 0) {
        // 从所有层中删除该节点
        for (i = 0; i < zsl->level; i++) {
            if (update[i]->level[i].forward == x) {
                update[i]->level[i].span += x->level[i].span - 1;
                update[i]->level[i].forward = x->level[i].forward;
            } else {
                update[i]->level[i].span -= 1;
            }
        }
        
        // 更新后退指针
        if (x->level[0].forward) {
            x->level[0].forward->backward = x->backward;
        } else {
            zsl->tail = x->backward;
        }
        
        // 移除空层
        while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL) {
            zsl->level--;
        }
        
        zsl->length--;
        if (node) *node = x;
        zslFreeNode(x);
        return 1;
    }
    return 0;
}

3.5 skiplist 的性能分析

操作平均时间复杂度最坏时间复杂度空间复杂度
查找O(log n)O(n)O(n)
插入O(log n)O(n)O(n)
删除O(log n)O(n)O(n)
范围查询O(log n + k)O(n)-

k 为范围内元素个数

与其他数据结构对比

数据结构查找插入删除范围查询实现复杂度
skiplistO(log n)O(log n)O(log n)O(log n + k)中等
红黑树O(log n)O(log n)O(log n)O(log n + k)
B+树O(log n)O(log n)O(log n)O(log n + k)
哈希表O(1)O(1)O(1)不支持

四、实战应用与性能对比

4.1 实战代码:三种数据结构的使用示例

import redis
import time
import random

# 连接 Redis
r = redis.Redis(host='localhost', port=6379, db=0)

# ===== 1. intset 实战 =====
def demo_intset():
    """演示 intset 的使用"""
    print("=== intset 演示 ===")
    
    # 清空数据
    r.delete('myset')
    
    # 插入整数
    for i in [1, 2, 3, 100, 200]:
        r.sadd('myset', i)
    
    # 查看底层数据结构
    encoding = r.object('encoding', 'myset')
    print(f"编码类型: {encoding}")  # 应该是 intset
    
    # 验证元素
    members = r.smembers('myset')
    print(f"集合成员: {sorted(members)}")

# ===== 2. ziplist 实战 =====
def demo_ziplist():
    """演示 ziplist 的使用"""
    print("\n=== ziplist 演示 ===")
    
    # 清空数据
    r.delete('mylist')
    r.delete('myhash')
    
    # 列表使用 ziplist(元素少且短)
    r.rpush('mylist', 'a', 'b', 'c', 'd')
    encoding = r.object('encoding', 'mylist')
    print(f"列表编码: {encoding}")  # 应该是 ziplist
    
    # 哈希使用 ziplist
    r.hset('myhash', mapping={'name': 'Redis', 'type': 'DB'})
    encoding = r.object('encoding', 'myhash')
    print(f"哈希编码: {encoding}")  # 应该是 ziplist

# ===== 3. skiplist 实战 =====
def demo_skiplist():
    """演示 skiplist 的使用"""
    print("\n=== skiplist 演示 ===")
    
    # 清空数据
    r.delete('leaderboard')
    
    # 插入大量数据触发 skiplist 转换
    users = [
        ('user1', 100),
        ('user2', 200),
        ('user3', 150),
        ('user4', 300),
        ('user5', 250)
    ]
    
    for user, score in users:
        r.zadd('leaderboard', {user: score})
    
    # 查看底层数据结构
    encoding = r.object('encoding', 'leaderboard')
    print(f"有序集合编码: {encoding}")  # 可能是 skiplist
    
    # 范围查询(skiplist 的优势)
    print("\n排行榜前3名:")
    top3 = r.zrevrange('leaderboard', 0, 2, withscores=True)
    for rank, (user, score) in enumerate(top3, 1):
        print(f"  第{rank}名: {user} - {score}分")
    
    # 查找特定分数范围
    print("\n分数在 150-250 之间的用户:")
    range_users = r.zrangebyscore('leaderboard', 150, 250, withscores=True)
    for user, score in range_users:
        print(f"  {user}: {score}")

# ===== 4. 性能对比测试 =====
def benchmark_performance():
    """性能对比测试"""
    print("\n=== 性能对比测试 ===")
    
    # intset vs hashset(自动转换)
    r.delete('set_small')
    r.delete('set_large')
    
    # 小集合使用 intset
    start = time.time()
    for i in range(100):
        r.sadd('set_small', i)
    intset_time = time.time() - start
    
    # 大集合使用 hashtable
    start = time.time()
    for i in range(10000):
        r.sadd('set_large', i)
    hashtable_time = time.time() - start
    
    print(f"intset 插入 100 个元素: {intset_time:.6f} 秒")
    print(f"hashtable 插入 10000 个元素: {hashtable_time:.6f} 秒")
    print(f"小集合编码: {r.object('encoding', 'set_small')}")
    print(f"大集合编码: {r.object('encoding', 'set_large')}")

if __name__ == '__main__':
    demo_intset()
    demo_ziplist()
    demo_skiplist()
    benchmark_performance()

4.2 性能对比表

数据结构适用场景内存占用查找性能插入性能删除性能
intset纯整数小集合极低O(log n)O(n)O(n)
ziplist小列表/小哈希极低O(n)O(n)O(n)
skiplist大有序集合中等O(log n)O(log n)O(log n)
hashtable大集合/大哈希较高O(1)O(1)O(1)
linkedlist大列表较高O(n)O(1)O(1)

五、最佳实践与调优建议

5.1 配置参数调优

Redis 提供了多个配置参数来控制数据结构的编码选择:

# 列表相关
list-max-ziplist-size -2  # 默认:每个元素不超过 64 字节
list-compress-depth 0     # 压缩深度,0 表示不压缩

# 哈希相关
hash-max-ziplist-entries 512  # 最大字段数
hash-max-ziplist-value 64     # 最大值长度(字节)

# 集合相关
set-max-intset-entries 512    # 使用 intset 的最大元素数

# 有序集合相关
zset-max-ziplist-entries 128  # 使用 ziplist 的最大元素数
zset-max-ziplist-value 64     # 最大成员长度(字节)

5.2 选择建议

graph TD
    A[选择数据结构] --> B{数据类型?}
    
    B -->|SET| C{元素都是整数且 < 512?}
    C -->|是| D[intset]
    C -->|否| E[hashtable]
    
    B -->|LIST| F{元素 < 512 且长度 < 64?}
    F -->|是| G[ziplist]
    F -->|否| H[linkedlist]
    
    B -->|HASH| I{字段 < 512 且值 < 64?}
    I -->|是| J[ziplist]
    I -->|否| K[hashtable]
    
    B -->|ZSET| L{元素 < 128 且成员 < 64?}
    L -->|是| M[ziplist]
    L -->|否| N[skiplist + hashtable]

5.3 监控命令

# 查看对象的编码
OBJECT encoding key

# 查看对象的内存占用
OBJECT memory key

# 查看对象引用数
OBJECT refcount key

# 查看底层信息(使用 MEMORY 命令)
MEMORY usage key

# 查看统计信息
INFO stats

5.4 常见问题与解决方案

问题原因解决方案
内存占用过高ziplist/skiplist 阈值设置不当调整 *-max-ziplist-* 参数
性能下降ziplist 连锁更新转换为其他数据结构
范围查询慢使用了 ziplist 而非 skiplist增加 zset 元素数量触发转换
CPU 占用高大量数据使用 intset/ziplist放宽转换阈值

总结

Redis 的强大之处在于它不是简单地将高级数据结构映射到底层实现,而是根据数据特征和访问模式动态选择最优的数据结构。

核心要点回顾

  1. intset:通过编码升级和二分查找,在纯整数集合上实现了性能与内存的平衡
  2. ziplist:通过紧凑存储在内存敏感场景下表现优异,但需警惕连锁更新
  3. skiplist:以简单实现提供了接近平衡树的性能,特别适合范围查询

最佳实践

  • 小数据量优先使用 ziplist/intset 以节省内存
  • 大数据量或需要高性能查询时使用 hashtable/skiplist
  • 使用 OBJECT encoding 监控数据结构变化
  • 根据业务特点调整 *-max-ziplist-* 参数

理解这些底层实现,不仅能帮助我们更好地使用 Redis,还能在面对性能问题时快速定位根本原因。


相关阅读: - Redis 官方文档:https://redis.io/docs/data-types/ - Redis 源码:https://github.com/redis/redis/tree/7.2/src - 《Redis 设计与实现》(黄健宏)

标签Redis 数据结构 源码解析 intset ziplist skiplist 性能优化

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐