Skip to content

春招百度后端一面面经

1、HTTP长连接?

HTTP长连接是指客户端与服务器端建立连接后,可以发送多个请求和接收多个响应,而不是每次请求都建立新的连接。这种机制可以减少连接建立的开销,提高通信效率。

在HTTP/1.0中,默认情况下是短连接,即客户端发送完请求并接收到响应后,连接就会关闭。而在HTTP/1.1中,默认使用长连接,即客户端发送完请求并接收到响应后,保持连接打开,可以继续发送请求。

使用长连接可以减少TCP的连接次数,减少网络延迟,提高效率。但需要注意的是,长连接会占用服务器资源,因此服务器需要根据实际情况来配置连接超时时间和最大连接数等参数,以防止资源被耗尽。

2、HTTP无状态?

HTTP是一种无状态协议,服务器在处理客户端请求时不会保存任何状态信息。每个请求都是独立的,服务器无法区分两个不同请求来自同一个客户端还是不同客户端。

HTTP无状态的特性有以下几个方面:

  1. 无状态性:每个请求都是独立的,服务器不会保存之前的请求信息,也不会记录客户端的状态。因此,服务器无法直接识别出多个请求来自同一个客户端。
  2. 简单性:由于HTTP是无状态的,所以实现起来比较简单,服务器只需要处理当前请求,不需要考虑之前的请求状态。
  3. 可伸缩性:由于服务器不需要保存请求状态,所以可以更容易地实现负载均衡和扩展。

为了在HTTP中实现状态管理,引入了Cookie和Session机制。Cookie通过在客户端存储信息来跟踪用户状态,而Session则是在服务器端保存用户状态的一种方式。这些机制使得HTTP在无状态的基础上可以实现一定程度的状态管理,从而提供更丰富的应用场景。

3、内核态和用户态?

内核态和用户态是指操作系统内核和用户程序运行的两种不同的权限级别或状态。在现代操作系统中,CPU通常支持至少两种特权级别,即内核态和用户态。

  1. 内核态(Kernel Mode):在内核态下,操作系统具有对系统硬件和资源的完全访问权限,可以执行任意指令,并且可以访问所有内存空间。在内核态下运行的代码具有最高的特权级别,可以执行特权指令(如I/O指令、修改控制寄存器等),并且可以处理中断和异常。
  2. 用户态(User Mode):在用户态下,程序只能访问受限的内存空间和受限的硬件资源,并且不能执行特权指令。用户程序通常在用户态下运行,无法直接访问系统资源,必须通过系统调用(System Call)向操作系统请求服务。

操作系统通过将进程的代码和数据分为内核空间和用户空间来实现内核态和用户态的区分。内核空间用于存放操作系统内核的代码和数据结构,只有在内核态下才能访问,而用户空间用于存放用户程序的代码和数据,只有在用户态下才能访问。

切换状态:当用户程序需要访问系统资源或执行特权操作时,需要向操作系统发起系统调用请求,此时会触发从用户态到内核态的切换。在这种切换过程中,操作系统会将当前进程的状态保存起来,并且切换到内核态去执行相应的系统调用。完成系统调用后,操作系统将进程的状态恢复,并返回到用户态继续执行用户程序。

4、TLS握手过程,证书验证机制?

握手过程

  1. 客户端发送支持的TLS版本和加密算法列表:客户端向服务器发送ClientHello消息,包含支持的TLS版本和加密算法列表。
  2. 服务器回应:服务器收到ClientHello消息后,选择一个加密算法和TLS版本,并将其发送给客户端,同时还包含服务器的数字证书(包括公钥)、服务器支持的加密算法列表以及一个随机数。
  3. 客户端验证证书:客户端收到服务器的回应后,首先验证服务器的证书是否有效,包括证书的颁发机构是否受信任、证书是否过期等。
  4. 生成密钥:客户端生成一个随机数,并使用服务器的公钥加密这个随机数,然后将加密后的随机数发送给服务器,用于协商对称密钥。
  5. 服务器解密:服务器收到客户端发送的加密随机数后,使用自己的私钥解密,得到客户端生成的随机数。
  6. 双方生成对称密钥:客户端和服务器都有了用于生成对称密钥的随机数,双方使用这些随机数生成对称密钥,用于加密通信数据。
  7. 开始加密通信:握手完成后,双方开始使用对称密钥进行加密通信。

证书验证机制主要包括以下几个方面:

  • 证书合法性验证:验证证书的签发机构是否受信任、证书是否过期、证书是否被吊销等。
  • 主体验证:验证证书中的主体信息是否与服务器的域名匹配。
  • 链完整性验证:验证证书链的完整性,确保证书链中的每个证书都是有效的。
  • 证书撤销检查:检查证书是否被吊销,可以通过在线证书撤销列表(CRL)或在线状态查询(OCSP)来检查。

5、mysql默认事务隔离级别,是怎么防止并行问题的?

MySQL默认的事务隔离级别是可重复读(REPEATABLE READ)。事务隔离级别是指在多个事务同时访问数据库时,数据库管理系统为了防止并发问题(如脏读、不可重复读、幻读)而采取的一种机制。

在可重复读隔离级别下,MySQL通过多版本并发控制(MVCC)机制来防止并发问题。MVCC机制通过在每条记录后面保存多个版本,每个版本对应不同事务的快照,从而实现不同事务之间的隔离。具体来说,对于读操作,MySQL会根据事务开始的时间戳(或者说版本号)来选择合适的版本,从而避免了不可重复读和幻读的问题。对于写操作,MySQL会为新插入的数据创建一个新版本,而不是直接更新原有数据,这样可以保证其他事务在读取数据时不会受到影响。

6、redis主从复制机制?

