云计算百科
云计算领域专业知识百科平台

Redis八股高频面试总结个人开源笔记

redis底层数据结构

Redis对外暴露了5种基本数据类型,但底层为了兼顾内存效率和操作性能,实际使用了多种物理数据结构。同一种上层类型在不同数据量下,底层类型还会自动切换。

1.String底层数据结构是SDS,简单动态字符串,可以在O(1)时间复杂度内获取长度,支持自动扩容避免缓冲区溢出,增长时支持空间预分配,截短时将多出的空间放到free里,之后无需重新分配,不以\\0判断字符串结束,可以安全存储图片,序列号对象

[图片]
2.List早期是ZipList+LinkedList,到3.2的QuickList,再到7.0用ListPack替代ZipList
小数据量用ZipList,它是一块连续内存,所有元素紧凑排列,非常省内存;大数据量用双向链表。但两者各有缺陷——ZipList修改效率低且有连锁更新风险,LinkedList小数据时每个节点都要存两个指针+一个数据指针(共24字节开销),指针比数据还大,内存浪费严重。
QuickList,统一了这两种结构。本质是双向链表+ZipList的混合体:外层用双向链表连接多个节点,每个节点内部是一个ZipList,保留了链表的灵活修改能力,又保持了节点内部的内存紧凑。
用ListPack替代了ZipList。ZipList的核心缺陷是连锁更新——每个元素记录前驱长度,一次插入可能触发后续所有元素的级联扩容。ListPack的解决方案是去掉前驱长度,改为在末尾记录自身长度,使每个元素的大小变化完全独立,从根本上消除了连锁更新。

3.Redis Hash的底层采用自适应编码策略:小数据用ZipList/ListPack数组节省内存,大数据用哈希表保证性能,两者之间通过阈值自动切换,且扩容时使用渐进式rehash避免阻塞主线程。
当HashTable需要扩容时,传统做法是一次性将所有数据迁移到新表:Redis是单线程模型,任何阻塞都会导致所有请求停滞
dict结构内部维护了两个哈希表ht[0]和ht[1]。当需要扩容时,先分配ht[1]。之后每次操作都会迁移ht[0]中的一个桶到ht[1]。rehash期间,新增操作只写入ht[1],查找先查ht[1]再查ht[0]。这样就把原本一次性迁移,分摊到了每次微秒级的操作中。

4.Redis Set的底层采用内容感知编码:当所有元素都是整数且数量较小时,用IntSet;一旦混入字符串或数量超512,自动升级为HashTable。Set的HashTable只用key不用value,value字段始终为NULL。对于纯整数的小集合,IntSet能节省90%以上的内存。

5.ZSet,小数据用ZipList(7.0后为ListPack),大数据量时使用跳表(SkipList)+ 哈希表(HashTable),跳表负责按score排序和范围查询,哈希表负责O(1)通过member查score。
跳表的核心设计是多层索引:最底层包含所有节点,高层是低层的快速通道。查找时从最高层开始,能前进就前进,不能前进就下降.

为什么选跳表而不是红黑树,主要有三个原因:一是范围查询友好,找到起点后沿最底层顺序遍历即可,红黑树需要复杂的中序遍历;二是实现简单,不需要旋转和变色;三是并发友好,局部修改易于加锁。
[图片]
[图片]
[图片]

[图片]

持久化:

Redis是内存数据库,数据全在内存中。一旦进程崩溃或机器断电,数据全部丢失。持久化就是将内存数据定期或实时地保存到磁盘,重启后恢复。
Redis提供RDB和AOF两种持久化机制:RDB是时间点快照,适合备份和灾难恢复;AOF是命令日志追加,数据安全性更高。Redis 4.0引入的混合持久化结合了两者的优点,是当前生产环境的推荐方案。
RDB

  • 核心定位:全量快照,在指定的时间点,手动或自动把内存里所有的数据打包成一个紧凑的二进制文件(dump.rdb)。把内存中的所有数据都记录到磁盘RDB文件中,当Redis实例故障重启后,读取磁盘快照文件,恢复数据速度极快
    手动save会阻塞主线程,用bgsave,去fork子进程异步执行,主线程继续服务
    copy-on-write(COW):
    主进程如果在写数据,触发COW,单独拷贝一份数据,让主进程在上面写,而子进程fork的是那一瞬间的数据
    如果写入量大,COW会导致额外内存消耗,极端情况下可能翻倍。生产环境要预留足够内存。

