深入解析Redis 7.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视角特性:
-
二进制安全:可存储任意二进制数据(如图片),非仅文本
-
内存预分配:
-
扩容策略:
len < 1MB
时双倍扩容,≥1MB
时每次扩1MB -
惰性释放:缩短时不立即回收内存
-
-
编码优化:
-
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; // 指向ziplistsize_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(哈希表)
底层结构:
-
Ziplist(紧凑列表):
-
触发条件:字段数≤
hash-max-ziplist-entries
(默认512)且 所有值长度≤hash-max-ziplist-value
(默认64字节) -
存储格式:
<key1><value1><key2><value2>...
-
-
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(集合)
底层结构:
-
Intset(整数集合):
typedef struct intset {uint32_t encoding; // INTSET_ENC_INT16/32/64uint32_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->scorezskiplist *zsl; // 跳跃表:按score排序
} zset;// 跳跃表节点
typedef struct zskiplistNode {sds ele;double score;struct zskiplistNode *backward; // 后退指针struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span; // 跨越节点数} level[]; // 柔性数组
} zskiplistNode;
跳跃表特性:
-
层高随机生成(1/4概率升级)
-
支持O(log n)范围查询(ZRANGEBYSCORE)
-
平衡树替代方案:避免旋转操作,更易实现
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_SPARSEuint8_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; // 是否包含keyuint32_t isnull:1; // 关联值是否为空uint32_t iscompr:1; // 是否压缩存储uint32_t size:29; // 子节点数/压缩字符串长度unsigned char data[]; // 柔性数组存储路径
} raxNode;
-
消息ID:<millisecondsTime>-<sequenceNumber>
-
消费者组:通过PEL(Pending Entries List)实现
Java开发者启示
-
内存优化:
-
小数据用ziplist/intset(调整max-*-entries参数)
-
避免大Key:String≤10KB,集合元素≤5000
-
-
API选择:
// 错误:多次网络IO for(String item : items) {jedis.sadd(key, item); }// 正确:管道批处理 Pipeline p = jedis.pipelined(); for(String item : items) {p.sadd(key, item); } p.sync();
-
持久化影响:
-
RDB:ziplist可直接序列化,内存页高效dump
-
AOF:SDS格式对rewrite友好
-
-
集群分片:
-
HashTag确保相关数据同slot:
{user1000}.orders
-
黄金法则:
理解底层结构才能写出高性能Redis代码,特别是当QPS > 10k时,数据结构选择直接影响集群规模成本。