🔥 面试 | Redis 相关



Redis 为什么这么快?

  1. Redis uses RAM not disk,
  2. I/O mutiplexing and single-threaded event loop
  3. Efficient Data Structure

But Redis memory cost is too high, and data persistence provided by Redis still has the risk of data loss.

为什么要用 Redis?

  1. Faster access speed
  2. high concurrency - its QPS reaches easily 50-100k
  3. conprehesive features - besides as a cache, also used for distributed locks, current limiting, message queues, delay queues.

为什么用 redis 而不用本地缓存?

Local Cache can not share , rely on JVM memory. It just suitable for standalone system.

Redis can share memory with multiple instances, preserve consisitency, implement hight-performance access and persistent storage. Redis is suitable for distribued system.

Redis 单线程为什么还能这么快?

Because memory operations are fast, single-threaded operations avoid lock contention, and combined with efficient I/O multiplexing mechanisms, performance is very high.

内存操作本来就快,单线程操作也避免了锁竞争,同时结合了高效的 I/O多路复用机制。

Redis 里适合添加哪些数据?

  1. 高频查询、但不常更新的数据

    • 商品详情、用户基本信息、系统配置参数
    • 优点:减少 DB 压力,响应快
  2. 计算开销大、但结果可复用的数据

    • 统计结果(PV/UV、排行榜)
    • 推荐结果(个性化推荐列表)
    • 复杂 SQL 聚合结果
  3. 需要快速读写的数据

    • Session(分布式会话管理)
    • Token / 验证码
    • 临时状态数据(购物车、点赞数)
  4. 热点计数/排行榜

    • 浏览量、点赞量 → String / Hash 计数
    • 排行榜 → ZSet 排序
  5. 限流、防刷相关数据

    • IP 访问频率 → String + INCR
    • 用户操作频次 → Token Bucket / Leaky Bucket

不适合放进 Redis 的数据

  • 冷数据(很少访问) → 放缓存没意义,还占内存。
  • 频繁更新且强一致性要求的数据 → 比如金融交易流水,应直接落库。
  • 超大对象 / 大文件 → Redis 不适合存大文件,应该放 OSS、磁盘。

Redis 如何判断 KEY 是否过期呢?

There are two Dicts in Redis, also named Hashtable. the one records key-value pairs, the other one records the key and the expiry time. we check whether a key is expired, we just need to query in the second Dict by the key.

在Redis中会有两个Dict,也就是HashTable,其中一个记录KEY-VALUE键值对,另一个记录KEY和过期时间。要判断一个KEY是否过期,只需要到记录过期时间的Dict中根据KEY查询即可。

Redis何时删除过期KEY?如何删除?

Redis has two strategies for handling expired keys: lazy deletion and periodic deletion.

lazy deletion checks the key’s expiration time each time a user accesses it. If the key has expired, it is deleted; if not, it is ignored.

Periodic deletion has two modes:
slow mode means regular sampling and detection of some keys with TTL through scheduled tasks.
fast mode means sampling and detection of some keys with TTL before Redis handle NIO event.

Redis的过期KEY处理有两种策略,分别是惰性删除和周期删除。
惰性删除是指在每次用户访问某个KEY时,判断KEY的过期时间:如果过期则删除;如果未过期则忽略。
周期删除有两种模式:

  • SLOW模式:通过一个定时任务,定期的抽样部分带有TTL的KEY,判断其是否过期。默认情况下定时任务的执行频率是每秒10次,但每次执行不能超过25毫秒。如果执行抽样后发现时间还有剩余,并且过期KEY的比例较高,则会多次抽样。
  • FAST模式:在Redis每次处理NIO事件之前,都会抽样部分带有TTL的KEY,判断是否过期,因此执行频率较高。但是每次执行时长不能超过1ms,如果时间充足并且过期KEY比例过高,也会多次抽样

当 Redis 内存不足时会怎么做?

It depends on the configured memory eviction policy. Redis supports a variety of memory eviction policies, such as LRU, LFU and Random.

However, the default pilicy directly rejects new write requests.

If other policies are configured, Redis will check whether the memory usage has reached a threshold after each command execution.

If the threshold is reached, Redis will attempt to evict memory based on the configured eviction policy until the memory usage falls below the tghreshold.

这取决于配置的内存淘汰策略,Redis支持很多种内存淘汰策略,例如LRU、LFU、Random. 但默认的策略是直接拒绝新的写入请求。而如果设置了其它策略,则会在每次执行命令后判断占用内存是否达到阈值。如果达到阈值则会基于配置的淘汰策略尝试进行内存淘汰,直到占用内存小于阈值为止。

那你能聊聊LRU和LFU吗?

LRU counts the keys that have been unused for the longest time. Each time memory eviction is required, a sample of keys is sampled, the keys with the longest idle time are found, and then these keys will be deleted.

