Redis 介绍与 Node.js 使用教程

Redis 介绍与 Node.js 使用教程

📚 目录

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

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安装

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

  2. 安装redis-server

  3. 运行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]
});

🔗 相关链接