总结遇到的面试题-Redis篇

1. Redis基本数据结构和底层

https://zhuanlan.zhihu.com/p/148562122

介绍一下跳表:跳表是一种多层索引的链表结构,查找、插入、删除的平均复杂度是 O(log⁡n)。在 ZSet 中,Redis 同时用哈希表存储成员到分值的映射,用跳表存储分值到成员的映射,这样既能快速定位单个元素,又能高效支持范围查询。

2. Redis为什么快

这个问题大致来说可以从两个方向来回答:内存读写相比于磁盘读写速度要快上一个数量级,磁盘读写由于物理读写方式访问速度比较慢;另一个方面就可以对Redis的设计具体展开,包括但不限于:Redis单线程模型、Redis数据结构设计、Redis的I/O模型,这一部分可以对自己更熟悉的部分深入讲解。例如就单线程模型而言,Redis使用单线程避免了锁的竞争和多线程的复杂,同时Redis使用 I/O 多路复用机制,一个线程就能同时处理大量连接。

3. 如何估算某个业务场景下Redis内存占用大小

这是一个相对来说开放的情景题,如果没有非常贴的实习经历可能没有办法给出准确而深入的回答,不过也可以尽力从基础知识出发回答(至少展现有基础)。很容易知道总占用内存是并发用户 * 每个用户的信息占用Redis,并发用户可以通过业务API的QPS判断,每个用户的信息占用Redis可以举例一个相对具体的场景:例如在登录场景中,可以用String存储session相关信息,通过Hash来存储用户信息,除此之外需要考虑Redis数据结构的开销,对象头、指针、对齐方面的开销;最后从整体来看还需要考虑一部分的冗余,Redis的内存碎片、主从/集群冗余提高可用性;在登陆场景中,用户登陆的验证码或者是session不会永久存储,需要设置TTL例如30min,对于整体数据的淘汰策略通常选择 allkeys-lruvolatile-lru控制内存占用。实际应用场景中也可以通过监控来查看内存的使用情况。此外Redis持久化缓冲区可能会暂时占用内存,不过持久化用到的RDB快照和AOF日志都是存储到磁盘中的。总的来说主要要考虑使用什么数据结构,采取什么淘汰策略,围绕Redis的设计来谈(所以其实也可以认为回到了问题1)。

4. Redis除了缓存还可以用于什么

Redis除了用于缓存还可以作为分布式锁、消息队列来使用,这里可以选择一个自己相对熟悉的方面来展开。如果是从消息队列的角度展开,Redis作为消息队列相比于专业的消息队列的优点是简单快速轻量级,但根本上来说由于Redis本身是基于内存的中间件,

内存是易失性存储,所以基于 redis 实现的 mq 一定是存在消息丢失的风险的。

从数据结构选取来说,Redis实现消息队列时,可以用Redis中list这个数据结构,天然符合队列模型,如果是延时消息队列则可以用zset实现,score是时间戳;如果消费采取的是pull模型,可以用brpop指令阻塞式获取;为了保证消息不丢,Redis streams中实现了ACK机制,通过ack和引入持久化来保证可靠性。由于Redis是内存存储,通过Redis streams持久化也是异步执行的,所以依然有数据丢失的风险,为了优化这一点可以基于观察者模式设计Redis监控系统,XPENDING 用于统计未确认消息和失败次数,根据需求设置重试策略,如果某条消息多次失败,可以通过 XPENDING 获取其 delivery count,当超过阈值时,将消息写入数据库并触发提醒。另外Redis不支持消息分区,相比于专业的MQ吞吐量会小,再一次说明了Redis作为队列适用轻量级场景。

这篇文章对Redis作为消息队列解析得非常透彻https://zhuanlan.zhihu.com/p/651758438

PHP对Redis作为队列的支持https://docs.golaravel.com/docs/9.x/queues#dealing-with-failed-jobs

5. Redis是CP模型还是AP模型?Redis是如何实现高可用的(Availability)?

Redis 默认采用异步复制,主节点写入后立即返回,从节点异步接收数据。在网络分区或主从延迟时,可能出现读到旧数据的情况,因此 Redis 更倾向于AP系统。

