面试基本信息
- 公司: 腾讯云CSIG(机器学习平台部门)
- 职位: 后端开发实习生
- 面试轮次: 一面(技术面)
- 面试形式: 腾讯会议面试(不需要开视频)
- 面试时长: 约20分钟
- 面试官: 技术人员,负责AI/大模型训练平台相关开发
- 面试时间: 2025年06月11日 下午07点58分
面试问题与回答
问题1:自我介绍
面试官问题:
“要不你先给我先介绍一下你这边的项目经理”
我的回答:
“面试官你好,我是王宇哲,然后是华东师范大学软件工程硕士,研究方向是加密信息检索。然后本科三年专业第一,获得过国家奖学金和蓝桥国赛二等奖。简历上的项目主要是通过go编写的分布式应用。第一个项目的话是独立实现的分布式缓存项目,它的单点性能能超过5000QPS,然后采用了一致性哈希算法实现了数据切片。然后通过ETCD做服务发现,用gRPC去做通信,并实现了防缓存击穿的single fly机制。第二个项目的话是微服务架构的抖音商城系统,然后负责整体架构设计以及70%以上的代码开发任务。使用github action自动部署上云。集成open telemetry去实现了可观测性,然后通过分布式锁去解决了超卖问题。然后用canal加消息队列实现了mysql、redis、elasticsearch这三个数据库的同步。第三个项目的话是AI网关项目。然后思路上实现有点像现在比较火的MCP。然后它能够根据用户输入提示词去规划任务,然后通过RPC去调用对应的微服务的对应接口。然后我设计了多代理分层的方式去调度,减少了上下文窗口带来的遗忘问题。然后提高了任务的准确度,并通过泛化调用来实现AI层与实际函数层的解耦。然后最近的话也为开源项目mineru(一个30k star的开源项目)贡献了自己编写的MCP的代码。平常的话我对各种AI工具熟练使用,比如cursor之类的。然后对于现代前沿的AI技术和理念也有所了解,也熟练使用各种AI去组合出一套开发流程来快速开发。”
标准/建议回答:
自我介绍部分回答很完整,涵盖了教育背景、项目经验和技术特长。建议可以更加简洁一些,突出重点:
“您好,我是王宇哲,华东师范大学软件工程硕士,主要专注于Go语言后端开发。我有三个比较有代表性的项目:首先是分布式缓存系统,单点QPS超过5000,使用了一致性哈希和ETCD服务发现;然后是微服务电商系统,负责架构设计和大部分开发工作,集成了可观测性和分布式锁;最后是AI网关项目,类似MCP模式,能够根据提示词智能调度微服务。最近也为mineru开源项目贡献了MCP代码。我比较熟悉AI工具辅助开发,对现代后端技术栈有深入理解。”
总结反思:
- 项目介绍比较详细,展现了技术广度
- 面试官对mineru项目感兴趣,进行了追问
- 自我介绍略长,可以更加精炼
问题2:编程题 - 合并两个有序数组
面试官问题:
“写一个简单的,合并两个有序数组”
我的回答:
当时在在线编辑器中实现了合并两个有序数组的算法,面试官确认不需要开辟新空间,要求原地合并到第一个数组中。
标准/建议回答:
这是一个经典的双指针问题。由于要求原地合并,可以这样思考:
"这个题目的关键是要原地合并,不能开辟新空间。我的思路是从后往前填充,这样不会覆盖还没处理的元素。
具体实现是用三个指针:i指向nums1的有效元素末尾,j指向nums2的末尾,k指向nums1的真实末尾。然后比较nums1[i]和nums2[j],把较大的放到nums1[k]位置,然后相应指针前移。
这样时间复杂度是O(m+n),空间复杂度是O(1),满足原地合并的要求。"
1 | func merge(nums1 []int, m int, nums2 []int, n int) { |
总结反思:
- 编程过程中出现了一些小错误,需要调试
- 面试官耐心等待并给予了提示
- 应该先想清楚思路再开始编写
问题3:Go语言垃圾回收机制
面试官问题:
“你知道Go现在它的那个垃圾回收机制是什么样子的?”
我的回答:
“他现在的话最新的我没有去了解,但是的话再往前一点,我不知道最新有没有去更新。但再往前一点的话,是更新到了三色标记法加那个读写屏障。然后三色标记法的话,我的理解就是…他应该是18和19版本以后就是更新的这个三色标记法加读写屏障。然后他这个读写屏障的话相当于之前的话他会去从主函数开始,以及堆顶开始,一个个去遍历所有便利标记那些可以被访问到的那些对象,然后再把未标记的全都删掉。以前是这样的,但是会发现这样的话会有一个叫stop the world,就是它会需要把整个程序暂停,然后去进行这样的垃圾回收。这样的话是很影响性能的。于是他就想出了一个三色标记法,然后这样就可以和那个程序去并行的,在程序运行过程中并行的去进行垃圾回收。这样就感受不到那种卡顿了。它相当于好像是先用一个颜色,我是不太记得了。但是的话先是所有对象是白色,然后它会他好像是白色,然后他会把所有的主函数中直接能获取到的对象,就堆顶能直接混淆的对象标记为黑色。然后在每一通过这个黑色的对象再往下遍历,然后遍历完便利后的对象应该会被标记为。标记为灰色,然后标记为灰色对象。我想一想…”
标准/建议回答:
Go的垃圾回收确实是基于三色标记法,我来系统地说一下:
"Go目前使用的是三色标记清除算法配合写屏障。三色指的是白色、灰色、黑色三种状态:
白色对象:未被标记的对象,垃圾回收结束后会被清理
灰色对象:已被标记但其引用的对象还未完全扫描的对象
黑色对象:已被标记且其引用的对象都已扫描完成的对象
具体流程是这样的:首先从GC Root开始,把直接可达的对象标记为灰色。然后不断地取出灰色对象,把它标记为黑色,同时把它引用的白色对象标记为灰色。当没有灰色对象时,所有白色对象就是垃圾,可以清理了。
写屏障的作用是在并发标记过程中,如果程序修改了指针引用,会触发屏障函数,确保新的引用关系被正确处理,避免错误回收。
这样就实现了并发垃圾回收,大大减少了STW时间,提高了程序性能。"
总结反思:
- 对概念有基本了解,但表达不够清晰
- 细节记忆模糊,应该更系统地复习
- 面试官看出了我的不确定,及时转换了话题
问题4:Go语言Map实现
面试官问题:
“比如说你那个Go的map,它是怎么实现的?”
我的回答:
“map它底层是用一个哈希表去实现的。然后怎么说呢?就它一开始的长度为零,然后它会每次往里面添加元素的时候,它会如果说超出了它的容量,它会去扩增。然后每次乘2,然后到好像1024的时候,他就后面不是乘2了。后1024以后就每次会增加1024个元素的存储空间,好像是这样。”
面试官追问:
“那你了解他现在那些碰撞算法是怎么实现的吗?”
我的回答:
“碰撞算法如果它碰撞了之后,它会…Go中的我没有去了解,但是我大概知道几种碰撞,一种就是哈希表碰撞解决办法一般第一种叫好像什么开放寻址法之类的。他有些会就是往后就往后一个个去找。然后还有一些的话,他就是会先往右一格,就是这一个如果说发生碰撞的话,那它就会到这个索引加一的位置再找。然后如果说再找再被占用的话,到加1,然后再到加2这样去查找。然后还有一种的话就是一直往后找,就index一直加1加1加1。”
标准/建议回答:
Go的map实现确实比较复杂,我来详细说明一下:
"Go的map底层是哈希表,具体实现上使用的是拉链法解决冲突。它的数据结构包含buckets数组,每个bucket可以存储8个key-value对。
当发生哈希冲突时,Go不是用开放寻址法,而是用拉链法。如果一个bucket满了,会创建overflow bucket链接到后面。这样相同哈希值的元素会形成一个链表结构。
关于扩容机制,Go map有两种扩容方式:
- 翻倍扩容:当负载因子超过6.5时触发,bucket数量翻倍
- 等量扩容:当overflow bucket过多时触发,重新整理数据但不增加bucket数量
扩容是渐进式的,不是一次性完成,而是在每次访问时逐步迁移,这样避免了长时间阻塞。
还有一个重要特性是map遍历是随机的,这是Go故意设计的,为了防止开发者依赖遍历顺序。"
总结反思:
- 对基本原理有了解,但细节不够准确
- 混淆了Go实际使用的碰撞解决方案
- 应该区分不同语言的哈希表实现差异
问题5:Go语言反射机制
面试官问题:
“那个Go它那个反射它是怎么去实现的?比如说Go那个struct里面,它不是有可以定义一些那个type,对吧?比如说你marshal的时候,你知道那个type怎么实现的?”
我的回答:
“不太了解Go的底层,我没有太研究过,主要是编程应用。”
标准/建议回答:
这个问题考察的是Go反射的底层实现,确实比较深入:
"Go的反射是基于interface{}的底层实现来实现的。每个interface{}实际上包含两个指针:一个指向类型信息(type),一个指向具体数据(data)。
Go的类型系统中,每个类型都有一个runtime.Type结构,包含了类型的元数据信息,比如类型名称、方法列表、字段信息等。
当我们使用reflect.TypeOf()时,实际上是获取了interface{}中的类型信息部分。reflect.ValueOf()则是获取了数据部分,并提供了一系列方法来操作这个值。
对于struct的tag,比如json标签,这些信息是存储在类型的元数据中的。编译器会把这些tag信息编译到二进制文件中,运行时通过反射可以读取到这些信息。
marshal/unmarshal的过程就是:先通过反射获取类型信息和tag信息,然后根据tag规则进行序列化和反序列化。
这就是为什么反射会有性能开销,因为需要在运行时查找和解析这些元数据信息。"
总结反思:
- 承认不了解比直接乱答要好
- 这是一个比较深入的问题,需要对Go运行时有深入理解
- 可以平时多关注一些底层实现的文章
问题6:缓存过期机制
面试官问题:
“你你你那个缓存是吧?缓存它过期是怎么实现的?”
我的回答:
“缓存过期的话,我是模仿那个redis里面的。然后我看那个缓存过期的时候,我并不会直接将它删除,而是等下一次再访问这个缓存的时候。就每次访问缓存之前,我会去判断一下。不访问的话,他后台会有一个协程去监听,然后就是会定期去清理,就是全局去查找,然后去清理过期的那些缓存。但是这种方式其实也是有优化的,但我还没有做优化,因为全局清理的话会比较耗时间。”
标准/建议回答:
缓存过期机制的实现思路是对的,可以更详细地说明一下:
"我实现的缓存过期机制主要采用了两种策略,参考了Redis的设计:
第一种是惰性删除:当客户端访问某个key时,先检查是否过期,如果过期就删除并返回空值。这种方式的优点是不会浪费CPU资源,缺点是如果某些key一直不被访问,就会一直占用内存。
第二种是定期删除:启动一个后台协程,定期扫描所有key,删除过期的key。为了避免一次性扫描所有key导致性能问题,我采用了分批处理的方式。
更好的优化方案可以考虑:
- 随机采样:每次只检查一部分key,而不是全量扫描
- 时间轮算法:按过期时间对key进行分组,减少检查频率
- LRU淘汰:当内存不足时,主动淘汰最久未使用的key
这样既能保证过期key及时清理,又不会对系统性能造成太大影响。"
总结反思:
- 基本思路正确,参考了Redis的实现
- 意识到了性能优化的问题
- 可以进一步了解更多优化策略
面试整体感受
- 难度评价: 适中,主要考察Go语言基础和系统设计思维
- 面试官风格: 比较友善,会给予提示和引导
- 题目类型: 基础概念 + 编程实现 + 项目经验
- 准备建议: 重点复习Go语言特性、数据结构实现和分布式系统概念
面试结果
- 当场反馈: 面试官询问了实习时间安排,表示可以接受
- 后续流程: 面试官表示会安排后续通知
- 个人感受: 整体表现中等,有些基础概念需要加强
经验总结
做得好的地方
- 自我介绍比较全面,项目经验丰富
- 编程题最终实现正确,思路清晰
- 对于不了解的问题诚实回答,没有强行回答
需要改进的地方
- Go语言基础概念掌握不够深入,特别是底层实现
- 表达时有些卡顿,逻辑性有待提高
- 对于垃圾回收等核心概念应该更加准确
准备建议
- Go语言基础: 深入学习GMP模型、垃圾回收、map实现等核心概念
- 系统设计: 加强对分布式系统、缓存策略的理解
- 算法实现: 多练习双指针、数组操作等基础算法
- 表达能力: 提前准备技术概念的标准表述,避免口语化过重
重要知识点复习
- Go语言三色标记垃圾回收算法
- Go map的底层实现和扩容机制
- Go反射的实现原理
- 缓存过期策略设计
- 分布式系统常见问题和解决方案