LOADING

加载过慢请开启缓存 浏览器默认开启

面经1

2025/12/22 面经

2025.12.22

讲一讲 GMP 是什么

GMP 模型

GMP 是 Go 语言的调度模型,代表 Goroutine、Machine、Processor。它帮助 Go 高效地管理并发任务。

  1. **Goroutine (G)**:
    • Goroutine 是 Go 中的 轻量级线程。你可以通过 go 关键字启动一个 Goroutine,来执行并发任务。每个 Goroutine 占用很少的内存,通常是 2KB。
  2. **Machine (M)**:
    • Machine 是 Go 语言中的 操作系统线程。每个 M 负责执行一个或多个 Goroutine。M 直接与操作系统的线程绑定。
  3. **Processor (P)**:
    • Processor 是 虚拟处理器,负责调度和管理 Goroutine。P 有一个本地队列,存放着需要执行的 Goroutine。每个 P 会将任务交给 M 执行。

调度原理

  • 每个 P 会选择一个 M 来执行它的任务队列中的 Goroutine。
  • 当 P 的队列空了,P 会从其他 P 的队列中 偷取 一些任务,这叫做 工作窃取
  • 这样,Go 语言的调度器能够有效地分配任务,让多个 Goroutine 并发执行。

go一个协程写了一个死循环会一直占有CPU吗?

情况 1:死循环没有阻塞操作

如果死循环内的代码没有任何 阻塞操作(比如 time.Sleep()channel 的发送或接收等),那么这个 Goroutine 会 持续执行,并且它会一直占用 CPU,导致 CPU 资源被完全占用

情况 2:死循环有阻塞操作

如果死循环中的代码包含了 阻塞操作,比如 time.Sleep()channel 的操作(发送、接收),那么 Goroutine 会在 阻塞释放 CPU,让其他 Goroutine 有机会执行。

kafka 为什么能应对高并发场景

分布式架构

  • 分布式设计:Kafka 设计上就是一个分布式系统,可以水平扩展。它将数据分为多个 分区(Partition),每个分区可以分布在不同的服务器上,分布式的设计让 Kafka 可以横向扩展,处理更高的并发和流量。
  • 高可用性:Kafka 的分区可以有多个副本(Replicas),即使某个节点宕机,其他副本可以继续提供服务,确保系统的高可用性。

分区机制

  • Kafka 使用 分区 来管理数据,每个 Topic 可以有多个 Partition,每个 Partition 负责一部分数据的存储和处理。每个分区的数据是有序的,可以并行处理不同分区的数据,极大地提升了 Kafka 的吞吐量和并发处理能力。
  • 在生产者和消费者之间,Kafka 会根据消息的键(key)或者 轮询(Round-robin) 等方式将数据分布到不同的分区,从而支持并行写入和读取。

顺序写入

  • Kafka 在 磁盘 上的写入是顺序的,而不是随机的。磁盘的顺序写入性能远高于随机写入,因此 Kafka 能够以极高的速度写入大量数据。顺序写入减少了磁盘的 I/O 操作,提高了系统的吞吐量。

高效的消息消费模型

  • Kafka 的消费者是 分布式的,并且支持 异步消费。消费者可以 并行 消费多个分区的数据,每个消费者只负责自己的分区,因此能够在多个消费者之间均衡负载,从而有效应对高并发的消费需求。
  • 消费者组(Consumer Group)可以将多个消费者组合起来并行处理数据。每个消费者组中的消费者会分配到不同的分区,确保每个消息只能被一个消费者处理。这样多个消费者可以同时处理多个分区,提高了处理的并发能力。

持久化和消息压缩

  • Kafka 的数据是持久化存储的,但 Kafka 会 将数据追加到文件末尾,而不是修改文件。这样写操作效率极高。Kafka 还支持 消息压缩,能够在传输时压缩数据,减少带宽和存储占用,进而提升性能。
  • Kafka 允许根据需要配置 数据保留策略,比如基于时间或空间来决定是否删除过期数据,这样也能保持良好的性能。

异步生产和消费

  • Kafka 的生产者和消费者都是 异步 的,这意味着生产者将消息异步发送到 Kafka 集群,而消费者则异步地从 Kafka 中消费消息。这种设计减少了请求的等待时间,提升了整体的吞吐量和响应速度。

