Ốp caching bằng redis vào ứng dụng Nodejs của bạn có gì khó ?

Làm sao để ốp caching với Redis vào ứng dụng của bạn

Caching ứng dụng web của bạn là rất cần thiết và có thể mang lại độ tối ưu về hiệu năng khi bạn mở rộng quy mô. Sẽ là một công cụ bạn thực sự muốn sử dụng với các yêu cầu thường xuyên mà vẫn đảm bảo có được độ trễ tối thiểu.

Caching là gì ? Và Redis là gì ? Tại sao lại chọn Redis ?

Caching được biết đến như là một cơ chế lưu trữ lại dữ liệu đã có tức thời và được sử dụng lại nhằm mục đích giảm tải cho hệ thống và cho phép sử dụng lại dữ liệu đã lấy giúp tăng tốc cho việc truy xuất dữ liệu ở những lần sau. Redis là một cơ sở dữ liệu NoSQL mã nguồn mở hiệu suất cao chủ yếu được sử dụng như bộ nhớ đệm cho các loại ứng dụng khác nhau. Đặc biệt là nó lưu trữ tất cả dữ liệu trong RAM và chắc bạn cũng biết với khả năng đọc và ghi dữ liệu trên RAM sẽ cực kỳ tối ưu. Redis cũng hỗ trợ nhiều kiểu dữ liệu và với nhiều ngôn ngữ lập trình khác nhau. Chi tiết bạn có thể tìm đọc ở đây

Tổng quan

Chúng ta sẽ cùng triển khai caching cơ bản trên một simple project NodeJS.Bắt đầu với việc lưu trữ dữ liệu Starships từ API response vào bộ nhớ cache. Các request trong tương lai từ sẽ tìm kiếm từ bộ nhớ cache và chỉ gửi yêu cầu lấy dữ liệu mới đến API Star Wars nếu bộ nhớ cache không có dữ liệu mà chúng ta cần hoặc dữ liệu đã hết hạn. Điều này sẽ cho phép chúng ta gửi ít yêu cầu hơn đến API của bên thứ ba và tăng tốc tổng thể ứng dụng của chúng ta. Để đảm bảo rằng bộ nhớ cache của chúng ta luôn được cập nhật và giá trị trong bộ nhớ cache dữ liệu sẽ được đính kèm với thời gian tồn tại (TTL) nó sẽ hết hạn sau một khoảng thời gian nhất định. Cùng đầu nào!

Khởi động Redis Server và Redis CLI

Trên máy của bạn có thể chạy lệnh sau để khởi động Redis Server

redis-server

Tương tự với Redis CLI

redis-cli

Cũng giống như bất kỳ cơ sở dữ liệu nào khác được cài đặt local trên máy của bạn, bạn có thể tương tác với Redis bằng CLI của nó. Tuy nhiên, chúng ta sẽ chỉ tập trung vào việc thiết lập Redis làm cache cho ứng dụng web NodeJS của chúng ta và chỉ tương tác với nó thông qua web server của chúng ta.

Thiết lập dự án NodeJS

Để bắt đầu một dự án nodejs chắc hẳn bạn cũng đã biết câu lệnh quen thuộc chạy npm init để thiết lập dự án NodeJS cùng chạy và đến bước tiếp theo thôi nào.

Project Dependencies

Chúng ta sẽ sử dụng một số các dependencies với ứng dụng NodeJS của chúng ta. Chạy lệnh sau từ terminal :

npm i express redis axios

Express sẽ giúp chúng ta đặt server. Chúng ta sẽ sử dụng package redis để kết nối ứng dụng của chúng ta với Redis Server đang chạy trên Local và sẽ sử dụng axios để lấy dữ liệu thông qua HTTP request từ API Star Wars.

Dev Dependencies

Chúng ta cũng sẽ sử dụng nodemon cho dev-dependency phục vụ cho khi có bất kỳ thay đổi sẽ tự động chạy lại server của chúng ta mà không cần phải restart lại.