Redis的主从复制(Master-Slave Replication)是指将一个Redis服务器(Master)的数据复制到另一个Redis服务器(Slave)的过程,从而实现数据的备份、故障恢复和读写分离等功能。主从复制的机制如下:

  1. 建立连接:Slave通过向Master发送SYNC命令请求复制数据。Master接收到请求后开始生成快照(RDB文件)并将快照发送给Slave,之后还会将新的写命令发送给Slave。
  2. 初次复制:Slave收到Master发送的快照后,将其加载到内存中,然后开始接收Master发送的增量数据(写命令)。当增量数据全部接收完毕后,Slave就完成了对Master数据的初次复制。
  3. 增量复制:Slave在初次复制后,会持续接收Master发送的增量数据,保持和Master数据的同步。
  4. 心跳检测:Master和Slave之间会定期发送PING命令来检测对方的存活状态。
  5. 重新连接:如果Slave和Master连接断开,Slave会尝试重新连接Master,并进行部分重同步。

通过主从复制,可以提高系统的可用性和性能。例如,可以将读请求分发给Slave,减轻Master的压力;当Master发生故障时,可以快速切换到Slave以保证服务的可用性。

7、I/O多路复用机制?

I/O多路复用(I/O Multiplexing)是一种同时监控多个文件描述符(sockets、pipes等)状态变化的机制,常用于实现高并发网络编程。在Unix/Linux系统中,常见的I/O多路复用函数有select、poll和epoll。

  1. select:select函数允许程序指示内核等待多个文件描述符中的任何一个变为就绪状态,它会阻塞直到有文件描述符就绪或超时。select的缺点是效率较低,因为每次调用都需要遍历所有文件描述符,同时select函数有文件描述符数量的限制。
  2. poll:poll函数与select类似,也是等待多个文件描述符中的任何一个变为就绪状态。与select不同的是,poll没有文件描述符数量的限制,因为它使用了链表来保存文件描述符。
  3. epoll:epoll是Linux特有的高性能I/O多路复用机制,它通过内核事件表(eventpoll)来管理文件描述符,不需要遍历所有文件描述符,因此效率更高。epoll提供了三个函数:epoll_create创建一个epoll句柄,epoll_ctl向epoll句柄中添加或删除文件描述符,epoll_wait等待文件描述符就绪。

使用I/O多路复用机制可以避免使用多线程或多进程来处理多个连接,从而减少了系统资源的消耗,提高了系统的并发性能。

8、给段实际代码,三个地方判断变量是否为nil(自定义类型赋值给error)?

在Go语言中,可以使用类型断言(type assertion)来判断接口类型变量是否为nil,并将自定义类型赋值给error。

给段代码:

go
package main

import (
	"errors"
	"fmt"
)

type MyError struct {
	message string
}

func (e *MyError) Error() string {
	return e.message
}

func main() {
	var err error

	// 判断第一个地方
	if err == nil {
		err = &MyError{message: "First error occurred"}
	}

	// 判断第二个地方
	if e, ok := err.(*MyError); ok && e != nil {
		fmt.Println("Second error:", e.Error())
	}

	// 判断第三个地方
	if _, ok := err.(*MyError); !ok {
		err = errors.New("Third error occurred")
	}

	fmt.Println("Final error:", err.Error())
}

9、给段实际代码,判断defer函数中对返回变量的影响?

Go语言中,defer语句可以延迟函数的执行直到外围函数返回。defer语句中的函数会在defer语句所在函数执行结束时执行,但是defer语句中对变量的影响可能会因为变量的作用域和闭包的使用而有所不同。

给个例子:

go
package main

import "fmt"

func main() {
	fmt.Println("start")

	fmt.Println("result:", test())

	fmt.Println("end")
}

func test() (result int) {
	defer func() {
		result += 100
	}()

	result = 1

	return result
}

10、两个goroutine交替打印数字和字母,先打印数字?

要实现两个goroutine交替打印数字和字母,并且先打印数字,可以利用两个channel来进行通信,一个channel用于控制数字的打印,另一个channel用于控制字母的打印。

思路

  1. 创建两个channel,一个用于控制数字的打印,一个用于控制字母的打印。
  2. 启动一个goroutine来打印数字,另一个goroutine来打印字母。
  3. 在每个goroutine中,使用for循环不断地从对应的channel中读取数据,并打印相应的内容。
  4. 在主goroutine中,向数字channel发送一个初始值,使得数字goroutine首先执行。

参考代码

go
package main

import (
	"fmt"
	"sync"
)

func main() {
	// 用于控制数字的打印
	numCh := make(chan struct{})
	// 用于控制字母的打印
	charCh := make(chan struct{})

	// 使用sync.WaitGroup等待两个goroutine结束
	var wg sync.WaitGroup
	wg.Add(2)

	// 打印数字的goroutine
	go func() {
		defer wg.Done()
		// 先等待数字channel发送初始值
		<-numCh
		for i := 1; i <= 26; i++ {
			// 打印数字
			fmt.Print(i)
			// 通过字母channel通知字母goroutine可以打印字母了
			charCh <- struct{}{}
			// 等待字母goroutine打印完毕
			<-numCh
		}
	}()

	// 打印字母的goroutine
	go func() {
		defer wg.Done()
		for i := 'A'; i <= 'Z'; i++ {
			// 等待数字goroutine通知可以打印字母
			<-charCh
			// 打印字母
			fmt.Printf("%c ", i)
			// 通过数字channel通知数字goroutine可以打印数字了
			numCh <- struct{}{}
		}
	}()

	// 向数字channel发送初始值,使得数字goroutine首先执行
	numCh <- struct{}{}

	// 等待两个goroutine结束
	wg.Wait()
}