LFU counts the keys with the least recent use, that means the key access frequency. Each time memory eviction is required, a sample of keys is sampled, the keys with the lowest logical access count are found, and then these keys will be eliminated.

LRU是最近最久未使用。Redis的Key都是RedisObject,当启用LRU算法后,Redis会在Key的头信息中使用24个bit记录每个key的最近一次使用的时间lru。每次需要内存淘汰时,就会抽样一部分KEY,找出其中空闲时间最长的,也就是now - lru结果最大的,然后将其删除。如果内存依然不足,就重复这个过程。

由于采用了抽样来计算,这种算法只能说是一种近似LRU算法。因此在Redis4.0以后又引入了LFU算法,这种算法是统计最近最少使用,也就是按key的访问频率来统计。当启用LFU算法后,Redis会在key的头信息中使用24bit记录最近一次使用时间和逻辑访问频率。其中高16位是以分钟为单位的最近访问时间,后8位是逻辑访问次数。与LFU类似,每次需要内存淘汰时,就会抽样一部分KEY,找出其中逻辑访问次数最小的,将其淘汰。

如何保证缓存的双写一致性?

Double-write consistency in caches is difficult to guarantee strong consistency. we can only minimize the probability of inconsistencies and ensure eventual consistency. Our project uses Cache Aside model.

Simply put, the cache is deleted after the database is updated. During queries, the cache is consulted first. If a miss occurs, the database is consulted and the cache is written.

We also set an expiration time for the cache as a fallback. If inconsistencies do occur, we can ensure eventual consistency by expiring the cache.

缓存的双写一致性很难保证强一致,只能尽可能降低不一致的概率,确保最终一致。

我们项目中采用的是 Cache Aside 模式。Cache Aside Pattern 中服务端需要同时维系 db 和 cache,并且是以 db 的结果为准。

简单来说,就是在更新数据库之后删除缓存;在查询时先查询缓存,如果未命中则查询数据库返回并写入缓存。同时我们会给缓存设置过期时间作为兜底方案,如果真的出现了不一致的情况,也可以通过缓存过期来保证最终一致。

在写数据的过程中,可以先删除 cache ,后更新 db 么?在写数据的过程中,先更新 db,后删除 cache 就没有问题了么?

不行,先删除 cache ,后更新 db 会造成数据库和缓存数据不一致。

先更新 db,后删除 cache 理论上还是会出现数据不一致性的问题,不过概率很小,因为缓存的写入速度是比数据库的写入速度快很多。

为什么不采用延迟双删机制?

The first deletion in a delayed double delete operation is meaningless. The second delayed deletion is primarily used to address latency issues in database master-slave consistency issue and has nothing to do with cache synchronization.

Since the master node data has been updated, the Redis cache should also be updated. Furthermore, delayed double delete increases cache complexity and fails to completely eliminate cache consistency issues, resulting in a low return on investment.

答:延迟双删的第一次删除并没有实际意义,第二次采用延迟删除主要是解决数据库主从同步的延迟问题,我认为这是数据库主从的一致性问题,与缓存同步无关。既然主节点数据已经更新,Redis的缓存理应更新。而且延迟双删会增加缓存业务复杂度,也没能完全避免缓存一致性问题,投入回报比太低。

如何解决缓存穿透问题?

Cache penetration, occurs when a request accesses a value that doesn’t exist in the database. This causes a cache hit failure, forcing the request to access the database. So Highly concurrent access to such an interface can place signicant pressure on the database.

our project uses Bloom filters to handle cache penetration. when a cache miss occurs, the filter checks whether the data exists. If it doesn’t exist, the database access is skipped.

Alternatively, caching null values can be used, but this solution will waste memory.

缓存穿透也可以说是穿透攻击,具体来说是因为请求访问到了数据库不存在的值,这样缓存无法命中,必然访问数据库。如果高并发的访问这样的接口,会给数据库带来巨大压力。
我们项目中都是基于布隆过滤器来解决缓存穿透问题的,当缓存未命中时基于布隆过滤器判断数据是否存在。如果不存在则不去访问数据库。
当然,也可以使用缓存空值的方式解决,不过这种方案比较浪费内存。

如何解决缓存雪崩问题?

there are two common causes of cache avalanche.
First, a large number of keys is expired at the same time. To handle this, we can set different TTL values for cache keys to prevent simultaneous expiration.

Second, Redis crash leads to cache unavailability. To handle this, we can use clustering or implement multi-level caching to improve Redis availability.

缓存雪崩的常见原因有两个,第一是因为大量key同时过期。针对问这个题我们可以可以给缓存key设置不同的TTL值,避免key同时过期。
第二个原因是Redis宕机导致缓存不可用。针对这个问题我们可以利用集群提高Redis的可用性。也可以添加多级缓存,当Redis宕机时还有本地缓存可用。

如何解决缓存击穿问题?

Cache breakdown is often caused by hot keys. when a hot key expires, a large number of requests access redis simultaneously. any cache miss will result in a database access, causing significant database pressure.