npm i -D nodemon

Thiết lập tập lệnh bắt đầu trong package.json

Thay thế các tập lệnh hiện có trong package.json bằng tập lệnh sau để chúng ta có thể server với nodemon

"start": "nodemon index"
{
  "name": "",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index"
  },
  "author": "HxC",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.19.0",
    "express": "^4.17.1",
    "redis": "^2.8.0"
  },
  "devDependencies": {
    "nodemon": "^1.19.4"
  }
}

Khởi tạo Server Entry Point Thiết lập: index.js

Bạn có thể khởi tạo thông thường thông qua text editer hay VScode hay bất kể ứng dụng editer nào hoặc bạn có thể khởi tạo ngay qua terminal với

touch index.js

Sau đó thêm một số cài đặt khởi tạo cho server của chúng ta bằng một số câu lệnh quen thuộc

//set up dependencies
const express = require("express");
const redis = require("redis");
const axios = require("axios");
const bodyParser = require("body-parser");

//setup port constants
const port_redis = process.env.PORT || 6379;
const port = process.env.PORT || 5000;

//configure redis client on port 6379
const redis_client = redis.createClient(port_redis);

//configure express server
const app = express();

//Body Parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

//listen on port 5000;
app.listen(port, () => console.log(`Server running on Port ${port}`));

Nếu bạn từng làm hoặc tìm hiểu về NodeJS và ExpressJS cấu trúc code này sẽ thực sự rất quen thuộc. Đầu tiên, chúng tôi thiết lập các dependencies mà chúng ta đã cài đặt trước đó. Tiếp đó là chúng ta thiết lập hằng số cổng và tạo ứng dụng Redis client. Ngoài ra chúng ta cũng thiết lập Body-Parser trên máy chủ của mình để có thể phân tích cú pháp dữ liệu JSON. Bạn có thể chạy lệnh sau trong thiết bị đầu cuối để bắt đầu chạy web server bằng cách sử dụng tập lệnh bắt đầu từ package.json

npm start

Thiết lập Server Endpoint để gửi yêu cầu tới API Star Wars API

Bây giờ chúng ta đã thiết lập dự án của mình, cần viết thêm một số đoạn code gửi yêu cầu GET dữ liệu từ API Star Wars. Chúng ta sẽ thêm đoạn code sau vào file index.js

// Endpoint:  GET /starships/:id
// @desc Return Starships data for particular starship id
app.get("/starships/:id", async (req, res) => {
  try {
       const { id } = req.params;
       const starShipInfo = await axios.get(
       `https://swapi.co/api/starships/${id}`
       );
       
       //get data from response
       const starShipInfoData = starShipInfo.data;
       return res.json(starShipInfoData);
  } 
  catch (error) {
       
       console.log(error);
       return res.status(500).json(error);
   }
});

Chúng ta sẽ sử dụng chức năng gọi lại bất đồng bộ thông thường đặt trong try catch để thực hiện các yêu cầu GET tới API Star Wars bằng axios. Khi thành công, chúng ta sẽ trả về dữ liệu của Starship tương ứng với id trong URL. Và ngược lại với lỗi Bây giờ hãy chạy thử endpont cuối của chúng ta bằng cách tìm kiếm Starship có id = 9. 769 mili giây. Khá là chậm! Vậy đây là thời điểm mà chúng ta cần Redis. Vậy redis làm được cái gì chúng ta hãy cùng tiếp tục nhé.

Triển khai Redis Cache trên Endpoint của chúng ta

Thêm vào Cache

Vì Redis lưu trữ dữ liệu dạng key value, nên chúng ta cần đảm bảo rằng bất cứ khi nào yêu cầu được gửi đến API Star Wars mà chúng ta nhận được dữ liệu thành công, chúng ta sẽ lưu trữ key id Starship với dữ liệu của nó trong bộ nhớ cache. Để làm việc này chúng ta sẽ thêm đoạn code sau ngay sau khi chúng ta có dữ liệu trả về từ API

