被拷打四十多个问题!人都问懵了

464 阅读11分钟

好久没有分享最新的面经了,今天分享的是训练营内部的朋友在成都xx信息的实习一面面经,问了四十多个问题。

八股都是一些很常见的题目,项目相关的问题有些也值得参考学习一下

八股相关(与项目无关)

1. GC机制了解吗

从演进历程开始回答,把每个版本的GC内容以及做了哪些优化讲清楚,才能体现出对gc的理解很到位:

演进历程

  • Go 1.0:采用简单的“标记-清除”(Mark-Sweep)算法,全程STW,延迟较高。
  • Go 1.3:引入“并发标记”(Concurrent Mark),将标记阶段与用户代码并发执行,仅保留部分阶段STW,大幅缩短延迟。
  • Go 1.5:重构GC核心,使用“三色标记法”(Tricolor Marking)作为基础算法,并引入“写屏障”(Write Barrier)解决并发标记中的指针修改问题。
  • Go 1.8:引入“混合写屏障”(Hybrid Write Barrier),彻底解决了并发标记中的“丢失标记”问题,将STW时间缩短到微秒级。
  • 后续版本:持续优化内存分配、标记效率和清扫策略,进一步降低延迟和资源消耗。

核心原理:三色标记法

当前Go GC的核心算法是三色标记法,其核心思想是通过“颜色”标记对象的访问状态,实现并发标记(与用户代码同时运行)。

1. 三色标记的基本定义

  • 白色对象:未被GC访问过的对象(默认状态)。若标记结束后仍为白色,说明是“垃圾”,将被回收。
  • 灰色对象:已被GC访问过,但该对象引用的子对象尚未被处理(待遍历)。
  • 黑色对象:已被GC访问过,且其引用的所有子对象都已处理完毕(“安全”对象,不会被回收)。

2. 三色标记的流程

三色标记分为标记(Mark)清扫(Sweep) 两大阶段,整体流程如下:

(1)初始标记(Initial Mark,STW)
  • 目标:标记所有“根对象”(Root Objects,如全局变量、当前栈上的局部变量等直接可访问的对象)。
  • 操作:短暂暂停所有用户线程(STW),将根对象标记为“灰色”,放入标记队列,然后立即恢复用户线程。
  • 特点:STW时间极短(通常微秒级),仅处理根对象。
(2)并发标记(Concurrent Mark)
  • 目标:从灰色对象出发,递归遍历所有可达对象,将其标记为灰色→黑色。
  • 操作:与用户代码并发执行。GC线程从标记队列中取出灰色对象,遍历其引用的子对象:
    • 若子对象是白色,将其标记为灰色并加入队列;
    • 当灰色对象的所有子对象都处理完毕后,将其标记为黑色。
  • 关键问题:并发过程中,用户代码可能修改对象引用(如将A对象的引用从B改为C),导致“漏标记”(原本可达的对象被误判为垃圾)。
(3)重新标记(Remark,STW)
  • 目标:修正并发标记期间因用户代码修改引用而导致的漏标记问题。
  • 操作:再次短暂STW,处理并发标记中未被正确标记的对象(主要依赖写屏障记录的“脏对象”),确保所有可达对象都被标记为黑色。
  • 特点:STW时间仍很短(微秒级),因为大部分标记工作已在并发阶段完成。
(4)并发清扫(Concurrent Sweep)
  • 目标:回收所有白色对象(垃圾)的内存,并将其归还给堆,供后续内存分配使用。
  • 操作:与用户代码并发执行,遍历堆内存,释放白色对象的空间,同时将所有对象的颜色重置为白色(为下一次GC做准备)。

写屏障

在并发标记阶段,用户代码可能修改对象的引用关系(如a.b = c),若不处理,可能导致“漏标记”(例如:A是黑色对象,B是白色对象,A原本引用B,后改为引用C,此时B若没有其他引用会被误判为垃圾)。

Go通过写屏障(Write Barrier)解决这一问题。写屏障是一段在“修改对象引用”时触发的钩子函数,其作用是跟踪指针变化,确保并发标记的正确性

