Redis - 高性能内存数据库

1. 什么是Redis?

Redis(Remote Dictionary Server)是一个开源的、高性能的键值对存储数据库,它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。Redis的主要特点是将数据存储在内存中,因此具有极高的读写速度,同时也支持数据持久化到磁盘,确保数据安全。

1.1 核心特性

  • 高性能:内存存储,读写速度极快
  • 支持多种数据结构:字符串、哈希、列表、集合、有序集合、位图、超日志等
  • 数据持久化:支持RDB和AOF两种持久化方式
  • 主从复制:支持数据复制,实现高可用性
  • 集群:支持分布式集群,实现水平扩展
  • 发布/订阅:支持消息传递模式
  • 事务:支持原子性操作
  • Lua脚本:支持自定义脚本执行

2. 安装与配置

2.1 安装Redis

Linux系统

# Ubuntu/Debian
apt update
apt install redis-server

# CentOS/RHEL
yum install epel-release
yum install redis

# 启动Redis服务
systemctl start redis
systemctl enable redis

Windows系统

  1. GitHub 下载Redis Windows版本
  2. 解压到指定目录
  3. 运行 redis-server.exe 启动服务
  4. 运行 redis-cli.exe 启动客户端

macOS系统

# 使用Homebrew
brew install redis

# 启动Redis服务
brew services start redis

2.2 配置Redis

Redis的配置文件通常位于 /etc/redis/redis.conf(Linux)或Redis安装目录下(Windows)。

# 基本配置
port 6379                # 端口号
bind 127.0.0.1          # 绑定地址,0.0.0.0表示所有地址
requirepass yourpassword # 密码认证

# 持久化配置
save 900 1               # 900秒内有1个修改就持久化
save 300 10              # 300秒内有10个修改就持久化
save 60 10000            # 60秒内有10000个修改就持久化
appendonly yes           # 开启AOF持久化

# 内存配置
maxmemory 2gb            # 最大内存限制
maxmemory-policy allkeys-lru # 内存满时的淘汰策略

2.3 连接Redis

# 本地连接
redis-cli

# 远程连接
redis-cli -h host -p port -a password

# 测试连接
redis-cli ping
# 响应: PONG

3. 基本数据结构

3.1 字符串(String)

字符串是Redis最基本的数据类型,可存储任何类型的字符串,包括二进制数据。

# 设置值
SET key value

# 获取值
GET key

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

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

# 自增
INCR key

# 自增指定值
INCRBY key increment

# 自减
DECR key

# 自减指定值
DECRBY key decrement

# 追加字符串
APPEND key value

# 获取字符串长度
STRLEN key

3.2 哈希(Hash)

哈希适用于存储对象,如用户信息、商品信息等。

# 设置哈希字段
HSET key field value

# 获取哈希字段
HGET key field

# 设置多个哈希字段
HMSET key field1 value1 field2 value2

# 获取多个哈希字段
HMGET key field1 field2

# 获取所有哈希字段和值
HGETALL key

# 获取所有哈希字段
HKEYS key

# 获取所有哈希值
HVALS key

# 获取哈希字段数量
HLEN key

# 检查字段是否存在
HEXISTS key field

# 删除哈希字段
HDEL key field

# 哈希字段自增
HINCRBY key field increment

3.3 列表(List)

列表是有序的字符串集合,可在两端添加元素。

# 从左侧添加元素
LPUSH key value1 value2

# 从右侧添加元素
RPUSH key value1 value2

# 从左侧弹出元素
LPOP key

# 从右侧弹出元素
RPOP key

# 获取列表长度
LLEN key

# 获取列表元素(0表示第一个元素,-1表示最后一个)
LRANGE key start stop

# 获取指定索引的元素
LINDEX key index

# 设置指定索引的元素
LSET key index value

# 在指定元素前插入元素
LINSERT key BEFORE pivot value

# 在指定元素后插入元素
LINSERT key AFTER pivot value

# 删除指定数量的元素
LREM key count value

3.4 集合(Set)

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

# 添加元素
SADD key member1 member2

# 获取所有元素
SMEMBERS key

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

# 删除元素
SREM key member1 member2

# 获取集合大小
SCARD key

# 随机弹出元素
SPOP key

# 随机获取元素
SRANDMEMBER key [count]

# 集合交集
SINTER key1 key2

# 集合并集
SUNION key1 key2

# 集合差集
SDIFF key1 key2

3.5 有序集合(Sorted Set)

有序集合是有序的字符串集合,每个元素关联一个分数,用于排序。

