redis有哪些数据结构,讲一下常用的数据结构的使用场景
数据结构有
- string 基础的数据类型 kv类型。底层使用的是 SDS(简单动态字符串)。它和 C 语言原生的字符串不同,SDS 内部记录了
len和free,所以获取长度的时间复杂度是 O(1),并且支持动态扩容,避免了缓冲区溢出的问题。 - list 有序的字符串列表,支持从两端推入或弹出元素。早期是双向链表,现在底层主要使用
quicklist。它其实是“双向链表 + 压缩列表”的混合体,兼顾了链表的插入效率和连续内存的缓存命中率。 - hash 键值对集合,Hash 可以单独修改或获取对象中的某个字段,更省内存和网络带宽。Redis 会根据数据量动态切换。数据量小时使用
ziplist(压缩列表)来节省内存,数据量大时自动转为hashtable(哈希表)。 - set 无序的字符串集合,元素唯一,自动去重。底层由哈希表或整数集合实现,支持交集、并集、差集等数学集合运算。
- zset 在 Set 的基础上增加了一个权重分数(score),元素会根据 score 自动排序,且元素唯一。底层通常由跳表(skiplist)和哈希表实现,范围查找和排序性能极佳。
redis使用cluster集群模式,三主三从六个节点的情况下,如果某个节点出现宕机,会发生哪些问题
宕机的是从节点
- 影响:对集群的读写服务没有任何影响。
- 原因:从节点只负责复制主节点的数据,不参与数据的读写分配。它宕机后,它对应的主节点依然正常工作,集群依然能处理所有的读写请求。
- 风险:此时该主节点处于“裸奔”状态(没有从节点备份)。如果此时它的主节点也发生宕机,集群将直接面临数据丢失和部分服务不可用的风险。
宕机的是主节点
这是集群高可用机制发挥作用的核心场景。具体会经历以下几个过程:
- 故障发现与判定(Gossip 协议)
集群中的节点会通过 Gossip 协议互相发送 PING 消息。当其他主节点在超时时间(
cluster-node-timeout)内没有收到该主节点的 PONG 响应时,会将其标记为“主观下线(PFAIL)”。 当超过半数(在3主节点中即至少2个)的主节点都认为它挂了,该节点会被标记为“客观下线(FAIL)”。 - 故障转移与自动选举(Failover) 一旦主节点被判定为客观下线,它对应的从节点会发起选举。 集群中存活的主节点会进行投票,如果该从节点获得了超过半数主节点的选票,它就会成功当选。 当选后,这个从节点会晋升为新的主节点,接管原主节点负责的哈希槽(Hash Slots),继续对外提供读写服务。整个过程对客户端来说会有短暂的闪断,但集群整体依然可用。
- 原节点恢复 如果之前宕机的节点后续又重启恢复了,它不会再变回主节点,而是会作为从节点加入到集群中,自动复制当前新主节点的数据。
redis使用cluster集群彻底不可用情况
- 主节点及其从节点同时宕机:如果某个主节点和它唯一的从节点同时挂掉,这部分数据对应的哈希槽将无人接管,集群会直接判定为失败。
- 集群依然可用(部分可用)
前提条件:配置了
cluster-require-full-coverage no表现:集群不会彻底瘫痪。其他两个正常的主节点依然可以继续处理它们所负责的哈希槽的读写请求。 影响:只有当客户端尝试访问那个“无主”的哈希槽时,才会收到报错(如 CLUSTERDOWN 或 MOVED 错误)。 适用场景:这种配置在大多数业务中是推荐的。它能有效防止因为局部故障导致整个集群雪崩,保证了核心业务的可用性 - 集群彻底不可用(全局瘫痪)
前提条件:配置了
cluster-require-full-coverage yes(这是 Redis 的默认配置) 表现:集群会立刻进入 fail 状态,拒绝所有的写操作,并且读操作也会大面积失败。 原因:Redis Cluster 的设计初衷是保证数据的完整性。默认情况下,只要 16384 个哈希槽中有任何一个槽位没有被分配到存活的节点上,集群就会认为自身是不完整的,从而主动“罢工”,拒绝提供服务。
- 集群依然可用(部分可用)
前提条件:配置了
- 超过半数的哈希槽不可用:由于集群有 3 个主节点,如果同时有 2 个或 3 个主节点宕机(且没有从节点能成功顶上),导致集群中超过一半的哈希槽(16384个槽位)处于失联状态,集群将无法进行任何写操作,甚至读操作也会受限。
Redis进程到达设置的最大使用内存会发生什么
当 Redis 实例使用的内存达到了我们在配置文件中设置的 maxmemory(最大内存上限)时,会触发Redis 的内存淘汰机制。
Redis 一共提供了 8 种淘汰策略,我们可以通过两个维度来对它们进行分类:
-
按淘汰范围划分:
allkeys-*系列:从 Redis 中所有的键里挑选淘汰对象。volatile-*系列:只从设置了过期时间的键里挑选淘汰对象。
-
按淘汰算法划分:主要分为基于使用时间的(LRU/LFU)、基于过期时间的(TTL)、随机淘汰,以及不淘汰。
-
noeviction(不淘汰,默认策略):当内存满了之后,Redis 会拒绝所有写入操作并返回错误,但读操作不受影响。适用于数据绝对不能丢失的场景。 -
allkeys-lru(最常用):在所有键中,淘汰最近最少使用的数据。它的核心思想是“最近被访问的数据,未来被访问的概率也更高”。这是绝大多数纯缓存场景的首选。 -
allkeys-lfu:在所有键中,淘汰访问频率最低的数据。它关注的是数据在整个生命周期内的总访问次数,适合需要长期保留“高频热点数据”的场景。 -
volatile-ttl:在设置了过期时间的键中,淘汰剩余生存时间(TTL)最短的键,也就是最快要过期的数据。 -
volatile-lru/volatile-lfu:只在设置了过期时间的键中执行 LRU 或 LFU 算法。适用于 Redis 中既存储了缓存数据,又存储了必须保留的持久化数据的混合场景。 -
allkeys-random:在所有键中随机淘汰。volatile-random:在设置了过期时间的键中随机淘汰。
介绍一下redis的RDB和AOF
RDB
RDB 是 Redis 默认的持久化方式。它会在指定的时间间隔内,将内存中的全量数据生成一个快照,以二进制压缩文件(dump.rdb)的形式保存到磁盘上。
- 核心特征:
- 恢复速度极快:因为是紧凑的二进制文件,Redis 重启时直接加载到内存即可,非常适合大规模数据的灾难恢复和冷备份。
- 性能影响小:生成快照时,Redis 会 fork 出一个子进程在后台完成,主进程继续处理请求,不会阻塞客户端。
- 文件体积小:经过压缩的二进制文件,占用磁盘空间小。
- 主要缺点:
- 数据安全性较低:如果 Redis 在两次快照之间宕机,这期间产生的所有数据都会丢失。
AOF
AOF 是一种增量持久化方式。它会以日志的形式,将 Redis 接收到的每一个写命令都追加记录到文件(appendonly.aof)的末尾。重启时,通过重新执行这些命令来恢复数据。
- 核心特征:
- 数据安全性高:可以配置不同的同步策略(如每秒同步一次),即使宕机,最多也只会丢失 1 秒的数据。
- 文件可读性强:AOF 文件是纯文本格式,记录了类似
SET key value的 Redis 协议命令,方便人工查看和修复。
- 主要缺点:
- 文件体积大:因为记录了所有的写操作,包含很多冗余命令,文件通常比 RDB 大很多。
- 恢复速度慢:重启时需要逐条执行日志中的命令来重建数据,速度远慢于 RDB。
redis支持事务吗,如果需要同时处理多个key要怎么做
支持简单事务,通过 MULTI、EXEC、DISCARD 这三个命令来实现的。它最大的作用是保证隔离性:当客户端进入 MULTI 状态后,后续的命令会被放入一个队列中,直到执行 EXEC 时,这些命令才会被按顺序、一次性地执行,期间不会被其他客户端的命令插队。但是Redis 的事务不支持回滚。传统数据库(如 MySQL)如果在事务中有一条 SQL 执行失败,整个事务会回滚。但 Redis 不同,如果在执行 EXEC 时,队列中某一条命令执行报错,剩下的命令依然会继续执行,不会发生回滚。因此,Redis 事务只能保证命令是顺序执行的,无法保证严格的原子性。
如果需要多个key在某个事务内全部得到修改,则需要使lua脚本来进行执行,Redis 执行 Lua 脚本是单线程原子的,能完美替代事务实现“全成功或全失败”。
缓存穿透、缓存击穿、缓存雪崩
缓存穿透 (Penetration)
问题本质:请求查询的数据在缓存和数据库中都不存在,导致这些请求绕过缓存,持续打到数据库。 我的落地方案:
- 布隆过滤器 (Bloom Filter):在业务逻辑层,对于批量查询或者核心接口,我会在查 Redis 前先用布隆过滤器做前置校验。如果布隆过滤器判定“一定不存在”,就直接返回,不查缓存和 DB。
- 缓存空值 (Cache Null):对于普通查询,如果 DB 查不到数据,我也会在 Redis 里缓存一个空对象(比如空字符串),并设置一个较短的过期时间(比如 30 秒到 5 分钟),防止同一个非法 ID 被恶意高频请求。
缓存击穿 (Breakdown)
问题本质:针对的是单个热点 Key(比如爆款商品详情)。当这个 Key 刚好过期的瞬间,海量并发请求同时涌入,全部直接打到数据库。 我的落地方案:
- 使用
singleflight抑制并发:在 Go 语言项目中,我通常会引入官方扩展库golang.org/x/sync/singleflight。当发现缓存失效时,利用singleflight把同一时刻对同一个 Key 的并发请求合并,只让一个 Goroutine 去查 DB 并重建缓存,其他请求等待结果即可。这比手动实现分布式锁更轻量,且能有效防止击穿。 - 逻辑过期 (Logical Expiration):对于极热点的数据,我会在 Value 中额外存储一个逻辑过期时间,而 Redis 的 Key 本身设置为永不过期。当发现逻辑过期时,先返回旧数据保证高可用,同时异步启动一个 Goroutine 去更新缓存。
缓存雪崩 (Avalanche)
问题本质:大量 Key 在同一时间集体失效,或者 Redis 服务整体宕机,导致流量洪峰全部压向数据库。 我的落地方案:
- 过期时间随机化 (TTL Jitter):在设置缓存过期时间时,我会在基础时间上加一个随机偏移量(比如
基础时间 + rand(0, 300)秒),打散过期时间,从源头避免集体失效。 - 多级缓存与高可用兜底:为了防止 Redis 单点故障,我会引入本地缓存(如 Go 的
FreeCache或BigCache)作为第一道防线。即使 Redis 短暂不可用,本地缓存也能拦截大量请求。同时,配合 Redis 集群模式保证高可用,并在服务层做好限流和熔断降级,作为保护数据库的最后底线。