Bởi Tarique Ejaz
Hiệu suất là thông số cần thiết cần xem xét khi bạn thiết kế bất kỳ phần mềm nào. Điều này đặc biệt quan trọng khi nói đến những gì xảy ra ở hậu trường.
Chúng tôi, với tư cách là nhà phát triển và nhà công nghệ, áp dụng nhiều chỉnh sửa và triển khai để cải thiện hiệu suất. Đây là lúc bộ nhớ đệm phát huy tác dụng.
Bộ nhớ đệm được định nghĩa là một cơ chế lưu trữ dữ liệu hoặc tệp ở một vị trí lưu trữ tạm thời từ đó có thể truy cập ngay lập tức bất cứ khi nào được yêu cầu.
Bộ nhớ đệm đã trở thành một thứ bắt buộc phải có trong các ứng dụng web ngày nay. Chúng tôi có thể sử dụng Redis để tăng cường các API web của mình - được xây dựng bằng Node.js và MongoDB.
"Bộ nhớ đệm rõ ràng vẫn đóng một vai trò cực kỳ quan trọng sau 100 đến 200 năm nữa."
Redis:Tổng quan dành cho người không chuyên
Redis, theo tài liệu chính thức, được định nghĩa là kho lưu trữ cấu trúc dữ liệu trong bộ nhớ, được sử dụng làm cơ sở dữ liệu, trình trung chuyển tin nhắn hoặc bộ nhớ đệm. Nó hỗ trợ các cấu trúc dữ liệu như chuỗi, băm, danh sách, bộ, bộ được sắp xếp với truy vấn phạm vi, bitmap, hyperloglog, chỉ mục không gian địa lý với truy vấn bán kính và luồng.
Được rồi, có khá nhiều cấu trúc dữ liệu ở đó. Nói một cách đơn giản, hầu hết tất cả các cấu trúc dữ liệu được hỗ trợ đều có thể được cô đọng thành dạng chuỗi này hoặc dạng khác. Bạn sẽ hiểu rõ hơn khi chúng tôi thực hiện quá trình triển khai.
Nhưng có một điều rõ ràng. Redis rất mạnh mẽ và khi được sử dụng đúng cách có thể làm cho ứng dụng của chúng ta không chỉ nhanh hơn mà còn hiệu quả đáng kinh ngạc. Nói đủ rồi. Hãy bắt tay vào thực hiện.
Hãy nói chuyện về mã
Trước khi chúng tôi bắt đầu, bạn sẽ cần thiết lập lại redis trong hệ thống cục bộ của mình. Bạn có thể làm theo quy trình thiết lập nhanh này để thiết lập và chạy lại.
Xong chưa? Mát mẻ. Hãy bắt đầu. Chúng tôi có một ứng dụng đơn giản được tạo trong Express, ứng dụng này sử dụng một phiên bản trong MongoDB Atlas để đọc và ghi dữ liệu từ đó.
Chúng tôi có hai API chính được tạo trong /blogs tập tin lộ trình.
...
// GET - Fetches all blog posts for required user
blogsRouter.route('/:user')
.get(async (req, res, next) => {
const blogs = await Blog.find({ user: req.params.user });
res.status(200).json({
blogs,
});
});
// POST - Creates a new blog post
blogsRouter.route('/')
.post(async (req, res, next) => {
const existingBlog = await Blog.findOne({ title: req.body.title });
if (!existingBlog) {
let newBlog = new Blog(req.body);
const result = await newBlog.save();
return res.status(200).json({
message: `Blog ${result.id} is successfully created`,
result,
});
}
res.status(200).json({
message: 'Blog with same title exists',
});
});
...
Rắc một chút lòng tốt của Redis
Chúng tôi bắt đầu bằng cách tải xuống gói npm redis để kết nối với máy chủ redis cục bộ.
const mongoose = require('mongoose');
const redis = require('redis');
const util = require('util');
const redisUrl = 'redis://127.0.0.1:6379';
const client = redis.createClient(redisUrl);
client.hget = util.promisify(client.hget);
...
Chúng tôi sử dụng utils.promisify hàm chuyển đổi client.hget chức năng trả lại một lời hứa thay vì gọi lại. Bạn có thể đọc thêm về promisification đây.
Kết nối Redis đã sẵn sàng. Trước khi bắt đầu viết thêm bất kỳ mã bộ nhớ đệm nào, chúng ta hãy lùi lại một bước và cố gắng hiểu những yêu cầu chúng ta cần phải đáp ứng là gì và những thách thức có thể gặp phải.
Chiến lược bộ nhớ đệm của chúng tôi có thể giải quyết các điểm sau.
- Lưu vào bộ nhớ đệm yêu cầu cho tất cả bài đăng trên blog của một người dùng cụ thể
-
Xóa bộ nhớ đệm mỗi khi tạo bài đăng blog mới
Những thách thức có thể xảy ra mà chúng ta nên cẩn thận khi thực hiện chiến lược của mình là:
-
Cách xử lý đúng đắn việc tạo khóa để lưu trữ dữ liệu bộ đệm
- Logic hết hạn bộ nhớ đệm và hết hạn bắt buộc để duy trì độ mới của bộ nhớ đệm
- Triển khai logic bộ nhớ đệm có thể tái sử dụng
Được rồi. Chúng tôi đã ghi lại các điểm của mình và kết nối lại. Chuyển sang bước tiếp theo.
Ghi đè hàm thực thi Mongoose mặc định
Chúng tôi muốn logic bộ nhớ đệm của mình có thể tái sử dụng được. Và không chỉ có thể tái sử dụng, chúng tôi còn muốn nó trở thành điểm kiểm tra đầu tiên trước khi thực hiện bất kỳ truy vấn nào tới cơ sở dữ liệu. Điều này có thể dễ dàng được thực hiện bằng cách sử dụng một thủ thuật đơn giản là sao lưu hàm thực thi cầy mangut.
...
const exec = mongoose.Query.prototype.exec;
...
mongoose.Query.prototype.exec = async function() {
...
const result = await exec.apply(this, arguments);
console.log('Data Source: Database');
return result;
}
...
Chúng tôi sử dụng đối tượng nguyên mẫu của mongoose để thêm mã logic bộ nhớ đệm làm lần thực thi đầu tiên trong truy vấn.
Thêm bộ đệm dưới dạng truy vấn
Để biểu thị những truy vấn nào sẽ được lưu vào bộ nhớ đệm, chúng tôi tạo một truy vấn cầy mangut. Chúng tôi cung cấp khả năng vượt qua user được sử dụng làm khóa băm thông qua options đối tượng.
Lưu ý: Hashkey đóng vai trò là mã định danh cho cấu trúc dữ liệu băm, theo thuật ngữ thông thường, có thể được coi là khóa chính của một tập hợp các cặp khóa-giá trị. Qua đó, cho phép lưu vào bộ đệm số lượng lớn hơn bộ giá trị truy vấn. Bạn có thể đọc thêm về hàm băm trong redis tại đây.
...
mongoose.Query.prototype.cache = function(options = {}) {
this.enableCache = true;
this.hashKey = JSON.stringify(options.key || 'default');
return this;
};
...
Làm như vậy, chúng ta có thể dễ dàng sử dụng cache(<options argument>) query cùng với các truy vấn mà chúng ta muốn lưu vào bộ nhớ đệm theo cách sau.
...
const blogs = await Blog
.find({ user: req.params.user })
.cache({ key: req.params.user });
...
Tạo logic bộ đệm
Chúng tôi đã thiết lập một truy vấn có thể sử dụng lại phổ biến để biểu thị những truy vấn nào cần được lưu vào bộ đệm. Hãy tiếp tục và viết logic bộ nhớ đệm trung tâm.
...
mongoose.Query.prototype.exec = async function() {
if (!this.enableCache) {
console.log('Data Source: Database');
return exec.apply(this, arguments);
}
const key = JSON.stringify(Object.assign({}, this.getQuery(), {
collection: this.mongooseCollection.name,
}));
const cachedValue = await client.hget(this.hashKey, key);
if (cachedValue) {
const parsedCache = JSON.parse(cachedValue);
console.log('Data Source: Cache');
return Array.isArray(parsedCache)
? parsedCache.map(doc => new this.model(doc))
: new this.model(parsedCache);
}
const result = await exec.apply(this, arguments);
client.hmset(this.hashKey, key, JSON.stringify(result), 'EX', 300);
console.log('Data Source: Database');
return result;
};
...
Bất cứ khi nào chúng tôi sử dụng cache() truy vấn cùng với truy vấn chính của chúng tôi, chúng tôi đặt enableCache chìa khóa là đúng.
Nếu khóa sai, chúng tôi trả về exec chính truy vấn làm mặc định. Nếu không, trước tiên chúng tôi tạo khóa để tìm nạp và lưu trữ/làm mới dữ liệu bộ đệm.
Chúng tôi sử dụng collection tên cùng với truy vấn mặc định làm tên khóa vì mục đích duy nhất. Khóa băm được sử dụng là tên của user mà chúng tôi đã đặt trước đó trong cache() định nghĩa hàm.
Dữ liệu được lưu trong bộ nhớ đệm được tìm nạp bằng client.hget() hàm yêu cầu khóa băm và khóa hệ quả làm tham số.
Lưu ý: Chúng tôi luôn sử dụng JSON.parse() trong khi tìm nạp bất kỳ dữ liệu nào từ redis. Và tương tự, chúng ta sử dụng JSON.stringify() trên khóa và dữ liệu trước khi lưu trữ mọi thứ vào redis. Điều này được thực hiện vì redis không hỗ trợ cấu trúc dữ liệu JSON.
Sau khi lấy được dữ liệu được lưu trong bộ nhớ đệm, chúng ta phải chuyển đổi từng đối tượng được lưu trong bộ nhớ đệm thành mô hình cầy mangut. Điều này có thể được thực hiện bằng cách sử dụng new this.model(<object>) .
Nếu bộ đệm không chứa dữ liệu cần thiết, chúng tôi sẽ thực hiện truy vấn tới cơ sở dữ liệu. Sau đó, khi trả lại dữ liệu cho API, chúng tôi làm mới bộ đệm bằng cách sử dụng client.hmset() . Chúng tôi cũng đặt thời gian hết hạn bộ đệm mặc định là 300 giây. Điều này có thể tùy chỉnh dựa trên chiến lược bộ nhớ đệm của bạn.
Logic bộ nhớ đệm được đặt đúng chỗ. Chúng tôi cũng đã đặt thời gian hết hạn mặc định. Tiếp theo, chúng tôi xem xét việc buộc hết hạn bộ nhớ đệm bất cứ khi nào một bài đăng blog mới được tạo.
Bắt buộc hết hạn bộ nhớ đệm
Trong một số trường hợp nhất định, chẳng hạn như khi người dùng tạo một bài đăng blog mới, người dùng mong đợi rằng bài đăng mới sẽ có sẵn khi họ tìm nạp tất cả các bài đăng.
Để làm như vậy, chúng tôi phải xóa bộ nhớ đệm liên quan đến người dùng đó và cập nhật dữ liệu mới. Vì vậy chúng ta phải buộc hết hạn. Chúng ta có thể làm điều đó bằng cách gọi del() chức năng được cung cấp bởi redis.
...
module.exports = {
clearCache(hashKey) {
console.log('Cache cleaned');
client.del(JSON.stringify(hashKey));
}
}
...
Chúng tôi cũng phải lưu ý rằng chúng tôi sẽ buộc hết hạn trên nhiều lộ trình. Một cách có thể mở rộng là sử dụng clearCache() này làm phần mềm trung gian và gọi nó khi bất kỳ truy vấn nào liên quan đến tuyến đường đã thực hiện xong.
const { clearCache } = require('../services/cache');
module.exports = async (req, res, next) => {
// wait for route handler to finish running
await next();
clearCache(req.body.user);
}
Phần mềm trung gian này có thể được gọi dễ dàng trên một tuyến đường cụ thể theo cách sau.
...
blogsRouter.route('/')
.post(cleanCache, async (req, res, next) => {
...
}
...
Và chúng ta đã hoàn thành. Tôi đồng ý rằng đó là khá nhiều mã. Nhưng với phần cuối cùng đó, chúng tôi đã thiết lập redis với ứng dụng của mình và xử lý hầu hết các thách thức có thể xảy ra. Đã đến lúc xem chiến lược bộ nhớ đệm của chúng tôi hoạt động như thế nào.
Redis đang hoạt động
Chúng tôi sử dụng Postman làm ứng dụng khách API để xem chiến lược bộ nhớ đệm của chúng tôi đang hoạt động. Bắt đầu nào. Hãy cùng tìm hiểu từng thao tác API.
- Chúng tôi tạo một bài đăng blog mới bằng cách sử dụng
/blogstuyến đường
Tạo bài đăng blog mới
- Sau đó, chúng tôi tìm nạp tất cả các bài đăng trên blog liên quan đến người dùng
tejaz
Tìm nạp tất cả bài đăng trên blog cho người dùng tejaz
- Chúng tôi tìm nạp tất cả các bài đăng trên blog cho người dùng
tejazmột lần nữa.
Tìm nạp tất cả các bài đăng trên blog cho người dùng tejaz một lần nữa
Bạn có thể thấy rõ rằng khi chúng tôi tìm nạp từ bộ đệm, thời gian thực hiện đã giảm xuống từ 409 mili giây đến 24 mili giây . Điều này tăng cường API của bạn bằng cách giảm thời gian thực hiện gần 95%.
Ngoài ra, chúng ta có thể thấy rõ rằng các hoạt động cập nhật và hết hạn bộ nhớ đệm hoạt động như mong đợi.
Bạn có thể tìm thấy mã nguồn hoàn chỉnh trong redis-express thư mục ở đây.
Kết luận
Bộ nhớ đệm là một bước bắt buộc đối với bất kỳ ứng dụng sử dụng nhiều dữ liệu và hiệu suất cao. Redis giúp bạn dễ dàng đạt được điều này trong các ứng dụng web của mình. Nó là một công cụ siêu mạnh và nếu được sử dụng đúng cách, nó chắc chắn có thể mang lại trải nghiệm tuyệt vời cho các nhà phát triển cũng như người dùng xung quanh.
Bạn có thể tìm thấy bộ lệnh redis đầy đủ tại đây. Bạn có thể sử dụng nó với redis-cli để giám sát dữ liệu bộ đệm và quy trình ứng dụng của bạn.
Khả năng được cung cấp bởi bất kỳ công nghệ cụ thể nào thực sự là vô tận. Nếu bạn có bất kỳ thắc mắc nào, bạn có thể liên hệ với tôi theo số [LinkedIn](https://www.linkedin.com/in/tarique-ejaz/) .
Trong lúc đó, hãy tiếp tục viết mã.
Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu