В этом руководстве мы создадим масштабируемый сервис сокращения URL с использованием Node.js и Redis. Этот сервис будет использовать распределенное кэширование для эффективной обработки высокой нагрузки, снижения задержек и плавного масштабирования. Мы рассмотрим ключевые концепции, такие как согласованное хеширование, стратегии инвалидации кэша и шардинг, чтобы обеспечить быструю и надежную работу системы.
К концу этого руководства у вас будет полностью функциональный сервис сокращения URL, который использует распределенное кэширование для оптимизации производительности. Мы также создадим интерактивное демо, где пользователи смогут вводить URL-адреса и видеть метрики в реальном времени, такие как попадания и промахи кэша.
Что вы узнаете
-
Как создать сервис сокращения URL с использованием Node.js и Redis.
-
Как реализовать распределенное кэширование для оптимизации производительности.
-
Понимание согласованного хеширования и стратегий инвалидации кэша.
-
Использование Docker для моделирования нескольких экземпляров Redis для шардинга и масштабирования.
Предварительные требования
Прежде чем начать, убедитесь, что у вас установлены следующие компоненты:
-
Node.js (v14 или более поздняя версия)
-
Redis
-
Docker
-
Базовые знания JavaScript, Node.js и Redis.
Содержание
Обзор проекта
Мы создадим сервис для сокращения URL-адресов, где:
-
Пользователи могут сокращать длинные URL-адреса и получать оригинальные URL-адреса.
-
Сервис использует кэширование Redis для хранения соответствий между сокращенными URL-адресами и оригинальными URL-адресами.
-
Кэш распределен по нескольким экземплярам Redis для обработки высокой нагрузки.
-
Система будет демонстрировать попадания в кэш и промахи в реальном времени.
Архитектура системы
Для обеспечения масштабируемости и производительности мы разделим наш сервис на следующие компоненты:
-
Сервер API: Обрабатывает запросы на сокращение и извлечение URL-адресов.
-
Слой кеширования Redis: Использует несколько экземпляров Redis для распределенного кеширования.
-
Docker: Моделирует распределенную среду с несколькими контейнерами Redis.
Шаг 1: Настройка проекта
Давайте настроим наш проект, инициализируя приложение Node.js:
mkdir scalable-url-shortener
cd scalable-url-shortener
npm init -y
Теперь установите необходимые зависимости:
npm install express redis shortid dotenv
-
express
: Легкий веб-серверный фреймворк. -
redis
: Для работы с кешированием. -
shortid
: Для генерации коротких уникальных идентификаторов. -
dotenv
: Для управления переменными среды.
Создайте файл .env в корне вашего проекта:
PORT=3000
REDIS_HOST_1=localhost
REDIS_PORT_1=6379
REDIS_HOST_2=localhost
REDIS_PORT_2=6380
REDIS_HOST_3=localhost
REDIS_PORT_3=6381
Эти переменные определяют хосты и порты Redis, которые мы будем использовать.
Шаг 2: Настройка экземпляров Redis
Мы будем использовать Docker для имитации распределенной среды с несколькими экземплярами Redis.
Запустите следующие команды для запуска трех контейнеров Redis:
docker run -p 6379:6379 --name redis1 -d redis
docker run -p 6380:6379 --name redis2 -d redis
docker run -p 6381:6379 --name redis3 -d redis
Это настроит три экземпляра Redis, работающих на разных портах. Мы будем использовать эти экземпляры для реализации консистентного хеширования и шардинга.
Шаг 3: Реализация сервиса сокращения URL
Давайте создадим наш основной файл приложения, index.js:
require('dotenv').config();
const express = require('express');
const redis = require('redis');
const shortid = require('shortid');
const app = express();
app.use(express.json());
const redisClients = [
redis.createClient({ host: process.env.REDIS_HOST_1, port: process.env.REDIS_PORT_1 }),
redis.createClient({ host: process.env.REDIS_HOST_2, port: process.env.REDIS_PORT_2 }),
redis.createClient({ host: process.env.REDIS_HOST_3, port: process.env.REDIS_PORT_3 })
];
// Функция хеширования для распределения ключей между клиентами Redis
function getRedisClient(key) {
const hash = key.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
return redisClients[hash % redisClients.length];
}
// Конечная точка для сокращения URL
app.post('/shorten', async (req, res) => {
const { url } = req.body;
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url);
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
// Конечная точка для получения исходного URL
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
return res.status(404).send('URL not found');
}
res.redirect(url);
});
});
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
});
Как видно из этого кода, у нас есть:
-
Консистентное хеширование:
-
Мы распределяем ключи (сокращенные URL) между несколькими клиентами Redis, используя простую хеш-функцию.
-
Хеш-функция гарантирует равномерное распределение URL-адресов по экземплярам Redis.
-
-
Сокращение URL-адресов:
-
Эндпоинт /shorten принимает длинный URL и генерирует короткий идентификатор с использованием библиотеки shortid.
-
Сокращенный URL-адрес сохраняется в одном из экземпляров Redis с использованием нашей хэш-функции.
-
-
Перенаправление URL-адресов:
-
Эндпоинт /:shortId извлекает исходный URL-адрес из кэша и перенаправляет пользователя.
-
Если URL не найден в кэше, возвращается ответ 404.
-
Шаг 4: Внедрение инвалидации кэша
В реальном приложении URL-адреса могут истечь или измениться со временем. Для обработки этого необходимо реализовать инвалидацию кэша.
Добавление истечения срока действия для кешированных URL-адресов
Давайте изменить наш файл index.js, чтобы установить время истечения для каждой кешированной записи:
// Конечная точка для сокращения URL с истечением срока действия
app.post('/shorten', async (req, res) => {
const { url, ttl } = req.body; // ttl (время жизни) необязательно
if (!url) return res.status(400).send('URL is required');
const shortId = shortid.generate();
const redisClient = getRedisClient(shortId);
await redisClient.set(shortId, url, 'EX', ttl || 3600); // По умолчанию TTL 1 час
res.json({ shortUrl: `http://localhost:${process.env.PORT}/${shortId}` });
});
-
TTL (Время жизни): Мы устанавливаем время истечения по умолчанию 1 час для каждого сокращенного URL-адреса. При необходимости вы можете настроить TTL для каждого URL-адреса.
-
Инвалидация кэша: При истечении TTL запись автоматически удаляется из кэша.
Шаг 5: Мониторинг показателей кэша
Для отслеживания попаданий в кэш и промахов мы добавим некоторое логирование в наши конечные точки в index.js:
app.get('/:shortId', async (req, res) => {
const { shortId } = req.params;
const redisClient = getRedisClient(shortId);
redisClient.get(shortId, (err, url) => {
if (err || !url) {
console.log(`Cache miss for key: ${shortId}`);
return res.status(404).send('URL not found');
}
console.log(`Cache hit for key: ${shortId}`);
res.redirect(url);
});
});
Вот что происходит в этом коде:
-
Попадания в кэш: Если URL найден в кэше, это попадание в кэш.
-
Промахи в кэше: Если URL не найден, это промах в кэше.
-
Это логирование поможет вам отслеживать производительность вашего распределенного кэша.
Шаг 6: Тестирование приложения
- Запустите ваши экземпляры Redis:
docker start redis1 redis2 redis3
- Запустите сервер Node.js:
node index.js
-
Проверьте конечные точки с помощью
curl
или Postman:-
Укоротить URL:
POST http://localhost:3000/shorten Тело: "url": "https://example.com" }
-
Обратитесь по укороченному URL:
GET http://localhost:3000/{shortId}
-
Вывод: Что вы изучили
Поздравляем! Вы успешно создали масштабируемый сервис сокращения URL-адресов с распределенным кэшированием с использованием Node.js и Redis. В ходе этого учебного пособия вы узнали, как:
-
Реализуйте консистентное хеширование для распределения кэш-записей по нескольким экземплярам Redis.
-
Оптимизируйте ваше приложение с помощью стратегий инвалидации кэша, чтобы данные были актуальными.
-
Используйте Docker для моделирования распределенной среды с несколькими узлами Redis.
-
Отслеживайте попадания и промахи кэша, чтобы оптимизировать производительность.
Следующие шаги:
-
Добавьте базу данных: Храните URL-адреса в базе данных для сохранения данных за пределами кэша.
-
Реализуйте аналитику: Отслеживайте количество кликов и аналитику для сокращенных URL-адресов.
-
Разверните в облаке: Разверните ваше приложение с использованием Kubernetes для автомасштабирования и отказоустойчивости.
Счастливого кодинга!