Files
mengyanote/mengyanote-backend/data/mengyanote/实习求职/面试经历/Golang后端开发/字节/✅Go后端开发-字节一面.md

6.8 KiB
Raw Blame History

Go后端开发-字节一面

1.自我介绍

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

2.如果让你编译一个golang的程序在linux下执行包含哪些系统调用

编译阶段(编译器本身运行时)

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/writeIO 操作
  • epoll_create/epoll_waitnetpoller 底层
  • nanosleep:调度器 sleep
  • rt_sigreturn:信号处理返回 验证方式
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 递增控制:

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 AStream 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 只是一个轻量结构:

// 伪代码
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 告警,发现长事务及时干掉。