批量处理

  • Kafka 在生产者端支持 批量发送 消息,多个消息可以一起发送到 Kafka 中。这样减少了网络请求的次数,提高了吞吐量。
  • 消费者端也支持 批量消费,可以一次性从 Kafka 中读取多条消息,进一步提高了并发处理的效率。

消息顺序保证

  • Kafka 保证同一个 分区内的消息顺序。这对于需要保序的高并发场景(如日志、交易等)非常重要。消费者在消费消息时,可以保证按照发送的顺序处理,避免数据错乱。

kafka 如何保证消息的可靠性

数据复制:Kafka 会将每个分区的数据复制到多个节点(副本),保证即使某个节点宕机,数据也不会丢失。

消息持久化:Kafka 将消息写入磁盘,保证即使系统崩溃,消息不会丢失。

生产者确认机制(acks):通过配置生产者的 acks,可以控制消息写入成功的确认级别:

  • acks=0:不等待确认,可能丢失消息。
  • acks=1:等待 Leader 副本确认。
  • acks=all:等待所有副本确认,最可靠。

消费确认:消费者通过 提交位移(offset)来确保消息被成功消费,避免重复消费。

故障恢复:Kafka 自动选举新的 Leader 保证分区的可用性,并支持自动再平衡。

幂等性和事务:生产者支持 幂等性,确保消息不重复。支持 事务,确保多条消息的原子性。

WebSocket和HTTP的区别,为什么选用WebSocket?

WebSocket 与 HTTP 的区别

HTTP 是请求-响应模式,连接后会关闭,每次请求都需要重新建立连接。

WebSocket 是全双工通信,客户端和服务器可以保持持久连接,双向实时交换数据。

为什么选用 WebSocket 在即时通讯服务中?

  • 实时性:WebSocket 允许服务器主动推送消息,适合即时通讯服务中实时消息传递的需求。
  • 低延迟:WebSocket 在建立连接后,可以持续传输数据,减少了 HTTP 中每次请求的延迟。
  • 节省资源:WebSocket 保持长连接,避免了 HTTP 每次请求都需要建立新连接的开销,更高效。
  • 双向通信:WebSocket 支持客户端和服务器的双向实时通信,适合多人在线聊天和互动功能。

知道MySQL的隔离级别吗?有哪些?

读未提交(Read Uncommitted)

  • 特点:事务可以读取其他事务未提交的数据,可能会发生 脏读(Dirty Read),即读取到尚未提交的更改。

读已提交(Read Committed)

  • 特点:事务只能读取已提交的事务的数据,避免了脏读,但仍然可能发生 不可重复读(Non-repeatable Read),即一个事务在多次读取相同数据时,其他事务可能已经修改了该数据。

可重复读(Repeatable Read)

  • 特点:事务在整个过程中读取的数据是 一致的,即使其他事务修改了数据,也无法影响当前事务的读取。这避免了脏读和不可重复读。但在某些情况下,仍然可能发生 幻读(Phantom Read),即事务在查询过程中,另一事务插入了新的数据行,使得当前事务查询结果发生变化。

串行化(Serializable)

  • 特点:这是最严格的隔离级别,事务执行时会对涉及的数据加锁,完全避免脏读、不可重复读和幻读,确保事务之间 串行执行

知道什么是聚簇索引和非聚簇索引吗?它们的区别有什么?

聚簇索引(Clustered Index)

  • 定义:聚簇索引是 数据表的物理存储顺序 与索引的顺序相同。数据行本身就是按聚簇索引的顺序存储的。
  • 特点
    • 每个表只能有 一个聚簇索引,因为数据表的物理存储只能按一种顺序排序。
    • 聚簇索引的 叶节点 存储的是数据行本身。
    • 查询效率高,适用于范围查询。

非聚簇索引(Non-clustered Index)

  • 定义:非聚簇索引是单独存储的索引结构,它的 叶节点 存储的是数据行的 地址(指向实际数据的指针),而数据行的物理存储顺序与索引顺序无关。
  • 特点
    • 每个表可以有 多个非聚簇索引
    • 非聚簇索引的 叶节点 存储索引键值和对应的数据指针。
    • 查询效率较低,但支持更多的索引类型。

