使用Node.js提供的Express库搭建一个简易的网站后台,用MongoDB对数据进行存储,并使用Redis缓存数据,通过docker进行容器化部署。
头图来源:桜と風の国-凪白みと-pixiv
代码
Express是一个简洁而灵活的Node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的 HTTP工具。使用Express可以快速地搭建一个完整功能的网站。
# app.js
const express = require('express');
const router = express.Router();
const app = express();
const models = require('./models');
router.get('/', (req, res) => {
res.send('Hello');
})
//url='localhost:port/articles/{id}'
router.get('/articles/:id', async(req, res) => {
try {
const article_Data = await models.getArticle(parseInt(req.params.id));
res.json({ error: false, message: 'success', data: article_Data });
} catch (e) {
res.json({ error: true, message: 'Error occurred during processing' });
}
});
app.use('/', router)
app.listen(process.env.PORT || 3000, () => {
console.log(`App is listening at ${process.env.PORT||3000}`);
});
app.js中定义了路由,并实现了响应回调函数,用于处理http请求。
#models.js
const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017/articles';
const client = new MongoClient(url);
const redisClient = require('redis').createClient;
const redis = redisClient(6379, 'localhost');
var dbo = null;
client.connect((err, db) => {
if (err) {
process.exit(0);
}
dbo = db.db('articles');
console.log('connected to the database');
})
redis.on("error", (err) => {
console.log(err);
})
function getArticle(id) {
return new Promise((resolve, reject) => {
redis.get(id, (err, reply) => {
if (err) {
console.log(err);
} else if (reply) {
//缓存命中
resolve(reply);
} else {
//缓存未命中
dbo.collection('articles').find({
id: id
}).toArray((err, article_data) => {
if (err) {
return reject(err);
}
if (article_data.length > 0) {
//写入缓存
redis.set(id, JSON.stringify(article_data), (err, reply) => {
if (err) {
console.log(err);
} else {
console.log(reply);
}
});
}
resolve(article_data);
});
}
})
});
}
module.exports = {
getArticle: getArticle
};
models.js提供getAticle()
方法,该方法先尝试从redis缓存中获取数据,若缓存没有请求的数据则从MongoDB中获取,同时在缓存中保存该数据。
此外编写一个脚本用来向MongoDB中批量插入数据
const { MongoClient } = require('mongodb');
const url = 'mongodb://mongodb:27017/articles';
const client = new MongoClient(url);
var dbo = null;
client.connect((err, db) => {
if (err) {
process.exit(0);
}
dbo = db.db('articles');
console.log('connected to the database');
insertData();
});
async function insertData() {
var data = [];
for (let i = 1; i <= 1000; i++) {
var obj = {
id: i,
context: `This is article${i}, balabala`
}
data.push(obj);
}
await dbo.collection('articles').insertMany(data);
client.close();
}
运行脚本后使用VS Code提供的数据库可视化插件连接到MongoDB查看插入的数据。
测试
在启动了redis和mongo后,运行命令$ node app.js
,可以看到应用已经开始在监听3000端口了
使用Postman测试下API
Docker部署
制作镜像
首先制作网站后台代码镜像,编写Dockerfile
FROM node:16.6.2
WORKDIR /app
COPY ["package.json","package-lock.json","./"]
RUN npm install
COPY . .
CMD ["node","app.js"]
写一个.dockerignore
把node_modules
文件夹在COPY
的时候忽略掉。
COPY ["package.json","package-lock.json","./"]
和COPY . .
指令分开是因为在docker中每个指令会创建一层镜像,每次docker build
都会使用镜像缓存来缩短镜像创建的时间,而像ADD
或COPY
这类指令会计算镜像内的文件和构建目录文件的校验和然后做比较来判断本层是否有改动,如果发生改动则之后层的缓存将会失效。如果将上面的Dockerfile中的两个COPY合并的话,在修改代码后再执行COPY
将会使缓存失效而重新执行npm install
导致重新构建镜像时间无法缩短
编写docker-compose.yml
version: '3.8'
services:
node:
image: "mongocachedemo"
depends_on:
- redis
- mongodb
ports:
- 3000:3000
redis:
image: "redis"
ports:
- 6379:6379
mongodb:
image: "mongo"
ports:
- 27017:27017
volumes:
- /usr/local/var/mongodb:/data/db
- /usr/local/var/log/mongodb:/data/logs
之后为了使node容器和其他两个容器之间通信需将原来代码url
中的localhost
修改成对应的容器名
const { MongoClient } = require('mongodb');
const url = 'mongodb://mongodb:27017/articles'; //将localhost修改为mongo容器名
const client = new MongoClient(url);
const redisClient = require('redis').createClient;
const redis = redisClient(6379, 'redis'); //将localhost修改为redis容器名
输入命令$ docker-compose up
运行容器
docker-compose up
指令时,会发生以下情況:1.建立一个名为
${COMPOSE_PROJECT_NAME}_default
的Network2.使用node为服务名建立的容器以node这个名称加入Network
3.使用mongo为服务名建立的容器以mongodb这个名称加入Network
可以看到容器正常启动了,这时候再用Postman测试一下API。
输入
localhost:3000/articles/999
请求第999篇文章的内容再次请求发现响应内容多了个转义符
\
,这是从redis中获取的缓存数据(存储的数据类型为string
)使用VS Code连接Redis容器可以看到缓存下来的第999篇文章的数据