Redis 教程

项目概述

Redis(Remote Dictionary Server)是一个开源的内存数据结构存储,用作数据库、缓存和消息代理。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,提供了丰富的操作命令,并且具有高性能、可持久化、分布式等特性。

安装设置

1. 本地安装

Windows 安装

  1. 访问 Redis 官方 GitHub 发布页 下载 Windows 版本
  2. 解压到本地目录
  3. 运行 redis-server.exe 启动服务器
  4. 运行 redis-cli.exe 启动客户端

Linux 安装

# Ubuntu/Debian
sudo apt update
sudo apt install redis-server

# 启动 Redis 服务
sudo systemctl start redis-server

# 检查 Redis 服务状态
sudo systemctl status redis-server

macOS 安装

# 使用 Homebrew
brew install redis

# 启动 Redis 服务
brew services start redis

2. 使用 Docker

# 拉取 Redis 镜像
docker pull redis

# 运行 Redis 容器
docker run --name redis-container -p 6379:6379 -d redis

# 进入 Redis 容器并使用客户端
docker exec -it redis-container redis-cli

3. 客户端连接

// 使用 Node.js 客户端
const redis = require('redis');

// 创建客户端
const client = redis.createClient({
  url: 'redis://localhost:6379'
});

// 连接到 Redis
client.connect().then(() => {
  console.log('Redis 连接成功');
}).catch(err => {
  console.error('Redis 连接失败:', err);
});

// 关闭连接
client.quit();

核心数据结构

1. 字符串 (String)

字符串是 Redis 最基本的数据类型,最大可以存储 512MB 的数据。

# 设置字符串
SET key value

# 获取字符串
GET key

# 设置带过期时间的字符串
SETEX key seconds value

# 自增
INCR key

# 自减
DECR key

# 追加字符串
APPEND key value
// Node.js 示例
await client.set('name', 'Redis');
const name = await client.get('name');
console.log(name); // 输出: Redis

await client.setEx('temp', 60, '临时值'); // 60秒过期
await client.incr('counter'); // 自增

2. 哈希 (Hash)

哈希是一个键值对的集合,适合存储对象。

# 设置哈希字段
HSET key field value

# 获取哈希字段
HGET key field

# 获取所有字段和值
HGETALL key

# 获取所有字段
HKEYS key

# 获取所有值
HVALS key

# 删除字段
HDEL key field
// Node.js 示例
await client.hSet('user:1', {
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
});
const user = await client.hGetAll('user:1');
console.log(user); // 输出: { name: '张三', age: '25', email: 'zhangsan@example.com' }

3. 列表 (List)

列表是一个有序的字符串集合,支持在两端进行操作。

# 从左侧插入
LPUSH key value1 value2

# 从右侧插入
RPUSH key value1 value2

# 从左侧弹出
LPOP key

# 从右侧弹出
RPOP key

# 获取列表范围
LRANGE key start stop

# 获取列表长度
LLEN key
// Node.js 示例
await client.lPush('tasks', '任务1', '任务2');
await client.rPush('tasks', '任务3');
const tasks = await client.lRange('tasks', 0, -1);
console.log(tasks); // 输出: ['任务2', '任务1', '任务3']

4. 集合 (Set)

集合是一个无序的字符串集合,不允许重复元素。

# 添加元素
SADD key member1 member2

# 移除元素
SREM key member1 member2

# 检查元素是否存在
SISMEMBER key member

# 获取所有元素
SMEMBERS key

# 获取集合大小
SCARD key

# 集合交集
SINTER key1 key2

# 集合并集
SUNION key1 key2

# 集合差集
SDIFF key1 key2
// Node.js 示例
await client.sAdd('tags', 'javascript', 'nodejs', 'redis');
const tags = await client.sMembers('tags');
console.log(tags); // 输出: ['javascript', 'nodejs', 'redis']
const exists = await client.sIsMember('tags', 'javascript');
console.log(exists); // 输出: true

5. 有序集合 (Sorted Set)

有序集合是一个有序的字符串集合,每个元素都有一个分数,根据分数排序。

# 添加元素
ZADD key score1 member1 score2 member2

# 获取元素分数
ZSCORE key member

# 获取排名范围内的元素(按分数从小到大)
ZRANGE key start stop

# 获取排名范围内的元素(按分数从大到小)
ZREVRANGE key start stop

# 获取分数范围内的元素
ZRANGEBYSCORE key min max

# 增加元素分数
ZINCRBY key increment member

# 获取集合大小
ZCARD key
// Node.js 示例
await client.zAdd('leaderboard', {
  score: 100, member: '用户1'
}, {
  score: 90, member: '用户2'
}, {
  score: 80, member: '用户3'
});
const leaderboard = await client.zRevRange('leaderboard', 0, -1, { WITH_SCORES: true });
console.log(leaderboard); // 输出: ['用户1', '100', '用户2', '90', '用户3', '80']

高级功能

1. 发布/订阅

Redis 支持发布/订阅模式,用于消息传递。

# 发布消息
PUBLISH channel message

# 订阅频道
SUBSCRIBE channel

# 订阅模式
PSUBSCRIBE pattern
// 发布者
const publisher = redis.createClient();
await publisher.connect();
await publisher.publish('news', 'Redis 7.0 发布了!');

// 订阅者
const subscriber = redis.createClient();
await subscriber.connect();
await subscriber.subscribe('news', (message) => {
  console.log('收到消息:', message);
});

2. 事务

Redis 支持简单的事务,通过 MULTIEXECDISCARD 命令实现。

# 开始事务
MULTI

# 执行命令(入队)
SET key1 value1
SET key2 value2

