电科金仓一面:Redis 削峰与统计场景连环追问
- 轮次
- 一面
- 时间
- 2026-09
- 来源
- 牛客网
《面试题目》
项目 1:
- 项目中遇到的问题是什么,怎么解决的?
- 怎么用 Redis 做到流量削峰?
- 介绍下 Redis Stream,它有什么缺点?
- Redis 宕机会怎样,怎么优化?
- 签到统计为什么用 bitmap?
- 点赞统计为什么用 zset,怎么做分页优化?
项目 2:
- 解析知识库文档遇到过什么问题?
- 做 TopK 检索的时候,如果检索到的文档相关度低怎么办?
其他:
- Spring 的 AOP
@Transactional失效的原因- MySQL 索引结构
《参考解析》
Redis Stream 削峰:能顶住,但不是万能队列
把突发流量写进 Stream,消费者用 XREADGROUP 配合消费组按自己的处理能力拉取,前端只负责入队不做事,下游数据库就不会被瞬间打满,堆积量还能通过 XLEN、XPENDING 观测。它的缺点是:数据在内存里,成本高于磁盘 MQ;持久化依赖 RDB/AOF,主从切换或实例宕机时仍可能丢消息;没有 Kafka 那种分区并行和长期留存能力,横向扩展靠分片;消费确认要自己做,忘了 XACK 消息会一直挂在 PEL 里,需要 XAUTOCLAIM 之类兜底;堆积到百万级时内存和主线程都会吃紧。所以更稳的架构是 Redis 只做入口缓冲或计数,真正的削峰落到 Kafka / RocketMQ 这类磁盘队列上,并配好限流、降级和幂等消费。
Redis 宕机:先分级,再谈优化
要区分它承担的是缓存、分布式锁、计数器还是权威状态存储。只当缓存时,挂掉可以穿透到数据库,但必须配限流、熔断和请求合并,否则库会被瞬间打垮;分布式锁挂掉会让互斥失效,业务侧要有幂等和超时兜底;计数器类数据要考虑丢失能否接受,必要时落库对账。优化方向是高可用加可恢复:哨兵或 Cluster 做主从切换与故障转移,按「能丢多少数据」在 RDB、AOF everysec、always 之间取舍,客户端配好超时、重试上限、连接池和熔断(避免重试风暴),热点读用本地缓存兜一层。对绝对不能丢的数据,不要让 Redis 作为唯一存储。
签到用 bitmap、点赞用 zset
签到是「某用户某天签没签」这种一维布尔状态,按用户或按月建 key、用 SETBIT 写入,一个用户一年只占 365 位(约 46 字节),BITCOUNT 直接算总天数,BITOP 还能做多用户统计,比存一堆行或集合省得多。点赞的诉求是「谁点了赞、某人点过哪些、按时间排序取一页、看总数」,zset 用时间戳或自增序号当 score 正好支持范围查询和排序:ZADD 写入、ZCARD 取总数、ZREVRANGE 翻页。分页要避免大 offset 深翻,改成游标式——记住上一页最后一条的 score,用 ZREVRANGEBYSCORE key max min LIMIT 0 n 继续取,或把 (score, member) 当游标;热门内容还要考虑大 key 拆分(按内容 ID 分片)和读写分离。
文档解析与 TopK 相关度低
知识库解析的常见坑是格式五花八门(PDF 双栏、扫描件、表格、代码块)导致抽取错位、乱码或丢内容,以及切分颗粒度不当把上下文切断。工程上按格式分流解析器,保留页码、标题路径等溯源信息,做去重和去噪(页眉页脚、水印),并记录解析失败率。TopK 相关度低时要先判断是「没召回」还是「排错了」:召回层面加混合检索(向量 + BM25 关键词)、查询改写与扩展、父子块(小块检索、大块喂给模型);排序层面加 rerank 模型或按业务权重(时效、权威性、权限)重排;如果证据确实不足,正确行为是明确拒答或转人工,而不是硬编一个答案。上线后要有离线评测集(Recall@K、MRR)和线上指标(无依据回答率、人工转接率)来验证改动真的有效。
AOP 与 @Transactional 失效
Spring 的 AOP 默认基于代理:目标类实现接口时用 JDK 动态代理,否则用 CGLIB 生成子类。因此同类内部方法直接调用(this.method())不走代理,事务注解不生效;private、final 方法以及 final 类也代理不到。其他常见失效原因:异常被 catch 掉没有抛出;抛出的是受检异常,而默认只对 RuntimeException 和 Error 回滚(需要 rollbackFor);传播行为设成 REQUIRES_NEW 或 NOT_SUPPORTED,导致不在同一个事务里;多数据源或多事务管理器没有指定;注解加在调用方而不是被调用方。修法是把逻辑拆到另一个 Bean 里调用、用 AopContext.currentProxy() 或注入自身,或者干脆用 TransactionTemplate 显式控制事务边界。
MySQL 索引结构
InnoDB 用 B+ 树:非叶子节点只存键和指针,叶子节点存数据并按序用链表相连,所以树更矮(通常三四层就能放千万级数据)、范围扫描和排序都友好。主键索引是聚簇索引,叶子直接存整行;二级索引叶子存主键值,查非索引列需要回表,这就是覆盖索引能省一次查找的原因。联合索引遵循最左前缀,范围条件之后的列用不上索引;LIKE '%x'、对索引列做函数运算或隐式类型转换都会让索引失效。另外要理解页(默认 16KB)与缓冲池的关系:索引查找的代价主要来自磁盘 IO,命中缓冲池就快,这也是「索引建了但没走」时要看执行计划和回表次数的原因。