# 添加元素(with score)
ZADD key score1 member1 score2 member2

# 获取元素分数
ZSCORE key member

# 获取元素排名(从小到大)
ZRANK key member

# 获取元素排名(从大到小)
ZREVRANK key member

# 获取指定范围的元素(从小到大)
ZRANGE key start stop [WITHSCORES]

# 获取指定范围的元素(从大到小)
ZREVRANGE key start stop [WITHSCORES]

# 获取指定分数范围的元素
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

# 增加元素分数
ZINCRBY key increment member

# 删除元素
ZREM key member1 member2

# 获取集合大小
ZCARD key

# 获取指定分数范围内的元素数量
ZCOUNT key min max

4. 高级特性

4.1 发布/订阅(Pub/Sub)

Redis的发布/订阅功能允许消息的发布者和订阅者之间进行消息传递。

# 订阅频道
SUBSCRIBE channel1 channel2

# 发布消息
PUBLISH channel message

# 订阅模式
PSUBSCRIBE pattern*  # 如 news* 订阅所有以news开头的频道

# 取消订阅
UNSUBSCRIBE channel1 channel2

# 取消模式订阅
PUNSUBSCRIBE pattern*  # 如 news* 取消订阅所有以news开头的频道

4.2 事务(Transaction)

Redis事务允许将多个命令打包执行,要么全部执行,要么全部不执行。

# 开始事务
MULTI

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

# 执行事务
EXEC

# 取消事务
DISCARD

4.3 Lua脚本

Redis支持使用Lua脚本执行原子操作,提高性能和灵活性。

# 执行Lua脚本
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 key value

# 缓存Lua脚本
SCRIPT LOAD "return redis.call('SET', KEYS[1], ARGV[1])"
# 响应: "script_sha1"

# 执行缓存的脚本
EVALSHA script_sha1 1 key value

# 检查脚本是否存在
SCRIPT EXISTS script_sha1

# 清除所有缓存的脚本
SCRIPT FLUSH

4.4 管道(Pipeline)

管道允许将多个命令一次性发送到Redis服务器,减少网络往返时间。

# 使用redis-cli的管道
redis-cli << EOF
SET key1 value1
SET key2 value2
GET key1
GET key2
EOF

# 使用客户端库的管道(以Node.js为例)
const redis = require('redis');
const client = redis.createClient();

const pipeline = client.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.get('key1');
pipeline.get('key2');
pipeline.exec((err, results) => {
  console.log(results);
});

5. 持久化

5.1 RDB持久化

RDB(Redis Database)是Redis默认的持久化方式,通过创建数据快照的方式将数据持久化到磁盘。

# 手动触发RDB持久化
SAVE  # 阻塞式
BGSAVE # 非阻塞式,后台执行

# 查看RDB持久化状态
INFO persistence

5.2 AOF持久化

AOF(Append Only File)持久化通过记录所有写命令来恢复数据,更安全但文件较大。

# 开启AOF持久化(在配置文件中)
appendonly yes

# AOF重写(压缩AOF文件)
BGREWRITEAOF

# 查看AOF持久化状态
INFO persistence

5.3 混合持久化

Redis 4.0+支持混合持久化,结合了RDB和AOF的优点。

# 开启混合持久化(在配置文件中)
aof-use-rdb-preamble yes

6. 主从复制

6.1 配置主从复制

主服务器配置

# 主服务器无需特殊配置,默认即可
port 6379

从服务器配置

# 从服务器配置
port 6380
slaveof master_ip master_port
# Redis 5.0+ 使用 replicaof
# replicaof master_ip master_port

# 从服务器只读
slave-read-only yes
# Redis 5.0+ 使用 replica-read-only
# replica-read-only yes

6.2 检查复制状态

# 在主服务器上查看
INFO replication

# 在从服务器上查看
INFO replication

6.3 手动切换主从

# 在从服务器上执行,使其成为主服务器
SLAVEOF NO ONE
# Redis 5.0+ 使用
# REPLICAOF NO ONE

# 将其他从服务器指向新的主服务器
SLAVEOF new_master_ip new_master_port
# Redis 5.0+ 使用
# REPLICAOF new_master_ip new_master_port

7. Redis集群

7.1 集群架构

Redis集群采用去中心化的架构,由多个主节点和从节点组成,每个主节点负责一部分哈希槽。

7.2 搭建集群

# 创建集群目录
mkdir -p redis-cluster/{7000,7001,7002,7003,7004,7005}

# 配置每个节点(以7000为例)
cat > redis-cluster/7000/redis.conf << EOF
port 7000
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
appendonly yes
EOF

