哭晕,腾讯的面试太难了。。。
国庆假期刚结束,马上就收到了一位粉丝分享的腾讯音乐面试经历。这是一场质量很高的面试,大厂面试官确实实力强劲。
但是由于一直忙于交接和工作,他完全裸面,所以在面试后直呼:“想哭,面试中全程被面试官牵着走,没有掌握主动权将话题引向自己擅长的方向”。
所以我建议大家在面试前一定要充分准备,特别是项目相关内容要深入理解每个决策背后的逻辑。
下面看一下他分享的内容吧:
面试内容
(一)开场
面试官介绍岗位
岗位为腾讯音乐 - 全民 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):表示从切片指向的数组起始位置到数组末尾的元素个数。
底层结构:
3. Go 里并发读写 map 会出现问题,如何解决
使用互斥锁(Mutex)
使用 sync.Mutex 或 sync.RWMutex 对 map 的读写操作进行同步控制。这种方法适用于读写操作都较频繁的场景,示例:
使用 sync.Map:
Go 1.9 版本引入了 sync.Map,这是一个并发安全的 map 实现。sync.Map 适用于读多写少的场景,因为它在读取时不加锁,而在写入时才加锁,从而减少了锁的竞争,示例:
使用通道(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
个元素的位置。
常见类型的排序:
sort
包提供了对常见类型的切片进行排序的便捷函数:
Ints
:对[]int
类型的切片进行升序排序。Float64s
:对[]float64
类型的切片进行升序排序。Strings
:对[]string
类型的切片进行升序排序。
5. 协程和线程的区别,线程开销大的原因,协程有上下文切换为何线程消耗更多 CPU 资源
协程和线程的区别
资源消耗:
线程:每个线程有自己的栈空间(几十 KB 到几 MB),内存消耗大。
协程:栈空间较小(几 KB),动态调整,内存消耗小。
调度方式:
线程:由操作系统控制,抢占式调度,涉及内核态切换。
协程:由应用程序控制,协作式调度,在用户态进行。
上下文切换:
线程:涉及内核态和用户态切换,开销大。
协程:只在用户态进行,开销小。
并发粒度:
线程:适用于处理多个相对独立的任务。
协程:适用于在单个线程内实现多个任务的协作和切换。
线程开销大的原因
内存分配:
每个线程需要固定大小的栈空间,占用大量内存。
上下文切换:
涉及内核态和用户态切换,需要保存和恢复大量寄存器状态和栈指针。
调度开销:
操作系统维护复杂的调度算法和数据结构,增加系统开销。
同步机制:
使用锁、信号量等机制,高并发场景下可能导致性能瓶颈。
协程消耗更少 CPU 资源的原因
上下文切换简单:
只在用户态进行,不需要内核态介入,开销小。
调度灵活:
开发者控制调度,按需切换,减少不必要的上下文切换。
内存管理高效:
栈空间小且动态调整,内存利用率高。
同步机制简单:
通常通过通道(如 Go 语言中的 channel)实现同步,避免锁的竞争。
(四)算法与反问
算法手撕
反问
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:infoq 面试群。
评论 (1 条评论)