部署在Heroku上的Python-Binance Flask应用内存泄漏问题求助
问题背景
我在Heroku运行的Python-Binance Flask应用出现内存超限错误:
2023-03-13T00:51:10.044146+00:00 heroku[web.1]: Error R14 (Memory quota exceeded)
2023-03-13T00:51:32.620715+00:00 heroku[web.1]: Process running mem=632M(123.5%)
定位到check_tp()函数是问题根源,禁用该函数后内存恢复正常。已尝试gc.collect()强制回收和限制函数执行次数,但问题仍未解决。
内存泄漏原因分析
- Binance Client实例频繁创建未释放:
check_tp()循环中每次满足止盈条件时都会新建Client对象,每个Client持有HTTP连接池、会话等资源,频繁创建后这些资源无法被GC及时回收,导致内存累积。 - Google Sheets API调用冗余且数据未精简:
get_google_settings()多次调用API获取完整二维列表,大量未使用数据占据内存;同时API返回的result对象可能存在未被正确回收的内部缓存。 - 循环变量引用混乱:
check_tp()中外层循环变量td与内层遍历交易记录的变量td重名,可能导致外层变量引用无法被正确释放,造成内存泄漏。 - 局部变量未及时清理:循环内的
inf、trades等变量持有大量交易数据,循环结束后未主动清理,依赖自动GC存在延迟,尤其在高频执行场景下。
解决方法
1. 复用Binance Client实例
全局维护账号到Client的缓存字典,避免重复创建实例:
# 全局缓存Client实例,避免重复初始化 client_cache = {} def get_binance_client(acc): if acc not in client_cache: client_cache[acc] = Client(API_KEYS[acc], API_SECRETS[acc], tld='com') return client_cache[acc] # 在check_tp()中替换原创建Client的代码 bnan = get_binance_client(acc)
2. 优化Google Sheets API调用
精简get_google_settings(),减少API调用次数
将多次请求合并为一次,转换为账号到止盈比例的字典,只保留所需数据:
def get_google_settings(): result = service.spreadsheets().values().get( spreadsheetId=spreadsheet_id, range=f'{sheet_name}!A:F', valueRenderOption='UNFORMATTED_VALUE', dateTimeRenderOption='FORMATTED_STRING' ).execute() data = result.get('values', []) if not data: print('No data found.') return {} # 转换为账号->止盈比例的字典,跳过表头 settings_dict = {} for row in data[1:]: if len(row) >= 4: acc = row[0].lower() take_profit = row[3] settings_dict[acc] = take_profit return settings_dict
优化get_google_open_trades()返回数据
过滤表头和空行,只返回有效交易数据:
def get_google_open_trades(): result = service.spreadsheets().values().get( spreadsheetId=spreadsheet_id, range=f'{open_trades_sheet_name}!A:F', valueRenderOption='UNFORMATTED_VALUE', dateTimeRenderOption='FORMATTED_STRING' ).execute() data = result.get('values', []) if not data: print('No data found.') return [] # 过滤表头和空行,保留有效交易行 valid_trades = [] for row in data[1:]: if len(row) >= 6 and row[0] != "Account" and any(row): valid_trades.append(row) return valid_trades
3. 清理循环内变量并避免重名
修改循环变量名避免冲突,循环结束后主动清理局部变量:
def check_tp(): opt = get_google_open_trades() sets = get_google_settings() # 现在是字典,直接通过键取值 for trade_data in opt: sym = trade_data[4] order_id = trade_data[2] acc = trade_data[0].lower() price = trade_data[5] take_profit = sets.get(acc, 0.0) if not take_profit: continue price_float = float(price) TP = float(take_profit) target = price_float + ((price_float * TP) / 100) if TP > 0 and float(global_prices_read(sym)) >= target: bnan = get_binance_client(acc) inf = bnan.get_my_trades(symbol=sym, orderId=order_id) sell_commission = 0.0 executed_qty = 0.0 client_order_id = "" for trade_record in inf: if trade_record["orderId"] == order_id: sell_commission += float(trade_record["commission"]) executed_qty = float(trade_record['executedQty']) client_order_id = trade_record['clientOrderId'] newquant = executed_qty - sell_commission rnd_quantity = format_quantity(sym, newquant, global_prices[sym], bnan) order_response = order(binance, "SELL", rnd_quantity, sym, client_order_id) if order_response['side'].upper() == "SELL": remove_google_open_trade(order_response['clientOrderId']) print(order_response) # 主动清理当前循环的局部变量 del inf, sell_commission, executed_qty, client_order_id, newquant, rnd_quantity, order_response # 清理函数级变量 del opt, sets gc.collect()
4. 降低check_tp()执行频率
将原15秒执行间隔调整为30秒或更长,减少函数执行次数:
# 在handle_price_change中修改时间间隔 if not last_execution_time or current_time - float(last_execution_time) >= 30: cache.set('last_execution_time', current_time) check_tp()
5. 本地内存分析定位
使用memory_profiler工具逐行分析check_tp()的内存占用,精准找到内存增长点:
# 安装工具 pip install memory-profiler # 给check_tp函数添加装饰器 @profile def check_tp(): # 函数内容... # 运行代码生成内存报告 python -m memory_profiler your_app.py
内容的提问来源于stack exchange,提问作者Richard Read
相关产品推荐
相关产品推荐