一般来说高可用都可以从以下这几个方面回答:数据冗余、故障转移(根据备份的数据或者状态机设计)、负载均衡、分布式系统设计。对于Redis而言有好几种高可用的机制:如果是主从复制,主负责写从负责读,则是提供了数据冗余和读扩展能力;如果是哨兵机制,则进一步提供了故障转移能力,哨兵具有监控、选主和通知这三个功能,选择leader并不是通过Paxos或者Raft,而是通过投票+简单多数的原则实现的,选出leader后由leader选出主节点完成故障转移/自动化修复,在投票-选举-选主整个过程也可以理解为状态机状态转移,而且这个期间Redis集群会有短暂写不可用的期间,读可用但数据不一致,印证了CAP理论;如果是Redis cluster,则是将数据分片存储在多个节点上,实现水平扩展,提供自动分区和故障转移。除了Redis本身之外的高可用则可以考虑业务上负载均衡、限流熔断等等的实现。

附加题:如果Redis的哨兵也故障了会怎么样?

6. Redis单实例可承受的QPS?

主播没测过,问copilot得到的答案是:单实例 Redis 的 QPS 在简单读写操作下可以达到几十万到百万级别,通常受限于网络和内存带宽,而不是 CPU。实际生产环境中,稳定的 QPS 一般在几十万左右。如果需要更高吞吐量,可以通过 Redis Cluster 分片扩展。

7. Redis持久化

这篇文章解析得很详细https://www.cnblogs.com/xiaokang-coding/articles/18531836

有几个细节可能会被拷问:例如RDB的原理、AOF日志重写和混合持久化如果没被拷问知道也可以详细展开体现八股功力深厚(bushi

8. 在一个秒杀系统中,有一个消费券的库存ID,每秒可能有百万级别的访问,要怎么设计这个系统来确保库存ID不会超额扣减以及访问该数据的可用性/一致性?(用Redis作为缓存)

虽然这个问题说是用Redis作为缓存,但其实是一个相对综合的系统设计题,核心是高并发写冲突下的分布式一致性控制问题,答辩需要采取总分(总)的结构,首先说明框架,然后根据个人的基础知识/实习经历补充具有含金量的细节。总就是对于这样的高QPS场景有一个高可用的系统框架,需要考虑接入层、负载层、缓存服务、消息队列、持久层和监控系统。接下来可以按照请求的逻辑顺序进行细节展开:在进入后端服务之前,接入层一般的主流设计是nging在七层(传输层)的负载均衡和LVS在四层的负载均衡,采取CDN静态加载减轻源站压力,网关可以利用令牌桶/滑动窗口限流,API层面需要鉴权/风控/限流/用户频次限制;这种量级比较大的重要活动可以提前做准备,预先生成token列表,强制保证不超卖,每个请求通过RPOP拿到一个token,拿到了则可以异步生成订单并写库;另一种方案就是请求到达Redis做原子检查并扣减(可以通过Redis原子命令或者lua脚本实现),由于Redis单例可承受QPS在10w-20w左右,应对百万QPS还需要库存分段、Redis Cluster 多 slot 分散,然后再异步写入数据库(这里因为QPS高所以要异步);然后需要通过消息队列削峰,可以使用pull模式逐步处理避免数据库压力太大,这里就可以介绍MQ如何保证消息不丢不重和最终一致性了;数据库层面作为最终账本(source of record),使用数据库级的写条件(例如 UPDATE coupon SET stock = stock - 1 WHERE id = ? AND stock > 0)作为最后一层防线;监控系统方面需要观察机器的重要指标(latency、cpu_idle等),打印告警日志灰度扩容等等。在体量比较大和保证高可用的情况下,整个过程中用到的中间件和数据库都是集群部署的,如果订单地区跨度比较广,还可以按地域拆分库存,即单元化架构+流量染色,往往需要对不同地区的整个系统做单元化部署。

这里还有很多细节可以深入展开:例如令牌桶和滑动窗口的实现,运维方面的容器部署和容错(docker和k8s),就不继续深入展开了。

异地多活可以参考这篇文章https://www.cnblogs.com/crazymakercircle/p/19021590

拖了很久慢悠悠的写这篇总结,林林总总暂时想起来这些问题,依然鸣谢gpt和拷打指点我的面试官

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务