Redis 介绍与 Node.js 使用教程

目录

  1. Redis 简介
  2. 环境准备与快速开始
  3. 基础连接与配置
  4. 数据类型详解与示例
  5. 实际应用场景详解
  6. Express + Redis 完整实战
  7. 最佳实践与优化
  8. 常见问题与解决方案

Redis 简介

什么是 Redis?

Redis(Remote Dictionary Server)是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息代理。

主要特点

  • 内存存储:数据存储在内存中,读写速度极快
  • 数据持久化:支持 RDB 和 AOF 两种持久化方式
  • 丰富的数据类型:字符串、哈希、列表、集合、有序集合等
  • 高可用性:支持主从复制、哨兵模式、集群模式
  • 原子操作:所有操作都是原子性的

Redis 数据类型概览

1. 字符串 (String)

SET name "张三"
GET name
INCR counter
EXPIRE session:123 3600

2. 哈希 (Hash)

HSET user:1 name "李四" age 25 city "北京"
HGET user:1 name
HGETALL user:1

3. 列表 (List)

LPUSH tasks "任务1" "任务2"
RPOP tasks
LRANGE tasks 0 -1

4. 集合 (Set)

SADD fruits "苹果" "香蕉" "橘子"
SMEMBERS fruits
SISMEMBER fruits "苹果"

5. 有序集合 (Sorted Set)

ZADD scores 85 "张三" 92 "李四" 78 "王五"
ZRANGE scores 0 -1 WITHSCORES
ZRANK scores "李四"

环境准备与快速开始

环境要求

  • Node.js 16+
  • Redis 服务器
  • 基础 JavaScript 知识

服务器安装

这里采用docker安装

  1. 先下载dockerDesktop https://www.docker.com/products.../docker-desktop/

  1. 安装redis-server



3. 运行redis

redis 已经运行了,docker 安装的redis 没有密码,这里方便学习,就用docker安装,适合window

安装与启动

# 1. 安装依赖
npm install redis express

基础连接与配置

基础连接示例 (基于 index.js)

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();

连接配置选项

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) - 缓存与计数器

用途:缓存、计数器、会话存储、简单键值对

// 基本操作
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) - 对象存储

用途:对象属性存储、用户信息、配置管理

// 设置哈希字段
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) - 队列与时间线

用途:消息队列、任务队列、时间线、栈

// 从左侧添加元素
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) - 标签与去重

用途:标签系统、去重、集合运算、好友关系

// 添加元素到集合
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) - 排行榜

用途:排行榜、范围查询、优先级队列

// 添加有分数的成员
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)

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)

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)

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)

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)

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)

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. 缓存中间件

// 缓存中间件
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. 限流中间件

// 限流中间件
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. 会话中间件

// 会话中间件
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. 用户认证

// 用户登录
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. 数据缓存

// 获取用户列表(带缓存)
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. 访问统计

// 获取访问统计
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. 消息队列

// 发送消息到队列
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. 缓存管理

// 清空缓存
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. 健康检查

// 健康检查
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()
});
}
});

完整应用启动

// 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. 连接管理

// 连接池配置
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. 过期时间设置

// 缓存过期时间
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. 错误处理与降级

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. 性能优化

// 使用管道批量操作
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 连接失败?

// 重连机制
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: 如何处理内存不足?

// 监控内存使用
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: 如何实现分布式锁?

// 使用 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]
});

相关链接