Go 1.8后采用混合写屏障(Hybrid Write Barrier),规则如下:

  • 当修改对象A的字段(如A.field = B)时:
    1. 若A是黑色对象,将B标记为灰色(确保B及其子对象会被遍历);
    2. 若B是白色对象,将其标记为灰色(避免B被漏标记)。

混合写屏障结合了“插入写屏障”和“删除写屏障”的优点,无需在栈上启用写屏障(栈上对象生命周期短,且会在初始标记和重新标记阶段被完整处理),大幅降低了性能开销。

触发条件

  1. 内存增长阈值:当堆内存大小达到“上一次GC后堆大小 + 上一次堆大小 × GOGC%”时触发(默认GOGC=100,即堆内存翻倍时触发)。
  2. 定时触发:若长时间未触发GC(默认2分钟),会强制触发一次,避免内存泄漏累积。
  3. 手动触发:通过runtime.GC()函数手动触发(一般用于测试或特殊场景,不建议在生产代码中频繁调用)。

2. golang方面你有看过一些书吗

3. defer你用过吗

4. defer的顺序是什么样的

Go语言中,defer的执行顺序遵循“后进先出”(LIFO)原则:多个defer语句在定义时会像栈一样存储,后定义的defer会先执行,先定义的defer后执行。

5. map有用过吗

6. map是不是线程安全

Go语言中的原生map不是线程安全的。当多个goroutine同时对一个map进行并发读写时,会触发运行时错误(panic),因为原生map的实现没有做任何同步处理,无法应对并发场景下的指针操作冲突。

为了解决map的线程安全问题,标准库sync包提供了sync.Map,专门用于并发场景下的键值对存储。其核心设计原理是通过分离读写路径、结合原子操作与互斥锁,在保证线程安全的同时优化性能,尤其适合“读多写少”的场景。

sync.Map的核心原理可概括为:

  1. 内部维护两个map:read(只读map)和dirty(可写map)。

    • read通过原子操作访问,无锁,支持高效读取已存在的键;
    • dirty需要通过互斥锁(mutex)访问,用于处理新增或修改的键。
  2. 读取时优先查read,若未找到且dirty存在,会尝试查dirty并记录“未命中次数”(misses)。

  3. misses达到阈值(等于dirty的长度)时,会将dirty原子替换为read,并清空dirty,减少后续读取对锁的依赖。

  4. 写入时:若键已在read中且未被标记删除,直接原子更新;否则需加锁操作dirty(新增或修改)。

这种设计通过减少锁的竞争(大部分读操作无需加锁),在高并发读场景下性能优于“原生map+互斥锁”的组合。

7. 分页查询的n+1问题,你了解吗,怎么解决

分页查询中的“n+1问题”是数据库查询优化中常见的性能问题,主要出现在关联查询场景中:

当需要查询主表数据(如分页查询订单列表),并同时获取每条主表记录关联的子表数据(如每个订单对应的用户信息)时,若采用“先查主表,再循环查子表”的方式,会产生:

  • 1次查询主表(获取n条分页数据);
  • 然后循环n条主表记录,执行n次查询子表的操作;
  • 总共产生n+1次数据库查询,这就是“n+1问题”。

这种方式会导致数据库连接频繁建立、网络交互增多,尤其在分页数据量大时,性能损耗显著。

