Google Cloud Functions每次调用耗时逐次递增问题排查
问题核心原因
你的接口耗时持续上涨的根因是Redis连接逻辑完全违背了Cloud Functions的实例复用模型,叠加串行IO的性能问题,两者共同导致耗时随请求量线性恶化:
- 你在全局作用域创建了Redis客户端实例,但每次请求处理时都重复调用
client.connect()新建连接,请求结束又强制调用client.disconnect()断开连接。Cloud Functions会在冷启动后复用同一个运行实例处理多批请求,这种反复连断的逻辑会产生大量未正常释放的半开连接,很快占满Redis服务端的连接池,后续新的连接请求只能排队等待资源,请求量越大排队越久,耗时自然持续上涨。 - 两个核心业务接口(
/games、/playercount)中,你使用for循环串行执行Redis查询,每个查询都要等上一个查询返回才能发起,当游戏房间码数量随请求量上涨时,这部分串行IO的耗时会线性增长,和连接泄漏的问题叠加后,耗时会直接翻几十倍。 - 现有Redis逻辑没有做异常兜底,一旦某次连接出错,客户端内部状态会错乱,后续请求会卡在隐式重试、重复建连的流程里,进一步拉长响应时间。另外你写的
socket: 6379是无效配置参数,node-redis v4版本不会读取这个字段,配置错误也会增加不必要的重连开销。
修复方案
按以下步骤改完即可解决耗时持续上涨的问题:
- 改造Redis连接逻辑,全局复用长连接,禁止每个请求反复连断
把连接逻辑抽成全局单例,只在客户端未就绪时建连,不要在请求结束时主动断开连接,供同实例下的所有请求复用:
接口内直接调用const client = redis.createClient({ database: 0, password: "hunter123", url: "redis://database.skeld.net:6379" // 把端口直接拼到URL里,去掉无效的socket参数 }); let isRedisReady = false; async function getRedis() { if (!isRedisReady) { await client.connect(); isRedisReady = true; // 监听连接错误,标记状态以便后续自动重连 client.on("error", () => { isRedisReady = false; }); } return client; }const client = await getRedis()拿实例即可,删除所有请求内的client.connect()和client.disconnect()调用。 - 把串行Redis查询改成批量并行查询
不要在循环里逐个awaitRedis请求,改用Promise.all一次性发起所有查询,大幅降低IO等待时间,以/games接口为例:app.get("/games", async (request, response) => { console.log("/games request"); const client = await getRedis(); const codes = await client.sMembers("skeldsync.games"); response.set("Cache-Control", "public, max-age=10, s-maxage=20"); response.setHeader("Access-Control-Allow-Origin", "*"); const lobbyInfos = []; // 批量并行拉取所有房间信息 const lobbyList = await Promise.all( codes.map(code => client.hGetAll("ImpostorRedis" + code)) ); for (const lobbyInfo of lobbyList) { if (lobbyInfo.in_progress == "true") continue; lobbyInfos.push(lobbyInfo.lobby_data); } response.send(JSON.stringify(lobbyInfos)); console.log("Lobby infos length: " + lobbyInfos.length); });/playercount接口同理改造,用Promise.all批量拉取所有房间的玩家人数字段,再累加计算总数即可。 - 可选优化:你已经给接口配置了Cache-Control响应头,可以直接给Cloud Functions配层CDN,静态接口(
/regionInfo、/ip、/deeplink)的请求根本不需要打到函数实例,能进一步降低负载。
改完后连接泄漏问题会彻底消失,Redis连接数会稳定在合理范围,单条Redis命令的耗时会回归正常水平,加上并行查询的优化,哪怕请求量涨到数万级,接口耗时也会稳定在百毫秒级,不会出现持续上涨到十几秒的情况。
内容的提问来源于stack exchange,提问作者Daniel Budworth-Mead
相关产品推荐
相关产品推荐

