Triển khai Redis Sentinel - Đảm bảo high availability Redis cho ứng dụng của bạn
Giới thiệu
Trong môi trường phát triển ứng dụng ngày nay, việc đảm bảo khả dụng và tính chịu lỗi của hệ thống là vô cùng quan trọng. Trong cảnh đó, Redis Sentinel nổi lên như một giải pháp quan trọng cho việc quản lý và bảo vệ hệ thống Redis của bạn.
Redis Sentinel là một thành phần quản lý hệ thống tự động của Redis, được thiết kế để giám sát, quản lý và cung cấp khả năng chịu lỗi cho các node Redis. Bằng cách triển khai Redis Sentinel, bạn có thể tự tin xây dựng và vận hành các hệ thống Redis có khả năng tự phục hồi và chịu lỗi cao.
Trong bài viết này, chúng ta sẽ tìm hiểu về Redis Sentinel, bao gồm các chức năng chính, cách triển khai và cấu hình, cùng với những lợi ích mà nó mang lại cho việc quản lý hệ thống Redis của bạn. Hãy cùng tìm hiểu và khám phá cách Redis Sentinel có thể nâng cao hiệu suất và độ tin cậy của hệ thống Redis của bạn!
Hiểu về Sentinels
Redis Sentinel là một hệ thống phân tán bao gồm nhiều Redis instances được khởi động ở chế độ sentinel. Chúng ta gọi những instance này là Sentinels.
Nhóm Sentinels giám sát một Redis chính và các bản sao của nó. Nếu các sentinel phát hiện rằng Redis chính đã thất bại, các tiến trình sentinel sẽ tìm kiếm bản sao có dữ liệu mới nhất và sẽ thăng cấp bản sao đó thành Redis chính mới. Điều này giúp các client giao tiếp với cơ sở dữ liệu có thể kết nối lại với Redis chính mới và tiếp tục hoạt động như bình thường, gây ra ít gián đoạn nhất có thể cho người dùng.
-
Quyết định rằng Redis chính đã sập
- Để cho Sentinels có thể quyết định rằng Redis chính đã sập, chúng ta cần có đủ Sentinels đồng ý rằng máy chủ không thể truy cập được.
- Việc có một số Sentinels đồng ý rằng chúng cần thực hiện một hành động được gọi là đạt được quorum. Nếu các Sentinel không đạt được quorum, chúng không thể quyết định rằng Redis chính đã thất bại. Số lượng chính xác của Sentinels cần thiết cho quorum có thể được cấu hình.
-
Kích hoạt failove
- Sau khi các Sentinels đã quyết định rằng một Redis chính đã sập, chúng cần bầu ra và ủy quyền một leader (một instance Sentinel) sẽ thực hiện failover. Một lãnh đạo chỉ có thể được chọn nếu đa số các Sentinels đồng ý.
- Trong bước cuối cùng, leader sẽ cấu hình bản sao được chọn để trở thành chính bằng cách gửi lệnh REPLICAOF NO ONE và nó sẽ cấu hình các bản sao khác để theo dõi Redis chính mới được thăng cấp.
High availability với Redis Sentinel
Redis Sentinel cung cấp tính khả dụng cao cho Redis khi không sử dụng Redis Cluster
Redis Sentinel cũng cung cấp các nhiệm vụ phụ khác như giám sát, thông báo và hoạt động như một nhà cung cấp cấu hình cho các client.
Dưới đây là danh sách đầy đủ các khả năng của Sentinel ở mức độ tổng quan (tức là bức tranh tổng thể):
- Giám sát: Sentinel liên tục kiểm tra xem các instances master và replica có hoạt động như mong đợi không.
- Thông báo: Sentinel có thể thông báo cho quản trị hệ thống hoặc các chương trình máy tính khác thông qua một API rằng có vấn đề gì đó xảy ra với một trong số các instances Redis được giám sát.
- Tự động chuyển đổi: Nếu một master không hoạt động như mong đợi, Sentinel có thể bắt đầu một quá trình chuyển đổi tự động trong đó một replica được thăng cấp thành master, các replica khác được cấu hình lại để sử dụng master mới và các ứng dụng sử dụng máy chủ Redis được thông báo về địa chỉ mới để kết nối.
- Nhà cung cấp cấu hình: Sentinel hoạt động như một nguồn cấu hình cho việc khám phá dịch vụ của các client: các client kết nối với Sentinel để yêu cầu địa chỉ của master Redis hiện tại đảm nhận một dịch vụ cụ thể. Nếu có sự cố xảy ra, Sentinel sẽ báo cáo địa chỉ mới.
Sentinel như là một hệ thống phân tán
Sentinel được thiết kế để chạy trong cấu hình có nhiều tiến trình Sentinel hợp tác với nhau. Lợi ích của việc có nhiều tiến trình Sentinel hợp tác là:
- Phát hiện sự cố được thực hiện khi nhiều Sentinel đồng ý về việc một master cụ thể không còn khả dụng nữa. Điều này giảm khả năng nhận nhầm.
- Sentinel vẫn hoạt động ngay cả khi không phải tất cả các tiến trình Sentinel đều hoạt động, làm cho hệ thống mạnh mẽ chống lại sự cố.
Triển khai Redis Sentinel cho ứng dụng
Trong phần này, mình sẽ triển khai demo một ứng dụng NodeJS sử dụng ioredis để thiết lập Redis Sentinel.
Cấu hình Redis Sentinel trên Docker
Mình viết một file docker compose để chạy Redis Sentinel như bên dưới, nó sẽ bao gồm: 1 con master, 2 con slave và 3 con sentinel:
version: '3'
services:
redis-master:
image: bitnami/redis:latest
ports:
- '6379:6379'
environment:
- REDIS_REPLICATION_MODE=master
- ALLOW_EMPTY_PASSWORD=yes
- REDIS_AOF_ENABLED=yes
volumes:
- redis-master-db:/bitnami/redis/data
networks:
redis-net:
ipv4_address: 172.50.0.10
redis-slave1:
image: bitnami/redis:latest
ports:
- '6380:6379'
environment:
- REDIS_REPLICATION_MODE=slave
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- ALLOW_EMPTY_PASSWORD=yes
- REDIS_AOF_ENABLED=yes
volumes:
- redis-slave1-db:/bitnami/redis/data
networks:
redis-net:
ipv4_address: 172.50.0.11
depends_on:
- redis-master
redis-slave2:
image: bitnami/redis:latest
ports:
- '6381:6379'
environment:
- REDIS_REPLICATION_MODE=slave
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- ALLOW_EMPTY_PASSWORD=yes
- REDIS_AOF_ENABLED=yes
volumes:
- redis-slave2-db:/bitnami/redis/data
networks:
redis-net:
ipv4_address: 172.50.0.12
depends_on:
- redis-master
redis-sentinel1:
image: bitnami/redis-sentinel:latest
ports:
- '26379:26379'
environment:
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_SENTINEL_PORT_NUMBER=26379
networks:
redis-net:
ipv4_address: 172.50.0.13
depends_on:
- redis-master
- redis-slave1
- redis-slave2
redis-sentinel2:
image: bitnami/redis-sentinel:latest
ports:
- '26380:26379'
environment:
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_SENTINEL_PORT_NUMBER=26380
networks:
redis-net:
ipv4_address: 172.50.0.14
depends_on:
- redis-master
- redis-slave1
- redis-slave2
redis-sentinel3:
image: bitnami/redis-sentinel:latest
ports:
- '26381:26379'
environment:
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_SENTINEL_PORT_NUMBER=26381
networks:
redis-net:
ipv4_address: 172.50.0.15
depends_on:
- redis-master
- redis-slave1
- redis-slave2
networks:
redis-net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.50.0.0/24
volumes:
redis-master-db:
redis-slave1-db:
redis-slave2-db:
Đây là kết quả sau khi chạy file compose trên:
Bạn check log trong các container nếu hiển thị như bên dưới thì bạn đã chạy thành công:
- Log redis-sentinel-demo-redis-master-1:
- Log redis-sentinel-demo-redis-sentinel1-1:
Như vậy là hoàn thành bước setup phần DB.
Kết nối ứng dụng với Redis Senntinel
- Thông tin các biến môi trường cần thiết:
REDIS_MASTER_HOST_PORT_DOCKER=172.50.0.10:6379
REDIS_SLAVE_HOST_PORT_DOCKER_1=172.50.0.11:6379
REDIS_SLAVE_HOST_PORT_DOCKER_2=172.50.0.12:6379
REDIS_MASTER_MACHINE_HOST=your_machine_ip
REDIS_SLAVE_MACHINE_HOST_1=your_machine_ip
REDIS_SLAVE_MACHINE_HOST_2=your_machine_ip
REDIS_MASTER_MACHINE_PORT=6379
REDIS_SLAVE_MACHINE_PORT_1=6380
REDIS_SLAVE_MACHINE_PORT_2=6381
REDIS_SENTINEL_HOST_PORT_DOCKER_1=172.50.0.13:26379
REDIS_SENTINEL_HOST_PORT_DOCKER_2=172.50.0.14:26379
REDIS_SENTINEL_HOST_PORT_DOCKER_3=172.50.0.15:26379
REDIS_SENTINEL_HOST_1=your_machine_ip
REDIS_SENTINEL_HOST_2=your_machine_ip
REDIS_SENTINEL_HOST_3=your_machine_ip
REDIS_SENTINEL_PORT_1=26379
REDIS_SENTINEL_PORT_2=26380
REDIS_SENTINEL_PORT_3=26381
REDIS_MASTER_NAME=mymaster
REDIS_DB=0
- Viết file config để connect đến Redis Sentinel:
const dbConfig = {
redisSentinels: {
natMap: {
[`${process.env.REDIS_MASTER_HOST_PORT_DOCKER}`]: {
// Docker IP and internal port of redis-primary
host: process.env.REDIS_MASTER_MACHINE_HOST,
port: parseInt(process.env.REDIS_MASTER_MACHINE_PORT), // Exposed port in docker-compose
},
[`${process.env.REDIS_SLAVE_HOST_PORT_DOCKER_1}`]: {
// Docker IP and internal port of redis-replica 1
host: process.env.REDIS_SLAVE_MACHINE_HOST_1,
port: parseInt(process.env.REDIS_SLAVE_MACHINE_PORT_1), // Exposed port in docker-compose
},
[`${process.env.REDIS_SLAVE_HOST_PORT_DOCKER_2}`]: {
// Docker IP and internal port of redis-replica 2
host: process.env.REDIS_SLAVE_MACHINE_HOST_2,
port: parseInt(process.env.REDIS_SLAVE_MACHINE_PORT_2), // Exposed port in docker-compose
},
// Docker IP of Sentinel 1
[`${process.env.REDIS_SENTINEL_HOST_PORT_DOCKER_1}`]: {
host: process.env.REDIS_SENTINEL_HOST_1,
port: parseInt(process.env.REDIS_SENTINEL_PORT_1), // Exposed port in docker-compose
},
// Docker IP of Sentinel 2
[`${process.env.REDIS_SENTINEL_HOST_PORT_DOCKER_2}`]: {
host: process.env.REDIS_SENTINEL_HOST_2,
port: parseInt(process.env.REDIS_SENTINEL_PORT_2), // Exposed port in docker-compose
},
// Docker IP of Sentinel 3
[`${process.env.REDIS_SENTINEL_HOST_PORT_DOCKER_3}`]: {
host: process.env.REDIS_SENTINEL_HOST_3,
port: parseInt(process.env.REDIS_SENTINEL_PORT_3), // Exposed port in docker-compose
},
},
sentinels: [
{ host: process.env.REDIS_SENTINEL_HOST_1, port: parseInt(process.env.REDIS_SENTINEL_PORT_1) },
{ host: process.env.REDIS_SENTINEL_HOST_2, port: parseInt(process.env.REDIS_SENTINEL_PORT_2) },
{ host: process.env.REDIS_SENTINEL_HOST_3, port: parseInt(process.env.REDIS_SENTINEL_PORT_3) },
],
name: process.env.REDIS_MASTER_NAME || 'mymaster',
redisDB: parseInt(process.env.REDIS_DB, 10) || 0,
},
};
module.exports = dbConfig;
- Tạo client bằng ioredis để kết nối đến Sentinel:
const { Redis } = require('ioredis');
const config = require('../configs');
const redisClient = new Redis(
{
sentinels: config.redisSentinels.sentinels,
name: config.redisSentinels.name,
showFriendlyErrorStack: true,
db: config.redisSentinels.redisDB,
},
{ natMap: config.redisSentinels.natMap },
);
redisClient.on('connect', () => {
console.info('Connect Redis successfully');
});
redisClient.on('error', (err) => {
console.error('Redis client error', err);
});
module.exports = { redisClient };
- Viết một server ExpressJS đơn giản với 2 route: /write và /read để test đọc ghi data vào Redis qua kết nối Sentinel:
const express = require('express');
const app = express();
require('dotenv').config();
const { port } = require('./configs');
const { redisClient } = require('./database/redis');
app.post('/write', async (req, res) => {
try {
console.log('Write to redis');
await redisClient.set(req?.body?.key, JSON.stringify(req?.body?.value));
res.json({ success: true });
} catch (error) {
console.log('create ERROR: ', error)
res.json({ success: false, message: error?.stack || error?.message });
}
})
app.get('/read', async (req, res) => {
try {
console.log('Read from redis');
const result = await redisClient.get(req?.query?.key);
console.log(result);
res.json({ success: true, data: result });
} catch (error) {
console.log('read ERROR: ', error)
res.json({ success: false, message: error?.stack || error?.message });
}
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Kết quả
-
Chạy server và kiểm tra kết nối:
Như vậy server đã chạy và kết nối tới Redis Sentinel thành công.
-
Kiểm tra API ghi data:
-
Kiểm tra API đọc data:
Tổng kết
Trong bài viết này mình đã giới thiệu về Redis Sentinel, những tính năng mà nó mang lại khi áp dụng cho ứng dụng của chúng ta. Sau đó mình cũng có hướng dẫn chi tiết cách cấu hình để chạy lên một mô hình Redis Sentinel bằng Docker. Cuối cùng là phần hướng dẫn kết nối với một ứng dụng demo NodeJS dùng ioredis.
Hy vọng bài viết sẽ mang lại thêm kiến thức và giúp ích được cho những bạn đang cần triển khai Redis Sentinel cho ứng dụng của mình. Nếu thấy bài viết hay và hữu ích hãy vote cho mình nhé (đây là bài viết đầu tiên của mình nên nó sẽ là động lực rất lớn cho mình), mình cũng sẵn sàng lắng nghe, cải thiện và hỗ trợ nếu bạn cần nên cứ thoải mái để lại bình luận. Mình xin kết thúc bài viết này tại đây, nhớ vote cho mình nhé. Hẹn mọi người ở những bài viết sau.
All rights reserved