计算机系统概览¶
学习目标¶
学完本章后,学习者应该能够:
- 解释 CPU、内存、磁盘、网络在后端系统中的作用。
- 理解程序从代码到运行进程的大致过程。
- 建立“资源是有限的”这个工程意识。
- 看到性能问题时,能先判断可能属于哪类资源瓶颈。
后端服务依赖哪些基础资源¶
一个 Go 后端服务运行时,会同时使用多种系统资源:
flowchart TD
Code["Go 源代码"] --> Build["编译"]
Build --> Binary["可执行文件"]
Binary --> Process["运行中的进程"]
Process --> CPU["CPU"]
Process --> Memory["内存"]
Process --> Disk["磁盘 / 文件系统"]
Process --> Network["网络"]
Process --> OS["操作系统"]
这些资源任何一个出问题,服务都可能表现异常。
例如:
- CPU 忙:请求处理变慢,延迟升高。
- 内存不足:频繁 GC、OOM、进程退出。
- 磁盘慢:日志写入慢、数据库慢、文件上传慢。
- 网络差:请求超时、连接失败、依赖调用失败。
- 操作系统限制:文件描述符耗尽、端口耗尽、进程被杀。
CPU¶
CPU 负责执行指令。后端服务中的 CPU 消耗通常来自:
- JSON 编解码。
- 加解密。
- 压缩和解压缩。
- 复杂计算。
- 正则匹配。
- 大量日志格式化。
- 高频循环。
- GC 工作。
CPU 问题常见现象:
- 接口整体变慢。
- 机器 load 升高。
- pprof 显示某些函数占用大量 CPU。
- 容器被限制 CPU 后延迟抖动明显。
Go 服务排查 CPU 问题时,后续会学习 pprof、top、容器指标和 Kubernetes 资源限制。
内存¶
内存用于保存程序运行时的数据。
后端服务中的内存消耗通常来自:
- 请求对象。
- 响应对象。
- 缓存。
- 大 slice / map。
- 数据库查询结果。
- 文件上传缓冲区。
- goroutine 栈。
- 第三方库内部对象。
内存问题常见现象:
- 内存持续上涨。
- GC 频率变高。
- 请求延迟变大。
- 容器 OOMKilled。
- 程序被操作系统杀死。
新手常见问题是一次性把大量数据读入内存,比如导出全量数据、读取大文件、查询没有分页的大结果集。
磁盘与文件系统¶
磁盘用于持久化数据。后端服务直接或间接依赖磁盘:
- 写日志。
- 读配置。
- 上传文件。
- 数据库存储数据。
- 消息队列存储消息。
- 容器镜像和层文件。
磁盘问题常见现象:
- 日志写入失败。
- 数据库变慢。
- 磁盘打满导致服务异常。
- 文件上传失败。
- 容器无法启动或无法拉取镜像。
后端工程师不一定每天管理磁盘,但必须知道磁盘不是无限的,日志和临时文件都需要治理。
网络¶
后端服务通过网络和外部世界通信:
- 接收客户端请求。
- 调用其他服务。
- 连接数据库。
- 连接 Redis。
- 投递消息队列。
- 调用第三方 API。
网络问题常见现象:
- 连接超时。
- 请求偶发失败。
- DNS 解析失败。
- 连接数过多。
- 延迟升高。
- 服务间调用不稳定。
后端工程师要习惯给所有外部调用设置超时,而不是默认无限等待。
操作系统¶
操作系统负责管理 CPU、内存、文件、网络、进程和权限。
后端服务常接触的操作系统概念包括:
- 进程。
- 线程。
- 文件描述符。
- 端口。
- 环境变量。
- 系统调用。
- 用户权限。
- 信号。
- cgroups。
- namespace。
例如,Go 服务优雅退出时,需要处理 SIGTERM 信号;容器内存限制依赖 cgroups;网络连接依赖文件描述符。
编译与运行¶
Go 是编译型语言。一般流程是:
flowchart LR
Source[".go 源代码"] --> Compiler["go build"]
Compiler --> Binary["可执行文件"]
Binary --> Process["进程"]
Process --> Listen["监听端口"]
Listen --> Request["处理请求"]
这和解释型脚本语言不同。Go 程序通常会被编译成一个可执行文件,再放到服务器或容器里运行。
这也是 Go 很适合后端基础设施项目的原因之一:
- 部署产物简单。
- 启动速度快。
- 运行时依赖少。
- 容器镜像容易做小。
资源瓶颈的基本判断¶
遇到“服务慢”,不要只说“优化代码”,要先判断瓶颈在哪。
| 现象 | 可能方向 |
|---|---|
| CPU 持续很高 | 计算、编解码、GC、死循环 |
| 内存持续上涨 | 缓存无限增长、对象泄漏、大结果集 |
| 磁盘满 | 日志、临时文件、数据库数据、镜像 |
| 请求超时 | 网络、依赖服务、数据库、连接池 |
| 连接失败 | 端口、DNS、防火墙、服务未启动 |
| 偶发慢 | GC、锁竞争、外部依赖抖动、资源争抢 |
常见误区¶
- 误区一:程序慢一定是代码写得差。
可能是数据库慢、网络慢、磁盘慢、CPU 被限制,也可能是外部依赖不稳定。
- 误区二:内存越大越不需要管理。
内存越大,错误用法可能隐藏越久;一旦流量变大,问题会更难排查。
- 误区三:后端不需要懂操作系统。
服务运行在操作系统上。进程、端口、文件描述符、信号、权限都会影响服务。
实战任务¶
在你的电脑上找一个正在运行的服务或进程,完成下面观察:
- 进程 ID 是多少?
- 它是否监听端口?
- 它占用多少内存?
- 它有没有持续消耗 CPU?
- 它的日志在哪里?
面试题¶
1. CPU、内存、磁盘、网络分别会如何影响后端服务?¶
参考答案
CPU 影响计算能力,CPU 繁忙时接口处理会变慢,常见于编解码、加密、压缩、复杂计算或 GC 压力过高。内存影响运行时数据承载能力,内存不足会导致频繁 GC、延迟升高,严重时 OOM。
磁盘影响日志写入、文件读写、数据库存储等操作,磁盘满或 IO 慢会拖慢服务。网络影响客户端请求、服务间调用、数据库连接、缓存访问和第三方 API,网络抖动常表现为超时或偶发失败。
2. Go 程序从源码到运行经历了哪些步骤?¶
参考答案
Go 程序通常先通过 go build 编译,把 .go 源码编译成可执行文件。运行可执行文件后,操作系统会创建进程,分配内存、文件描述符等资源,程序再加载配置、初始化依赖、监听端口并处理请求。
简化流程是:源码 -> 编译 -> 可执行文件 -> 进程 -> 监听端口 -> 接收请求 -> 执行业务逻辑 -> 返回响应。
3. 什么情况下服务可能被 OOM?¶
参考答案
OOM 通常发生在进程使用的内存超过系统或容器限制时。常见原因包括一次性读取大文件、查询大量数据没有分页、缓存无限增长、goroutine 大量堆积、对象持续被引用无法释放、请求体没有限制大小等。
在 Kubernetes 中,如果容器超过 memory limit,可能会被标记为 OOMKilled。排查时要结合内存指标、堆 profile、日志、容器事件和最近流量变化。
4. 为什么外部调用必须设置超时?¶
参考答案
外部调用包括数据库、Redis、其他服务和第三方 API。它们都可能因为网络或自身故障而长时间不返回。如果没有超时,请求 goroutine 会一直等待,连接池和工作线程可能被耗尽,最终拖垮整个服务。
设置超时的目的不是让错误消失,而是让失败可控。超时后可以返回明确错误、触发降级、记录日志和指标,避免无限等待。
5. 如何初步判断一个接口慢是 CPU 问题还是 IO 问题?¶
参考答案
如果 CPU 使用率高、pprof 显示某些计算函数占用明显、请求处理主要耗在编解码或计算上,更可能是 CPU 问题。如果 CPU 不高,但请求大量等待数据库、缓存、文件读写、网络调用或锁,则更可能是 IO 或依赖问题。
实践中要结合指标、日志耗时分段、数据库慢查询、链路追踪和 pprof 判断。不要只看总耗时,要把请求拆成“应用处理时间”和“等待外部资源时间”。