Redis 介绍与 Node.js 使用教程的更多相关文章

  1. 【入门必备】最佳的 Node.js 学习教程和资料书籍

    Web 开发人员对 Node.js 的关注日益增多,更多的公司和开发者开始尝试使用 Node.js 来实现一些对实时性要求高,I/O密集型的业务.这篇文章中,我们整理了一批优秀的资源,你可以得到所有你 ...

  2. Node.Js安装教程

    Node.Js安装教程 介绍下我的环境 环境 值 操作系统 win10 64bit Node.Js 8.9.4 emmmm 表格中毒了,为什么出不来效果 一.下载及安装 这个可以去Node.Js官网上 ...

  3. 24个很赞的 Node.js 免费教程和在线指南

    JavaScript 最初是用来创建动态网站效果的的前端语言.而如今,这门脚本语言也可以用作后端开发,用于搭建 Web 服务器,开发接口,甚至创建博客.在下面这个列表中包括24个 Node.js 教程 ...

  4. 【特别推荐】Node.js 入门教程和学习资源汇总

    这篇文章与大家分享一批很有用的 Node.js 入门教程和学习资源.Node 是一个服务器端的 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用 ...

  5. Node.js 入门教程和学习资源汇总

    这篇文章与大家分享一批很有用的 Node.js 入门教程和学习资源.Node 是一个服务器端的 JavaScript 解释器,它将改变服务器应该如何工作的概念.它的目标是帮助程序员构建高度可伸缩的应用 ...

  6. Vue框架下的node.js安装教程

    Vue框架下的node.js安装教程 python服务器.php  ->aphche.java ->tomcat.   iis -->它是一个可以运行JAVASCRIPTR 的运行环 ...

  7. Node.js模块化教程

    Node.js模块化教程 下载安装node.js 创建项目结构 |-modules |-module1.js |-module2.js |-module3.js|-app.js|-package.js ...

  8. 《Node.js核心技术教程》学习笔记

    <Node.js核心技术教程>TOC \o "1-3" \h \z \u 1.章模块化编程 2019.2.19 13:30' PAGEREF _101 \h 1 08D ...

  9. Node.js入门教程:Node.js如何安装配置并部署第一个网站

    前言:作为一个资深的前端开发人员,不懂的Node.js 那你绝对是不能跟别人说你是资深的前端程序猿滴! 今天洋哥就来和大家一起学习被大牛称之为前端必学的技能之一Node! 那么Node到底是什么呢? ...

  10. node.js 入门教程(beginnder guide

    非常好的教程: node入门: JavaScript与Node.js JavaScript与你 简短申明 服务器端JavaScript “Hello World” 一个完整的基于Node.js的web ...

随机推荐

  1. HyperWorks基于 Shrink Warp Mesh 的零部件网格剖分

    Step01:读入模型 Exercise_4b.hm. Step02:在名为 loose_gap 的 component 中建立 Loose Shrink Warp Mesh. (1) 点击 Shad ...

  2. Docker安装与基础使用

    一.Docker介绍 Docker介绍 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.D ...

  3. 一文搞懂javascript中的var、let、const

    简介 var, let and const是JavaScript中三种定义变量的方式,它们之间有什么区别呢?这是前端面试中常见的一道题,今天我们来一文说透它. let和const区别不大,主要是con ...

  4. .net core webapi Post接收不到ajax请求data数据

    https://blog.csdn.net/weixin_44352179/article/details/106633989 在做.NET CORE WEBAPI接口案例的时候碰到了前端页面ajax ...

  5. 2023移动光猫H2-2超级密码获取教程

    记录信息 普通账户登录光猫后台,记录下宽带的账密.loid. 如果后台查询不到以上信息,则可以按照如下办法获得 宽带的账密不知道,也可以登录移动APP去查询和重置. loid不知道,则联系10086安 ...

  6. 斐讯n1进入u盘启动

    前言 我将n1刷完电视系统后,看了几天电视,发现还行吧. 过了几天,突然想玩游戏,发现插入u盘重启,依然进入电视,并不进入u盘的游戏机系统. 提供以下脚本,局域网下其他远程设备执行即可. window ...

  7. java 中的访问限制

    简介 1)仅对本类可见--private 2) 对所有类可见--public 3) 对本包和所有子类可见--protected 4) 对本包可见--默认,不需要修饰符

  8. 杂谈 1:论 [l, r] 之间的非平方数

    杂谈 1:论 \([l, r]\) 之间的非平方数 Part 0 例题 先看一道例题: 给定两个数 \(l, r\),求 \(l \sim r\) 之间有多少个数不是平方数.平方数的定义:是一个数的平 ...

  9. 全球人口数量、共享单车GPS轨迹、地铁上下客流数据获取平台分享

      本文对目前主要的人口数量与密度.共享单车GPS轨迹.地铁人流与轨迹等数据产品的获取网站加以整理与介绍. 目录 7 人口.共享单车与地铁数据 7.1 人口数据 7.1.1 WorldPop 7.1. ...

  10. SciTech-Physics-Particle-Atom:{Electron + Kernel{Proton+Neutron}} + Bands(Energy) + Movement{自由电子+空穴} + 导电 + Energy Transition: 电致发光

    SciTech-Physics-Particle Atom 组成+结构: Nucleus: Proton Neutron Bands(Energy) Electron: Movement with P ...