AOF

  • 核心定位:增量日志,记录每一个写命令在AOF文件
    刷盘策略:何时将缓冲区数据刷到磁盘
  • always:同步刷盘,写入内存数据后直接把命令写入AOF文件(优点是几乎零数据丢失;缺点是磁盘 IO 压力极大,严重拖慢 Redis 性能。)
  • everysec:每秒刷盘,写内存后先把命令放入Aof缓冲区,后台线性每隔一秒写入AOF文件(优点是性能极高,且最多只会丢失 1 秒钟的数据(适合 99% 的生产环境)。)
  • no:操作系统控制,写内存后把命令放Aof缓冲区,由操作系统决定何时将缓冲区内容写入AOF文件(Redis 性能最好,但一旦断电,可能会丢失几秒甚至几分钟的数据。)
    bg re write aof命令让AOF文件执行重写功能 最少的命令达到相同效果

开启混合持久化

  • 解法:在触发 AOF 重写时,子进程先把当前内存的全量数据以 RDB 格式写到文件前半段,然后把重写期间新产生的增量命令以 AOF 格式追加到后半段。
  • 效果:重启时,先秒级加载前半段的 RDB(解决恢复慢的问题),再快速重放后半段的 AOF(解决数据丢失的问题)。既保证了极速恢复,又保证了极高的数据安全性。

分布式锁:
redis命令实现:set unique_key 机器码+线程ID NX EX TTL

  • NX (互斥):只有钥匙不在门上(Key不存在)时,才能插进去。保证了同一时刻只有一个人能进。
  • EX (防死锁):钥匙上绑了一个倒计时炸弹(TTL)。如果进去的人突发心脏病(服务宕机/网络断开)没出来,倒计时结束后炸弹爆炸,锁自动销毁,后面的人还能继续进。
  • 机器码+线程ID (防误删):这是最关键的。假设你进去后业务卡住了,倒计时结束锁自动销毁了。这时别人拿到了新钥匙进去了。等你回过神来,想把锁删掉,如果不校验身份,你就会把别人的锁给删了! 所以,删锁前必须判断:“这锁是我刚才加的吗?”(通常配合 Lua 脚本保证判断和删除的原子性)
    Redisson实现:
    可重入:利用Hash结构,记录获取锁的线程和获取锁的次数,避免死锁问题
  • 大 Key:密室的门牌号(锁名)。
  • 小 Key (Field):你的身份证号(UUID + 线程ID)。
  • Value:你开门的次数(重入计数器)。
  • 加锁逻辑:你第一次进门,Hash 里写入 你的ID: 1;你再去开保险箱,Redisson 发现 Hash 里已经有你的 ID 了,于是把次数变成 你的ID: 2。
  • 解锁逻辑:每退出一扇门,次数减 1。只有当次数减到 0 时,才真正把这把锁从 Redis 中删掉。这就完美避免了“自己把自己锁死”的问题。
    看门狗机制:当没有指定锁的TTL时,默认会使用
  • 默认 TTL 为 30 秒,后台定时 每隔 10 秒 检查业务是否完成。
  • 若未完成 → 重置锁 TTL(续期至 30 秒),保证业务完成再释放锁

内存淘汰策略
8种
4针对设置了ttl的key->lru(最近最久未使用的,看最后访问时间),lfu(最近访问频率最低的),random,ttl(剩余时间最短的)
3针对所有key->lru(适合“热点数据明显”的场景,比如新闻首页缓存,经常看的新闻留下,几年没人看的旧新闻扔掉。),lfu,random
1noeviction:啥也不淘汰(适用场景:对数据安全性要求极高,宁可拒绝服务也不能丢数据的场景)

Redsi的key过期了怎么处理,是直接删除吗?
[图片]
Redis内存满了怎么处理?
[图片]

[图片]

[图片]

[图片]

[图片]

Redis为什么快?
[图片]
[图片]

Redis是单线程吗?
[图片]

[图片]

[图片]

[图片]

大key

[图片]
[图片]

热key

[图片]

redis和mysql数据一致性

先更新数据库再删除缓存
在极端并发下,它产生脏数据的概率远低于“先删缓存再更新数据库”。
如果先删缓存,因为写DB会慢很多,在缓存已经被删除,DB未更新成功之前这个间隙内,读请求就会把旧值 回填到缓存,造成脏数据;
而先更DB再删缓存,脏数据只在微秒级窗口内可能发生。

[图片]

缓存击穿雪崩穿透

[图片]

[图片]

[图片]

主从复制

[图片]

[图片]

[图片]

赞(0)
未经允许不得转载:网硕互联帮助中心 » Redis八股高频面试总结个人开源笔记
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!