# 腾讯云智 Java 面试

大家好,我是小林

腾讯云智是腾讯的子公司,主要负责是腾讯云相关的业务,所以负责的项目就是腾讯云的项目,在腾讯csig工作的同学也跟我说,经常跟腾讯云智的同事对接工作。

腾讯云智公司主要在二线城市,总部是西安,然后长沙、武汉都有办公地点。

这次我们来看看 25 届「腾讯云智」开发岗的校招薪资情况。

img

  • 14.5k x 16 + 1k x 12(房补) + 2.4w签字费,同学 bg 本科,base 武汉
  • 14k x 16 + 1k x 12(房补) + 2.4w签字费,同学 bg 硕士 211,base 西安
  • 13k x 16 + 2w 签字费 + 1k x 12(房补),同学 bg 本科,base 西安
  • 12k x 16 + 2w 签字费 + 1k x 12(房补),同学 bg 本科,base 武汉

普通档的 offer 整体年包在 22~24w,sp offer 年包在 26w~27w,在二线城市的话,还算不错的。

那腾讯云智的面试难度如何?

这次我们来看看腾讯云智后端开发的校招面经,面试风格挺类似腾讯,喜欢问计算机基础多一些,然后每个常用的后端组件拷打几个问题,也是有算法题,所以想冲腾讯云智的同学,算法还是得多刷刷。

img

# 操作系统

# 堆区和栈区的区别

  • 分配方式:堆是动态分配内存,由程序员手动申请和释放内存,通常用于存储动态数据结构和对象。栈是静态分配内存,由编译器自动分配和释放内存,用于存储函数的局部变量和函数调用信息。
  • 内存管理:堆需要程序员手动管理内存的分配和释放,如果管理不当可能会导致内存泄漏或内存溢出。栈由编译器自动管理内存,遵循后进先出的原则,变量的生命周期由其作用域决定,函数调用时分配内存,函数返回时释放内存。
  • 大小和速度:堆通常比栈大,内存空间较大,动态分配和释放内存需要时间开销。栈大小有限,通常比较小,内存分配和释放速度较快,因为是编译器自动管理。

# 为什么临时变量放在栈里面,对象放在堆,为什么不能反过来?

临时变量放在栈上的原因:

  • 生命周期管理简单:栈上的内存管理是自动的,遵循后进先出(LIFO)的原则。时变量的生命周期通常与其作用域相关,当作用域结束时,栈上的内存会自动释放。
  • 访问速度快:栈内存的访问速度通常比堆内存快,因为栈内存的分配和释放是连续的,不需要复杂的指针操作。

对象放在堆上的原因:

  • 动态大小:对象的大小在编译时通常是未知的,需要在运行时动态分配内存。堆内存允许动态分配任意大小的内存块,适合存储对象。
  • **共享和持久性:**堆上的对象可以被多个变量引用,实现对象的共享。堆上的对象可以在函数调用结束后仍然存在,适合需要持久化的数据。

为什么不能反过来?

  • **栈内存的局限性:**栈内存的大小有限,如果将大对象或动态大小的对象放在栈上,可能会导致栈溢出。栈内存的分配和释放是连续的,不适合频繁的动态内存管理。
  • 对象生命周期管理的复杂性:如果将对象放在栈上,对象的生命周期将与其作用域绑定,这会限制对象的共享和持久性。需要手动管理对象的生命周期,增加了编程的复杂性和出错的可能性。
  • 性能问题:栈内存的访问速度虽然快,但如果频繁地在栈上进行大对象的分配和释放,会导致栈内存碎片化,影响性能。堆内存的分配和释放虽然相对较慢,但可以通过内存池等技术进行优化。

# 多进程和多线程区别?

资源占用与共享的区别:

  • 多进程是每个进程拥有独立的内存空间和系统资源。进程间的数据共享复杂且开销较大,通常需要使用IPC(进程间通信)机制如管道、消息队列、共享内存等。
  • 多线程是所有线程共享同一进程的内存空间和资源,线程间的数据共享简单高效,可以直接通过内存访问,但这也带来了同步和互斥的问题,需要使用锁、信号量等机制来避免竞争条件。

