如何让Google Cloud中闲置的Node.js应用持续运行并保留缓存?
解决Google App Engine Node.js应用闲置回收导致缓存丢失的问题
你的核心问题是App Engine标准环境下,实例闲置30分钟后会被自动回收,重启时需要重新加载Google Sheet数据,导致Slack请求响应延迟。以下是针对性的解决方案:
一、优化App Engine自动缩放配置
修改你的app.yaml,添加闲置超时配置,延长实例存活时间:
runtime: nodejs22 entrypoint: node index.js instance_class: F1 automatic_scaling: min_instances: 1 min_idle_instances: 1 max_idle_instances: 1 min_pending_latency: automatic max_pending_latency: automatic max_concurrent_requests: 50 idle_timeout_sec: 86400 # 设置为1天,避免闲置回收(可根据需求调整) env: standard handlers: - url: /_ah/warmup script: auto login: admin secure: always - url: /.* script: auto readiness_check: path: /_ah/health check_interval_sec: 5 timeout_sec: 4 failure_threshold: 5 success_threshold: 2 inbound_services: - warmup
说明:idle_timeout_sec默认是1800秒(30分钟),延长这个值可以避免实例因闲置被快速回收。
二、处理Warmup请求预加载缓存
当前你的代码没有处理/_ah/warmup路由,导致实例启动时不会自动预加载Sheet数据。添加以下路由到你的Express应用中:
// 处理warmup请求,预加载缓存 app.get('/_ah/warmup', async (req, res) => { try { await fetchFilteredSheetData(); console.log('Warmup completed: cache loaded'); res.status(200).send('Warmed up'); } catch (error) { console.error('Warmup failed:', error); res.status(500).send('Warmup failed'); } }); // 处理健康检查请求 app.get('/_ah/health', (req, res) => { res.status(200).send('Healthy'); });
说明:Warmup请求会在实例启动前触发,提前加载缓存,确保用户请求到达时缓存已经就绪,消除启动延迟。
三、添加心跳机制防止实例闲置
即使设置了min_instances,App Engine仍可能在极端情况下回收实例。添加定时心跳请求,每25分钟向自己的实例发送请求,避免进入闲置状态:
// 在startApp函数中添加心跳任务 cron.schedule('*/25 * * * *', async () => { try { // 生产环境使用App Engine分配的服务URL,本地测试用localhost const appUrl = process.env.GCP_SERVICE_URL || `http://localhost:${port}`; await fetch(`${appUrl}/_ah/health`); console.log('Heartbeat sent to keep instance alive'); } catch (error) { console.error('Heartbeat request failed:', error); } });
说明:定时请求会让实例保持活跃状态,避免触发闲置回收逻辑。
四、备选方案:缓存持久化到外部存储
如果实例仍不可避免被回收,可以将缓存数据存储到外部服务(如Cloud Memorystore Redis或Firestore),重启时直接从外部加载,比重新读取Google Sheet快得多:
// 示例:使用Redis存储缓存(需先安装redis包) const redis = require('redis'); const client = redis.createClient({ url: process.env.REDIS_URL }); // 修改fetchFilteredSheetData,将数据存入Redis async function fetchFilteredSheetData() { // ... 原有获取Sheet数据的逻辑 ... // 将缓存数据存入Redis,设置过期时间为1天(与定时更新频率匹配) await client.setEx('sheet_cache', 86400, JSON.stringify({ cachedData, dataDictionary })); } // 启动时优先从Redis加载缓存 async function startApp() { console.log("Starting up the app..."); await client.connect(); try { // 尝试从Redis加载缓存 const cached = await client.get('sheet_cache'); if (cached) { const { cachedData: redisCached, dataDictionary: redisDict } = JSON.parse(cached); cachedData = redisCached; dataDictionary = redisDict; console.log('Loaded cache from Redis'); } else { // Redis无缓存时,从Sheet加载 await fetchFilteredSheetData(); console.log('Loaded cache from Google Sheet'); } // ... 原有定时任务和启动服务器的逻辑 ... } catch (error) { console.error('Error during startup:', error); } }
说明:外部缓存可以让实例重启后快速恢复缓存状态,无需重新读取整个Sheet。
内容的提问来源于stack exchange,提问作者Jim Burke
相关产品推荐
相关产品推荐