# 执行事务
EXEC

# 取消事务
DISCARD
// Node.js 示例
const transaction = client.multi();
transaction.set('key1', 'value1');
transaction.set('key2', 'value2');
const result = await transaction.exec();
console.log(result); // 输出: [OK, OK]

3. 持久化

Redis 提供两种持久化方式:RDB 和 AOF。

RDB(Redis Database)

  • 定期将内存中的数据快照保存到磁盘
  • 适合备份和灾难恢复
  • 配置示例:
# 900秒内有1个键被修改时保存
save 900 1
# 300秒内有10个键被修改时保存
save 300 10
# 60秒内有10000个键被修改时保存
save 60 10000

AOF(Append Only File)

  • 记录每个写操作到日志文件
  • 更可靠,但文件会逐渐增大
  • 配置示例:
# 启用 AOF
appendonly yes
# 每秒同步一次
appendfsync everysec

4. 过期时间

Redis 支持为键设置过期时间,过期后自动删除。

# 设置过期时间(秒)
EXPIRE key seconds

# 设置过期时间(毫秒)
PEXPIRE key milliseconds

# 查看剩余过期时间(秒)
TTL key

# 查看剩余过期时间(毫秒)
PTTL key

# 移除过期时间
PERSIST key
// Node.js 示例
await client.set('temp', '临时值');
await client.expire('temp', 60); // 60秒过期
const ttl = await client.ttl('temp');
console.log(ttl); // 输出: 剩余过期时间(秒)

实际应用场景

1. 缓存系统

// 缓存示例
const getCachedData = async (key) => {
  // 尝试从缓存获取
  const cachedData = await client.get(key);
  if (cachedData) {
    console.log('从缓存获取数据');
    return JSON.parse(cachedData);
  }
  
  // 缓存未命中,从数据库获取
  console.log('从数据库获取数据');
  const data = await fetchDataFromDatabase();
  
  // 存入缓存,设置过期时间
  await client.setEx(key, 3600, JSON.stringify(data));
  return data;
};

// 使用缓存
const userData = await getCachedData('user:123');

2. 会话存储

// 会话存储示例
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

const app = express();

app.use(session({
  store: new RedisStore({ client }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 24 * 60 * 60 * 1000 // 24小时
  }
}));

app.get('/', (req, res) => {
  if (req.session.views) {
    req.session.views++;
  } else {
    req.session.views = 1;
  }
  res.send(`浏览次数: ${req.session.views}`);
});

3. 消息队列

// 简单消息队列
const queueName = 'tasks';

// 生产者
const produceMessage = async (message) => {
  await client.lPush(queueName, JSON.stringify(message));
  console.log('消息已入队:', message);
};

// 消费者
const consumeMessages = async () => {
  while (true) {
    // 从队列右侧弹出消息,阻塞式
    const message = await client.brPop(queueName, 0);
    if (message) {
      const task = JSON.parse(message.element);
      console.log('处理消息:', task);
      // 处理任务...
    }
  }
};

// 启动消费者
consumeMessages();

// 生产消息
produceMessage({ id: 1, name: '任务1' });
produceMessage({ id: 2, name: '任务2' });

4. 计数器

// 页面访问计数器
const incrementPageView = async (pageId) => {
  const key = `page:${pageId}:views`;
  const count = await client.incr(key);
  return count;
};

// 获取页面访问量
const getPageViews = async (pageId) => {
  const key = `page:${pageId}:views`;
  const count = await client.get(key);
  return parseInt(count) || 0;
};

// 使用
const views = await incrementPageView('home');
console.log('首页访问量:', views);

5. 分布式锁

// 简单分布式锁
const acquireLock = async (lockName, timeout = 10000) => {
  const lockKey = `lock:${lockName}`;
  const lockValue = Date.now() + timeout;
  
  // 尝试获取锁
  const acquired = await client.set(lockKey, lockValue, {
    NX: true, // 仅当键不存在时设置
    EX: Math.ceil(timeout / 1000) // 过期时间(秒)
  });
  
  return acquired ? lockValue : false;
};

const releaseLock = async (lockName, lockValue) => {
  const lockKey = `lock:${lockName}`;
  const currentValue = await client.get(lockKey);
  
  // 只有持有锁的线程才能释放
  if (currentValue === lockValue.toString()) {
    await client.del(lockKey);
    return true;
  }
  return false;
};

// 使用锁
const lockValue = await acquireLock('resource');
if (lockValue) {
  try {
    // 执行临界区代码
    console.log('获取锁成功,执行操作');
  } finally {
    // 释放锁
    await releaseLock('resource', lockValue);
    console.log('锁已释放');
  }
} else {
  console.log('获取锁失败,资源忙');
}

代码优化建议

  1. 连接管理:使用连接池管理 Redis 连接,避免频繁创建和销毁连接
  2. 键命名规范:使用统一的键命名规范,如 {prefix}:{id}:{field}
  3. 过期时间:为缓存数据设置合理的过期时间,避免内存溢出
  4. 批量操作:使用批量操作减少网络往返时间,如 MSETHMSET
  5. 管道操作:使用管道(Pipeline)执行多个命令,提高性能
  6. 内存优化
    • 使用适当的数据结构
    • 避免存储过大的值
    • 定期清理无用数据
  7. 错误处理:实现 Redis 连接失败的重试机制和错误处理
  8. 监控和告警:监控 Redis 内存使用、命中率等指标,设置合理的告警阈值

参考资源

« 上一篇 Mongoose 教程 - MongoDB 的对象建模工具 下一篇 » Sequelize 教程 - 基于 Promise 的 Node.js ORM