调度与切换的区别:

  • 进程是是操作系统资源分配的基本单位,调度开销相对较大,进程间的切换需要保存和恢复更多的上下文信息。
  • 线程是CPU调度的基本单位,调度开销较小。线程间的切换只需保存和恢复较少的寄存器和栈信息。

稳定性与安全性:

  • 在多进程中,一个进程崩溃通常不会影响其他进程,具有较好的隔离性和容错性。
  • 在多线程中,一个线程出错可能导致整个进程崩溃,需要更加谨慎地处理异常和同步问题。

# 线程和协程区别?

  • 首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己的独立内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的开销也较大,因为需要保存和恢复整个进程的状态。
  • 接下来是线程。线程是进程内的一个执行单元,也是CPU调度和分派的基本单位。与进程不同,线程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。
  • 最后是协程。协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,协程需要程序员显式地进行调度和管理,相对于线程和进程来说,其编程模型更为复杂。

# 进程切换和线程切换哪个快?

线程切换更快一些,线程切换比进程切换快是因为线程共享同一进程的地址空间和资源,线程切换时只需切换堆栈和程序计数器等少量信息,而不需要切换地址空间,避免了进程切换时需要切换内存映射表等大量资源的开销,从而节省了时间和系统资源。

# 网络

# DNS工作流程?

  1. 客户端首先会发出一个 DNS 请求,问 http://www.server.com的 IP 是啥,并发给本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。
  2. 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到 http://www.server.com,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器:“老大, 能告诉我 http://www.server.com 的 IP 地址吗?” 根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。
  3. 根 DNS 收到来自本地 DNS 的请求后,发现后置是 .com,说:“http://www.server.com 这个域名归 .com 区域管理”,我给你 .com 顶级域名服务器地址给你,你去问问它吧。”
  4. 本地 DNS 收到顶级域名服务器的地址后,发起请求问“老二, 你能告诉我 http://www.server.com 的 IP 地址吗?”
  5. 顶级域名服务器说:“我给你负责 http://www.server.com 区域的权威 DNS 服务器的地址,你去问它应该能问到”。
  6. 本地 DNS 于是转向问权威 DNS 服务器:“老三,http://www.server.com对应的IP是啥呀?” http://www.server.com的权威 DNS 服务器,它是域名解析结果的原出处。为啥叫权威呢?就是我的域名我做主。
  7. 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
  8. 本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。

至此,我们完成了 DNS 的解析过程。现在总结一下,整个过程我画成了一个图。

img

# 访问DNS服务器用的什么协议?

DNS 基于UDP协议实现,DNS使用UDP协议进行域名解析和数据传输。因为基于UDP实现DNS能够提供低延迟、简单快速、轻量级的特性,更适合DNS这种需要快速响应的域名解析服务。

  • 低延迟: UDP是一种无连接的协议,不需要在数据传输前建立连接,因此可以减少传输时延,适合DNS这种需要快速响应的应用场景。
  • 简单快速: UDP相比于TCP更简单,没有TCP的连接管理和流量控制机制,传输效率更高,适合DNS这种需要快速传输数据的场景。
  • 轻量级:UDP头部较小,占用较少的网络资源,对于小型请求和响应来说更加轻量级,适合DNS这种频繁且短小的数据交换。

# UDP可靠性不高是怎么解决了

尽管 UDP 存在丢包和数据包损坏的风险,但在 DNS 的设计中,这些风险是可以被容忍的。DNS 使用了一些机制来提高可靠性,例如查询超时重传、请求重试、缓存等,以确保数据传输的可靠性和正确性。

# ping用的什么协议,为什么不用UDP,要用ICMP?

ping 使用 ICMP 而不是 UDP 的主要原因是 ICMP 是专门为网络诊断和错误报告设计的协议,具有简单性、内置的错误处理机制和广泛的支持。

UDP 是一个无连接的协议,不提供内置的错误报告机制,如果使用 UDP 进行类似 ping 的操作,需要应用程序自己实现错误检测和报告逻辑。

ICMP 提供了内置的错误处理机制,如目标不可达、超时等。这使得 ping 命令能够自动处理这些错误,并提供有用的诊断信息。ping 命令只需要发送一个 ICMP Echo 请求数据包,并等待相应的 Echo 回应数据包。