# 复制配置到其他节点
cp redis-cluster/7000/redis.conf redis-cluster/7001/
cp redis-cluster/7000/redis.conf redis-cluster/7002/
cp redis-cluster/7000/redis.conf redis-cluster/7003/
cp redis-cluster/7000/redis.conf redis-cluster/7004/
cp redis-cluster/7000/redis.conf redis-cluster/7005/

# 修改每个节点的端口号
# 编辑 redis-cluster/7001/redis.conf 等文件

# 启动所有节点
for port in {7000..7005}; do
  redis-server redis-cluster/${port}/redis.conf
 done

# 创建集群
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1

# 连接集群
redis-cli -c -p 7000

# 检查集群状态
CLUSTER INFO
CLUSTER NODES

7.3 集群操作

# 添加节点
redis-cli --cluster add-node new_node_ip:new_node_port existing_node_ip:existing_node_port

# 添加从节点
redis-cli --cluster add-node --cluster-slave --cluster-master-id master_id new_node_ip:new_node_port existing_node_ip:existing_node_port

# 删除节点
redis-cli --cluster del-node existing_node_ip:existing_node_port node_id

# 重新分片
redis-cli --cluster reshard existing_node_ip:existing_node_port

8. 内存管理

8.1 内存配置

# 最大内存限制
maxmemory 2gb

# 内存淘汰策略
# allkeys-lru: 从所有键中选择最近最少使用的
# volatile-lru: 从设置了过期时间的键中选择最近最少使用的
# allkeys-random: 从所有键中随机选择
# volatile-random: 从设置了过期时间的键中随机选择
# volatile-ttl: 从设置了过期时间的键中选择剩余时间最短的
# noeviction: 不淘汰,内存满时拒绝写入
maxmemory-policy allkeys-lru

8.2 内存优化

  1. 合理设置过期时间:对于临时数据,设置适当的过期时间
  2. 使用合适的数据结构:根据业务场景选择最合适的数据结构
  3. 压缩数据:对于字符串,可以使用压缩算法
  4. 避免大键:将大键拆分为多个小键
  5. 定期清理无用数据:删除不再使用的键

8.3 监控内存使用

# 查看内存使用情况
INFO memory

# 查看大键
redis-cli --bigkeys

# 查看键的过期时间
TTL key
PTTL key

# 查看所有设置了过期时间的键
SCAN 0 MATCH * COUNT 1000
# 然后对每个键执行 TTL

9. 最佳实践

9.1 命名规范

  • 使用冒号分隔命名空间service:module:id
  • 使用小写字母:避免大小写敏感问题
  • 保持命名简洁:便于记忆和使用
  • 使用统一前缀:便于管理和监控

9.2 性能优化

  1. 使用连接池:减少连接开销
  2. 批量操作:使用MSET、MGET等批量命令
  3. 使用管道:减少网络往返时间
  4. 避免阻塞命令:如KEYS、SMEMBERS等
  5. 使用Lua脚本:减少网络往返和保证原子性
  6. 合理设置持久化策略:根据业务需求选择合适的持久化方式

9.3 高可用性

  1. 主从复制:实现数据备份和读写分离
  2. 哨兵模式:实现自动故障转移
  3. 集群模式:实现水平扩展和高可用性
  4. 定期备份:定期备份RDB和AOF文件
  5. 监控告警:监控Redis的运行状态和性能指标

9.4 安全措施

  1. 设置密码:使用强密码
  2. 绑定IP:只允许特定IP访问
  3. 使用SSL:加密传输
  4. 最小权限:使用普通用户运行Redis
  5. 定期更新:及时更新Redis版本
  6. 禁用危险命令:如FLUSHALL、FLUSHDB等

10. 实际应用场景

10.1 缓存

Redis最常见的应用场景是作为缓存,减少数据库压力。

// 以Node.js为例
const redis = require('redis');
const client = redis.createClient();

async function getData(key) {
  // 尝试从缓存获取
  const cachedData = await client.get(key);
  if (cachedData) {
    return JSON.parse(cachedData);
  }
  
  // 从数据库获取
  const data = await db.query('SELECT * FROM table WHERE id = ?', key);
  
  // 存入缓存,设置过期时间
  await client.setex(key, 3600, JSON.stringify(data));
  
  return data;
}

10.2 会话管理

Redis可用于存储用户会话数据,支持分布式部署。

// 以Express.js为例
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

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

10.3 计数器

