面灵AI→

腾讯云智武汉腾讯会议工作室后端开发实习一面面经

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 自我介绍。
  2. Java 的常见集合类型有哪些?
  3. 说说 CopyOnWriteArrayList。
  4. 实习期间做了哪些内容?
  5. 个人博客项目里边,文章缓存是怎么设计的?
  6. 博客项目的指纹限流是怎么设计的?
  7. 说一下 MySQL 的索引下推。
  8. Redis 的数据结构有哪些?
  9. Redis 的 String 类型底层结构是什么?
  10. 手撕:用自定义双向链表实现 LRU。
  11. 能实习多久?
  12. 反问:业务方向与工作地点。

《参考解析》

Java 常见集合

Collection 下面分 List、Set、Queue 三条线,Map 单独一支。List 常用 ArrayList 和 LinkedList:前者是数组,随机访问 O(1)、中间插入要搬数据;后者是双向链表,头尾增删 O(1)、按索引访问要遍历。Set 里 HashSet 基于 HashMap,元素无序;LinkedHashSet 用双向链表额外维护插入顺序;TreeSet 基于红黑树,天然有序,范围查询方便。Queue 侧有 ArrayDeque、PriorityQueue 和阻塞队列 ArrayBlockingQueue、LinkedBlockingQueue。Map 最常用 HashMap,需要有序时用 TreeMap,需要并发时用 ConcurrentHashMap。选型的落点是「要不要顺序、要不要并发、读多还是写多」。

CopyOnWriteArrayList

写时复制:任何修改操作都先加锁、复制一份新数组、在新数组上改完,再把内部引用指向新数组,读操作完全不加锁,直接读当前引用。好处是读的性能极高且不会抛 ConcurrentModificationException,迭代器拿到的是一个快照。代价有两条:一是每次写都要全量复制,写多的时候内存和 CPU 都吃不消;二是写完之后别的线程可能还在读旧数组,只能保证最终一致,不保证实时可见。所以它适合「读远多于写、且集合本身不大」的场景,典型是监听器列表、配置白名单,不适合当缓存容器。

博客的文章缓存设计

关键要回答三件事:缓存什么、怎么更新、失效了怎么办。缓存的粒度上,列表页和详情页分开,详情按文章 id 缓存实体,列表页缓存 id 列表再回表,避免文章一改就要清掉整个列表。更新策略上选「先更新数据库、再删缓存」(Cache Aside),不用先删缓存再更新库,因为并发下那样更容易留下脏数据;删除失败时靠延迟双删或者订阅 binlog 兜底。防穿透上对不存在的 id 缓存空值并设短 TTL,或者用布隆过滤器挡在前面;防击穿用互斥锁或单飞合并同一篇的并发回源;防雪崩给过期时间加随机抖动。热点文章可以用本地缓存加一层,但要接受多实例间的短暂不一致。

指纹限流

按「设备指纹 + 接口 + 时间窗」计数,比单纯按 IP 更抗代理池,比按用户 id 更能覆盖未登录场景。实现上一般用 Redis 的滑动窗口:用 ZSET 存请求时间戳,每次请求先按窗口起点清理过期成员再计数,超阈值拒绝;量特别大的接口用固定窗口加计数器更省内存,代价是窗口边界可能被突刺。要额外处理两点:一是失败请求要不要计数(通常只计成功和业务失败,不计系统错误),二是被限流后返回 429 并带上 Retry-After,让客户端退避而不是继续打。

MySQL 索引下推

索引下推(Index Condition Pushdown,ICP)是 MySQL 5.6 引入的优化:在联合索引上,把本该在 Server 层做的过滤条件下推到存储引擎层,在遍历索引的过程中就判断掉不满足的记录,只把命中的行回表。举个典型例子,联合索引 (name, age) 上执行 where name like '张%' and age = 20,没有 ICP 时引擎会先把所有 name 前缀为「张」的主键都取出来回表,再交给 Server 层筛 age;有 ICP 时,age 的条件在索引上就判断完了,回表次数大幅减少。它的适用前提是条件里的字段都在索引中,且访问类型是 range、ref 这类;explain 的 Extra 里出现 Using index condition 就说明生效了。

Redis 的 String 底层结构

不是直接用 C 字符串,而是 SDS(Simple Dynamic String):结构里同时存 len(已用长度)、alloc(已分配容量)和 char buf[]。这样取长度是 O(1),不用像 strlen 那样遍历;追加时先看剩余空间够不够,不够才扩容,扩容策略是小于 1 MB 时翻倍、大于 1 MB 时每次加 1 MB;因为有 len 记录,内容里可以正常保存 ‘\0’,二进制安全。SDS 还保留了末尾的 ‘\0’ 以便复用部分 C 字符串函数。另外 String 类型的值在 Redis 内部还有三种编码:整数用 int 存,长度不超过 44 字节的短字符串用 embstr(对象头和内容一次分配),超过 44 字节用 raw(分开分配)。

手写双向链表实现 LRU

核心是「哈希表 + 双向链表」:哈希表存 key 到节点的映射,保证 O(1) 定位;双向链表的头部放最近使用的节点,尾部放最久未使用的。get 命中时把节点摘下再插到头部;put 时如果 key 已存在就更新值并移到头部,不存在就新建节点插头部,同时判断容量是否超限,超了就把尾部节点删掉并从哈希表移除。用双向链表而不是单向,是因为删除节点需要拿到前驱,用哨兵头尾节点可以省掉大量空指针判断。面试里常见的手写要求还包括线程安全版本,那就把整条链路的操作放进同一个锁或者分段锁里。

逆问:业务方向与工作地点

这一类问题要问得具体:团队当前负责的是腾讯会议哪一块能力(会控、录制、还是计费),实习生进来通常会先接手什么模块,以及有没有机会接触线上流量。工作地点直接确认是武汉还是深圳、需不需要两地跑,以及实习的考勤和转正口径。