Python线上实战复盘:本地跑的好好的,部署生产就各种诡异故障
Python线上实战复盘:本地跑的好好的,部署生产就各种诡异故障
前言
Python开发很多人都有这样的体验:本地开发调试一切顺利,脚本跑起来丝滑流畅,单元测试全部通过。一旦部署到服务器生产环境,就开始出现各种偶现问题:接口莫名卡顿、内存越跑越大、接口偶发报错、数据错乱、任务重复执行。
问题很难复现,日志提示模糊,排查要耗费大量时间。很多人把问题归罪于服务器、网络、第三方库版本,实际上大量线上故障来源于Python语言特性、GIL锁、资源释放、并发模型、编码与依赖环境等隐性问题。
本文结合线上真实项目踩坑,整理Python生产环境高频故障点,附带错误示例、根因分析、生产可用代码,适合后端、脚本、爬虫、定时任务开发者收藏参考。

一、全局变量+多线程,引发数据错乱
Python多线程开发中,很多新手直接使用全局变量存储业务数据,本地单线程测试看不出问题,并发运行直接出现数据覆盖、结果错乱。
❌错误示例
python
count = 0
def add():
global count
for _ in range(100000):
count +=1
import threading
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(count)
预期200000,线上经常输出小于200000的随机数字
根因:count +=1不是原子操作,会被GIL切换打断,多线程同时读写全局变量产生竞态条件。
✅生产正确写法,使用线程锁保护临界资源
python
import threading
count = 0
lock = threading.Lock()
def add():
global count
for _ in range(100000):
with lock:
count +=1
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()
print(count)
注意:IO密集场景适合多线程;CPU密集场景,Python多线程无法利用多核,优先使用多进程。
二、异常粗暴捕获,吞噬堆栈,线上问题无迹可寻
项目中大量存在except Exception:直接捕获全部异常,只打印简短提示,不输出完整堆栈,生产环境出问题看不到报错行号,排查极其痛苦。
❌反面代码
python
def business_task():
try:
1 / 0
except Exception as e:
只打印错误信息,丢失堆栈
print(f"执行失败:{e}")
✅标准生产写法,打印完整异常堆栈
python
import logging
def business_task():
try:
1 / 0
except Exception as e:
logging.exception("业务任务执行异常")
logging.exception会自动把完整调用堆栈写入日志,禁止空except,不要随意捕获顶级Exception,能向上抛出就交给全局异常处理器处理。
三、文件、数据库连接忘记关闭,句柄泄露服务慢慢卡死
处理文件、mysql、redis连接,如果只创建不关闭,本地测试脚本执行完进程销毁看不出问题。长期运行服务、定时任务,会持续占用文件句柄,最终句柄耗尽,所有请求全部失败。
❌危险写法
python
def read_file():
f = open("test.txt","r",encoding="utf-8")
data = f.read()
没有close,发生异常的时候不会执行关闭
return data
✅推荐使用with上下文管理器,自动释放资源
python
def read_file():
with open("test.txt","r",encoding="utf-8") as f:
data = f.read()
return data
数据库、redis连接同样遵循该原则;如果使用连接池,不要手动频繁创建销毁连接,复用池内连接。
四、列表字典作为默认参数,出现诡异的缓存副作用
这是Python非常经典的隐形坑,函数默认参数只会在函数定义时初始化一次,不会每次调用重新创建。
❌错误示例
python
def func(item, data_list = []):
data_list.append(item)
return data_list
print(func(1))
print(func(2))
预期输出 1 、[2]
实际输出 1 、[1,2]
本地简单测试不容易发现,接口、定时任务反复调用,会出现数据持续累加,业务逻辑完全错乱。
✅修复方案,默认参数设置为None,函数内部初始化
python
def func(item, data_list = None):
if data_list is None:
data_list = []
data_list.append(item)
return data_list
五、定时任务重复并发执行,导致重复扣款、重复统计
很多人使用crontab执行Python脚本,如果任务执行耗时超过调度周期,上一轮脚本还没结束,下一轮新进程又启动,多个进程同时运行,造成重复统计、重复推送、重复数据写入。
❌问题场景:每5分钟执行一次脚本,脚本执行需要8分钟,进程不断叠加。
✅利用文件锁实现单实例运行,同一时刻只允许一个进程运行
python
import fcntl
import sys
def single_instance():
f = open("/tmp/task.lock","w")
try:
fcntl.flock(f,fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
print("任务正在运行,直接退出")
sys.exit(0)
return f
if name == "main":
lock_file = single_instance()
此处写业务逻辑
print("执行定时任务")
windows环境没有fcntl模块,可以使用redis分布式锁实现任务互斥。
六、字符串编码问题,生产环境乱码、报错UnicodeDecodeError
Windows、macOS本地默认编码和Linux服务器编码不一样,本地读写文件正常,部署Linux直接报编码异常。
❌隐患代码,不指定encoding
python
linux环境容易出现乱码、报错
f = open("data.txt","w")
f.write("中文内容")
f.close()
✅生产环境读写文件强制指定encoding="utf‑8"
python
with open("data.txt","w",encoding="utf-8") as f:
f.write("中文内容")
七、内存泄漏:脚本越跑内存越高,最终OOM退出
Python虽然自带垃圾回收,但依然会出现内存泄漏:长生命周期对象持续持有大列表、大对象引用;全局缓存只增加不清理;循环引用。
排查小技巧:
- 定时打印内存指标,观察内存走势
- 不要在全局变量无限追加数据
- 不用的大对象手动
del obj解除引用 长期运行服务,设置定时重启机制,规避内存堆积
总结
Python很多线上bug,并不是语法错误,而是语言底层特性带来的隐性陷阱。本地环境流量小、生命周期短,掩盖全部问题;一旦上生产高并发长时间运行,全部问题暴露。
写生产级Python代码,需要重点关注:
- 多线程下共享资源的锁保护
- 异常完整堆栈日志输出
- IO、连接资源必须自动释放
- 警惕函数可变默认参数陷阱
- 定时任务做好进程互斥锁
- 文件读写强制指定utf‑8编码
- 关注内存变化,避免无限累积对象
本文为个人技术学习总结,仅供技术参考。
友情链接:凡尘博客文摘:wz.fanchenblog.com