使用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查看插入的数据。

测试

在启动了redismongo后,运行命令$ node app.js,可以看到应用已经开始在监听3000端口了

使用Postman测试下API
请求articles1内容

Docker部署

制作镜像

首先制作网站后台代码镜像,编写Dockerfile

FROM node:16.6.2

WORKDIR /app

COPY ["package.json","package-lock.json","./"]

RUN npm install

COPY . . 

CMD ["node","app.js"]

写一个.dockerignorenode_modules文件夹在COPY的时候忽略掉。

这里把COPY ["package.json","package-lock.json","./"]COPY . .指令分开是因为在docker中每个指令会创建一层镜像,每次docker build都会使用镜像缓存来缩短镜像创建的时间,而像ADDCOPY这类指令会计算镜像内的文件和构建目录文件的校验和然后做比较来判断本层是否有改动,如果发生改动则之后层的缓存将会失效。如果将上面的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的Network
2.使用node为服务名建立的容器以node这个名称加入Network
3.使用mongo为服务名建立的容器以mongodb这个名称加入Network


可以看到容器正常启动了,这时候再用Postman测试一下API
输入localhost:3000/articles/999请求第999篇文章的内容

再次请求发现响应内容多了个转义符\,这是从redis中获取的缓存数据(存储的数据类型为string)

使用VS Code连接Redis容器可以看到缓存下来的第999篇文章的数据

Last modification:September 4th, 2023 at 10:12 pm
If you think my article is useful to you, please feel free to appreciate