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

Gunicorn+Flask并行请求同一路由性能优化咨询

问题描述

服务器与应用配置

  • 服务器:72核CPU、515703MB内存
  • Flask应用部署:直接通过Gunicorn启动(无Nginx),启动命令:
gunicorn --workers=10 --threads=12 -b 0.0.0.0:5000 "run:app" --access-logfile user_activity_logs/user_activity.log --preload
  • 进程线程总数:120(理想配置约为2*72+1)

业务场景与异常现象

  • 分页API:从CSV文件返回60-70MB数据,按1-2MB分块返回;前端会并行发送n个请求(对应n页数据),部分日期下n可达25-30+
  • 异常现象:前端仅初始4个请求能快速并行处理,后续请求进入pending状态,仅维持约4个并行请求;单请求耗时约3秒,但总耗时达2-3分钟,服务器资源未被充分利用
  • 对比测试:使用ab工具测试时,300个请求可并发处理(htop显示所有进程CPU占比非0),测试命令:
seq 1 300 | xargs -n 1 -P 300 -I {} ab -n 1 -c 1 "10.40.1.56:5000/…{}&page_size=10000"

前端核心代码

const page_size = 20000;

async function fetchInitDetails() {
    return await fetchApi('/paginate-refdata/init', {
        "method": "GET",
        "data": {
            'date': $('#date').val(),
            'exchange': $('#exchange').val(),
            'page_size': page_size
        }
    })
}

async function fetchAllPages(initDetails) {
    let columns = [];
    for (let i = 0; i < show_columns.length; i++) {
        if (initDetails.columns.includes(show_columns[i])) {
            if(show_columns[i] == 'expiry_date') {
                columns.push({
                    headerName: titleCase(show_columns[i]),
                    field: show_columns[i],
                    filter: 'agTextColumnFilter',
                    minWidth: 150,
                    sort: 'desc', 
                    sortIndex: 0
                })
            } else {
                columns.push({
                    headerName: titleCase(show_columns[i]),
                    field: show_columns[i],
                    filter: 'agTextColumnFilter',
                    minWidth: 150,
                })
            }
        }
    }

    for (let i = 0; i < initDetails.columns.length; i++) {
        if (!show_columns.includes(initDetails.columns[i])) {
            columns.push({
                headerName: titleCase(initDetails.columns[i]),
                field: initDetails.columns[i],
                filter: 'agTextColumnFilter',
                minWidth: 150,
                hide: true
            })
        }
    }

    grid.ref_db.gridOptions.columnDefs = columns;

    const { total_pages } = initDetails;
    const parallelRequests = Array.from({ length: total_pages }, (_, index) => index + 1);
    let pages = 1;
    const responses = await Promise.all(parallelRequests.map(async (page) => {
        const response =  await fetchApi('/paginate-refdata', {
            "method": "GET",
            "data": {
                'date': $('#date').val(),
                'exchange': $('#exchange').val(),
                'page_size': page_size,
                'page': page
            }
        });
        show_loader(pages/initDetails.total_pages * 100, (initDetails.total_pages - pages++) * 1.2, rerender_html=false);
        return response;
    }));

    return responses;
}

fetchInitDetails()
.then(initDetails => fetchAllPages(initDetails))
.then(responses => {
    let rows_data = []
    for(let i=0;i<responses.length;i++) {
        for(let j=0;j<responses[i]['data'].length;j++) {
            rows_data.push(responses[i]['data'][j]);
        }
    }
    grid['ref_db'].renderGrid(rows_data);
    Swal.fire({
        toast: true,
        position: 'bottom',
        icon: 'success',
        title: 'Data loaded',
        showConfirmButton: false,
        timer: 5000,
    });
})
.catch(error => {
    grid['ref_db'].renderGrid([]);
})
.finally(() => {
    onclose();
})

后端核心代码

@blueprint.route('/paginate-refdata')
@login_required
def paginate_refdata():
    try:
        date = request.args['date'].replace('-', '')
        exchange = request.args['exchange'].lower()
        page = int(request.args['page'])
        page_size = int(request.args.get('page_size', '10'))
    except:
        return jsonify({ 'msg': 'Please provide date, exchange & page', 'status': False }), 400

    refdata_df = get_or_load_refdata_from_cache(exchange, date)

    total_pages = int(len(refdata_df) / page_size) + 1
    curr_page = paginate_array(refdata_df, page_size, page).to_dict('records')
    result = {
        'page': page,
        'total_pages': total_pages,
        'page_size': len(curr_page),
        'total': len(refdata_df),
        'data': curr_page,
    }
    
    response = Response(gzip.compress(JSON.dumps(result).encode('utf-8')), content_type='application/json')
    response.headers['Content-Encoding'] = 'gzip'
    return response
优化方案

1. 解决浏览器并发限制

浏览器对同一域名的并发请求数默认限制为6左右,这是前端请求无法并行的核心原因,可通过以下方式解决:

  • 域名分片:将API请求分发到多个子域名(如api1.internal.com、api2.internal.com),指向同一服务器,绕过浏览器并发限制
  • 合并请求:新增批量获取接口(如/paginate-refdata/batch),前端一次性传入所有需要的页码,后端批量返回数据,减少请求数
  • 流式返回:后端直接返回完整CSV数据流,前端通过流式解析处理,彻底避免分页请求

2. 优化Gunicorn配置

当前配置未充分利用72核CPU,建议调整:

  • 配置逻辑:CPU密集型应用建议workers=CPU核心数,IO密集型建议workers=2*CPU核心数+1
  • 调整后的启动命令示例:
gunicorn --workers=72 --threads=2 -b 0.0.0.0:5000 "run:app" --access-logfile user_activity_logs/user_activity.log --preload
  • 注意:根据实际IO/CPU占比微调threads数,避免workers过高导致进程上下文切换开销增大

3. 后端性能优化

  • 序列化优化:替换低效的序列化逻辑,用pandas.DataFrame.to_json(orient='records')直接生成JSON字符串,或改用ujson替代标准库json,提升序列化速度
  • 缓存优化:确认get_or_load_refdata_from_cache的缓存命中情况,避免重复加载CSV;可提前将DataFrame按页拆分并缓存序列化后的JSON数据,减少分页时的转换开销
  • 分页逻辑优化:在加载CSV到缓存时,提前完成分页预处理,避免每次请求都执行分页计算

4. 引入Nginx作为反向代理

Nginx并发处理能力优于Gunicorn,可作为前置代理提升请求处理效率:

  • 配置worker_processes为CPU核心数,开启keepalive连接
  • 示例Nginx配置片段:
http {
    worker_processes 72;
    events {
        worker_connections 10240;
    }
    server {
        listen 80;
        server_name your-domain;
        location / {
            proxy_pass http://127.0.0.1:5000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            keepalive_timeout 65;
            keepalive_requests 1000;
        }
    }
}

内容的提问来源于stack exchange,提问作者DARK_C0D3R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 00:52:11