深入解析Redis 7.0中每种数据类型的底层实现

发布于:2025-07-06 ⋅ 阅读:(25) ⋅ 点赞:(0)

一、String(字符串)

核心实现:SDS(Simple Dynamic String)

struct sdshdr {
    uint64_t len;     // 已使用长度(O(1)获取长度)
    uint64_t alloc;   // 总分配空间(不含header)
    unsigned char flags; // 类型标识(SDS_TYPE_8/16/32/64)
    char buf[];       // 柔性数组(实际数据)
};

Java视角特性

  1. 二进制安全:可存储任意二进制数据(如图片),非仅文本

  2. 内存预分配

    • 扩容策略:len < 1MB 时双倍扩容,≥1MB 时每次扩1MB

    • 惰性释放:缩短时不立即回收内存

  3. 编码优化

  • EMBSTR:redisObject和SDS连续存储(减少内存碎片)

  • INT:直接存储长整型值(无额外内存开销)

二、List(列表)

底层结构演进

  • ≤Redis 2.8:双向链表(linkedlist

  • Redis 3.2+:QuickList

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;     // 总元素数
    unsigned long len;       // quicklistNode数量
    int fill : QL_FILL_BITS; // ziplist最大大小
    unsigned int compress : QL_COMP_BITS; // 压缩深度
} quicklist;

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;        // 指向ziplist
    size_t sz;                // ziplist字节大小
    unsigned int count : 16;  // ziplist元素数
    unsigned int encoding : 2; // 编码类型
    unsigned int container : 2; // 存储方式
    unsigned int recompress : 1; // 临时解压标记
} quicklistNode;

Java应用场景

  • 消息队列:LPUSH/RPOP实现FIFO

  • 分页查询:LRANGE key 0 9 获取前10条

  • 性能优化点

    • 调整list-max-ziplist-size控制ziplist节点大小

    • 设置list-compress-depth压缩中间节点(首尾不压缩)

三、Hash(哈希表)

底层结构

  1. Ziplist(紧凑列表):

    • 触发条件:字段数≤hash-max-ziplist-entries(默认512)且 所有值长度≤hash-max-ziplist-value(默认64字节)

    • 存储格式:<key1><value1><key2><value2>...

  2. Dict(哈希表):

    typedef struct dict {
        dictEntry **table;      // 哈希桶数组
        unsigned long size;     // 桶数量
        unsigned long sizemask; // size-1(用于取模)
        unsigned long used;     // 元素数量
    } dict;
    
    typedef struct dictEntry {
        void *key;
        union {
            void *val;
            uint64_t u64;
            double d;
        } v;
        struct dictEntry *next; // 链地址法解决冲突
    } dictEntry;

    Java最佳实践

    • 存储对象:替代JSON序列化,支持字段级操作

    • 配置优化:根据字段平均大小调整hash-max-ziplist-value

四、Set(集合)

底层结构

  1. Intset(整数集合):

typedef struct intset {
    uint32_t encoding; // INTSET_ENC_INT16/32/64
    uint32_t length;
    int8_t contents[]; // 有序数组
} intset;
  • 触发条件:元素均为整数且数量≤set-max-intset-entries(默认512)

  • Dict(哈希表):

    • 特殊实现:value固定为NULL,仅用key存储数据

Java场景对比

操作 Intset复杂度 Dict复杂度 Java等价类
SADD O(n) O(1) HashSet.add()
SISMEMBER O(log n) O(1) HashSet.contains()
SUNION O(n) O(n) Set.addAll()

五、ZSet(有序集合)

双重结构

typedef struct zset {
    dict *dict;        // 哈希表:member->score
    zskiplist *zsl;    // 跳跃表:按score排序
} zset;

// 跳跃表节点
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward; // 后退指针
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span; // 跨越节点数
    } level[]; // 柔性数组
} zskiplistNode;

跳跃表特性

  1. 层高随机生成(1/4概率升级)

  2. 支持O(log n)范围查询(ZRANGEBYSCORE)

  3. 平衡树替代方案:避免旋转操作,更易实现

Java使用场景

  • 排行榜:ZINCRBY + ZREVRANGE

  • 延迟队列:score设为执行时间戳

六、高级类型实现

1. Bitmap(位图)

本质:String的位操作扩展

  • SETBIT key offset 1:扩展SDS并置位

  • GETBIT:位运算定位字节位置

  • BITCOUNT:使用SWAR算法加速统计

    // 汉明重量计算(每4位查表)
    static const unsigned char popcount_tab[256] = {
        0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, ... 
    };

2. HyperLogLog

核心结构

struct hllhdr {
    char magic[4];      // "HYLL"
    uint8_t encoding;   // HLL_DENSE 或 HLL_SPARSE
    uint8_t notused[3]; // 保留字段
    uint8_t card[8];    // 缓存基数
    uint8_t registers[]; // 16384个6bit寄存器
};
  • 误差率:0.81%/√m(m=16384)

3. Stream

实现:Rax树(Radix Tree)

typedef struct raxNode {
    uint32_t iskey:1;     // 是否包含key
    uint32_t isnull:1;    // 关联值是否为空
    uint32_t iscompr:1;   // 是否压缩存储
    uint32_t size:29;     // 子节点数/压缩字符串长度
    unsigned char data[]; // 柔性数组存储路径
} raxNode;
  • 消息ID:<millisecondsTime>-<sequenceNumber>

  • 消费者组:通过PEL(Pending Entries List)实现

Java开发者启示

  1. 内存优化

    • 小数据用ziplist/intset(调整max-*-entries参数)

    • 避免大Key:String≤10KB,集合元素≤5000

  2. API选择

    // 错误:多次网络IO
    for(String item : items) {
        jedis.sadd(key, item);
    }
    
    // 正确:管道批处理
    Pipeline p = jedis.pipelined();
    for(String item : items) {
        p.sadd(key, item);
    }
    p.sync();

  3. 持久化影响

    • RDB:ziplist可直接序列化,内存页高效dump

    • AOF:SDS格式对rewrite友好

  4. 集群分片

    • HashTag确保相关数据同slot:{user1000}.orders

黄金法则
理解底层结构才能写出高性能Redis代码,特别是当QPS > 10k时,数据结构选择直接影响集群规模成本。