//add data to Redis
redis_client.setex(id, 3600, JSON.stringify(starShipInfoData));

Với đoạn mã này chúng ta thêm với key = id và expiration = 3600 giây, value = JSON dữ liệu của Starship vào bộ nhớ đệm. Bây giờ, nếu chúng ta quay lại truy cập vào endpoint get starship của chúng ta dữ liệu đó cũng sẽ được thêm vào bộ nhớ cache Redis. Bạn có thể kiểm tra dữ liệu thông qua redis-cli mà chúng ta đã cài đặt trước đó. Kiểm tra với get 9 và chúng ta sẽ nhận được

Kiểm tra và truy xuất từ bộ nhớ cache

Bây giờ chúng ta sẽ chỉ cần gửi request lấy dữ liệu tới API start Wars chỉ nếu dữ liệu chúng ta yêu cầu không nằm trong bộ nhớ cache. Chúng ta sẽ tận dụng Express middleware để kiểm tra bộ nhớ cache của chúng ta trước khi thực thi mã bên trong endpoint. Middleware sẽ như sau:

//Middleware Function to Check Cache
checkCache = (req, res, next) => {
       const { id } = req.params;
       
       //get data value for key =id
       redis_client.get(id, (err, data) => {
           if (err) {
               console.log(err);
               res.status(500).send(err);
           }
           //if no match found
           if (data != null) {
               res.send(data);
           } 
           else {
               //proceed to next middleware function
               next();
           }
        });
};

File Index.js tổng quan bây giờ sẽ như sau


//set up dependencies
const express = require("express");
const redis = require("redis");
const axios = require("axios");
const bodyParser = require("body-parser");

//setup port constants
const port_redis = process.env.PORT || 6379;
const port = process.env.PORT || 5000;

//configure redis client on port 6379
const redis_client = redis.createClient(port_redis);

//configure express server
const app = express();

//Body Parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

//Middleware Function to Check Cache
checkCache = (req, res, next) => {
  const { id } = req.params;

  redis_client.get(id, (err, data) => {
    if (err) {
      console.log(err);
      res.status(500).send(err);
    }
    //if no match found
    if (data != null) {
      res.send(data);
    } else {
      //proceed to next middleware function
      next();
    }
  });
};

//  Endpoint:  GET /starships/:id
//  @desc Return Starships data for particular starship id
app.get("/starships/:id", checkCache, async (req, res) => {
  try {
    const { id } = req.params;
    const starShipInfo = await axios.get(
      `https://swapi.co/api/starships/${id}`
    );

    //get data from response
    const starShipInfoData = starShipInfo.data;

    //add data to Redis
    redis_client.setex(id, 3600, JSON.stringify(starShipInfoData));

    return res.json(starShipInfoData);
  } catch (error) {
    console.log(error);
    return res.status(500).json(error);
  }
});

app.listen(port, () => console.log(`Server running on Port ${port}`));

Bây giờ hãy tạo một request đến endpoint của chúng ta với id=9 và cùng so sánh nào: 115 ms gấp gần 7 lần phải không.

Tổng kết

Bài viết hôm nay tôi đã cũng với các bạn tìm hiểu tổng quan về redis trong nodejs và ốp nó vào dự án của mh như nào. Ở bài viết này mọi thứ vẫn chưa phải là tất cả việc quản lý caching luôn gặp phải 2 chiều là tối ưu giảm tải được hệ thống nhưng lại gặp phải khó khăn về tính chính xác của dữ liệu. Ngoài ra nếu như bạn đang tìm một giải pháp tối ưu cho web server của mình thì sử dụng caching không phải là cách duy nhất bạn có thể tìm cách tối ưu các truy vấn database của bạn, Kiểm tra tất cả Error Scripts bằng Logging, Triển khai HTTP / 2, Clustering ứng dụng Node .js của bạn


All Rights Reserved