You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

部署在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 01:12:47