长晴百度一面

根据口述整理,补充了面试中适合展开的回答口径。

面试问题#

Go#

  1. 介绍一下 GMP 是什么。
  2. channel 的作用以及注意事项。
  3. map 并发安全性,以及安全方案。
  4. slice 并发安全性。
  5. 进程、线程、协程的区别。
  6. 下面代码结果是什么?
func main() {
	s := make([]int, 3)
	s = append(s, 1, 2, 3)
	fmt.Print(s)
}
  1. 使用两个协程交替打印 1-100。

Redis#

  1. Redis 常用数据结构。
  2. 排行榜应该用哪个数据结构。
  3. 如果 ZSet 做排行榜时要获取某个分数范围内的用户,应该使用哪个命令。
  4. 分布式锁。
  5. Redis 里面的自增命令是哪个。

网络#

  1. 介绍一下 400、499 状态码。
  2. HTTPS 和 HTTP 的区别。
  3. HTTPS 的 TLS 加密是对称加密还是非对称加密。

RabbitMQ#

  1. RabbitMQ 如何保证消息的顺序性。
  2. RabbitMQ 如何保证消息消费的幂等性。

MySQL#

  1. SQL 题题干待补充。

参考答案(AI 生成)#

以下答案由 AI 生成,仅供面试复盘参考。

1. 介绍一下 GMP 是什么。#

答:GMP 是 Go runtime 的调度模型。G 代表 goroutine,M 代表操作系统线程,P 代表调度器处理器;M 需要绑定 P 才能执行 G。P 维护本地运行队列,调度器也有全局队列,空闲 P 会通过 work stealing 从其他 P 的队列里偷取 G。遇到网络 I/O 时,G 通常挂到 netpoll;遇到阻塞系统调用时,P 会转交给其他 M,让可运行的 G 继续被调度。

2. channel 的作用以及注意事项。#

答:channel 用于 goroutine 之间传递数据、同步执行顺序、实现生产消费模型和退出通知。无缓冲 channel 同时完成通信和同步,缓冲 channel 能吸收一定流量峰值并形成背压。关闭 channel 通常由发送方负责,接收方用 v, ok := <-ch 判断通道状态;向已关闭的 channel 发送会 panic。nil channel 会永久阻塞,select 常用于超时控制、取消控制和多路等待。

3. map 并发安全性,以及安全方案。#

答:普通 map 并发读写会产生 data race,严重时触发 fatal error: concurrent map writes。常见安全方案有 sync.RWMutex + map、sync.Map、分片 map、单 goroutine 持有 map 并通过 channel 串行访问。读多写少且 key 相对稳定的场景适合 sync.Map;业务逻辑复杂、需要组合操作时,RWMutex + map 更直观。

4. slice 并发安全性。#

答:slice 由指针、长度和容量组成,append 可能修改底层数组,也可能触发扩容并替换底层数组。多个 goroutine 同时 append 或写同一底层数组会产生 data race。常见做法是加锁保护,或提前分配固定长度后让每个 goroutine 写互相独立的下标,或让各 goroutine 生成局部 slice 后汇总合并。

5. 进程、线程、协程的区别。#

答:进程是资源分配单位,拥有独立地址空间;线程是 CPU 调度单位,同一进程内的线程共享地址空间和文件描述符等资源;协程是用户态轻量执行单元,由语言运行时或用户态调度器管理。进程隔离强,线程共享成本低,协程创建和切换成本更低。Go 的 goroutine 属于协程的一种实现,由 GMP 调度到操作系统线程上运行。

6. Redis 常用数据结构。#

答:常用结构包括 String、Hash、List、Set、ZSet、Bitmap、HyperLogLog、Stream、Geo。String 常用于缓存、计数器和分布式锁;Hash 常用于对象字段存储;List 常用于简单队列;Set 常用于去重和集合运算;ZSet 常用于排行榜、延迟队列和按分数范围查询;Stream 常用于消息流。

7. 排行榜应该用哪个数据结构。#

答:排行榜通常用 ZSet。用户 ID 作为 member,分数作为 score,通过 ZADD 写入或更新分数,通过 ZREVRANGE key start stop WITHSCORES 获取高分榜,通过 ZREVRANK 获取用户排名,通过 ZSCORE 获取用户分数。

