3. 常见异常类型详解:超时异常、业务异常、系统异常、网络异常
异常分类这件事,说实话不算难。但很多团队踩坑就踩在“分不清”上。
我见过一个项目,把所有异常都扔进同一个 catch 里打日志,结果线上出了问题,排查了整整两天才找到根因——其实就是个简单的超时。后来我每带一个项目,第一件事就是让团队把异常分类先定清楚。
今天咱们就聊聊最常见的四类异常:超时异常、业务异常、系统异常、网络异常。这四种几乎涵盖了 90% 的线上问题。
3.1 超时异常
超时,说白了就是“我等得不耐烦了”。调用方设定了一个等待上限,被调用方在规定时间内没回应,那就抛个异常出来。
常见场景
- 数据库连接池耗尽,查询卡死
- 外部 API 响应慢(比如支付通道、短信网关)
- 分布式 RPC 调用链路超时
- 线程池任务排队过长,任务被丢弃或超时
我个人习惯在超时异常里加个超时时间戳字段,方便定位是哪一步慢了。你想想看,有时候不是整个请求慢,而是某一段调用出了问题。没有时间戳,你根本不知道哪个环节超了。
import requests
from requests.exceptions import Timeout
try:
resp = requests.get('https://api.example.com/data', timeout=3)
except Timeout:
# 我一般会在这里打一个详细的告警,带上当前系统时间戳
log.error('调用外部API超时,timeout=3s')
raise TimeoutError('调用外部API超时')
3.2 业务异常
业务异常不是系统故障,而是逻辑上的不合规。比如余额不足、库存为负、用户没有权限——这些都不是程序写错了,而是业务规则被违反了。
常见场景
- 下单时库存不足
- 用户重复提交(幂等校验)
- 参数校验失败(比如年龄为负数)
- 流程状态不允许(如已取消的订单不允许再次支付)
我建议把业务异常单独定义成一个类,比如 BusinessException,里面带 errorCode 和 errorMessage。这样上层处理时,可以统一转成标准 API 响应,而不是跟系统异常混在一起。
class BusinessException(Exception):
def __init__(self, error_code: int, message: str):
self.error_code = error_code
self.message = message
super().__init__(f'[{error_code}] {message}')
# 使用示例
raise BusinessException(1001, '库存不足,当前可用库存为0')
3.3 系统异常
系统异常是 “真的出大事了”。内存溢出、空指针、线程死锁、磁盘写满……这些往往意味着代码 bug 或资源耗尽。
常见场景
- OutOfMemoryError(内存泄漏)
- NullPointerException / IndexOutOfBoundsException
- 数据库连接池满,新的请求无法获取连接
- 文件系统只读或磁盘空间不足
嗯,这里要注意,系统异常的日志一定要打全堆栈信息。我记得有一次线上 OOM,就是因为堆栈被日志框架截断了,排查时只看到“OutOfMemoryError”,但根本不知道是哪个对象撑爆的。后来统一配置了 -XX:+HeapDumpOnOutOfMemoryError 才解决。
try:
# 可能抛出系统异常的操作
data = [1] * 10**9 # 模拟大对象分配
except MemoryError as e:
# 一定要打完整堆栈,并且立刻告警
log.critical('系统内存不足', exc_info=True)
# 这里可以做熔断,比如停止接受新请求
raise SystemException('内存资源耗尽', cause=e)
3.4 网络异常
网络异常是最“无厘头”的一类。你代码写得再漂亮,网络一扔包、DNS 解析失败、SSL 握手超时,照样挂了。
常见场景
- DNS 解析失败
- TCP 连接被重置(connection reset by peer)
- SSL/TLS 证书过期或握手失败
- 防火墙拦截导致包丢失
- 服务端主动断开连接(比如健康检查失败后关闭连接)
我曾经遇到过一个非常隐蔽的问题:某个第三方服务每周五下午都会断连几十秒,原因是他们自己的运维在做网络割接。我们没做熔断,结果整个调用链全部超时,最终影响了线上用户。从那之后,我在网络相关的调用上都加了滑动窗口熔断逻辑。
import socket
import ssl
def fetch_https(url):
try:
ctx = ssl.create_default_context()
with socket.create_connection(('api.example.com', 443), timeout=5) as sock:
with ctx.wrap_socket(sock, server_hostname='api.example.com') as ssock:
ssock.sendall(b'GET /data HTTP/1.1\r\nHost: ...\r\n\r\n')
response = ssock.recv(4096)
except (socket.timeout, ssl.SSLError, ConnectionResetError) as e:
# 网络异常统一处理,建议先记录网络抖动次数
log.warning(f'网络异常: {type(e).__name__} {e}')
raise NetworkException('网络连接失败', cause=e)
四种异常对比
为了让你更直观地对比,我整理了一张表。实际开发中,我经常用这张表来教新同学区分异常。
| 异常类型 | 触发原因 | 典型特征 | 处理方式 |
|---|---|---|---|
| 超时异常 | 响应时间超过阈值 | 无返回 / 超时标志 | 重试(带退避)、熔断、超时升级 |
| 业务异常 | 业务规则不符 | 明确错误码、错误消息 | 返回标准错误响应,不重试 |
| 系统异常 | 资源耗竭 / 代码 bug | 堆栈异常、无法恢复 | 立即告警、熔断、人工介入 |
| 网络异常 | 网络不可达、连接重置 | 主机/端口不可达、连接断开 | 重试 + 限流 + 熔断 |
四种异常的知识结构图
下面这张图把四种异常的核心特征和检测关注点串起来了。你可以把它作为团队内部培训的参考。
好了,以上就是四种常见异常类型的详细讲解。我个人做异常检测时,始终遵循一个原则:先分类、再定级、最后处理。不要把系统异常当成业务异常来处理,也不要把网络异常当成超时异常来重试——分类对了,后面的检测和告警才能精准。