好久没有分享最新的面经了,今天分享的是训练营内部的朋友在成都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)时:- 若A是黑色对象,将B标记为灰色(确保B及其子对象会被遍历);
- 若B是白色对象,将其标记为灰色(避免B被漏标记)。
混合写屏障结合了“插入写屏障”和“删除写屏障”的优点,无需在栈上启用写屏障(栈上对象生命周期短,且会在初始标记和重新标记阶段被完整处理),大幅降低了性能开销。
触发条件
- 内存增长阈值:当堆内存大小达到“上一次GC后堆大小 + 上一次堆大小 × GOGC%”时触发(默认GOGC=100,即堆内存翻倍时触发)。
- 定时触发:若长时间未触发GC(默认2分钟),会强制触发一次,避免内存泄漏累积。
- 手动触发:通过
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的核心原理可概括为:
-
内部维护两个map:
read(只读map)和dirty(可写map)。read通过原子操作访问,无锁,支持高效读取已存在的键;dirty需要通过互斥锁(mutex)访问,用于处理新增或修改的键。
-
读取时优先查
read,若未找到且dirty存在,会尝试查dirty并记录“未命中次数”(misses)。 -
当
misses达到阈值(等于dirty的长度)时,会将dirty原子替换为read,并清空dirty,减少后续读取对锁的依赖。 -
写入时:若键已在
read中且未被标记删除,直接原子更新;否则需加锁操作dirty(新增或修改)。
这种设计通过减少锁的竞争(大部分读操作无需加锁),在高并发读场景下性能优于“原生map+互斥锁”的组合。
7. 分页查询的n+1问题,你了解吗,怎么解决
分页查询中的“n+1问题”是数据库查询优化中常见的性能问题,主要出现在关联查询场景中:
当需要查询主表数据(如分页查询订单列表),并同时获取每条主表记录关联的子表数据(如每个订单对应的用户信息)时,若采用“先查主表,再循环查子表”的方式,会产生:
- 1次查询主表(获取n条分页数据);
- 然后循环n条主表记录,执行n次查询子表的操作;
- 总共产生n+1次数据库查询,这就是“n+1问题”。
这种方式会导致数据库连接频繁建立、网络交互增多,尤其在分页数据量大时,性能损耗显著。
解决“n+1问题”的核心思路是减少查询次数,将多次查询合并为少量查询,常见方案有:
-
预加载(贪婪加载)
利用ORM框架的“预加载”功能(如Go的GORM中的Preload、Java的MyBatis的fetchType="eager"),在查询主表时,通过1次额外的批量查询获取所有关联的子表数据,再在内存中与主表数据关联。
例如:查询订单时,一次性查询所有订单ID对应的用户信息,而非逐条查询,总查询次数降为2次(1次主表+1次批量子表)。 -
JOIN关联查询
通过SQL的JOIN语句(如LEFT JOIN),将主表和子表在1次查询中关联,直接获取包含主表和子表字段的完整结果集。
例如:SELECT 订单.*, 用户.名称 FROM 订单 LEFT JOIN 用户 ON 订单.用户ID=用户.ID LIMIT 10 OFFSET 0,只需1次查询即可获取所有需要的数据。 -
子查询/IN查询批量获取
先查询主表得到分页数据,提取所有关联的子表ID(如用户ID),再通过IN语句批量查询子表:
例如:SELECT * FROM 用户 WHERE ID IN (1,2,3,...n)(n为分页主表记录的关联ID),总查询次数为2次,避免循环查询。 -
缓存关联数据
若关联数据(如用户信息)更新不频繁,可将其缓存到Redis等缓存中间件中。查询时先从缓存获取子表数据,未命中再查数据库,减少数据库查询次数。
8. 建索引的原则是什么
- 优先在查询频繁(WHERE、JOIN、ORDER BY等)的列上建索引;
- 避免在频繁更新的列上建索引,减少维护开销;
- 选择基数高(值区分度大)的列,如身份证号而非性别;
- 复合索引遵循“最左前缀原则”,将过滤性强的列放前面;
- 控制数量,避免过度索引(一般单表不超5-6个);
- 小表无需索引,全表扫描可能更快。
9. 慢查询该怎么优化
- 索引优化:检查并添加合适索引,避免索引失效(如函数操作、隐式转换、like '%xxx'等);
- 优化查询语句:避免select *,拆分复杂查询,用join替代多层子查询,优化order by/group by逻辑;
- 分析执行计划:用explain查看是否全表扫描、是否使用正确索引,针对性调整;
- 表结构优化:大表分表(水平/垂直),优化字段类型(如用tinyint代替int),减少冗余关联;
- 缓存热点数据:将高频查询结果缓存到Redis,减少数据库访问;
- 调整数据库配置:合理设置缓存大小(如innodb_buffer_pool_size)、连接数等。
10. 最近有没有去学习什么新的知识
项目相关
- 考试系统是怎么样的一个流程
- 考试资源是怎么回收的
- 考试系统是网页形式还是客户端形式
- 试卷管理涉及到批改那部分吗
- 组卷的时候怎么从题库导入题目的
- 为啥试卷锁需要lua呢
- 这个考试系统有用到channel吗
- 试卷列表查询重构,原来是什么样的
- 这里你们用分页查询吗
- AI批改这里,你们是遇到什么问题吗
- 技术上是怎么做的
- 限流的话你是用go去做还是
- 单元测试是自己做的吗
- 测试有用mock吗
- 这个项目有上线吗
- 数据库优化你是怎么做的
- 你有研究过试卷的status是否适合建索引呢
- 你们团队内有没有发生过什么冲突
- 怎么解决的
- 你们老师没有一开始给你们做一些规范培训吗
- 如果你的项目经历知识不够,不理解你的方案,那你通过什么方式去推你的方案呢
- 你们考试系统有什么版本时间点的要求吗
- 那有没有出现在一些很紧张的时间点的情况下去完成
- 没有甲方顶着这个时间点,必须要这个时间评审的情况,这种情况怎么解决
- 你们最晚弄到几点
- 你们这个班是一共几个班来着
- 开发下来对外评审了多少次呢
- 题库管理需要那么多版本完成吗,每个阶段完成哪些事啊
- 总共做了多长时间呢
- 你参加整个实习过程对你最有成就感的点在哪
- 这个整个项目的过程有用AI编程
- 像AI写的代码的话你怎么去验证正确性
- 你自己会把那些代码重新输一遍吗
坚定不移,听话照做,按部就班,早日上岸!
加我微信,免费领面经,升职加薪:wangzhongyang1993,备注:面经。