哭晕,腾讯的面试太难了。。。
国庆假期刚结束,马上就收到了一位粉丝分享的腾讯音乐面试经历。这是一场质量很高的面试,大厂面试官确实实力强劲。
但是由于一直忙于交接和工作,他完全裸面,所以在面试后直呼:“想哭,面试中全程被面试官牵着走,没有掌握主动权将话题引向自己擅长的方向”。
所以我建议大家在面试前一定要充分准备,特别是项目相关内容要深入理解每个决策背后的逻辑。
下面看一下他分享的内容吧:
面试内容
(一)开场
面试官介绍岗位
岗位为腾讯音乐 - 全民K歌 - 国际版 - 直播歌房后台研发工程师,主要负责直播相关功能,开发语言为Go和C++。
关于求职者基本情况
要求5分钟以内的自我介绍。
被问到所在公司技术团队裁撤后为何会留下自己。
岗位base深圳能否接受。
(二)项目相关
系统重构 - 数据迁移项目
微服务相关
如何理解微服务,为什么微服务能提升系统可拓展性。
微服务相比于单体服务的缺点。
服务间通信成本指的是什么,成本体现在哪。
数据库迁移原因与成本评估
把数据库从MongoDB迁移到MySQL的原因。
如何评估迁移异构数据库(MongoDB到MySQL)的成本,是否仅考虑成熟度和被接受程度,因为迁移涉及数据同步工作量巨大。
技术选型相关
如果采用最终一致性方案,MySQL支持事务而MongoDB不支持事务,有什么原因不使用最终一致性方案。数据同步与异常处理
迁移过程中数据同步怎么做(新数据库写MySQL,写脚本迁移Mongo数据到MySQL)。
业务要读写新旧数据时怎么办。
迁移比较暴力,如何发现数据异常,异构数据库迁移时如何保证每条数据正常迁移到MySQL。
唯一键场景下(Mongo已有数据,切换到MySQL写时可能重复写入)的处理。
优化服务器项目
分布式缓存实现
优化服务器引入分布式缓存技术(具体是Redis),如何实现的。
缓存与数据库一致性
如何保证Redis缓存和数据库的数据一致性(回答缓存先写数据库后写存在问题)。
写缓存成功数据库失败时缓存是否为脏数据。
先写数据库成功再写Redis,缓存更新失败怎么办,如何知道何时将数据库数据同步到缓存及同步的做法。
缓存是否有过期时间机制,有没有不过期一直生效的情况,有过期的话多久过期。
缓存同时过期会有什么问题以及如何解决。
数据抓取业务项目
Kafka相关
Kafka在业务中的角色。
业务为什么要经过Kafka这一层,账号量有多大。
定时任务可分散执行时间,Kafka在这种情况下削峰意义不大,是否有其他考虑使用Kafka的因素。
防止账号丢失已有确认机制,当作下游处理失败重新触发即可,为何还需要Kafka。
抖音微信小游戏归因业务项目
Token缓存相关
微信小游戏的token存于sync.map(服务内存),为何使用服务内存而不使用Redis或其他外部缓存方案。
用户量相关,当有一定数量实例和用户量时这种方案是否有缺陷(此处存在理解偏差导致回答失误)。
(三)Go和MySQL
1. MySQL相关
1. MySQL中索引如何实现,为什么MySQL里不使用B树。
在MySQL中,索引的实现主要依赖于特定的数据结构,以提高数据检索的速度。其中最常见的索引类型是基于B+树(B+Tree)的数据结构。
MySQL中的索引实现
B+树索引:
MySQL中的大多数存储引擎(如InnoDB和MyISAM)默认使用B+树作为索引的数据结构。
B+树的所有叶子节点都处于同一层,且相互之间有指针相连,这样可以方便地进行范围查询。
叶子节点包含了指向记录的指针,而非内部节点,这使得B+树更适合用于全键值、范围或排序操作。
B树索引:
虽然B树也可以用作索引,但在MySQL中使用较少。B树的每个节点都可以存储数据,包括内部节点,这可能导致更多的磁盘I/O操作,因为每次查询可能需要遍历更多的节点。
为什么MySQL里不使用B树
尽管B树在理论上可以用来实现索引,但在MySQL中更倾向于使用B+树,主要原因包括:
磁盘I/O优化:
B+树的所有数据都在叶子节点上,这意味着对于非叶子节点来说,只需要存储键值和指向下一层节点的指针,这使得每个节点可以容纳更多的键值,进一步降低了树的高度,减少了磁盘I/O次数。
范围查询优化:
B+树的叶子节点之间有链接,这使得范围查询更加高效。执行范围查询时,可以从第一个满足条件的叶子节点开始,沿着节点间的链接快速访问后续节点,无需返回上层节点重新寻找下一个满足条件的节点。
插入删除效率:
在B+树中,插入和删除操作通常只影响叶子节点,不会影响非叶子节点,这简化了树的维护过程,提高了操作效率。
Go相关
2. 切片如何实现
一个切片实际上是一个包含三个字段的结构体:
指针(Pointer):指向底层数组的一个指针。
长度(Length):表示切片当前包含的元素个数。
容量(Capacity):表示从切片指向的数组起始位置到数组末尾的元素个数。
底层结构:
type slice struct { array uintptr // 指向底层数组的指针 len int // 切片的长度 cap int // 切片的容量}
3. Go里并发读写map会出现问题,如何解决
使用互斥锁(Mutex)
使用 sync.Mutex 或 sync.RWMutex 对 map 的读写操作进行同步控制。这种方法适用于读写操作都较频繁的场景,示例:
import ( "sync")type SafeMap struct { mu sync.Mutex m map[string]int}func (sm *SafeMap) Set(key string, value int) { sm.mu.Lock() defer sm.mu.Unlock() sm.m[key] = value}func (sm *SafeMap) Get(key string) (int, bool) { sm.mu.Lock() defer sm.mu.Unlock() value, ok := sm.m[key] return value, ok}
使用 sync.Map:
Go 1.9 版本引入了 sync.Map,这是一个并发安全的 map 实现。sync.Map 适用于读多写少的场景,因为它在读取时不加锁,而在写入时才加锁,从而减少了锁的竞争,示例:
import ( "sync")var sm sync.Mapfunc Set(key, value interface{}) { sm.Store(key, value)}func Get(key interface{}) (interface{}, bool) { return sm.Load(key)}
使用通道(Channel):
通过通道协调对 map 的访问,确保同一时间只有一个goroutine可以读写 map。
4. Go里的sort排序如何实现
sort 包提供了多种排序功能,可以对不同类型的切片和自定义集合进行排序。sort 包的核心思想是通过实现 sort.Interface 接口来提供排序功能:
sort.Interface 是一个接口,定义了排序所需的三个方法:
Len() :返回要排序的元素个数。
Less(i, j int) :如果第 i 个元素应该排在第 j 个元素之前,则返回 true。
Swap(i, j int) :交换第 i 个元素和第 j 个元素的位置。
type Interface interface { Len() int Less(i, j int) bool Swap(i, j int)}
常见类型的排序:
sort 包提供了对常见类型的切片进行排序的便捷函数:
Ints :对 []int 类型的切片进行升序排序。
Float64s :对 []float64 类型的切片进行升序排序。
Strings :对 []string 类型的切片进行升序排序。
5. 协程和线程的区别,线程开销大的原因,协程有上下文切换为何线程消耗更多CPU资源
协程和线程的区别
资源消耗:
线程:每个线程有自己的栈空间(几十KB到几MB),内存消耗大。
协程:栈空间较小(几KB),动态调整,内存消耗小。
调度方式:
线程:由操作系统控制,抢占式调度,涉及内核态切换。
协程:由应用程序控制,协作式调度,在用户态进行。
上下文切换:
线程:涉及内核态和用户态切换,开销大。
协程:只在用户态进行,开销小。
并发粒度:
线程:适用于处理多个相对独立的任务。协程:适用于在单个线程内实现多个任务的协作和切换。
线程开销大的原因
内存分配:
每个线程需要固定大小的栈空间,占用大量内存。
上下文切换:
涉及内核态和用户态切换,需要保存和恢复大量寄存器状态和栈指针。
调度开销:
操作系统维护复杂的调度算法和数据结构,增加系统开销。
同步机制:
使用锁、信号量等机制,高并发场景下可能导致性能瓶颈。
协程消耗更少CPU资源的原因
上下文切换简单:
只在用户态进行,不需要内核态介入,开销小。
调度灵活:
开发者控制调度,按需切换,减少不必要的上下文切换。
内存管理高效:
栈空间小且动态调整,内存利用率高。
同步机制简单:
通常通过通道(如Go语言中的channel)实现同步,避免锁的竞争。
(四)算法与反问
算法手撕
反问
#腾讯##腾讯面试##Go##面经##腾讯音乐#这里汇总了大量了跟Go相关的面试题和面经,希望对大家有所帮助!