# MySQL

# MySQL 索引为什么用 b+树?

  • B+Tree vs B Tree:B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。
  • B+Tree vs 二叉树:对于有 N 个叶子节点的 B+Tree,其搜索复杂度为O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。在实际的应用当中, d 值是大于100的,这样就保证了,即使数据达到千万级别时,B+Tree 的高度依然维持在 3~4 层左右,也就是说一次数据查询操作只需要做 3~4 次的磁盘 I/O 操作就能查询到目标数据。而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 O(logN),这已经比 B+Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘 I/O 次数要更多。
  • B+Tree vs Hash:Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因

# MySQL行锁和表锁介绍一下?

在 MySQL 里,根据加锁的范围,可以分为全局锁、表级锁和行锁三类。

img

  • 全局锁:通过flush tables with read lock 语句会将整个数据库就处于只读状态了,这时其他线程执行以下操作,增删改或者表结构修改都会阻塞。全局锁主要应用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。
  • 表级锁:MySQL 里面表级别的锁有这几种:
    • 表锁:通过lock tables 语句可以对表加表锁,表锁除了会限制别的线程的读写外,也会限制本线程接下来的读写操作。
    • 元数据锁:当我们对数据库表进行操作时,会自动给这个表加上 MDL,对一张表进行 CRUD 操作时,加的是 MDL 读锁;对一张表做结构变更操作的时候,加的是 MDL 写锁;MDL 是为了保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
    • 意向锁:当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。意向锁的目的是为了快速判断表里是否有记录被加锁
  • 行级锁:InnoDB 引擎是支持行级锁的,而 MyISAM 引擎并不支持行级锁。
  • 记录锁,锁住的是一条记录。而且记录锁是有 S 锁和 X 锁之分的,满足读写互斥,写写互斥
  • 间隙锁,只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。
  • Next-Key Lock 称为临键锁,是 Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。

# Redis

# Redis为什么快

官方使用基准测试的结果是,单线程的 Redis 吞吐量可以达到 10W/每秒,如下图所示:

img

之所以 Redis 采用单线程(网络 I/O 和执行命令)那么快,有如下几个原因:

  • Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,因此 Redis 瓶颈可能是机器的内存或者网络带宽,而并非 CPU,既然 CPU 不是瓶颈,那么自然就采用单线程的解决方案了;
  • Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题。
  • Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

# kafka

# kafka怎么做的吞吐量那么大

  • **顺序写入优化:**Kafka将消息顺序写入磁盘,减少了磁盘的寻道时间。这种方式比随机写入更高效,因为磁盘读写头在顺序写入时只需移动一次。
  • 批量处理技术:Kafka支持批量发送消息,这意味着生产者在发送消息时可以等待直到有足够的数据积累到一定量,然后再发送。这种方法减少了网络开销和磁盘I/O操作的次数,从而提高了吞吐量。
  • 零拷贝技术:Kafka使用零拷贝技术,可以直接将数据从磁盘发送到网络套接字,避免了在用户空间和内核空间之间的多次数据拷贝。这大幅降低了CPU和内存的负载,提高了数据传输效率。
  • 压缩技术:Kafka支持对消息进行压缩,这不仅减少了网络传输的数据量,还提高了整体的吞吐量。

# docker

# docker底层是怎么实现的??

  • 基于 Namespace 的视图隔离:Docker利用Linux命名空间(Namespace)来实现不同容器之间的隔离。每个容器都运行在自己的一组命名空间中,包括PID(进程)、网络、挂载点、IPC(进程间通信)等。这样,容器中的进程只能看到自己所在命名空间内的进程,而不会影响其他容器中的进程。
  • 基于 cgroups 的资源隔离**:cgroups 是Linux内核的一个功能,允许在进程组之间分配、限制和优先处理系统资源,如CPU、内存和磁盘I/O。它们提供了一种机制,用于管理和隔离进程集合的资源使用,有助于资源限制、工作负载隔离以及在不同进程组之间进行资源优先处理。

# 算法题

  • 区间合并

对了,最新的互联网大厂后端面经都会在公众号首发,别忘记关注哦!!如果你想加入百人技术交流群,扫码下方二维码回复「加群」。

img