So if we solve this problem, we have to prevent multiple threads from concurrently rebuilding the cache.

the first solution is based on a mutex lock. when a cache miss occurs, redis needs to get the mutex lock , then rebuild cache, and finally release the lock after finishing rebuilding. This ensures that only one thread is executing a cache rebuild at a time.

the second solution is based on logical expiration. that means we don’t set the expiration time for the hot key, we just add an expiration field to the data. This ensures that the data is always available in the cache.

答:缓存击穿往往是由热点Key引起的,当热点Key过期时,大量请求涌入同时查询,发现缓存未命中都会去访问数据库,导致数据库压力激增。解决这个问题的主要思路就是避免多线程并发去重建缓存,因此方案有两种。
第一种是基于互斥锁,当发现缓存未命中时需要先获取互斥锁,再重建缓存,缓存重建完成释放锁。这样就可以保证缓存重建同一时刻只会有一个线程执行。不过这种做法会导致缓存重建时性能下降严重。
第二种是基于逻辑过期,也就是不给热点Key设置过期时间,而是给数据添加一个过期时间的字段。这样热点Key就不会过期,缓存中永远有数据。
查询到数据时基于其中的过期时间判断key是否过期,如果过期开启独立新线程异步的重建缓存,而查询请求先返回旧数据即可。当然,这个过程也要加互斥锁,但由于重建缓存是异步的,而且获取锁失败也无需等待,而是返回旧数据,这样性能几乎不受影响。

需要注意的是,无论是采用哪种方式,在获取互斥锁后一定要再次判断缓存是否命中,做dubbo check. 因为当你获取锁成功时,可能是在你之前有其它线程已经重建缓存了。

Sentinel的三个作用是什么?

  • 集群监控
  • 故障恢复
  • 状态通知

Sentinel如何判断一个redis实例是否健康?

  • 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线(sdown)
  • 如果大多数sentinel都认为实例主观下线,则判定服务客观下线(odown)

故障转移步骤有哪些?

  • 首先要在sentinel中选出一个leader,由 leader 执行 failover
  • 选定一个 slave 作为新的 master,执行 slaveof no one,切换到 master 模式
  • 然后让所有节点都执行 slaveof 新 master
  • 修改故障节点配置,添加 slaveof 新 master

sentinel 选举 leader 的依据是什么?

  • 票数超过 sentinel 节点数量 1 半
  • 票数超过 quorum 数量
  • 一般情况下最先发起 failover 的节点会当选

sentinel 从 slave 中选取 master 的依据是什么?

  • 首先会判断 slave 节点与 master 节点断开时间长短,如果超过down-after-milliseconds * 10则会排除该 slave 节点
  • 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0则永不参与选举(默认都是1)。
  • 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高
  • 最后是判断 slave 节点的 run_id 大小,越小优先级越高(通过info server 可以查看 run_id)。

如何实现排行榜

use sorted set, store the score in score, ZADD add, use ZREVRANGE to gain the top users.

如何实现延时队列?

use sorted set, save expiration time in score, periodically poll for expired tasks.

如何实现分布式session

we can save user infomation session to Redis, and share with multiple applicaitons.

redis 的数据持久化有哪些方式?Data persistence

RDB、AOF、mixture of RDB and AOF

RDB uses periodic snapshot, saving memory data to disk.

AOF can append logs to record logs for each write operation.

redis 持久化 RDB 和 AOF 有哪些优缺点?

RDB : small file, fast recovery, but the data of last few seconds may be lost.

AOF: more safe, almost no data is lost, but the files are large, recovery is slow.

Redis 分布式锁的应用场景

  1. 秒杀 / 抢购
  • 多人同时购买库存有限的商品,需要防止超卖。
  • 解决思路:在减库存前获取分布式锁,保证同一时间只有一个线程能修改库存。
  1. 防止重复下单
  • 用户点击多次支付,可能生成多笔订单。
  • 解决思路:针对 userId:productId 设置锁,保证同一用户同一商品只会生成一笔订单。
  1. 定时任务并发控制
  • 分布式系统中,同一任务可能被多个节点同时执行。
  • 解决思路:利用分布式锁,确保只有一个节点执行任务,其它节点等待或放弃。
  1. 资源抢占
  • 比如多个服务节点竞争写某个配置文件,或者执行一次初始化操作。
  • 解决思路:先抢到锁的节点才能执行,其他节点等锁释放。
  1. 接口限流 / 幂等控制
  • 避免接口在同一时间被并发调用多次,造成数据不一致。

分布式锁的注意事项

  • 锁要有过期时间:避免宕机后永远不释放。
  • 释放时要校验 value:保证只释放自己的锁。
  • 原子性操作:获取和设置过期时间必须是原子操作(用 SET key value NX PX)。
  • 高可用:单节点 Redis 可能挂掉,业务常用 Redisson 或 Redlock 来保证可靠性。