Redis的INCR命令可用于实现各种计数器,如页面访问量、下载次数等。

// 页面访问量统计
app.get('/page/:id', async (req, res) => {
  const pageId = req.params.id;
  // 自增计数器
  await client.incr(`page:view:${pageId}`);
  // 获取当前访问量
  const views = await client.get(`page:view:${pageId}`);
  res.render('page', { views });
});

10.4 排行榜

有序集合非常适合实现排行榜功能。

// 添加分数
await client.zadd('leaderboard', score, userId);

// 获取排行榜(从高到低)
const leaderboard = await client.zrevrange('leaderboard', 0, 9, 'WITHSCORES');

// 获取用户排名
const rank = await client.zrevrank('leaderboard', userId);

10.5 分布式锁

Redis可用于实现分布式锁,解决并发问题。

async function acquireLock(key, value, expireTime) {
  // 使用SETNX命令尝试获取锁
  const result = await client.setnx(key, value);
  if (result === 1) {
    // 设置过期时间,避免死锁
    await client.expire(key, expireTime);
    return true;
  }
  return false;
}

async function releaseLock(key, value) {
  // 使用Lua脚本保证原子性
  const script = `
    if redis.call('get', KEYS[1]) == ARGV[1] then
      return redis.call('del', KEYS[1])
    else
      return 0
    end
  `;
  const result = await client.eval(script, 1, key, value);
  return result === 1;
}

10.6 消息队列

Redis的列表可用于实现简单的消息队列。

// 生产者
async function produceMessage(queue, message) {
  await client.lpush(queue, JSON.stringify(message));
}

// 消费者
async function consumeMessage(queue) {
  // 阻塞式获取消息,设置超时时间
  const message = await client.brpop(queue, 0);
  if (message) {
    return JSON.parse(message[1]);
  }
  return null;
}

11. 监控与维护

11.1 监控工具

  1. Redis自带监控INFO 命令
  2. Redis Sentinel:监控主从复制和故障转移
  3. Redis Cluster:监控集群状态
  4. 第三方监控工具
    • Prometheus + Grafana
    • Datadog
    • New Relic
    • Zabbix

11.2 常见问题与解决方案

问题 症状 解决方案
内存不足 OOM command not allowed when used memory &gt; &#39;maxmemory&#39; 增加内存、调整淘汰策略、清理无用数据
连接数过多 max number of clients reached 增加 maxclients 配置、使用连接池
持久化失败 日志中出现持久化错误 检查磁盘空间、调整持久化策略
复制延迟 从服务器数据落后于主服务器 检查网络连接、调整复制配置
集群故障 集群状态异常 检查节点状态、重新分片、手动故障转移

11.3 备份与恢复

# 备份RDB文件
cp /var/lib/redis/dump.rdb /backup/

# 备份AOF文件
cp /var/lib/redis/appendonly.aof /backup/

# 恢复数据
# 1. 停止Redis服务
# 2. 将备份文件复制到Redis数据目录
# 3. 启动Redis服务

12. 总结

Redis是一个功能强大、性能优异的内存数据库,广泛应用于缓存、会话管理、计数器、排行榜、分布式锁、消息队列等场景。通过本教程的学习,您应该已经掌握了Redis的基本概念、数据结构、高级特性和最佳实践,可以开始在实际项目中应用它来构建高性能的应用系统。

12.1 核心优势

  • 高性能:内存存储,读写速度极快
  • 丰富的数据结构:支持多种数据结构,满足不同业务需求
  • 灵活的持久化:支持RDB和AOF两种持久化方式
  • 高可用性:支持主从复制和集群
  • 丰富的功能:支持发布/订阅、事务、Lua脚本等
  • 简单易用:命令简洁明了,学习曲线平缓

12.2 适用场景

  • 缓存:减少数据库压力,提高响应速度
  • 会话管理:支持分布式部署
  • 计数器:实时统计
  • 排行榜:游戏、活动等场景
  • 分布式锁:解决并发问题
  • 消息队列:异步处理任务
  • 实时数据分析:快速处理和分析数据

12.3 未来发展

Redis不断演进,新版本引入了更多功能和性能优化:

  • Redis 6.0:引入多线程IO、ACL访问控制等
  • Redis 7.0:引入时间序列数据结构、函数等
  • Redis 8.0:持续优化性能和功能

通过持续学习和实践,您可以充分发挥Redis的优势,为您的应用系统提供更高的性能和可靠性。

« 上一篇 60. Sequelize - Node.js ORM框架 下一篇 » 62. Node.js - 服务器端JavaScript运行时