Express JS高CPU占用求助:m3u8直播50+用户时CPU飙升
高并发m3u8直播转发CPU占用过高的优化方案
针对你的场景,先梳理问题根源,再给出可落地的优化方案:
问题根源
同步IO阻塞事件循环
最初的方案中fs.writeFileSync是同步阻塞操作,每个请求都会卡住Node.js的事件循环。当用户数达到50+时,大量同步操作堆积,事件循环无法及时处理其他请求,直接导致CPU占用率暴涨。而且m3u8内容完全不需要落地磁盘,流式转发即可规避磁盘IO和同步阻塞的双重开销。HTTP连接未复用
即使使用request(newurl).pipe(res)的流式转发,request库默认的连接池配置有限,且未充分启用TCP keep-alive。每个m3u8索引和ts片段请求都会新建与原服务器的TCP连接,频繁的三次握手、四次挥手会消耗大量CPU资源。重定向的隐性负载
直接重定向到原URL时,你的服务器仍需处理每个用户的重定向请求(返回302响应),加上客户端会频繁拉取m3u8和ts片段,总请求量依然很高。如果客户端未启用连接复用,大量短连接的建立也会推高CPU占用。
优化方案
1. 移除同步文件操作,改用纯流式转发
替换落地磁盘的逻辑,直接将原服务器的响应流式转发给客户端,同时启用TCP连接复用:
const http = require('http'); const { URL } = require('url'); // 复用连接的HTTP Agent const keepAliveAgent = new http.Agent({ keepAlive: true, maxSockets: 100, // 根据并发量调整 keepAliveMsecs: 30000 }); function proxyLiveResource(req, res, targetUrl) { const urlObj = new URL(targetUrl); const proxyOptions = { hostname: urlObj.hostname, port: urlObj.port || (urlObj.protocol === 'https:' ? 443 : 80), path: urlObj.pathname + urlObj.search, method: req.method, headers: { ...req.headers, host: urlObj.hostname, // 修正Host头,确保原服务器正确识别 'Connection': 'keep-alive' }, agent: keepAliveAgent }; const proxyReq = http.request(proxyOptions, (proxyRes) => { // 透传原响应头 res.writeHead(proxyRes.statusCode, proxyRes.headers); // 流式转发响应体 proxyRes.pipe(res); }); proxyReq.on('error', (err) => { res.statusCode = 500; res.end('Proxy error'); }); // 将客户端请求体转发给原服务器(如果是POST等有请求体的方法) req.pipe(proxyReq); }
2. 缓存m3u8索引文件
m3u8索引更新频率较低(通常3-10秒一次),短期缓存可避免重复请求原服务器:
let cachedM3U8 = ''; let cacheExpiry = 0; const CACHE_TTL = 5000; // 缓存5秒,根据实际更新频率调整 async function getCachedM3U8(originalUrl) { const now = Date.now(); if (now < cacheExpiry) return cachedM3U8; const response = await fetch(originalUrl); if (!response.ok) throw new Error('Failed to fetch m3u8'); cachedM3U8 = await response.text(); cacheExpiry = now + CACHE_TTL; return cachedM3U8; } // 在请求处理中使用 app.get('/live/channel1.m3u8', async (req, res) => { try { const m3u8Content = await getCachedM3U8('https://original-server/channel1.m3u8'); // 如果原m3u8中的ts是相对路径,替换为你的转发域名 const processedContent = m3u8Content.replace(/(\/.*\.ts)/g, `https://your-domain.com/live$1`); res.setHeader('Content-Type', 'application/vnd.apple.mpegurl'); res.end(processedContent); } catch (err) { res.statusCode = 500; res.end('Failed to load live stream'); } });
3. 用Nginx做反向代理(高并发场景首选)
Node.js适合处理业务逻辑,但Nginx在静态资源转发上性能远超Node.js。可以让Node.js处理登录权限校验,将m3u8/ts请求交给Nginx代理:
server { listen 80; server_name your-domain.com; # 权限校验接口交给Node.js location /api/check-auth { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 直播资源代理 location /live { # 校验用户权限(可选,通过Cookie或请求头) if ($http_cookie !~* "auth_token=.*") { return 403; } proxy_pass http://original-m3u8-server.com; proxy_set_header Host original-m3u8-server.com; proxy_http_version 1.1; proxy_set_header Connection ""; # 启用keep-alive # 缓存m3u8索引 proxy_cache_path /var/cache/nginx/m3u8 levels=1:2 keys_zone=m3u8_cache:10m max_size=10g inactive=60m; proxy_cache m3u8_cache; proxy_cache_valid 200 5s; # ts片段不缓存(避免旧片段被复用) proxy_cache_bypass $request_uri ~* \.ts$; } }
Nginx的连接池和缓存机制能大幅降低CPU占用,即使数百用户并发也能稳定运行。
4. 其他优化点
- 升级Node.js版本到最新稳定版,V8引擎的性能优化能降低CPU开销
- 禁用不必要的日志输出,日志IO也会占用CPU资源
- 调整视频播放器的缓冲策略,减少过于频繁的请求
内容的提问来源于stack exchange,提问作者leshcode
相关产品推荐
相关产品推荐

