Go后端开发-字节一面
## 1.自我介绍
好的。
我目前有大概 6 年后端开发经验，前几年在一家中型电商公司做业务后端，后来转到一家做 SaaS 的公司担任 Tech Lead，带过 5 人左右的小团队。
技术栈主要是 Go，早期也写过一段时间 Java。做过的系统包括高并发订单、Feed 流、消息推送这类，QPS 峰值在几万级别，算不上超大规模但该踩的坑基本都踩过。
最近一年多在做 AI 工程化方向，主要是 RAG 系统落地和 Agent 框架搭建，也做过一些推理侧的优化——主要是降延迟和控成本，用过 vLLM，对 KV Cache、量化这些有实践经验，不算特别深但都是真实上线过的。
对字节感兴趣主要是两点：一是规模，字节的系统体量能接触到我现在遇不到的问题；二是 AI 基础设施这块，字节在这方面投入很大，我想在更大的平台上深耕这个方向。
有什么想深入了解的可以直接问。

## 2.如果让你编译一个golang的程序，在linux下执行包含哪些系统调用
**编译阶段**（编译器本身运行时）
```bash
go build main.go
```
编译器进程本身会用到：
- `execve`：启动编译器进程
- `open/read`：读取源文件、依赖包
- `write`：写出目标文件
- `mmap`：映射文件到内存
- `fork/clone`：编译器可能启动子进程（汇编器、链接器）
**程序启动阶段**（./main 执行时）
Go runtime 初始化涉及：
- `execve`：启动进程
- `mmap`：申请虚拟地址空间（堆、栈）
- `mprotect`：设置内存页权限
- `clone`：创建系统线程（M）
- `sigaction`：注册信号处理（GC、抢占调度依赖信号）
- `futex`：线程同步
**运行阶段**
- `mmap/munmap`：堆内存扩缩
- `read/write`：IO 操作
- `epoll_create/epoll_wait`：netpoller 底层
- `nanosleep`：调度器 sleep
- `rt_sigreturn`：信号处理返回
**验证方式**
```bash
strace ./main 2>&1 | head -50
```
实际跑一下一目了然，Go 程序启动时 mmap 调用特别多，VSZ 看起来大就是这个原因。这块我是结合 strace 实际观察过，但内核源码层面没深入读过。

## 3.一个http请求的整个路由过程
**从网络到业务代码的完整路径**

**1. 网卡 → 内核网络栈**

```
网卡收包 → DMA 写入 Ring Buffer
→ 硬中断通知 CPU → 软中断（NET_RX）
→ TCP/IP 协议栈处理 → 数据写入 socket 接收缓冲区
```

**2. epoll 唤醒** Go netpoller 基于 epoll：

```
epoll_wait 检测到 fd 可读
→ 唤醒对应 goroutine
→ 从 socket buffer 读取数据
```

**3. HTTP 解析**

```
读取原始字节流
→ 解析请求行（Method、Path、Version）
→ 解析 Header
→ 读取 Body
```

**4. 路由匹配** 以 gin 为例：

```
注册时构建路由树（Radix Tree）
请求到来 → 按 Method 找对应树
→ 前缀匹配找到 handler
→ 提取路径参数（:id、*path）
```

**5. 中间件链执行**

```
middleware1 → middleware2 → handler → middleware2 → middleware1
```

洋葱模型，gin 用 index 递增控制：

```go
c.Next()  // 推进到下一个handler
c.Abort() // 中断链路
```

**6. 响应写回**

```
handler 写 ResponseWriter
→ 序列化响应体
→ 写入 socket 发送缓冲区
→ 内核发送
```

踩过的坑：路由注册顺序影响匹配，`/user/profile` 和 `/user/:id` 同时注册时要注意静态路由优先级高于参数路由，gin 的 radix tree 处理了这个但自己实现路由容易踩。

4.拷打项目（拷打的非常多）
## 5.http2和http3的区别，http3不可靠的udp怎么实现可靠
**HTTP/2 vs HTTP/3 核心区别**

||HTTP/2|HTTP/3|
|---|---|---|
|传输层|TCP|QUIC（基于 UDP）|
|队头阻塞|解决了 HTTP 层，TCP 层还有|彻底解决|
|握手|TLS 1~2 RTT|0~1 RTT|
|连接迁移|不支持|支持（换网络不断连）|

**HTTP/2 的遗留问题**

多路复用解决了 HTTP 层队头阻塞，但 TCP 是字节流，一个包丢失整条连接都要等重传——**TCP 层队头阻塞**依然存在，弱网环境下反而比 HTTP/1.1 更差。

**HTTP/3 = HTTP over QUIC**

QUIC 在 UDP 之上自己实现了可靠传输：

**可靠性保障机制**

```
序列号：每个 QUIC 包有唯一 Packet Number，单调递增
ACK：接收方确认，发送方维护发送窗口
重传：超时或收到 NACK 触发重传
流量控制：连接级 + 流级别双重控制
```

**彻底解决队头阻塞**

```
Stream A: [pkt1] [pkt2丢失] [pkt3]
Stream B: [pkt4] [pkt5]
```

Stream A 丢包只阻塞 Stream A，Stream B 不受影响——因为 QUIC 在用户态管理每个 Stream 的状态。

**0-RTT 握手**

```
首次连接：1-RTT（比TLS1.3 over TCP 少一次）
再次连接：0-RTT，直接带上之前的会话票据
```

**连接迁移** TCP 连接由四元组标识，换 IP 就断了。QUIC 用 **Connection ID** 标识连接，切换网络（WiFi → 4G）连接不中断。

这块我了解原理，生产环境还没大规模用过 HTTP/3，主要是服务端支持和中间件兼容性还不够成熟。

6.手撕代码：二叉树的右视图
## 7.mysql给一个比较长的读取操作，例如对全表进行读取，再读取的同时来了一个写入操作会发生什么；快照会把所有的数据都copy一份吗；每一次读取都需要把undo log回放一遍吗
**全表读 + 并发写入会发生什么**

MySQL InnoDB 默认 RR 隔离级别，普通 SELECT 走 **MVCC 一致性读**：

```
全表扫描开始 → 创建 ReadView（记录当前活跃事务列表）
→ 读取每行时，根据 ReadView 判断该版本是否可见
→ 期间写入操作正常进行，不阻塞读
→ 读到的始终是事务开始时的快照版本
```

写入不会被读阻塞，读也不会被写阻塞——这是 MVCC 的核心价值。

**快照会 copy 所有数据吗**

**不会**，这是个常见误解。

ReadView 只是一个轻量结构：

```go
// 伪代码
ReadView {
    creator_trx_id  // 创建者事务ID
    trx_ids         // 创建时活跃事务列表
    up_limit_id     // 活跃事务最小ID
    low_limit_id    // 下一个事务ID
}
```

数据还在原地，版本链挂在行记录上，通过 undo log 串起来。读取时按需回溯，不是提前 copy。

**每次读都要回放整个 undo log 吗**

**不是回放，是版本链查找**，而且不一定每次都要遍历：

```
行记录 → trx_id 可见？直接读
       → 不可见 → 找 roll_pointer → 上一版本
       → 再判断可见性 → 直到找到可见版本
```

代价取决于版本链长度——长事务 + 高频更新的行，版本链可能很长，读性能会下降。

**踩过的坑**：长事务不提交导致 undo log 无法清理，版本链越来越长，后来读这些热点行明显变慢。监控里加了 `INFORMATION_SCHEMA.INNODB_TRX` 告警，发现长事务及时干掉。
