外观
一句话答案
缓存(String/Hash)、分布式锁(SETNX)、计数器(INCR)、排行榜(ZSet)、Session、延迟队列(ZSet)、限流。
核心要点
Hash 的底层编码(两种):
- listpack(JDK 7.0 前叫 ziplist):小数据量时用,连续内存,节省内存
- 触发条件:字段数 ≤ 128 且每个值 ≤ 64 字节
- hashtable(字典 dict):大数据量时用,支持 O(1) 查找
hashtable(dict)结构:
c
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2]; // 两个哈希表(用于渐进式 rehash)
long rehashidx; // -1 表示未在 rehash;否则表示当前 rehash 到的桶索引
int iterators;
} dict;渐进式 rehash(Redis 的扩容机制):
为什么用渐进式:
- 如果一次性 rehash 所有 key,数据量大时会阻塞 Redis 服务(Redis 单线程),导致响应超时
渐进式 rehash 过程:
触发条件:
ht[0] 的 used/size(负载因子)> 1(安全模式下 > 5)
开始 rehash:
1. 分配 ht[1](容量为下一个 >= ht[0].used*2 的 2 的幂)
2. 设置 rehashidx = 0(表示从桶 0 开始 rehash)
渐进迁移(每次操作时顺带迁移):
每次对 dict 的增删改查操作,额外将 ht[0].table[rehashidx] 桶的数据迁移到 ht[1]
迁移完毕,rehashidx++
迁移期间的查询:
先查 ht[0],找不到再查 ht[1]
迁移期间的写入:
新数据直接写入 ht[1](保证 ht[0] 只减不增,最终清空)
rehash 完成:
ht[1] 成为新的 ht[0],重置 rehashidx = -1追问与易错
追问方向:
- 排行榜数据量大怎么办?
- 延迟队列用 ZSet 什么问题?
- 分布式限流用 Redis 怎么做?
易错点:
- ❌ Redis 能解决所有缓存问题——需考虑容量和一致性
- ❌ 所有场景都用 String——选对数据结构很重要
💡 记忆锚点
数据结构选型口诀:String存对象/计数,Hash存字段(用户画像),List做队列,Set做去重/交集(共同好友),ZSet做排行榜/延迟队列。记住"选错数据结构=用螺丝刀钉钉子"。