Redis 介绍与 Node.js 使用教程
📚 目录
- [Redis 简介](#Redis 简介)
- 环境准备与快速开始
- 基础连接与配置
- 数据类型详解与示例
- 实际应用场景详解
- [Express + Redis 完整实战](#Express + Redis 完整实战)
- 最佳实践与优化
- 常见问题与解决方案
Redis 简介
什么是 Redis?
Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息代理。
主要特点
- 内存存储:数据存储在内存中,读写速度极快
- 数据持久化:支持 RDB 和 AOF 两种持久化方式
- 丰富的数据类型:字符串、哈希、列表、集合、有序集合等
- 高可用性:支持主从复制、哨兵模式、集群模式
- 原子操作:所有操作都是原子性的
Redis 数据类型概览
1. 字符串 (String)
bash
SET name "张三"
GET name
INCR counter
EXPIRE session:123 3600
2. 哈希 (Hash)
bash
HSET user:1 name "李四" age 25 city "北京"
HGET user:1 name
HGETALL user:1
3. 列表 (List)
bash
LPUSH tasks "任务1" "任务2"
RPOP tasks
LRANGE tasks 0 -1
4. 集合 (Set)
bash
SADD fruits "苹果" "香蕉" "橘子"
SMEMBERS fruits
SISMEMBER fruits "苹果"
5. 有序集合 (Sorted Set)
bash
ZADD scores 85 "张三" 92 "李四" 78 "王五"
ZRANGE scores 0 -1 WITHSCORES
ZRANK scores "李四"
环境准备与快速开始
环境要求
- Node.js 16+
- Redis 服务器
- 基础 JavaScript 知识
服务器安装
这里采用docker安装
-
先下载dockerDesktop https://www.docker.com/products.../docker-desktop/
-
安装redis-server
-
运行redis
redis 已经运行了,docker 安装的redis 没有密码,这里方便学习,就用docker安装,适合window
安装与启动
bash
# 1. 安装依赖
npm install redis express
基础连接与配置
基础连接示例 (基于 index.js
)
javascript
import { createClient } from 'redis';
// 创建 Redis 客户端
const client = createClient({
socket: {
host: 'localhost',
port: 6379
},
password: '', // 如果设置了密码
database: 0 // 默认数据库
});
// 事件监听
client.on('error', (err) => {
console.error('Redis 连接错误:', err);
});
client.on('connect', () => {
console.log('Redis 连接成功');
});
client.on('ready', () => {
console.log('Redis 准备就绪');
});
client.on('end', () => {
console.log('Redis 连接关闭');
});
// 连接到 Redis
await client.connect();
// 使用完毕后断开连接
// await client.disconnect();
连接配置选项
javascript
const client = createClient({
socket: {
host: 'localhost', // Redis 服务器地址
port: 6379, // Redis 端口
connectTimeout: 10000, // 连接超时时间
lazyConnect: true // 延迟连接
},
password: 'your_password', // Redis 密码
database: 0, // 数据库编号
retryDelayOnFailover: 100, // 故障转移重试延迟
maxRetriesPerRequest: 3 // 每个请求最大重试次数
});
数据类型详解与示例
1. 字符串 (String) - 缓存与计数器
用途:缓存、计数器、会话存储、简单键值对
javascript
// 基本操作
await client.set('name', '张三');
await client.set('age', '20');
const name = await client.get('name');
console.log('姓名:', name); // 张三
// 设置过期时间
await client.setEx('session:123', 3600, 'session_data');
// 数字操作
await client.set('counter', 0);
await client.incr('counter'); // 自增1
await client.incrBy('counter', 5); // 增加5
const counter = await client.get('counter');
console.log('计数器:', counter); // 6
// 批量操作
await client.mSet({
'user:1:name': '张三',
'user:1:age': '25',
'user:1:city': '北京'
});
const userData = await client.mGet(['user:1:name', 'user:1:age', 'user:1:city']);
console.log('用户数据:', userData); // ['张三', '25', '北京']
// 检查键是否存在
const exists = await client.exists('name');
console.log('name 键存在:', exists); // 1
// 删除键
await client.del('name');
2. 哈希 (Hash) - 对象存储
用途:对象属性存储、用户信息、配置管理
javascript
// 设置哈希字段
await client.hSet('user:1', {
name: '李四',
age: 25,
city: '北京',
email: 'lisi@example.com'
});
// 获取单个字段
const userName = await client.hGet('user:1', 'name');
console.log('用户名:', userName); // 李四
// 获取所有字段
const user = await client.hGetAll('user:1');
console.log('用户信息:', user);
// { name: '李四', age: '25', city: '北京', email: 'lisi@example.com' }
// 获取多个字段
const fields = await client.hmGet('user:1', ['name', 'age']);
console.log('姓名和年龄:', fields); // ['李四', '25']
// 检查字段是否存在
const hasEmail = await client.hExists('user:1', 'email');
console.log('有邮箱字段:', hasEmail); // 1
// 删除字段
await client.hDel('user:1', 'email');
// 获取字段数量
const fieldCount = await client.hLen('user:1');
console.log('字段数量:', fieldCount); // 3
3. 列表 (List) - 队列与时间线
用途:消息队列、任务队列、时间线、栈
javascript
// 从左侧添加元素
await client.lPush('tasks', '任务1', '任务2', '任务3');
// 从右侧添加元素
await client.rPush('tasks', '任务4');
// 获取列表长度
const length = await client.lLen('tasks');
console.log('任务数量:', length); // 4
// 获取范围内的元素
const allTasks = await client.lRange('tasks', 0, -1);
console.log('所有任务:', allTasks);
// ['任务3', '任务2', '任务1', '任务4']
// 从左侧弹出元素 (FIFO 队列)
const leftTask = await client.lPop('tasks');
console.log('左侧弹出:', leftTask); // 任务3
// 从右侧弹出元素 (LIFO 栈)
const rightTask = await client.rPop('tasks');
console.log('右侧弹出:', rightTask); // 任务4
// 阻塞式弹出 (消息队列)
const message = await client.brPop('message_queue', 5); // 5秒超时
if (message) {
console.log('收到消息:', message.element);
}
// 插入元素
await client.lInsert('tasks', 'BEFORE', '任务2', '新任务');
4. 集合 (Set) - 标签与去重
用途:标签系统、去重、集合运算、好友关系
javascript
// 添加元素到集合
await client.sAdd('fruits', '苹果', '香蕉', '橘子');
await client.sAdd('vegetables', '白菜', '萝卜', '苹果'); // 注意苹果重复
// 获取集合所有成员
const fruits = await client.sMembers('fruits');
console.log('水果:', fruits); // ['苹果', '香蕉', '橘子']
// 检查成员是否存在
const hasApple = await client.sIsMember('fruits', '苹果');
console.log('有苹果:', hasApple); // 1
// 获取集合大小
const fruitsCount = await client.sCard('fruits');
console.log('水果种类数:', fruitsCount); // 3
// 集合运算
const intersection = await client.sInter(['fruits', 'vegetables']);
console.log('交集:', intersection); // ['苹果']
const union = await client.sUnion(['fruits', 'vegetables']);
console.log('并集:', union); // ['苹果', '香蕉', '橘子', '白菜', '萝卜']
const difference = await client.sDiff(['fruits', 'vegetables']);
console.log('差集:', difference); // ['香蕉', '橘子']
// 随机获取成员
const randomFruit = await client.sRandMember('fruits');
console.log('随机水果:', randomFruit);
// 移除成员
await client.sRem('fruits', '苹果');
5. 有序集合 (Sorted Set) - 排行榜
用途:排行榜、范围查询、优先级队列
javascript
// 添加有分数的成员
await client.zAdd('scores', [
{ score: 85, value: '张三' },
{ score: 92, value: '李四' },
{ score: 78, value: '王五' },
{ score: 95, value: '赵六' }
]);
// 按分数范围获取成员(升序)
const topScores = await client.zRangeWithScores('scores', 0, -1);
console.log('成绩排名(升序):', topScores);
// [
// { score: 78, value: '王五' },
// { score: 85, value: '张三' },
// { score: 92, value: '李四' },
// { score: 95, value: '赵六' }
// ]
// 按分数范围获取成员(降序)
const topScoresDesc = await client.zRevRangeWithScores('scores', 0, 2);
console.log('前三名(降序):', topScoresDesc);
// [
// { score: 95, value: '赵六' },
// { score: 92, value: '李四' },
// { score: 85, value: '张三' }
// ]
// 获取成员排名
const zhangRank = await client.zRevRank('scores', '张三');
console.log('张三排名(从0开始):', zhangRank); // 2
// 获取成员分数
const liScore = await client.zScore('scores', '李四');
console.log('李四分数:', liScore); // 92
// 按分数范围查询
const highScores = await client.zRangeByScore('scores', 90, 100);
console.log('90分以上:', highScores); // ['李四', '赵六']
// 增加成员分数
await client.zIncrBy('scores', 5, '张三');
const newScore = await client.zScore('scores', '张三');
console.log('张三新分数:', newScore); // 90
🎯 实际应用场景详解
1. 缓存管理 (CacheManager
)
javascript
class CacheManager {
constructor(redisClient) {
this.redis = redisClient;
}
// 设置缓存
async set(key, data, expireSeconds = 3600) {
const value = JSON.stringify(data);
await this.redis.setEx(key, expireSeconds, value);
console.log(`缓存已设置: ${key}, 过期时间: ${expireSeconds}秒`);
}
// 获取缓存
async get(key) {
const value = await this.redis.get(key);
if (value) {
console.log(`缓存命中: ${key}`);
return JSON.parse(value);
}
console.log(`缓存未命中: ${key}`);
return null;
}
// 删除缓存
async del(key) {
const result = await this.redis.del(key);
console.log(`缓存已删除: ${key}`);
return result;
}
// 缓存用户信息示例
async cacheUser(userId, userData) {
const key = `user:${userId}`;
await this.set(key, userData, 1800); // 30分钟过期
}
async getUser(userId) {
const key = `user:${userId}`;
return await this.get(key);
}
}
// 使用示例
const cache = new CacheManager(client);
await cache.cacheUser(123, { name: '张三', email: 'zhang@example.com' });
const cachedUser = await cache.getUser(123);
console.log('缓存的用户:', cachedUser);
2. 会话管理 (SessionManager
)
javascript
class SessionManager {
constructor(redisClient) {
this.redis = redisClient;
this.sessionPrefix = 'session:';
this.sessionExpire = 7200; // 2小时
}
// 创建会话
async createSession(sessionId, userData) {
const key = this.sessionPrefix + sessionId;
const sessionData = {
...userData,
createdAt: new Date().toISOString(),
lastAccess: new Date().toISOString()
};
await this.redis.setEx(key, this.sessionExpire, JSON.stringify(sessionData));
console.log(`会话已创建: ${sessionId}`);
return sessionId;
}
// 获取会话
async getSession(sessionId) {
const key = this.sessionPrefix + sessionId;
const sessionData = await this.redis.get(key);
if (sessionData) {
const data = JSON.parse(sessionData);
// 更新最后访问时间
data.lastAccess = new Date().toISOString();
await this.redis.setEx(key, this.sessionExpire, JSON.stringify(data));
console.log(`会话已获取: ${sessionId}`);
return data;
}
console.log(`会话不存在: ${sessionId}`);
return null;
}
// 删除会话
async destroySession(sessionId) {
const key = this.sessionPrefix + sessionId;
const result = await this.redis.del(key);
console.log(`会话已删除: ${sessionId}`);
return result;
}
}
// 使用示例
const session = new SessionManager(client);
const sessionId = 'sess_' + Date.now();
await session.createSession(sessionId, { userId: 123, username: '张三' });
const sessionData = await session.getSession(sessionId);
console.log('会话数据:', sessionData);
3. 限流器 (RateLimiter
)
javascript
class RateLimiter {
constructor(redisClient) {
this.redis = redisClient;
}
// 固定窗口限流
async checkLimit(key, limit, windowSeconds) {
const current = await this.redis.incr(key);
if (current === 1) {
// 第一次访问,设置过期时间
await this.redis.expire(key, windowSeconds);
}
const result = {
allowed: current <= limit,
current: current,
limit: limit,
remaining: Math.max(0, limit - current)
};
console.log(`限流检查: ${key}`, result);
return result;
}
// API限流示例
async checkApiLimit(userId, endpoint) {
const key = `rate_limit:${userId}:${endpoint}`;
return await this.checkLimit(key, 100, 3600); // 每小时100次
}
// IP限流示例
async checkIpLimit(ip, limit = 1000, windowSeconds = 3600) {
const key = `rate_limit:ip:${ip}`;
return await this.checkLimit(key, limit, windowSeconds);
}
}
// 使用示例
const rateLimiter = new RateLimiter(client);
const limitCheck = await rateLimiter.checkApiLimit(123, '/api/data');
if (!limitCheck.allowed) {
console.log('请求被限流');
} else {
console.log(`剩余请求次数: ${limitCheck.remaining}`);
}
4. 消息队列 (MessageQueue
)
javascript
class MessageQueue {
constructor(redisClient) {
this.redis = redisClient;
}
// 发送消息到队列
async sendMessage(queueName, message) {
const messageData = {
id: Date.now().toString(),
data: message,
timestamp: new Date().toISOString()
};
await this.redis.lPush(queueName, JSON.stringify(messageData));
console.log(`消息已发送到队列 ${queueName}:`, messageData.id);
}
// 从队列获取消息(阻塞模式)
async receiveMessage(queueName, timeoutSeconds = 0) {
const result = await this.redis.brPop(queueName, timeoutSeconds);
if (result) {
const message = JSON.parse(result.element);
console.log(`从队列 ${queueName} 收到消息:`, message.id);
return message;
}
console.log(`队列 ${queueName} 超时,未收到消息`);
return null;
}
// 获取队列长度
async getQueueLength(queueName) {
const length = await this.redis.lLen(queueName);
console.log(`队列 ${queueName} 长度: ${length}`);
return length;
}
// 处理队列消息
async processQueue(queueName, processor, batchSize = 1) {
console.log(`开始处理队列: ${queueName}`);
while (true) {
try {
const messages = [];
for (let i = 0; i < batchSize; i++) {
const message = await this.receiveMessage(queueName, 5); // 5秒超时
if (message) {
messages.push(message);
} else {
break;
}
}
if (messages.length > 0) {
await processor(messages);
} else {
console.log('队列为空,等待新消息...');
}
} catch (error) {
console.error('处理队列消息出错:', error);
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试
}
}
}
}
// 使用示例
const queue = new MessageQueue(client);
await queue.sendMessage('task_queue', { task: 'process_data', userId: 123 });
console.log('队列长度:', await queue.getQueueLength('task_queue'));
// 消息处理器
const messageProcessor = async (messages) => {
for (const message of messages) {
console.log('处理消息:', message.data);
// 模拟处理时间
await new Promise(resolve => setTimeout(resolve, 1000));
}
};
// 启动队列处理器
// queue.processQueue('task_queue', messageProcessor);
5. 分布式锁 (DistributedLock
)
javascript
class DistributedLock {
constructor(redisClient) {
this.redis = redisClient;
}
// 获取锁
async acquireLock(lockKey, lockValue, expireSeconds = 30) {
const result = await this.redis.set(lockKey, lockValue, {
NX: true, // 只在键不存在时设置
EX: expireSeconds // 设置过期时间
});
const acquired = result === 'OK';
console.log(`锁获取${acquired ? '成功' : '失败'}: ${lockKey}`);
return acquired;
}
// 释放锁
async releaseLock(lockKey, lockValue) {
const luaScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await this.redis.eval(luaScript, {
keys: [lockKey],
arguments: [lockValue]
});
const released = result === 1;
console.log(`锁释放${released ? '成功' : '失败'}: ${lockKey}`);
return released;
}
// 使用锁执行操作
async withLock(lockKey, operation, expireSeconds = 30) {
const lockValue = `${Date.now()}-${Math.random()}`;
const acquired = await this.acquireLock(lockKey, lockValue, expireSeconds);
if (!acquired) {
throw new Error('无法获取锁');
}
try {
console.log(`开始执行需要锁保护的操作: ${lockKey}`);
const result = await operation();
console.log(`操作执行完成: ${lockKey}`);
return result;
} finally {
await this.releaseLock(lockKey, lockValue);
}
}
// 尝试获取锁(非阻塞)
async tryLock(lockKey, operation, expireSeconds = 30) {
const lockValue = `${Date.now()}-${Math.random()}`;
const acquired = await this.acquireLock(lockKey, lockValue, expireSeconds);
if (!acquired) {
console.log(`锁被占用,跳过操作: ${lockKey}`);
return null;
}
try {
return await operation();
} finally {
await this.releaseLock(lockKey, lockValue);
}
}
}
// 使用示例
const lock = new DistributedLock(client);
// 方式1:使用 withLock(阻塞)
await lock.withLock('resource_lock', async () => {
console.log('执行需要锁保护的操作');
// 模拟耗时操作
await new Promise(resolve => setTimeout(resolve, 1000));
});
// 方式2:使用 tryLock(非阻塞)
await lock.tryLock('resource_lock', async () => {
console.log('尝试执行操作');
await new Promise(resolve => setTimeout(resolve, 1000));
});
6. 实时计数器 (RealTimeCounter
)
javascript
class RealTimeCounter {
constructor(redisClient) {
this.redis = redisClient;
}
// 增加计数
async increment(key, amount = 1) {
const count = await this.redis.incrBy(key, amount);
console.log(`计数器 ${key} 增加 ${amount},当前值: ${count}`);
return count;
}
// 获取计数
async getCount(key) {
const count = await this.redis.get(key);
const result = count ? parseInt(count) : 0;
console.log(`计数器 ${key} 当前值: ${result}`);
return result;
}
// 重置计数
async resetCount(key) {
await this.redis.del(key);
console.log(`计数器 ${key} 已重置`);
}
// 网站访问统计示例
async recordPageView(page) {
const today = new Date().toISOString().split('T')[0];
const dailyKey = `page_views:${page}:${today}`;
const totalKey = `page_views:${page}:total`;
await Promise.all([
this.increment(dailyKey),
this.increment(totalKey)
]);
// 设置日统计过期时间(30天)
await this.redis.expire(dailyKey, 30 * 24 * 3600);
console.log(`页面 ${page} 访问已记录`);
}
// 获取页面访问统计
async getPageViewStats(page) {
const today = new Date().toISOString().split('T')[0];
const dailyKey = `page_views:${page}:${today}`;
const totalKey = `page_views:${page}:total`;
const [dailyViews, totalViews] = await Promise.all([
this.getCount(dailyKey),
this.getCount(totalKey)
]);
return {
page,
dailyViews,
totalViews,
date: today
};
}
// 获取热门页面
async getTopPages(limit = 10) {
const pattern = 'page_views:*:total';
const keys = await this.redis.keys(pattern);
const pages = [];
for (const key of keys) {
const page = key.split(':')[1];
const views = await this.getCount(key);
pages.push({ page, views });
}
return pages
.sort((a, b) => b.views - a.views)
.slice(0, limit);
}
}
// 使用示例
const counter = new RealTimeCounter(client);
await counter.recordPageView('/home');
await counter.recordPageView('/about');
await counter.recordPageView('/home');
const homeStats = await counter.getPageViewStats('/home');
console.log('首页统计:', homeStats);
const topPages = await counter.getTopPages(5);
console.log('热门页面:', topPages);
Express + Redis 完整实战
中间件开发
1. 缓存中间件
javascript
// 缓存中间件
function cacheMiddleware(expireSeconds = 300) {
return async (req, res, next) => {
const key = `cache:${req.originalUrl}`;
try {
const cachedData = await redis.get(key);
if (cachedData) {
console.log('缓存命中:', key);
return res.json(JSON.parse(cachedData));
}
// 缓存未命中,继续处理请求
res.originalJson = res.json;
res.json = (data) => {
// 缓存响应数据
redis.setEx(key, expireSeconds, JSON.stringify(data));
res.originalJson(data);
};
next();
} catch (error) {
console.error('缓存错误:', error);
next();
}
};
}
2. 限流中间件
javascript
// 限流中间件
function rateLimitMiddleware(maxRequests = 100, windowSeconds = 3600) {
return async (req, res, next) => {
const clientIp = req.ip || req.connection.remoteAddress;
const key = `rate_limit:${clientIp}`;
try {
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, windowSeconds);
}
if (current > maxRequests) {
return res.status(429).json({
error: '请求过于频繁',
message: `每${windowSeconds}秒最多${maxRequests}次请求`,
retryAfter: windowSeconds
});
}
// 设置响应头
res.set('X-RateLimit-Limit', maxRequests);
res.set('X-RateLimit-Remaining', Math.max(0, maxRequests - current));
res.set('X-RateLimit-Reset', new Date(Date.now() + windowSeconds * 1000).toISOString());
next();
} catch (error) {
console.error('限流错误:', error);
next();
}
};
}
3. 会话中间件
javascript
// 会话中间件
async function sessionMiddleware(req, res, next) {
const sessionId = req.headers['x-session-id'];
if (sessionId) {
try {
const sessionData = await redis.get(`session:${sessionId}`);
if (sessionData) {
req.session = JSON.parse(sessionData);
// 延长会话有效期
await redis.expire(`session:${sessionId}`, 3600);
console.log('会话已加载:', sessionId);
}
} catch (error) {
console.error('会话错误:', error);
}
}
next();
}
API 路由实现
1. 用户认证
javascript
// 用户登录
app.post('/api/login', async (req, res) => {
const { username, password } = req.body;
// 简化的用户验证
if (username === 'admin' && password === 'password') {
const sessionId = 'sess_' + Date.now() + '_' + Math.random();
const sessionData = {
userId: 1,
username: 'admin',
loginTime: new Date().toISOString()
};
await redis.setEx(`session:${sessionId}`, 3600, JSON.stringify(sessionData));
res.json({
success: true,
sessionId: sessionId,
message: '登录成功'
});
} else {
res.status(401).json({
success: false,
message: '用户名或密码错误'
});
}
});
// 用户信息(需要会话)
app.get('/api/user', async (req, res) => {
if (!req.session) {
return res.status(401).json({
success: false,
message: '未登录'
});
}
res.json({
success: true,
user: req.session
});
});
2. 数据缓存
javascript
// 获取用户列表(带缓存)
app.get('/api/users', cacheMiddleware(600), async (req, res) => {
// 模拟数据库查询
console.log('从数据库获取用户列表...');
await new Promise(resolve => setTimeout(resolve, 1000)); // 模拟延迟
const users = [
{ id: 1, name: '张三', email: 'zhang@example.com' },
{ id: 2, name: '李四', email: 'li@example.com' },
{ id: 3, name: '王五', email: 'wang@example.com' }
];
res.json({
success: true,
data: users
});
});
3. 访问统计
javascript
// 获取访问统计
app.get('/api/stats/page-views', async (req, res) => {
const { page = '/home' } = req.query;
const today = new Date().toISOString().split('T')[0];
const [dailyViews, totalViews] = await Promise.all([
redis.get(`page_views:${page}:${today}`),
redis.get(`page_views:${page}:total`)
]);
res.json({
success: true,
data: {
page: page,
dailyViews: parseInt(dailyViews) || 0,
totalViews: parseInt(totalViews) || 0,
date: today
}
});
});
// 记录页面访问
app.post('/api/stats/page-view', async (req, res) => {
const { page } = req.body;
if (!page) {
return res.status(400).json({
success: false,
message: '页面参数必填'
});
}
const today = new Date().toISOString().split('T')[0];
const dailyKey = `page_views:${page}:${today}`;
const totalKey = `page_views:${page}:total`;
await Promise.all([
redis.incr(dailyKey),
redis.incr(totalKey)
]);
// 设置日统计过期时间
await redis.expire(dailyKey, 30 * 24 * 3600);
res.json({
success: true,
message: '访问记录已更新'
});
});
4. 消息队列
javascript
// 发送消息到队列
app.post('/api/queue/send', rateLimitMiddleware(10, 60), async (req, res) => {
const { message, priority = 0 } = req.body;
if (!message) {
return res.status(400).json({
success: false,
message: '消息内容不能为空'
});
}
const messageData = {
id: Date.now().toString(),
message: message,
priority: priority,
timestamp: new Date().toISOString()
};
// 根据优先级选择队列
const queueName = priority > 0 ? 'high_priority_queue' : 'normal_queue';
await redis.lPush(queueName, JSON.stringify(messageData));
res.json({
success: true,
message: '消息已添加到队列',
queueName: queueName,
messageId: messageData.id
});
});
// 获取队列状态
app.get('/api/queue/status', async (req, res) => {
const [normalQueueLength, highPriorityQueueLength] = await Promise.all([
redis.lLen('normal_queue'),
redis.lLen('high_priority_queue')
]);
res.json({
success: true,
data: {
normalQueue: normalQueueLength,
highPriorityQueue: highPriorityQueueLength,
total: normalQueueLength + highPriorityQueueLength
}
});
});
5. 缓存管理
javascript
// 清空缓存
app.delete('/api/cache', async (req, res) => {
const { pattern = 'cache:*' } = req.query;
try {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(keys);
}
res.json({
success: true,
message: `清除了 ${keys.length} 个缓存项`,
clearedKeys: keys
});
} catch (error) {
res.status(500).json({
success: false,
message: '清除缓存失败',
error: error.message
});
}
});
// 获取缓存信息
app.get('/api/cache/info', async (req, res) => {
try {
const keys = await redis.keys('cache:*');
const cacheInfo = [];
for (const key of keys) {
const ttl = await redis.ttl(key);
cacheInfo.push({
key,
ttl: ttl > 0 ? ttl : '永不过期'
});
}
res.json({
success: true,
data: {
totalKeys: keys.length,
caches: cacheInfo
}
});
} catch (error) {
res.status(500).json({
success: false,
message: '获取缓存信息失败',
error: error.message
});
}
});
6. 健康检查
javascript
// 健康检查
app.get('/api/health', async (req, res) => {
try {
const startTime = Date.now();
await redis.ping();
const responseTime = Date.now() - startTime;
res.json({
success: true,
message: 'Service is healthy',
redis: 'connected',
responseTime: `${responseTime}ms`,
timestamp: new Date().toISOString()
});
} catch (error) {
res.status(500).json({
success: false,
message: 'Service is unhealthy',
redis: 'disconnected',
error: error.message,
timestamp: new Date().toISOString()
});
}
});
完整应用启动
javascript
// expressRedis.js 完整示例
import express from 'express';
import { createClient } from 'redis';
const app = express();
const port = 3000;
// Redis 客户端
const redis = createClient();
await redis.connect();
app.use(express.json());
// 应用中间件
app.use(sessionMiddleware);
// 路由定义
// ... (所有上述路由)
// 错误处理
app.use((error, req, res, next) => {
console.error('应用错误:', error);
res.status(500).json({
success: false,
message: '服务器内部错误'
});
});
// 启动服务器
app.listen(port, () => {
console.log(`服务器运行在 http://localhost:${port}`);
});
最佳实践与优化
1. 连接管理
javascript
// 连接池配置
const client = createClient({
socket: {
host: 'localhost',
port: 6379,
connectTimeout: 10000,
lazyConnect: true
},
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3
});
// 优雅关闭
process.on('SIGINT', async () => {
console.log('正在关闭 Redis 连接...');
await client.disconnect();
process.exit(0);
});
2. 键命名规范
# 用户相关
user:123 # 用户基本信息
user:123:profile # 用户详细资料
user:123:sessions # 用户会话列表
# 缓存相关
cache:api:users # API 缓存
cache:page:home # 页面缓存
# 会话相关
session:abc123 # 会话数据
session:abc123:permissions # 会话权限
# 限流相关
rate_limit:ip:192.168.1.1 # IP 限流
rate_limit:user:123:api # 用户 API 限流
# 统计相关
stats:page_views:home:2024-01-01 # 日统计
stats:page_views:home:total # 总统计
3. 过期时间设置
javascript
// 缓存过期时间
const CACHE_EXPIRES = {
USER_INFO: 1800, // 30分钟
API_RESPONSE: 300, // 5分钟
SESSION: 7200, // 2小时
TEMP_DATA: 86400, // 1天
STATS_DAILY: 2592000 // 30天
};
// 设置过期时间
await redis.setEx(key, CACHE_EXPIRES.USER_INFO, value);
4. 错误处理与降级
javascript
class RedisService {
constructor(redisClient) {
this.redis = redisClient;
}
async safeGet(key, fallback = null) {
try {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : fallback;
} catch (error) {
console.error('Redis GET 错误:', error);
return fallback;
}
}
async safeSet(key, value, expireSeconds = 3600) {
try {
await this.redis.setEx(key, expireSeconds, JSON.stringify(value));
return true;
} catch (error) {
console.error('Redis SET 错误:', error);
return false;
}
}
}
5. 性能优化
javascript
// 使用管道批量操作
const pipeline = redis.multi();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.set('key3', 'value3');
const results = await pipeline.exec();
// 使用事务
const multi = redis.multi();
multi.incr('counter');
multi.expire('counter', 3600);
const results = await multi.exec();
❓ 常见问题与解决方案
Q1: 如何选择合适的数据类型?
- 字符串:简单键值对、计数器、缓存
- 哈希:对象属性存储、用户信息
- 列表:队列、时间线、栈
- 集合:标签、去重、好友关系
- 有序集合:排行榜、范围查询、优先级队列
Q2: 如何处理 Redis 连接失败?
javascript
// 重连机制
client.on('error', async (err) => {
console.error('Redis 连接错误:', err);
// 实现重连逻辑
setTimeout(async () => {
try {
await client.connect();
console.log('Redis 重连成功');
} catch (error) {
console.error('Redis 重连失败:', error);
}
}, 5000);
});
Q3: 如何优化 Redis 性能?
- 使用管道(Pipeline)批量操作
- 合理设置过期时间
- 避免大键操作
- 使用合适的数据类型
- 监控内存使用情况
Q4: 如何处理内存不足?
javascript
// 监控内存使用
const info = await redis.memory('usage');
console.log('Redis 内存使用:', info);
// 设置最大内存
await redis.config('set', 'maxmemory', '100mb');
await redis.config('set', 'maxmemory-policy', 'allkeys-lru');
Q5: 如何实现分布式锁?
javascript
// 使用 Lua 脚本确保原子性
const lockScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`;
const result = await redis.eval(lockScript, {
keys: [lockKey],
arguments: [lockValue]
});