跳转至

计算机系统概览

学习目标

学完本章后,学习者应该能够:

  1. 解释 CPU、内存、磁盘、网络在后端系统中的作用。
  2. 理解程序从代码到运行进程的大致过程。
  3. 建立“资源是有限的”这个工程意识。
  4. 看到性能问题时,能先判断可能属于哪类资源瓶颈。

后端服务依赖哪些基础资源

一个 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 问题时,后续会学习 pproftop、容器指标和 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 被限制,也可能是外部依赖不稳定。

  • 误区二:内存越大越不需要管理。

内存越大,错误用法可能隐藏越久;一旦流量变大,问题会更难排查。

  • 误区三:后端不需要懂操作系统。

服务运行在操作系统上。进程、端口、文件描述符、信号、权限都会影响服务。

实战任务

在你的电脑上找一个正在运行的服务或进程,完成下面观察:

  1. 进程 ID 是多少?
  2. 它是否监听端口?
  3. 它占用多少内存?
  4. 它有没有持续消耗 CPU?
  5. 它的日志在哪里?
Get-Process
netstat -ano
ps aux
ss -lntp
top

面试题

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 判断。不要只看总耗时,要把请求拆成“应用处理时间”和“等待外部资源时间”。