今天分享的是腾讯校招的一面面经 ,大厂拷问的知识点都很广泛,如果你也准备冲击大厂,一定要做足了准备,语言基础、数据库、缓存、消息队列、操作系统、计算机网络、算法、项目 等等,基本上都会问到而且会问的很全很细,而且去大厂面试,要是没通过的话还会有记录,后面还想再去面就难咯。
先唠到这,下面开始分享热乎乎的面经:
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 协程时,你遇到过哪些问题? 常见的问题有以下这些:
资源竞争 :多个协程同时访问和修改共享数据时,可能会引发数据竞争,导致程序出现不可预期的结果。死锁与活锁 :在使用通道(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. 监控与告警
实时监控任务队列的长度、任务执行时间、系统资源使用情况等指标。 当队列长度超过阈值、高优先级任务等待时间过长时,及时发出告警,以便管理员进行干预。