为何仅存于内存变量的Heroku Express API数据会持久化?
我使用Express搭建了一个简易API并部署到Heroku平台,完整实现代码如下:
const express = require("express"); const app = express(); const cors = require("cors"); app.use(express.json()); app.use(cors()); app.use(express.static("build")); let notes = [ { id: 1, content: "HTML is easy", date: "2022-05-30T17:30:31.098Z", important: true, }, { id: 2, content: "Browser can execute only Javascript", date: "2022-05-30T18:39:34.091Z", important: false, }, { id: 3, content: "GET and POST are the most important methods of HTTP protocol", date: "2022-05-30T19:20:14.298Z", important: true, }, ]; const generateId = (arr) => { const maxId = arr.length < 0 ? 0 : Math.max(...arr.map((item) => item.id)); return maxId + 1; }; app.get("/", (req, res) => { res.send(`<h1>Hello World!</h1>`); }); app.get("/api/notes", (req, res) => { res.json(notes); }); app.get("/api/notes/:id", (req, res) => { const id = Number(req.params.id); const note = notes.find((note) => note.id === id); if (note) { res.json(note); } else { res.status(404).end(); } }); app.delete("/api/notes/:id", (req, res) => { const { id } = Number(req.params); notes = notes.filter((note) => note.id !== id); res.status(204).end(); }); app.post("/api/notes", (req, res) => { const body = req.body; if (!body.content) { return res.status(400).json({ error: "Content Missing", }); } const note = { content: body.content, important: body.important || false, date: new Date(), id: generateId(notes), }; notes = notes.concat(note); res.json(note); }); app.put("/api/notes/:id", (req, res) => { const newNote = req.body; notes = notes.map((note) => (note.id !== newNote.id ? note : newNote)); res.json(newNote); }); const PORT = process.env.PORT || 3001; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });
如代码所示,为前端React应用提供服务的数据来自/api/notes接口,该接口直接返回notes数组作为响应内容。
将项目部署到Heroku后,新增笔记、修改笔记重要性标记的功能均可正常运行,但出现了超出预期的现象:刷新页面、甚至使用其他设备访问该应用时,之前操作新增/修改的数据依然存在,表现出持久化特性。但这份数据仅存储在内存中的notes变量里,既没有接入数据库,也没有做其他持久化配置。
现象原理解释
这不是Heroku平台自动修改变量或做了持久化配置,本质是Heroku容器运行机制造成的持久化假象,核心逻辑如下:
- 代码中的
notes变量存储在Node.js进程的运行内存中,只要承载服务的Node进程不退出、不重启,内存中的数据就会持续保留,所有路由到该进程的请求都可以读写这份数据。 - Heroku以dyno(轻量容器)为单位运行应用,应用首次部署完成后,默认分配的单个dyno会在无异常的情况下持续运行Node服务进程。这段时间内不管是同设备刷新还是跨设备访问,请求都会被路由到这个正在运行的dyno上,自然能读到之前修改过的内存数据。
- 这种内存级的数据保留完全不可靠,触发以下任意场景时,所有修改过的数据都会被清空,恢复为代码中写死的初始3条笔记:
- 免费档位dyno在半小时无访问后会自动休眠,下次唤醒时会重新拉起Node进程,内存数据重置
- 重新推送代码部署应用时,旧dyno会被直接销毁,新启动的dyno会运行全新的Node进程
- Heroku平台例行维护、dyno崩溃自动重启时,内存数据会被全部清除
- 如果将应用扩容到多个dyno,负载均衡会把请求分发到不同dyno实例,每个实例持有独立的
notes内存变量,还会出现不同请求返回数据不一致的问题
Heroku dyno的本地文件系统和内存都是临时的,所有运行时产生的修改都会在dyno生命周期结束时永久丢失,平台不会自动持久化任何用户数据。要实现真正可靠的数据持久化,必须外接独立的数据库服务。
内容的提问来源于stack exchange,提问作者Rammah_N
相关产品推荐
相关产品推荐

