面试官问 Redis 为什么能扛高并发,你会结合项目里的延迟和瓶颈怎么回答?
这个问题我会先说项目结论:Redis 快来自内存访问、高效数据结构、事件驱动 I/O、紧凑协议和较少上下文切换。经典核心命令主要串行执行,避免对共享数据结构频繁加锁;现代版本可以用 I/O 线程处理网络读写,但命令语义仍主要在核心线程执行。
我会先交代项目背景和选型结论。从内存访问、事件循环、数据结构和 I/O 多线程解释 Redis 性能。Redis 快来自内存访问、高效数据结构、事件驱动 I/O、紧凑协议和较少上下文切换。经典核心命令主要串行执行,避免对共享数据结构频繁加锁;现代版本可以用 I/O 线程处理网络读写,但命令语义仍主要在核心线程执行。 “单线程”不是整个 Redis 进程只有一个线程,持久化、异步释放、复制和 I/O 都可能使用其他线程。
具体落地时,我会沿着实际调用链来讲。沿着「客户端发送命令 → 事件循环读取请求 → 单线程执行核心命令 → 内存数据结构完成操作 → 缓冲响应并批量写回」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 客户端发送命令 RESP 协议解析简单,连接复用和 pipeline 可以摊薄网络往返,但大请求仍会占用带宽和解析时间。 事件循环读取请求 Redis 用事件驱动 I/O 处理大量连接,新版本可用 I/O 线程分担读写,命令执行核心仍保持顺序语义。 src/server.c processCommand:命令分派入口。 src/ae.c:事件循环与文件事件。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
参数和容量不能靠默认值,我会结合业务量来定。用相同连接分别测试单命令、pipeline 与一个 O(n) 大命令,观察吞吐和其他客户端尾延迟。
效果要用数据证明,线上问题也要能闭环。固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「event loop lag」为主基线,记录值应满足「亚毫秒基线」;同时保存 commandstats 延迟、SLOWLOG,使后续变化能够回到同一时间轴比较。 某任务对超大 Set 执行 SMEMBERS,命令执行期间事件循环无法服务其他请求,形成数百毫秒阻塞。改为 SSCAN 分批、限制集合大小并监控慢命令后,尾延迟恢复。 O(n) 命令阻塞事件循环:commandstats 延迟:大集合改 SCAN/分批。 大 Key 删除造成长停顿:SLOWLOG:设置 slowlog 告警。
最后我会主动说明这个方案不适合什么场景。方案:更适合的场景:主要收益:代价与边界。 单次命令:低频或强依赖前序结果:语义直接:多次网络 RTT。 Pipeline:批量独立命令:显著减少 RTT:响应积累占内存,非原子。 Lua/Function:短小读改写需原子执行:服务端一次完成:长脚本会阻塞整个实例。 选型至少带上 Key 数量、Value 大小、命令复杂度、热点分布和过期速率,并用上面的量化基线验证;未知数据应明确为待测假设。