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
相关产品推荐
相关产品推荐

