老酸奶网易雷火web后端开发一面
- 讲一下 GMP
- 如果阻塞了会怎么样
- Gin 比起传统库的好处,然后讲一下 Gin 架构的请求处理流程
- HTTP 的报文是怎么组成的
- Feed 流的热榜是怎么做的?
- 这个热榜时间复杂度是多少,有没有方法可以优化一下这个热榜功能?
- 讲一下为什么你这个 Feed 流是先改库再进入消息队列的?
- 别的项目和实习相关的
参考答案(AI 生成)#
以下答案由 AI 生成,仅供面试复盘参考。
1. 讲一下 GMP#
答:GMP 是 Go 调度器的核心模型。G(Goroutine)代表一个 goroutine,包含栈、指令指针等上下文信息;M(Machine)代表操作系统线程,是实际执行单元;P(Processor)代表逻辑处理器,持有本地运行队列,是 G 和 M 之间的桥梁。P 的数量由 GOMAXPROCS 决定,默认等于 CPU 核数。调度流程:每个 P 有一个本地 G 队列,M 绑定 P 后从本地队列取 G 执行;本地队列空时从全局队列或其他 P 的队列偷取(work stealing)。这种两级队列 + 偷取机制减少了全局锁竞争,提升了并发效率。
2. 如果阻塞了会怎么样#
答:Goroutine 阻塞分两种情况:用户态阻塞(如 channel、互斥锁)和系统调用阻塞。用户态阻塞时,Go 调度器会将当前 G 挂起放到等待队列,M 继续绑定 P 执行其他 G,不会浪费线程资源。系统调用阻塞时,M 会和 P 解绑,P 被其他 M 接管继续调度,阻塞的 M 等待系统调用返回后尝试重新获取 P。网络 IO 阻塞则通过 netpoller 实现异步,goroutine 在等待网络数据时被挂起,数据就绪后由 netpoller 唤醒,整个过程不需要额外的线程阻塞。
3. Gin 比起传统库的好处,然后讲一下 Gin 架构的请求处理流程#
答:Gin 相比 net/http 的好处:路由性能更高(基于 radix tree 的 httprouter),支持路由分组、中间件链、参数绑定/校验、JSON 渲染等开箱即用的功能,代码更精简。请求处理流程:请求到达后先经过 Engine.ServeHTTP,Engine 内维护了路由树和中间件链。首先执行全局中间件(如 Logger、Recovery),然后根据 HTTP method 和 path 在 radix tree 中查找匹配的路由。匹配后依次执行路由组级别的中间件和最终的处理函数(HandlerFunc)。中间件通过 c.Next() 实现洋葱模型——Next 前是请求前处理,Next 后是响应后处理。最后 Gin 将响应写入 http.ResponseWriter。
4. HTTP 的报文是怎么组成的#
答:HTTP 报文分请求报文和响应报文。请求报文由四部分组成:请求行(方法 + URL + 协议版本,如 GET /index.html HTTP/1.1)、请求头(键值对,如 Host、User-Agent、Content-Type 等)、空行(CRLF,分隔头部和体)、请求体(可选,如 POST 的 JSON 数据)。响应报文也由四部分组成:状态行(协议版本 + 状态码 + 状态描述,如 HTTP/1.1 200 OK)、响应头(如 Content-Type、Content-Length、Set-Cookie 等)、空行、响应体(HTML/JSON 等实际内容)。HTTP/2 和 HTTP/3 改用二进制帧格式,但语义层概念保持一致。
5. Feed 流的热榜是怎么做的?#
答:常见方案是用 Redis 的 ZSet(有序集合)实现。ZSet 的 member 存内容 ID,score 存热度分数。热度分数通常由时间衰减 + 互动加权计算得出,例如 score = 点赞数*权重1 + 评论数*权重2 + 分享数*权重3 - 时间衰减因子。定时任务(如每分钟)重新计算热度分数并更新 ZSet。热榜查询时直接 ZREVRANGE 按 score 倒序取 Top N。拉取 Feed 流时,先拉热榜内容再补时间线内容,做去重合并后返回给用户。对于大规模场景,可以分层:先在本地缓存热榜 Top N,再用定时任务异步更新。
6. 这个热榜时间复杂度是多少,有没有方法可以优化一下这个热榜功能?#
答:Redis ZSet 的热榜查询(ZREVRANGE)时间复杂度是 O(log(N) + M),N 为 ZSet 元素总数,M 为返回条数。实时计算所有内容的热度分数会是 O(N),在数据量大时成为瓶颈。优化方案:
- 分层热榜:按时间分桶(如每小时一个 ZSet),查询时只合并最近几个桶,减少每次计算量;
- 异步离线计算:不在请求链路中实时算热度,而是定时任务批量计算后写入缓存;
- 近似热榜:只对近期活跃内容计算热度,非活跃内容直接排除,使用布隆过滤器过滤;
- 本地缓存:热点榜单缓存到本地内存,设置短 TTL(如 10s),减少 Redis 访问;
- 热度增量更新:每次互动事件发生时增量更新 score(
ZINCRBY),避免全量重算。
7. 讲一下为什么你这个 Feed 流是先改库再进入消息队列的?#
答:先改库再发消息队列是为了保证数据一致性和可追溯性。数据库中记录的是一次操作的真实结果,即使消息队列投递失败或消费者处理异常,数据本身不会丢失,可以通过补偿机制(定时扫表、重试)修复。如果反过来先发消息再改库,消息发出去了但写库失败,会导致下游拿到了不存在或过期的数据,且难以回滚和排查。这种"先持久化、后通知"的模式本质上是 outbox 模式的简化版。
8. 别的项目和实习相关的#
答:这题主要是面试官结合简历进行自由追问,重点准备:每个项目的背景、技术选型理由、核心难点、个人贡献、可量化的成果。回答时按 STAR 原则组织(Situation、Task、Action、Result),每个亮点都要能落到具体的技术细节。实习经历要突出业务理解、团队协作和工程落地能力。