区别

  1. 存储方式
    • 聚簇索引:数据表的物理存储顺序与索引顺序一致。
    • 非聚簇索引:索引结构与数据表的物理存储顺序无关,索引和数据分开存储。
  2. 数量
    • 聚簇索引:每个表只能有 一个聚簇索引
    • 非聚簇索引:每个表可以有多个非聚簇索引。
  3. 性能
    • 聚簇索引:适合 范围查询,因为数据是有序存储的。
    • 非聚簇索引:查找时需要额外的 指针跳转,性能稍差,但支持更多的查询方式。

知道什么是回表吗?能说一下什么时候会发生回表吗?

回表(Back To Table)是指在数据库中使用 非聚簇索引 查询时,当索引查询到数据的 索引键值 后,仍然需要通过索引中的 指针 再去 数据表中查询实际的记录

回表通常发生在以下两种情况:

  1. 使用非聚簇索引查询
    • 当使用 非聚簇索引 进行查询时,如果查询的列不在索引中,数据库就会先通过索引获取到对应的 行地址行标识符,然后再通过这些信息去实际的数据表中获取完整的数据行,这就叫做回表。
  2. 查询列不在索引中
    • 如果你创建了一个非聚簇索引,但是查询的列 不包含在索引的字段 中,那么即使通过索引查找到了对应的数据行的地址,依然需要访问数据表来获取完整的数据内容,导致回表。

知道什么是索引下推吗?

索引下推(Index Pushdown) 是一种优化技术,用于将 过滤条件 下推到索引扫描阶段,而不是在数据表查询阶段进行过滤。通过这种优化,数据库能够在索引扫描时就过滤掉不符合条件的记录,减少不必要的数据访问,提高查询效率。

简单说明

  • 传统方式:在没有索引下推的情况下,数据库首先通过索引找到对应的行,然后将过滤条件应用于数据行,最终返回符合条件的结果。
  • 索引下推:在使用索引扫描时,数据库会将过滤条件下推到索引扫描中,只有符合条件的记录会从索引中提取,减少数据表扫描的工作量。

举个例子

假设有一个表 users,并在 agestatus 字段上创建了复合索引:

CREATE INDEX idx_age_status ON users(age, status);

查询如下:

SELECT * FROM users WHERE age > 30 AND status = 'active';

如果启用了 索引下推,数据库会在扫描索引时就应用 age > 30status = 'active' 的过滤条件,直接从索引中获取符合条件的记录,避免回表查询整个数据表。

知道redis的持久化策略有哪些吗?现在Redis用的是哪种?

RDB快照

  • 原理:Redis 会在指定的时间间隔内将内存中的数据快照保存到磁盘,生成一个 RDB 文件(通常是 dump.rdb)。

AOF日志

  • 原理:Redis 会记录每个写操作,并将其追加到 AOF 文件中。每次启动 Redis 时,会根据 AOF 文件恢复数据。

Redis 默认使用 RDB 持久化,但也支持 AOF 持久化,用户可以根据需求在配置文件中选择开启其中一种或两种。

讲一讲RDB和AOF的优缺点

RDB(Redis Database)

优点

  1. 性能高:生成快照时性能较好,不会影响 Redis 的正常操作。
  2. 恢复快:RDB 文件是整个数据库的快照,加载速度快,适合快速恢复。
  3. 磁盘空间小:比 AOF 文件小,磁盘 I/O 开销较低。
  4. 适合备份:可以定期生成快照,用于灾难恢复。

缺点

  1. 数据丢失风险:两次快照之间的数据可能丢失,尤其是 Redis 崩溃时。
  2. 内存消耗大:生成快照时会占用额外内存。
  3. 恢复间隔不灵活:只能按配置的间隔进行快照,无法实时保存每次操作。

AOF(Append Only File)

优点

  1. 数据持久化精确:记录每个写操作,数据丢失风险小。
  2. 灵活性高:可以设置不同的同步策略(如每秒同步、每次操作同步)。
  3. 精确恢复:可以重放命令恢复到最近的状态。

缺点

  1. 性能开销大:每次写操作都要追加到文件,增加了磁盘 I/O 和 CPU 开销。
  2. 磁盘空间大:AOF 文件通常较大,写入频繁时文件膨胀较快。
  3. 恢复慢:恢复时需要重放所有写命令,速度相对较慢。