8. 如果 ZSet 做排行榜时要获取某个分数范围内的用户,应该使用哪个命令。#

答:按分数范围查询用 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]。需要从高分到低分返回时,可以用 ZREVRANGEBYSCORE key max min WITHSCORES。Redis 6.2 之后也可以用 ZRANGE key min max BYSCORE REV WITHSCORES LIMIT offset count。

9. 分布式锁。#

答:Redis 分布式锁常用 SET lock_key token NX PX ttl 实现。NX 保证只有一个客户端能加锁,PX 设置过期时间,token 用于标识锁归属。释放锁时用 Lua 脚本先比较 token 再删除,保证只释放自己持有的锁。业务执行时间可能超过 TTL 时,需要续期机制;对强一致要求高的场景,优先使用数据库事务锁或一致性系统提供的锁能力。

10. Redis 里面的自增命令是哪个。#

答:String 自增用 INCR key,按指定步长自增用 INCRBY key increment。Hash 字段自增用 HINCRBY key field increment。浮点数自增用 INCRBYFLOAT 或 HINCRBYFLOAT。

11. 介绍一下 400、499 状态码。#

答:400 Bad Request 表示客户端请求格式或参数有问题,服务端无法正常解析或处理。499 Client Closed Request 是 Nginx 常见扩展状态码,表示客户端在服务端返回响应前主动断开连接,常见原因包括客户端取消请求、超时、网络中断或网关提前关闭连接。

12. HTTPS 和 HTTP 的区别。#

答:HTTP 是明文传输协议,默认端口 80。HTTPS 是 HTTP 运行在 TLS 之上,默认端口 443,提供传输加密、身份认证和完整性校验。HTTPS 握手阶段会校验证书并协商会话密钥,后续 HTTP 报文在 TLS 加密通道中传输。

13. HTTPS 的 TLS 加密是对称加密还是非对称加密。#

答:TLS 同时使用非对称能力和对称加密。握手阶段通过证书、非对称加密或密钥交换算法完成身份校验和密钥协商;数据传输阶段使用协商出的会话密钥做对称加密。这样兼顾身份可信和传输性能。

14. RabbitMQ 如何保证消息的顺序性。#

答:顺序保证的边界通常是单队列、单消费者、串行处理。需要提高吞吐时,可以按业务 key 做分片,把同一 key 的消息路由到同一个队列或同一个消费者实例。消费者侧设置合适的 prefetch,严格顺序场景常设为 1,并在处理成功后 ack。失败重试要维持同一 key 的处理顺序,可以暂停该 key 后续消息或把重试逻辑放到同一顺序链路里。

15. RabbitMQ 如何保证消息消费的幂等性。#

答:幂等性核心是为每条消息设计唯一业务 ID,例如订单号、流水号或 message_id。消费前先用数据库唯一索引、Redis SETNX 或幂等表记录处理状态;命中已处理记录时直接 ack。业务落库和幂等记录最好放在同一个事务里,ack 放在业务成功之后。状态机类业务可以用状态流转条件约束,例如只允许 INIT -> PAID,重复消息自然落到已完成状态。

16. 代码结果是什么。#

答:输出结果是:

[0 0 0 1 2 3]

make([]int, 3) 创建长度为 3 的 slice,初始元素都是 0。append(s, 1, 2, 3) 从当前长度位置继续追加元素,所以结果包含前 3 个零值和后追加的 3 个元素。

17. SQL 题。#

答:题干待补充。复盘 SQL 题时建议记录表结构、索引、样例数据、目标输出和限制条件,这些信息决定查询写法和优化点。

18. 使用两个协程交替打印 1-100。#

答:可以用两个无缓冲 channel 控制执行权交替传递。

package main

import (
	"fmt"
	"sync"
)

func main() {
	odd := make(chan struct{})
	even := make(chan struct{})

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		for i := 1; i <= 99; i += 2 {
			<-odd
			fmt.Println(i)
			even <- struct{}{}
		}
	}()

	go func() {
		defer wg.Done()
		for i := 2; i <= 100; i += 2 {
			<-even
			fmt.Println(i)
			if i < 100 {
				odd <- struct{}{}
			}
		}
	}()

	odd <- struct{}{}
	wg.Wait()
}