解决“n+1问题”的核心思路是减少查询次数,将多次查询合并为少量查询,常见方案有:

  1. 预加载(贪婪加载)
    利用ORM框架的“预加载”功能(如Go的GORM中的Preload、Java的MyBatis的fetchType="eager"),在查询主表时,通过1次额外的批量查询获取所有关联的子表数据,再在内存中与主表数据关联。
    例如:查询订单时,一次性查询所有订单ID对应的用户信息,而非逐条查询,总查询次数降为2次(1次主表+1次批量子表)。

  2. JOIN关联查询
    通过SQL的JOIN语句(如LEFT JOIN),将主表和子表在1次查询中关联,直接获取包含主表和子表字段的完整结果集。
    例如:SELECT 订单.*, 用户.名称 FROM 订单 LEFT JOIN 用户 ON 订单.用户ID=用户.ID LIMIT 10 OFFSET 0,只需1次查询即可获取所有需要的数据。

  3. 子查询/IN查询批量获取
    先查询主表得到分页数据,提取所有关联的子表ID(如用户ID),再通过IN语句批量查询子表:
    例如:SELECT * FROM 用户 WHERE ID IN (1,2,3,...n)(n为分页主表记录的关联ID),总查询次数为2次,避免循环查询。

  4. 缓存关联数据
    若关联数据(如用户信息)更新不频繁,可将其缓存到Redis等缓存中间件中。查询时先从缓存获取子表数据,未命中再查数据库,减少数据库查询次数。

8. 建索引的原则是什么

  1. 优先在查询频繁(WHERE、JOIN、ORDER BY等)的列上建索引;
  2. 避免在频繁更新的列上建索引,减少维护开销;
  3. 选择基数高(值区分度大)的列,如身份证号而非性别;
  4. 复合索引遵循“最左前缀原则”,将过滤性强的列放前面;
  5. 控制数量,避免过度索引(一般单表不超5-6个);
  6. 小表无需索引,全表扫描可能更快。

9. 慢查询该怎么优化

  1. 索引优化:检查并添加合适索引,避免索引失效(如函数操作、隐式转换、like '%xxx'等);
  2. 优化查询语句:避免select *,拆分复杂查询,用join替代多层子查询,优化order by/group by逻辑;
  3. 分析执行计划:用explain查看是否全表扫描、是否使用正确索引,针对性调整;
  4. 表结构优化:大表分表(水平/垂直),优化字段类型(如用tinyint代替int),减少冗余关联;
  5. 缓存热点数据:将高频查询结果缓存到Redis,减少数据库访问;
  6. 调整数据库配置:合理设置缓存大小(如innodb_buffer_pool_size)、连接数等。

10. 最近有没有去学习什么新的知识

项目相关

  1. 考试系统是怎么样的一个流程
  2. 考试资源是怎么回收的
  3. 考试系统是网页形式还是客户端形式
  4. 试卷管理涉及到批改那部分吗
  5. 组卷的时候怎么从题库导入题目的
  6. 为啥试卷锁需要lua呢
  7. 这个考试系统有用到channel吗
  8. 试卷列表查询重构,原来是什么样的
  9. 这里你们用分页查询吗
  10. AI批改这里,你们是遇到什么问题吗
  11. 技术上是怎么做的
  12. 限流的话你是用go去做还是
  13. 单元测试是自己做的吗
  14. 测试有用mock吗
  15. 这个项目有上线吗
  16. 数据库优化你是怎么做的
  17. 你有研究过试卷的status是否适合建索引呢
  18. 你们团队内有没有发生过什么冲突
  19. 怎么解决的
  20. 你们老师没有一开始给你们做一些规范培训吗
  21. 如果你的项目经历知识不够,不理解你的方案,那你通过什么方式去推你的方案呢
  22. 你们考试系统有什么版本时间点的要求吗
  23. 那有没有出现在一些很紧张的时间点的情况下去完成
  24. 没有甲方顶着这个时间点,必须要这个时间评审的情况,这种情况怎么解决
  25. 你们最晚弄到几点
  26. 你们这个班是一共几个班来着
  27. 开发下来对外评审了多少次呢
  28. 题库管理需要那么多版本完成吗,每个阶段完成哪些事啊
  29. 总共做了多长时间呢
  30. 你参加整个实习过程对你最有成就感的点在哪
  31. 这个整个项目的过程有用AI编程
  32. 像AI写的代码的话你怎么去验证正确性
  33. 你自己会把那些代码重新输一遍吗

坚定不移,听话照做,按部就班,早日上岸!

加我微信,免费领面经,升职加薪:wangzhongyang1993,备注:面经。

点击这里查看原文链接,我的公众号更新了570多篇原创内容,欢迎关注,第一时间查看干货内容。