腾讯的面试,拷问的太全面了

今天分享的是腾讯校招的一面面经,大厂拷问的知识点都很广泛,如果你也准备冲击大厂,一定要做足了准备,语言基础、数据库、缓存、消息队列、操作系统、计算机网络、算法、项目等等,基本上都会问到而且会问的很全很细,而且去大厂面试,要是没通过的话还会有记录,后面还想再去面就难咯。
先唠到这,下面开始分享热乎乎的面经:

1. 请简单做一个自我介绍
这个问题以及最后一个问题我昨天也提到了,想知道回答思路的可以移步这篇文章。
2. Go 语言里怎样处理哈希冲突?
在 Go 语言中,哈希冲突的处理采用的是链地址法,也被叫做拉链法。其具体做法是,当多个键值对通过哈希函数计算后,得到相同的哈希值,这些键值对会被存储在同一个哈希桶里,而每个哈希桶都连着一个链表或者其他数据结构。以 Go 语言的原生哈希表 map
为例,它的底层实现是数组加上链表。当出现哈希冲突时,新的键值对会被添加到对应桶的链表中。要是链表变得过长,为了提升查询效率,还会对链表进行优化,比如把链表转换为红黑树。
3. 链地址法有什么优点和缺点?
优点:
实现起来较为简单,在设计哈希表时,无需对哈希函数提出过高要求,就能有效处理哈希冲突。
具备良好的扩展性,链表的长度可以根据实际存储的数据量动态调整。
对删除操作友好,在链表中删除一个节点的时间复杂度较低。
缺点:
当链表过长时,会使哈希表的查询、插入和删除操作的时间复杂度增加,退化为 O (n)。
由于链表中的节点在内存中是离散存储的,不利于 CPU 缓存的优化,会影响性能。
每个链表节点除了要存储数据,还需要额外的指针,这会增加内存的开销。
4. Java 的 HashMap 是如何应对哈希冲突的?
Java 的 HashMap 主要依靠链地址法来处理哈希冲突,其底层数据结构是数组和链表(或红黑树)。当发生哈希冲突时,新的元素会被添加到对应桶的链表尾部。为了避免链表过长导致性能下降,Java 8 引入了红黑树优化。当链表长度超过 8 且哈希表容量大于 64 时,链表会转换为红黑树,此时查询、插入和删除操作的时间复杂度从 O (n) 降低到 O (log n)。而当树的节点数减少到 6 时,红黑树又会退化为链表。
5. 进程和线程有什么本质上的不同?协程有哪些优势和特点?
进程和线程的本质区别:
进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间、文件描述符等资源。
线程是进程中的一个执行单元,是 CPU 调度和分派的基本单位。同一进程内的多个线程共享进程的资源,如内存、文件句柄等,但每个线程有自己独立的栈空间和程序计数器。
协程的优势和特点:
轻量级:协程的创建和销毁开销远小于线程,占用的内存资源也更少,因此可以在单个进程中创建大量协程。
高效的上下文切换:协程的上下文切换由用户程序控制,不需要操作系统介入,避免了内核态和用户态的切换开销,提高了并发性能。
基于事件驱动:协程通常配合异步 I/O 使用,在等待 I/O 操作时会主动让出执行权,让其他协程继续执行,从而提高系统的并发处理能力。
简化异步编程:协程可以使用同步的编程方式编写异步代码,避免了回调地狱,使代码更易于理解和维护。
6. 在使用 Go 协程时,你遇到过哪些问题?
在使用 Go 协程的过程中,常见的问题有以下这些:
资源竞争:多个协程同时访问和修改共享数据时,可能会引发数据竞争,导致程序出现不可预期的结果。
死锁与活锁:在使用通道(channel)或者锁进行协程同步时,如果设计不当,可能会造成死锁或者活锁,使程序无法正常运行。
内存泄漏:若协程中存在长时间运行的任务,且没有适当的退出机制,或者通道没有正确关闭,就容易导致内存泄漏。
goroutine 泄漏:协程启动后,如果因为某些异常情况未能正常结束,会不断积累,最终耗尽系统资源。
性能问题:大量协程同时运行可能会增加调度开销,而且不合理的协程数量可能会导致系统资源过度使用,影响性能。
7. 子协程发生 panic 会造成什么后果?通常怎样解决?
后果:子协程发生 panic 时,若没有进行处理,会导致该协程崩溃并打印错误堆栈信息,但不会直接致使整个程序崩溃。不过,要是父协程依赖子协程的结果,或者子协程负责关键资源的释放,那么子协程的崩溃可能会让程序处于不一致的状态。
解决方法:
使用 recover 捕获 panic:在子协程中使用
defer
和recover
来捕获 panic,避免协程异常退出。通过通道传递错误:子协程可以将错误信息通过通道传递给父协程,由父协程进行统一处理。
使用 context 控制协程生命周期:借助 context 包来管理协程的取消和超时,保证在出现异常时能够及时关闭协程。
监控与告警:对系统进行监控,一旦发现有未处理的 panic,及时发出告警,以便运维人员及时处理。
8. 简述 TCP 的三次握手和四次挥手过程
三次握手:
客户端发送 SYN:客户端向服务器发送一个 SYN 包,并随机初始化一个序列号
seq = x
,以此表明客户端希望建立连接并请求同步初始序列号。服务器回复 SYN + ACK:服务器收到 SYN 包后,会向客户端发送一个 SYN + ACK 包。其中,
SYN
包的序列号seq = y
是服务器随机生成的,ACK
包的确认号ack = x + 1
,用于确认客户端的 SYN 包。客户端发送 ACK:客户端收到服务器的 SYN + ACK 包后,会向服务器发送一个 ACK 包,确认号
ack = y + 1
,表示客户端已收到服务器的 SYN 包。此时,连接建立成功。
四次挥手:
客户端发送 FIN:客户端向服务器发送一个 FIN 包,
seq = u
,表示客户端想要关闭连接。服务器回复 ACK:服务器收到 FIN 包后,会向客户端发送一个 ACK 包,
ack = u + 1
,确认客户端的 FIN 包。此时,服务器到客户端的连接仍处于开放状态。服务器发送 FIN:服务器处理完数据后,向客户端发送一个 FIN 包,
seq = v
,表示服务器也想要关闭连接。客户端回复 ACK:客户端收到服务器的 FIN 包后,会向服务器发送一个 ACK 包,
ack = v + 1
,确认服务器的 FIN 包。此时,连接彻底关闭。
9. 四次挥手之后,用 linux 命令查看会出现 time_wait 状态,它有什么意义?
TIME_WAIT 状态存在的意义主要有以下两点:
确保最后的 ACK 能到达对方:在四次挥手的最后一步,客户端发送的 ACK 包有可能会丢失。如果服务器没有收到这个 ACK 包,就会重新发送 FIN 包。客户端处于 TIME_WAIT 状态,能够重新发送 ACK 包,从而保证连接的正常关闭。
防止新旧连接混淆:TIME_WAIT 状态会持续 2MSL(Maximum Segment Lifetime,报文最大生存时间)。在这段时间内,本次连接的所有报文都会在网络中消失。这样一来,当新的连接建立时,就不会受到旧连接残留报文的干扰,避免了数据混淆的问题。
10. 为什么 TCP 建立连接需要三次握手,断开连接需要四次挥手?
三次握手的原因:
三次握手的主要目的是同步客户端和服务器的初始序列号,确保双方都有发送和接收数据的能力。
第一次握手让服务器知道客户端有发送数据的能力;第二次握手让客户端知道服务器有接收和发送数据的能力;第三次握手让服务器知道客户端有接收数据的能力。
两次握手无法保证双方初始序列号的同步,也不能确保双方都具备数据收发能力。
四次挥手的原因:
TCP 连接是全双工的,这意味着双方可以同时进行数据的发送和接收。因此,关闭连接时需要分别关闭两个方向的连接。
四次挥手将关闭连接的过程分为两个阶段:首先由客户端请求关闭发送方向的连接,然后服务器请求关闭接收方向的连接。由于服务器在收到客户端的 FIN 包后,可能还有数据需要发送,所以 ACK 和 FIN 通常分开发送,这就导致了四次挥手的过程。
11. 客户端通过三次握手建立连接后,怎样维持这个连接?
TCP 连接建立之后,主要通过以下几种方式来维持连接:
心跳机制:应用层可以实现心跳机制,定期发送心跳包,以此确认对方是否在线。例如,在长连接的场景中,客户端和服务器会定期交换 PING/PONG 消息。
TCP Keep-Alive:TCP 协议本身提供了 Keep-Alive 机制。在连接长时间没有数据传输时,TCP 会发送 Keep-Alive 包。如果对方没有响应,经过多次重试后,TCP 会认为连接已经断开,并关闭该连接。
滑动窗口机制:通过滑动窗口协议,接收方会不断向发送方确认已接收的数据,保证数据的可靠传输,同时也间接维持了连接的状态。
超时重传:发送方发送数据后会启动定时器,如果在规定时间内没有收到确认,就会重传数据,确保连接的可靠性。
12. 你使用过哪些数据库?
我使用过多种数据库,包括关系型数据库和非关系型数据库。具体如下:
关系型数据库:MySQL、PostgreSQL、Oracle、SQL Server。
非关系型数据库:MongoDB、Redis、Elasticsearch、Cassandra。
分布式数据库:TiDB、CockroachDB。
内存数据库:Redis、Memcached。
13. B + 树的优势体现在哪些方面?
B + 树作为数据库索引的常用数据结构,具有以下优势:
高度平衡:B + 树是一种平衡树,所有叶子节点都位于同一层,这使得查询效率稳定,时间复杂度为 O (log n)。
范围查询高效:B + 树的叶子节点通过指针连接成有序链表,因此范围查询时无需进行中序遍历,直接沿着链表顺序访问即可,大大提高了范围查询的效率。
磁盘读写优化:B + 树的节点可以存储多个键值,减少了树的高度,从而减少了磁盘 I/O 次数。同时,节点大小与磁盘块大小相匹配,进一步优化了磁盘读写性能。
查询效率稳定:所有查询都要从根节点遍历到叶子节点,路径长度相同,因此查询效率比较稳定。
插入和删除操作高效:B + 树通过分裂和合并节点来保持平衡,插入和删除操作的效率较高。
14. 什么情况下适合建立索引?
适合建立索引的情况主要有以下几种:
经常用于查询条件的字段:在 WHERE 子句、JOIN 条件中经常出现的字段,建立索引可以加快过滤和连接的速度。
用于排序的字段:对于 ORDER BY 和 GROUP BY 操作涉及的字段,索引可以避免文件排序,提高排序效率。
用于连接的字段:多表连接时,连接字段建立索引可以加快表之间的匹配速度。
高选择性的字段:字段的值分布比较分散,例如用户 ID、状态码等,建立索引后可以快速定位到少量数据。
经常需要查询的字段:对于一些经常单独查询的字段,可以创建覆盖索引,避免回表查询,提高查询效率。
15. MySQL 有哪些事务隔离级别?
MySQL 支持四种事务隔离级别,从低到高依次为:
READ UNCOMMITTED(读未提交):在这种隔离级别下,一个事务可以读取另一个未提交事务的数据,会产生脏读、不可重复读和幻读问题。
READ COMMITTED(读提交):一个事务只能读取另一个已经提交事务的数据,避免了脏读,但仍然存在不可重复读和幻读问题。这是大多数数据库的默认隔离级别,也是 MySQL 中 InnoDB 存储引擎的默认隔离级别。
REPEATABLE READ(可重复读):在同一个事务中,多次读取同一数据的结果是一致的,避免了脏读和不可重复读。不过,在这种隔离级别下,仍然可能出现幻读。MySQL 的 InnoDB 存储引擎通过 MVCC(多版本并发控制)和间隙锁解决了幻读问题。
SERIALIZABLE(串行化):事务串行执行,避免了脏读、不可重复读和幻读。但这种隔离级别会导致并发性能下降,通常只在对数据一致性要求极高且并发量较低的场景下使用。
16. 可重复读隔离级别是如何解决幻读问题的?
MySQL 的 InnoDB 存储引擎在可重复读隔离级别下,通过以下两种机制解决幻读问题:
MVCC(多版本并发控制):MVCC 为数据的每个版本都保存了一个时间戳,通过比较时间戳来决定哪些版本的数据是可见的。在可重复读隔离级别下,事务在开始时会创建一个一致性视图,之后的查询都会使用这个视图,保证了在同一个事务中多次读取同一数据的结果是一致的,避免了幻读。
间隙锁(Gap Lock):当执行范围查询时,InnoDB 不仅会锁定查询到的记录,还会锁定这些记录之间的间隙,防止其他事务在这些间隙中插入新记录,从而避免了幻读。例如,执行
SELECT * FROM table WHERE id BETWEEN 1 AND 10 FOR UPDATE
时,InnoDB 会锁定 ID 为 1 到 10 之间的间隙,阻止其他事务插入 ID 在这个范围内的记录。
17. 你参与过权限系统相关的项目吗?
18. 如果要为腾讯云的文档系统设计一个权限系统,你会怎么做?
为腾讯云文档系统设计权限系统时,可以参考以下设计思路:
1. 采用多层次权限模型
全局层级:对用户的注册、登录、找回密码等操作进行权限控制。
组织层级:在企业或团队层面,设置管理员、成员等角色,管理员能够管理组织的基本信息和成员。
空间层级:不同的空间可以设置不同的访问权限,例如公开空间、内部空间等。
文档层级:针对具体的文档或文件夹,设置创建、读取、更新、删除、分享等细粒度的权限。
2. 实现混合权限控制
基于角色的访问控制(RBAC):预先定义好角色,如管理员、编辑者、查看者等,每个角色对应一组权限。用户通过被分配角色来获得相应的权限,这种方式便于权限的批量管理。
基于属性的访问控制(ABAC):根据用户属性(如部门、职位)、资源属性(如文档类型、敏感度)、环境属性(如访问时间、IP 地址)等多方面因素动态计算用户的权限,增强权限控制的灵活性。
3. 设计权限继承与覆盖机制
文档的权限默认继承自父文件夹或空间,但也可以单独设置文档的权限,覆盖继承的权限。
当用户同时拥有多个角色或权限时,采用权限累加的原则,即用户拥有所有角色权限的并集。
4. 集成多因素认证
对于高敏感的文档或操作,如删除重要文档、导出大量数据等,要求用户进行二次认证,例如短信验证码、指纹识别等,提高系统的安全性。
5. 建立审计与监控机制
记录用户的所有权限变更操作和重要的文档访问行为,以便进行审计和追踪。
设置异常行为监控规则,如多次尝试访问受限文档、异常的权限变更操作等,及时发现并处理潜在的安全风险。
6. 提供灵活的权限管理界面
为管理员提供直观的权限管理界面,支持批量分配权限、权限模板管理等功能。
允许文档所有者或协作者自主管理文档的共享权限,方便日常使用。
7. 考虑与其他系统的集成
与企业的 LDAP、OAuth 等身份认证系统集成,实现单点登录。
与企业的组织结构同步,自动更新用户的角色和权限。
19. 如何应对突发的流量高峰?怎样设计一个智能的流量调度系统?
应对突发流量高峰的策略:
水平扩展:借助容器化技术(如 Kubernetes)和云服务(如 AWS Auto Scaling、腾讯云弹性伸缩),根据流量情况自动调整服务器数量,实现快速扩容和缩容。
缓存机制:在关键位置设置多级缓存,如 CDN、浏览器缓存、Redis 等,减少对后端服务的直接访问压力。
限流与熔断:
限流:对请求进行限速,例如采用令牌桶、漏桶算法,防止系统被过量请求压垮。
熔断:当服务出现故障或过载时,自动切断部分请求,避免级联失败。
降级策略:在流量高峰期间,暂时关闭非核心功能,保证核心业务的正常运行。例如,简化页面展示、暂停推荐算法等。
异步处理:将非实时性的任务放入消息队列(如 Kafka、RabbitMQ)进行异步处理,降低同步处理的压力。
负载均衡:使用高性能的负载均衡器(如 Nginx、HAProxy)将流量均匀分配到多个服务器上。
智能流量调度系统的设计:
多区域部署:采用多可用区、多地域部署服务,利用全局负载均衡(如 AWS Global Accelerator)将用户请求引导至最近且负载较低的区域。
智能路由:
依据用户地理位置,将请求路由到最近的服务器。
根据服务器的负载情况,动态调整流量分配比例。
对于特殊用户(如 VIP 用户),优先分配高性能服务器资源。
流量预测与预案:
基于历史数据和机器学习算法,预测流量高峰的时间和规模。
提前制定不同级别的应急预案,如扩容方案、限流阈值等。
自适应调整:
实时监控系统的负载、性能指标,动态调整流量分配策略。
结合 A/B 测试,自动选择最优的流量调度方案。
弹性资源分配:与云服务提供商合作,实现资源的弹性分配。例如,在流量高峰时自动租用更多的云服务器,流量下降后释放资源,降低成本。
20. 你实现过任务优先级调度系统吗?请介绍一下实现思路
实现任务优先级调度系统时,可以参考以下思路:
1. 任务模型设计
为每个任务定义优先级属性,例如使用整数(1 - 10)或枚举类型(LOW、MEDIUM、HIGH)来表示优先级。
除了优先级,任务还可以包含执行时间、超时时间、重试次数等其他属性。
2. 优先级队列实现
采用优先队列(如 Go 语言中的
container/heap
包、Java 中的PriorityQueue
)来存储任务,确保高优先级的任务能够优先被处理。当有新任务加入队列时,根据其优先级将任务插入到合适的位置。
3. 调度算法选择
抢占式调度:高优先级的任务可以中断正在执行的低优先级任务。这种调度方式适用于对实时性要求较高的场景。
非抢占式调度:高优先级的任务需要等待当前正在执行的任务完成后才能开始执行。这种调度方式实现相对简单,系统开销较小。
4. 资源分配策略
根据任务的优先级,为不同优先级的任务分配不同比例的系统资源,例如 CPU 时间、内存等。
对于高优先级的任务,可以分配更多的资源,确保其能够快速执行。
5. 任务执行与监控
使用工作线程池来执行任务,线程池的大小可以根据系统资源和负载情况动态调整。
监控任务的执行状态,记录任务的开始时间、完成时间、执行结果等信息。
对于超时未完成的任务,可以根据其优先级决定是否重新调度或终止执行。
6. 优先级调整机制
实现任务优先级的动态调整机制,例如任务等待时间过长时提高其优先级,避免饥饿现象。
支持人工干预,允许管理员手动调整任务的优先级。
7. 容错与重试策略
对于执行失败的任务,根据其优先级和重要性决定是否进行重试。
高优先级的任务可以设置更多的重试次数或更短的重试间隔。
8. 监控与告警
实时监控任务队列的长度、任务执行时间、系统资源使用情况等指标。
当队列长度超过阈值、高优先级任务等待时间过长时,及时发出告警,以便管理员进行干预。
21. 你有什么问题想要问我的吗?
这个问题以及第一个问题我昨天也提到了,想知道回答思路的可以移步这篇文章。
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:面试群。
版权声明: 本文为 InfoQ 作者【王中阳Go】的原创文章。
原文链接:【http://xie.infoq.cn/article/612eeee881aebbf7161cb3eeb】。文章转载请联系作者。
评论