第46章:Redis

第四十六章:Redis

46.1 Redis 简介

Redis——内存数据库的速度之王!

如果说MySQL是仓库,那么Redis就是超级VIP保鲜柜

为什么这么说?

  • 普通数据库(MySQL):数据存在硬盘
  • Redis:数据存在内存里!

这意味着什么?速度的碾压!

flowchart LR
    A[请求] --> B{Redis<br/>内存数据库}
    A --> C{MySQL<br/>硬盘数据库}
    
    B -->|纳秒级| D[响应 < 1ms]
    C -->|毫秒级| E[响应 10-100ms]
    
    style B fill:#ff9999
    style D fill:#90EE90

Redis有多快?

  • 读操作:每秒 10万-50万次
  • 写操作:每秒 5万-20万次
  • 延迟:亚毫秒级(< 1ms)

对比一下MySQL:

  • 读操作:每秒 1万-5万次
  • 写操作:每秒 几千-1万次
  • 延迟:几毫秒到几十毫秒

结论:Redis比MySQL快5-10倍!在极端场景下差距更大!

Redis的诞生

Redis的作者是意大利程序员 Salvatore Sanfilippo(网名Antirez)。

2009年,Salvatore为了解决网站的高并发问题,开发了Redis。一开始只是为了解决一个简单的问题:让网站能实时显示在线用户数量

没想到这个"临时解决方案"火了!

2009年:Redis诞生,解决实时统计问题
    ↓
2010年:VMware赞助Redis开发
    ↓
2013年:Redis 2.8,稳定版发布
    ↓
2015年:Redis 3.0,集群支持
    ↓
2017年:Redis 4.0,模块系统
    ↓
2018年:Redis 5.0,流数据类型
    ↓
2022年:Redis 7.0,ACLv2
    ↓
持续更新中...

Redis的名字来源

Redis = REmote DIctionary Server

翻译过来就是:远程字典服务器

为什么会叫这个名字?因为Redis的设计理念就是一个存储"键-值"对(Key-Value)的服务器,就像一本超级大字典,你查什么关键词,它就返回什么内容——比真的字典好用多了,至少不会查到"释义"看到"略"!

Redis的核心特点

特点说明
内存存储所有数据存在内存,读写速度飞快
持久化支持RDB和AOF,数据不会丢
数据结构丰富String、Hash、List、Set、ZSet…
主从复制一主多从,读写分离
高可用Sentinel哨兵 + Cluster集群
单线程使用C语言编写,事件循环模型
高性能基于内存,QPS轻松上10万+

Redis vs 其他数据库

flowchart TB
    A[数据库类型] --> B[关系型<br/>MySQL/PostgreSQL]
    A --> C[文档型<br/>MongoDB]
    A --> D[键值型<br/>Redis]
    
    B --> B1[磁盘存储]
    B --> B2[结构化查询]
    B --> B3[事务支持]
    
    C --> C1[文档存储]
    C --> C2[灵活Schema]
    C --> C3[聚合查询]
    
    D --> D1[内存存储]
    D --> D2[丰富数据结构]
    D --> D3[超高性能]
    
    style D fill:#FF6B6B

Redis的用途

用途说明示例
缓存层热点数据缓存商品信息、用户信息
会话存储用户登录状态Session、Token
实时排行榜有序集合游戏积分、热度排行
消息队列轻量级队列异步任务、实时通知
分布式锁多进程协调库存扣减、秒杀
限流器控制访问频率API限流

Redis的安装方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Ubuntu/Debian
sudo apt install redis-server

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

# Docker(最简单)
docker run -d --name redis -p 6379:6379 redis:latest

# macOS
brew install redis
brew services start redis

Redis客户端工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# redis-cli - 命令行客户端(自带)
redis-cli

# 连接远程Redis
redis-cli -h 192.168.1.100 -p 6379

# 常用命令
redis-cli PING        # 测试连接,返回PONG
redis-cli INFO        # 查看Redis信息
redis-cli DBSIZE      # 查看键数量
redis-cli FLUSHDB     # 清空当前数据库(危险!)
redis-cli FLUSHALL    # 清空所有数据库(超级危险!)

一图总结Redis

mindmap
  root((Redis))
    内存存储
      速度飞快
      亚毫秒延迟
    丰富数据结构
      String字符串
      Hash哈希
      List列表
      Set集合
      ZSet有序集合
    持久化
      RDB快照
      AOF追加
    高可用
      主从复制
      Sentinel哨兵
      Cluster集群
    适用场景
      缓存
      会话存储
      排行榜
      消息队列
      分布式锁

小结

Redis是高性能的内存数据库:

  • 所有数据存在内存,速度飞快
  • 丰富的数据结构,不只是简单的键值
  • 支持持久化,数据不会丢失
  • 支持主从复制和集群,高可用

下一节我们将学习如何安装和配置Redis!

46.2 Redis 安装

安装方式对比

安装方式优点缺点
系统包管理器简单版本可能较旧
Docker快速、隔离需要Docker
源码编译最新版本、完全可控编译耗时

Ubuntu/Debian 安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 更新软件源
sudo apt update

# 安装Redis
sudo apt install redis-server -y

# 安装Redis工具(包含redis-cli等)
sudo apt install redis-tools -y

# 查看Redis版本
redis-server --version
# Redis server v=6.0.16 sha=00000000:0 malloc=jemalloc-5.3.0 bits=64

CentOS/RHEL 安装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# CentOS 7/8
sudo yum install epel-release -y
sudo yum install redis -y

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

# 查看版本
redis-server --version

Docker 安装(跨平台通用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 拉取最新Redis镜像
docker pull redis:latest

# 运行Redis容器
docker run -d \
    --name redis-server \
    -p 6379:6379 \
    redis:latest

# 运行带密码的Redis
docker run -d \
    --name redis-secure \
    -p 6380:6379 \
    redis:latest \
    redis-server --requirepass MySecurePass123

# 查看运行状态
docker ps

源码编译安装(获取最新版本)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 1. 安装编译工具
sudo apt install build-essential -y

# 2. 下载Redis源码
cd /tmp
wget https://github.com/redis/redis/archive/refs/tags/7.2.4.tar.gz

# 3. 解压
tar xzf redis-7.2.4.tar.gz
cd redis-7.2.4

# 4. 编译
make -j$(nproc)

# 5. 安装(可选,把Redis安装到系统)
sudo make install

# 6. 验证
redis-server --version

安装后的配置

1. 启动Redis服务

1
2
3
4
5
6
# Ubuntu/Debian (systemd)
sudo systemctl start redis-server
sudo systemctl enable redis-server

# 检查状态
sudo systemctl status redis-server

执行结果:

● redis-server.service - Redis In-Memory Data Store
     Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2024-01-01 00:00:00 CST; 1min 30s ago
   Main PID: 1234 (redis-server)
     Status: "Ready to accept connections"

2. 连接测试

1
2
3
4
5
6
7
8
9
# 使用redis-cli连接
redis-cli

# 测试连接
127.0.0.1:6379> PING
PONG

# 查看Redis信息
127.0.0.1:6379> INFO

Redis配置文件

Redis的主要配置文件位置:

操作系统配置文件路径
Ubuntu/Debian/etc/redis/redis.conf
CentOS/RHEL/etc/redis.conf
Docker/etc/redis/redis.conf

常用配置项:

1
2
# 查看完整配置
sudo cat /etc/redis/redis.conf

关键配置项说明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 网络配置
bind 127.0.0.1              # 监听地址(改为0.0.0.0允许远程连接)
port 6379                    # 监听端口
tcp-backlog 511              # TCP连接队列长度

# 通用配置
daemonize no                 # 是否以守护进程运行(Docker设为no)
supervised no                # Upstart/systemd管理
pidfile /var/run/redis/redis-server.pid
loglevel notice              # 日志级别:debug/verbose/notice/warning
logfile ""                   # 日志文件(空则输出到stdout)

# 数据持久化配置
dir /var/lib/redis           # 数据目录(重要!磁盘空间要够)
dbfilename dump.rdb          # RDB文件名

# RDB持久化(快照)
save 900 1                   # 900秒内1次修改就保存
save 300 10                  # 300秒内10次修改就保存
save 60 10000                # 60秒内10000次修改就保存

# AOF持久化(追加)
appendonly no                # 是否开启AOF(建议生产环境开启)
appendfilename "appendonly.aof"
appendfsync everysec         # everysec:每秒同步(推荐)

# 内存配置
maxmemory 256mb              # 最大内存(根据服务器配置调整)
maxmemory-policy allkeys-lru # 内存满时的淘汰策略

# 安全配置
requirepass ""               # 密码(生产环境必须设置!)

生产环境配置建议

1
2
# 编辑配置文件
sudo nano /etc/redis/redis.conf

必须修改的配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 1. 允许远程连接(如果需要)
bind 0.0.0.0

# 2. 设置密码
requirepass YourSecurePassword123

# 3. 设置最大内存(根据服务器配置)
maxmemory 2gb

# 4. 开启AOF持久化(强烈建议)
appendonly yes
appendfsync everysec

# 5. 设置密码后,redis-cli需要认证
# redis-cli
# AUTH YourSecurePassword123

远程连接配置

方式1:通过redis-cli

1
2
3
4
5
6
7
8
9
# 连接远程Redis
redis-cli -h 192.168.1.100 -p 6379

# 如果有密码
redis-cli -h 192.168.1.100 -p 6379 -a YourPassword

# 连接后认证
redis-cli
AUTH YourPassword

方式2:开放防火墙

1
2
3
4
5
6
# Ubuntu (ufw)
sudo ufw allow 6379/tcp

# CentOS (firewalld)
sudo firewall-cmd --permanent --add-port=6379/tcp
sudo firewall-cmd --reload

一图总结安装流程

flowchart TD
    A[开始安装] --> B{操作系统}
    
    B --> C[Ubuntu/Debian]
    B --> D[CentOS/RHEL]
    B --> E[macOS]
    B --> F[Docker]
    
    C --> C1[apt install redis-server]
    D --> D1[yum install redis]
    E --> E1[brew install redis]
    F --> F1[docker run redis]
    
    C1 --> G[systemctl start redis]
    D1 --> G
    E1 --> G
    F1 --> G
    
    G --> H[redis-cli测试]
    H --> I[PING返回PONG]
    I --> J[✅ 安装成功]
    
    style J fill:#90EE90

小结

Redis安装要点:

  • Ubuntu:apt install redis-server
  • CentOS:yum install redis
  • Docker:docker run -d -p 6379:6379 redis
  • 配置文件:/etc/redis/redis.conf
  • 生产环境必须设置密码

下一节我们将学习Redis的数据类型,这是Redis最强大的地方!

46.3 数据类型

Redis不只是简单的键值存储,它支持丰富的数据结构

这就像一个工具箱,不只有锤子,还有螺丝刀、钳子、锯子…每种工具都有它的用武之地。

Redis的五种基本数据类型

flowchart LR
    A[Redis数据类型] --> B[String<br/>字符串]
    A --> C[Hash<br/>哈希]
    A --> D[List<br/>列表]
    A --> E[Set<br/>集合]
    A --> F[Sorted Set<br/>有序集合]

46.3.1 String(字符串)

String是Redis最基本的数据类型,可以存储任何字符串(文本、数字、二进制数据)。

常用命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 设置值
SET key "hello"
SET user:1:name "xiaoming"
SET counter 100

# 获取值
GET key
# "hello"

# 设置多个值
MSET user:1:name "xiaoming" user:1:age "25" user:1:city "Beijing"

# 获取多个值
MGET user:1:name user:1:age user:1:city
# 1) "xiaoming"
# 2) "25"
# 3) "Beijing"

# 设置带过期时间的值(秒)
SET session:abc123 "user_id:1" EX 3600  # 1小时后过期

# 设置带过期时间的值(毫秒)
SET token "abc123" PX 3600000  # 1小时后过期

# 设置值(仅当key不存在)
SETNX newkey "value"  # 如果newkey不存在才设置

# 设置值(仅当key存在)
SETEX key 3600 "value"  # 设置值并指定过期时间

# 获取旧值并设置新值
GETSET key "newvalue"
# 返回旧值

# 自增/自减
SET counter 100
INCR counter       # counter变成101
INCRBY counter 5   # counter变成106
DECR counter       # counter变成105
DECRBY counter 10  # counter变成95

# 自增浮点数
SET price 99.99
INCRBYFLOAT price 0.01  # price变成100.00

# 字符串操作
APPEND key "world"     # 在key的值后面追加"world"
STRLEN key             # 获取字符串长度
GETRANGE key 0 4       # 获取子字符串(索引0-4)
SETRANGE key 0 "HELLO" # 从索引0开始替换

String的用途:

场景示例
缓存网页SET page:home "<html>..."
存储TokenSET token:xxx "user_id"
计数器INCR article:views:1
分布式锁SET lock:order "1" NX EX 30

计数器实战:

1
2
3
4
5
6
7
8
9
# 文章阅读量统计
INCR article:views:1001
INCR article:views:1001
INCR article:views:1001
# 阅读量:3

# 获取阅读量
GET article:views:1001
# "3"

分布式锁实战:

1
2
3
4
5
# 获取锁(仅当key不存在时设置,并设置过期时间)
SET lock:order:123456 "locked" NX EX 30

# 释放锁(删除key)
DEL lock:order:123456

46.3.2 Hash(哈希)

Hash就像一个对象字典,存储键值对集合。非常适合存储对象数据!

数据结构示意:

user:1 = {
    name: "xiaoming",
    age: "25",
    email: "xiaoming@example.com"
}

常用命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 设置Hash字段
HSET user:1 name "xiaoming"
HSET user:1 age 25
HSET user:1 email "xiaoming@example.com"

# 一次设置多个字段
HSET user:1 name "xiaoming" age 25 email "xiaoming@example.com"

# 获取字段值
HGET user:1 name
# "xiaoming"

# 获取多个字段值
HMGET user:1 name age email
# 1) "xiaoming"
# 2) "25"
# 3) "xiaoming@example.com"

# 获取所有字段和值
HGETALL user:1
# 1) "name"
# 2) "xiaoming"
# 3) "age"
# 4) "25"
# 5) "email"
# 6) "xiaoming@example.com"

# 获取所有字段
HKEYS user:1
# 1) "name"
# 2) "age"
# 3) "email"

# 获取所有值
HVALS user:1
# 1) "xiaoming"
# 2) "25"
# 3) "xiaoming@example.com"

# 字段是否存在
HEXISTS user:1 name
# (integer) 1

# 字段数量
HLEN user:1
# (integer) 3

# 删除字段
HDEL user:1 email
HGETALL user:1
# 1) "name"
# 2) "xiaoming"
# 3) "age"
# 4) "25"

# 自增(字段值必须是数字)
HSET user:1 age 25
HINCRBY user:1 age 1  # age变成26
HINCRBY user:1 age 5  # age变成31

# 字符串操作
HSET user:1 tags "java,python,go"
HSTRLEN user:1 name
# (integer) 8

Hash的用途:

场景示例
存储用户信息HSET user:1 name "xiaoming" age 25
存储商品信息HSET product:1001 name "iPhone" price 9999
存储配置信息HSET config:app port 8080 debug true

用户信息实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 存储用户信息
HSET user:1001 name "xiaoming" email "xiaoming@example.com" age 25 city "Beijing"

# 获取用户信息
HGETALL user:1001
# 1) "name"
# 2) "xiaoming"
# 3) "email"
# 4) "xiaoming@example.com"
# 5) "age"
# 6) "25"
# 7) "city"
# 8) "Beijing"

# 更新年龄
HINCRBY user:1001 age 1

# 只获取部分字段
HMGET user:1001 name email

46.3.3 List(列表)

List是一个有序的字符串列表,可以按照插入顺序存储多个元素。支持两端操作!

数据结构示意:

mylist = ["a", "b", "c", "d", "e"]
        left                    right

常用命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 从左边插入(头插)
LPUSH mylist "a"
LPUSH mylist "b"
LPUSH mylist "c"
# mylist: ["c", "b", "a"]

# 从右边插入(尾插)
RPUSH mylist "x"
RPUSH mylist "y"
# mylist: ["c", "b", "a", "x", "y"]

# 获取列表元素
LRANGE mylist 0 -1  # 获取所有元素
# 1) "c"
# 2) "b"
# 3) "a"
# 4) "x"
# 5) "y"

# 获取指定范围
LRANGE mylist 0 2  # 前3个元素
# 1) "c"
# 2) "b"
# 3) "a"

# 获取长度
LLEN mylist
# (integer) 5

# 从左边弹出(头出)
LPOP mylist
# "c"
# mylist: ["b", "a", "x", "y"]

# 从右边弹出(尾出)
RPOP mylist
# "y"
# mylist: ["b", "a", "x"]

# 按索引获取元素
LINDEX mylist 0  # 第一个元素
# "b"
LINDEX mylist -1 # 最后一个元素
# "x"

# 设置指定索引的值
LSET mylist 0 "B"
# mylist: ["B", "a", "x"]

# 截断列表(保留指定范围)
LTRIM mylist 0 1
# mylist: ["B", "a"]

# 阻塞操作(队列/消息队列用)
# 阻塞从左边弹出,列表为空则等待
BLPOP mylist 0  # 0表示一直等待
# 或 BLPOP mylist 10(等待10秒)

# 阻塞从右边弹出
BRPOP mylist 0

# 批量插入
RPUSH mylist "1" "2" "3" "4" "5"
# mylist: ["1", "2", "3", "4", "5"]

# 获取列表范围
LRANGE mylist 0 -1
# 1) "1"
# 2) "2"
# 3) "3"
# 4) "4"
# 5) "5"

List的用途:

场景示例
消息队列LPUSH queue "msg1" + BRPOP queue 0
最新消息列表LPUSH user:1:timeline "msg" + LTRIM user:1:timeline 0 99
任务队列LPUSH tasks "task1" + BRPOP tasks 0

消息队列实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 生产者:发送消息
LPUSH queue:orders "order:1001"
LPUSH queue:orders "order:1002"
LPUSH queue:orders "order:1003"

# 消费者:接收消息(阻塞等待)
BRPOP queue:orders 0
# "order:1001"
# 或
BLPOP queue:orders 0
# "order:1003"(左进右出,弹出最后进入的)

最新N条消息实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 用户发布消息
LPUSH user:1:posts "message 1"
LPUSH user:1:posts "message 2"
LPUSH user:1:posts "message 3"

# 只保留最新10条
LTRIM user:1:posts 0 9

# 获取最新消息
LRANGE user:1:posts 0 9

46.3.4 Set(集合)

Set是一个无序的、唯一的字符串集合。不能有重复元素!

数据结构示意:

myset = {"apple", "banana", "cherry"}
        唯一性:无重复

常用命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 添加元素
SADD myset "apple"
SADD myset "banana"
SADD myset "cherry"
SADD myset "apple"  # 重复元素会被忽略

# 获取所有元素
SMEMBERS myset
# 1) "apple"
# 2) "banana"
# 3) "cherry"

# 获取元素数量
SCARD myset
# (integer) 3

# 判断元素是否存在
SISMEMBER myset "apple"
# (integer) 1 (存在)
SISMEMBER myset "grape"
# (integer) 0 (不存在)

# 删除元素
SREM myset "banana"
SMEMBERS myset
# 1) "apple"
# 2) "cherry"

# 随机弹出元素
SPOP myset
# 返回弹出的元素
SMEMBERS myset

# 随机获取元素(不删除)
SRANDMEMBER myset 2  # 获取2个随机元素

# 集合运算
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"

# 交集
SINTER set1 set2
# 1) "b"
# 2) "c"

# 并集
SUNION set1 set2
# 1) "a"
# 2) "b"
# 3) "c"
# 4) "d"

# 差集(set1有set2没有的)
SDIFF set1 set2
# 1) "a"

# 将交集结果存入新key
SINTERSTORE set1_inter_set2 set1 set2
SMEMBERS set1_inter_set2
# 1) "b"
# 2) "c"

Set的用途:

场景示例
标签系统SADD article:1:tags "redis" "database"
好友关系SADD user:1:followers 2 3 4 5
去重统计SADD ip:20240101 "192.168.1.1" "192.168.1.2"
抽奖SRANDMEMBER prizes 3

标签系统实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 给文章添加标签
SADD article:1001:tags "Redis" "Database" "Cache"
SADD article:1002:tags "MySQL" "Database"

# 获取文章的所有标签
SMEMBERS article:1001:tags
# 1) "Redis"
# 2) "Database"
# 3) "Cache"

# 查找同时有多个标签的文章
SINTER article:1001:tags article:1002:tags
# 1) "Database"

# 获取用户感兴趣的标签
SUNION user:1:tags user:2:tags

去重统计实战:

1
2
3
4
5
6
7
# 统计每天的独立访客
SADD visits:20240101 "192.168.1.1" "192.168.1.2" "192.168.1.1"
# 只算1个IP

# 获取当天独立访客数
SCARD visits:20240101
# (integer) 2

46.3.5 Sorted Set(有序集合)

Sorted Set(ZSet)是Redis最强大的数据类型!它是一个有序的、唯一的字符串集合,每个元素都有一个分数(score)来排序。

数据结构示意:

zset = {
    "xiaoming": 95,   # 分数95
    "xiaohong": 88,   # 分数88
    "xiaogang": 92    # 分数92
}
按分数排序:xiaohong(88) < xiaogang(92) < xiaoming(95)

常用命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# 添加元素(带分数)
ZADD leaderboard 95 "xiaoming"
ZADD leaderboard 88 "xiaohong"
ZADD leaderboard 92 "xiaogang"

# 获取元素(按分数从小到大)
ZRANGE leaderboard 0 -1
# 1) "xiaohong"
# 2) "xiaogang"
# 3) "xiaoming"

# 获取元素(按分数从大到小)
ZREVRANGE leaderboard 0 -1
# 1) "xiaoming"
# 2) "xiaogang"
# 3) "xiaohong"

# 获取元素和分数
ZRANGE leaderboard 0 -1 WITHSCORES
# 1) "xiaohong"
# 2) "88"
# 3) "xiaogang"
# 4) "92"
# 5) "xiaoming"
# 6) "95"

# 获取指定分数范围的元素
ZRANGEBYSCORE leaderboard 80 90
# 1) "xiaohong"
# 2) "xiaogang"

# 获取排名(从0开始)
ZRANK leaderboard "xiaohong"
# (integer) 0  # 第一名

ZRANK leaderboard "xiaoming"
# (integer) 2  # 第三名

# 获取分数
ZSCORE leaderboard "xiaoming"
# "95"

# 获取元素数量
ZCARD leaderboard
# (integer) 3

# 删除元素
ZREM leaderboard "xiaohong"

# 删除指定分数范围的元素
ZREMRANGEBYSCORE leaderboard 80 90

# 自增分数
ZINCRBY leaderboard 5 "xiaohong"  # xiaohong分数从88变成93

# 统计分数范围内元素数量
ZCOUNT leaderboard 80 90
# (integer) 1

# 有序集合运算(交集、并集)
ZADD set1 100 "a" 200 "b"
ZADD set2 150 "b" 250 "c"

# 交集(元素分数相加)
ZINTERSTORE inter 2 set1 set2
ZRANGE inter 0 -1 WITHSCORES
# 1) "b"
# 2) "350"  # 200+150

Sorted Set的用途:

场景示例
排行榜ZADD game:leaderboard 10000 "xiaoming"
热搜排行ZINCRBY hot:search 1000 "热点新闻"
延时队列ZADD delay:queue <timestamp> "task"
滑动窗口限流ZADD rate:limit:user:1 <time> <request_id>

排行榜实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 游戏积分排行榜
ZADD game:scores 15000 "xiaoming"
ZADD game:scores 12000 "xiaohong"
ZADD game:scores 18000 "xiaogang"
ZADD game:scores 9000 "xiaoli"

# 获取前3名
ZREVRANGE game:scores 0 2 WITHSCORES
# 1) "xiaogang"
# 2) "18000"
# 3) "xiaoming"
# 4) "15000"
# 5) "xiaohong"
# 6) "12000"

# 获取玩家排名
ZREVRANK game:scores "xiaoming"
# (integer) 0  # 第1名

# 玩家得分变化后更新
ZINCRBY game:scores 5000 "xiaohong"
ZREVRANK game:scores "xiaohong"
# (integer) 1  # 变成第2名

热搜排行实战:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 用户搜索"Redis"
ZINCRBY hot:search 100 "Redis"

# 用户搜索"数据库"
ZINCRBY hot:search 80 "数据库"

# 用户搜索"MySQL"
ZINCRBY hot:search 60 "MySQL"

# 获取实时热搜榜(前10)
ZREVRANGE hot:search 0 9 WITHSCORES
# 1) "Redis"
# 2) "100"
# 3) "数据库"
# 4) "80"
# 5) "MySQL"
# 6) "60"

# 每小时重置热搜
# DEL hot:search

数据类型对比

flowchart TB
    A[选择数据类型] --> B{需求}
    
    B -->|简单键值| C[String]
    B -->|对象/字典| D[Hash]
    B -->|有序列表/队列| E[List]
    B -->|唯一集合| F[Set]
    B -->|排行榜/排序| G[Sorted Set]
    
    C --> C1[缓存<br/>计数器<br/>Token]
    D --> D1[用户信息<br/>商品信息]
    E --> E1[消息队列<br/>最新消息]
    F --> F1[标签<br/>好友列表]
    G --> G1[排行榜<br/>热搜榜]

小结

Redis五种数据类型:

类型特点常用命令适用场景
String简单键值SET/GET缓存、计数器、Token
Hash对象存储HSET/HGET用户信息、商品信息
List有序列表LPUSH/RPOP消息队列、最新消息
Set无序唯一SADD/SMEMBERS标签、好友、去重
ZSet有序唯一ZADD/ZRANGE排行榜、热搜

下一节我们将学习Redis的持久化机制!

46.4 持久化

Redis数据存在内存里,速度飞快!但是…万一服务器断电了怎么办?你的数据会不会像网吧停电一样,全部GG?

别担心!Redis有持久化机制,可以把数据保存到硬盘上,断电也不丢数据!

46.4.1 RDB:快照

RDB(Redis Database) 是一种快照持久化方式。它会在某个时刻,给内存数据拍一张"照片",保存到硬盘上。

原理:

时刻T1: 内存数据 -> "咔嚓" -> dump.rdb文件
                ↓
时刻T2: 服务器重启 -> 读取dump.rdb -> 数据恢复!

RDB就像给你的数据买了份"定期保险"——虽然可能丢失最后一次"拍照"之后的数据,但恢复起来快啊!

配置方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 编辑配置文件
sudo nano /etc/redis/redis.conf

# RDB触发条件(满足任一条件就保存快照)
# 格式:save 秒数 修改次数
save 900 1       # 900秒内至少1次修改就保存
save 300 10      # 300秒内至少10次修改就保存
save 60 10000    # 60秒内至少10000次修改就保存(高并发场景)

# RDB文件存放目录(要有足够空间!)
dir /var/lib/redis
dbfilename dump.rdb

# 是否压缩RDB文件(压缩省空间,但CPU开销)
rdbcompression yes

# RDB文件校验(保证文件完整性)
rdbchecksum yes

手动触发RDB快照:

1
2
3
4
5
6
7
8
9
# 方式1:阻塞方式(会暂停服务,不推荐在生产环境用)
redis-cli SAVE

# 方式2:非阻塞方式(在后台fork子进程执行,推荐!)
redis-cli BGSAVE

# 查看BGSAVE是否完成
redis-cli LASTSAVE
# 返回一个Unix时间戳,如果多次调用时间变了,说明保存完成

RDB的优点:

  • 文件紧凑(经过压缩),备份恢复快
  • 适合大规模数据备份和灾难恢复
  • fork()在后台执行,不阻塞主进程

RDB的缺点:

  • 可能丢失最后一次快照之后的数据(最多丢失几分钟)
  • 数据量大时,fork()操作会短暂阻塞

46.4.2 AOF:追加

AOF(Append Only File) 是一种日志持久化方式。它会像一个尽职的秘书,把你的每一个操作都记录下来。

原理:

你执行命令 -> "好的老板!" -> 追加到appendonly.aof文件
                    ↓
恢复数据 -> 重放所有命令 -> 数据恢复!

AOF就像给你的数据买了份"实时保险"——每做一次操作就记录一次,出事了从头来一遍!

配置方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 编辑配置文件
sudo nano /etc/redis/redis.conf

# 开启AOF
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# AOF同步策略(决定多久写一次硬盘)
appendfsync everysec   # everysec: 每秒同步一次(推荐!性能和安全的平衡)

# 其他策略:
# appendfsync always    # 每次操作都同步(最安全,但慢得像蜗牛)
# appendfsync no        # 让操作系统决定(最快,但可能丢数据)

三种同步策略对比:

策略安全性性能可能丢失的数据
always最高最慢最多丢失1条命令
everysec中等中等最多丢失1秒数据
no最低最快取决于系统

AOF文件内容长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 查看AOF文件
cat /var/lib/redis/appendonly.aof

# 内容示例(这是Redis的通信协议格式):
*3
$3
SET
$3
key
$5
value
*3
$3
SET
$4
name
$8
xiaoming

看不懂?没关系,反正Redis自己能看懂就行!

AOF重写(Rewrite):

AOF文件会越来越大——每次操作都记,小学生作文都能写成长篇小说!Redis会定期"压缩"这个小说。

1
2
3
4
5
6
# 手动触发AOF重写(后台执行)
redis-cli BGREWRITEAOF

# 配置自动重写条件
auto-aof-rewrite-percentage 100  # 文件比上次重写后大一倍就自动重写
auto-aof-rewrite-min-size 64mb   # 文件小于64MB先不重写

AOF的优点:

  • 数据安全性更高(everysec模式最多丢1秒数据)
  • 即使只追加写入,不会出现随机IO问题

AOF的缺点:

  • AOF文件通常比RDB文件大(因为记录了所有操作)
  • 恢复速度比RDB慢(需要重放所有命令)

RDB vs AOF 对比

对比项RDBAOF
持久化方式快照追加日志
文件大小小(紧凑)大(包含所有操作)
恢复速度
数据安全性可能丢失数据最多丢失1秒数据
CPU开销fork开销大写入频繁CPU开销大
适用场景备份、灾难恢复数据敏感业务

推荐的持久化配置(生产环境)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 编辑配置文件
sudo nano /etc/redis/redis.conf

# ============ RDB配置 ============
# 保存条件
save 900 1
save 300 10
save 60 10000

# 文件路径
dir /var/lib/redis
dbfilename dump.rdb

# ============ AOF配置 ============
# 开启AOF
appendonly yes
appendfilename "appendonly.aof"

# 同步策略(每秒同步)
appendfsync everysec

# AOF重写配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 允许AOF截断(如果损坏)
aof-load-truncated yes

# RDB+AOF混合持久化(Redis 7.0+)
aof-use-rdb-preamble yes

恢复数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 1. 先查看有哪些持久化文件
ls -la /var/lib/redis/

# 2. 如果同时有RDB和AOF,Redis会优先用AOF恢复
# 3. 停止Redis
sudo systemctl stop redis

# 4. 备份现有数据文件
sudo cp /var/lib/redis/*.rdb /backup/
sudo cp /var/lib/redis/*.aof /backup/

# 5. 把备份文件放回数据目录
sudo cp /backup/dump.rdb /var/lib/redis/
sudo chown redis:redis /var/lib/redis/dump.rdb

# 6. 启动Redis
sudo systemctl start redis

# 7. 验证数据
redis-cli GET key

一图总结持久化

flowchart LR
    A[Redis内存] -->|BGSAVE| B[RDB快照<br/>dump.rdb]
    A -->|追加| C[AOF日志<br/>appendonly.aof]
    
    B -->|恢复| D[快<br/>可能丢数据]
    C -->|恢复| E[慢<br/>更安全]
    
    style B fill:#F7DC6F
    style C fill:#85C1E9

小结

Redis持久化两种方式:

  • RDB:定时快照,文件小,恢复快,但可能丢数据
  • AOF:追加日志,安全性高,文件大,恢复慢

生产环境推荐:同时开启RDB和AOF,数据最安全!

下一节我们将学习Redis的集群模式!

46.5 集群模式

单机Redis能抗多少QPS?大概10万-20万。

但如果你的业务有100万QPS怎么办?

答案:Redis集群

46.5.1 主从复制

主从复制是最简单的Redis高可用方案。

原理:

主库(Master) -> 同步 -> 从库(Slave)
                    ↓
              数据一模一样

配置从库:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 方式1:修改配置文件(永久生效)
sudo nano /etc/redis/redis.conf

# 添加/修改
replicaof 192.168.1.100 6379
# 或者(新版本用这个)
replicaof <master-ip> <master-port>

# 如果主库有密码
masterauth <password>

# 从库只读(默认开启)
replica-read-only yes

# 重启从库
sudo systemctl restart redis

# 方式2:命令行动态设置(临时生效,重启失效)
redis-cli replicaof 192.168.1.100 6379

# 取消复制
redis-cli replicaof no one

验证主从状态:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 在主库查看
redis-cli INFO replication

# 输出示例:
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.101,port=6379,state=online,offset=12345,lag=0
# slave1:ip=192.168.1.102,port=6379,state=online,offset=12345,lag=0

# 在从库查看
redis-cli INFO replication

# 输出示例:
# role:slave
# master_host:192.168.1.100
# master_port:6379
# master_link_status:up

测试主从同步:

1
2
3
4
5
6
7
# 在主库写入
redis-cli SET testkey "hello"
redis-cli INCR counter

# 在从库读取(应该能看到)
redis-cli GET testkey
# "hello"

46.5.2 Sentinel 哨兵

主从复制有个问题:主库挂了怎么办?

Redis Sentinel(哨兵)就是来解决这个问题的!

哨兵的功能:

  • 监控:监控主库和从库是否正常运行
  • 自动故障转移:主库挂了,自动选举一个从库升级为新主库
  • 通知:故障转移后通知应用
  • 配置提供者:应用通过哨兵获取当前的主库地址
flowchart TB
    subgraph "Sentinel集群"
        S1[Sentinel1]
        S2[Sentinel2]
        S3[Sentinel3]
    end
    
    subgraph "Redis集群"
        M[主库]
        S[从库1]
        S2A[从库2]
    end
    
    S1 --> M
    S2 --> M
    S3 --> M
    
    M --> S
    M --> S2A
    
    style M fill:#ff9999
    style S1 fill:#98D8C8
    style S2 fill:#98D8C8
    style S3 fill:#98D8C8

配置哨兵:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装哨兵(Redis自带)
# 配置文件位置
sudo nano /etc/redis/sentinel.conf

# 最小配置:
port 26379

# 监控的主库名称和IP
sentinel monitor mymaster 192.168.1.100 6379 2
# mymaster是主库名称
# 2表示2个哨兵同意才进行故障转移

# 主库密码(如果有)
sentinel auth-pass mymaster YourPassword

# 故障转移超时时间
sentinel down-after-milliseconds mymaster 30000

# 并行同步从库数量
parallel-syncs 1

# 故障转移后执行脚本
sentinel notification-script mymaster /var/redis/notify.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 启动哨兵
redis-sentinel /etc/redis/sentinel.conf

# 或
redis-server /etc/redis/sentinel.conf --sentinel

# 查看哨兵状态
redis-cli -p 26379 INFO sentinel

# 输出示例:
# sentinel_masters:1
# master0:name=mymaster,status=ok,address=192.168.1.100:6379
# slaves=2

Sentinel工作流程:

sequenceDiagram
    participant S as 哨兵
    participant M as 主库
    participant R as 从库
    participant A as 应用
    
    Note over M: 主库正常运行
    S->>M: PING
    M->>S: PONG
    
    Note over M: 主库挂了
    S-xM: PING超时
    S->>S: 主观下线
    
    S->>S: 询问其他哨兵
    S->>S2: 主库挂了吗?
    S2->>S: 确认!
    
    Note over S: 客观下线,开始故障转移
    
    S->>R: 选举为新主库
    R->>S: 升职成功
    
    R->>S2A: 我是新主库,跟我同步
    S2A->>R: 收到!
    
    A->>S: 主库地址是多少?
    S->>A: 192.168.1.101:6379(新主库)

46.5.3 Cluster 集群

当数据量非常大,一台机器存不下怎么办?

Redis Cluster 来帮你!它把数据分片存储到多台机器上!

Cluster的核心概念:

16384个槽位 -> 分散到N个节点

节点1: 槽位 0-5460
节点2: 槽位 5461-10922
节点3: 槽位 10923-16383

为什么是16384个槽?

  • 足够多,可以均匀分布到多个节点
  • 槽信息不大,方便在集群节点间传输
  • 这个数字是Redis创始人经过权衡选择的结果

创建Redis Cluster(6台机器):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 假设3个主库 + 3个从库

# 节点1 (主)
redis-server --port 7001 --cluster-enabled yes --cluster-config-file nodes-7001.conf --daemonize yes

# 节点2 (主)
redis-server --port 7002 --cluster-enabled yes --cluster-config-file nodes-7002.conf --daemonize yes

# 节点3 (主)
redis-server --port 7003 --cluster-enabled yes --cluster-config-file nodes-7003.conf --daemonize yes

# 节点4 (从)
redis-server --port 7004 --cluster-enabled yes --cluster-config-file nodes-7004.conf --daemonize yes

# 节点5 (从)
redis-server --port 7005 --cluster-enabled yes --cluster-config-file nodes-7005.conf --daemonize yes

# 节点6 (从)
redis-server --port 7006 --cluster-enabled yes --cluster-config-file nodes-7006.conf --daemonize yes

# 创建集群(分配槽位和主从关系)
redis-cli --cluster create 192.168.1.101:7001 192.168.1.102:7002 192.168.1.103:7003 \
    192.168.1.104:7004 192.168.1.105:7005 192.168.1.106:7006 \
    --cluster-replicas 1
# --cluster-replicas 1 表示每个主库有1个从库

# 查看集群状态
redis-cli -p 7001 CLUSTER NODES

Cluster工作原理:

flowchart TB
    subgraph "Redis Cluster"
        M1[主库1<br/>槽0-5460] --> S1[从库1]
        M2[主库2<br/>槽5461-10922] --> S2[从库2]
        M3[主库3<br/>槽10923-16383] --> S3[从库3]
    end
    
    A[应用] -->|请求key1| M1
    A -->|请求key2| M2
    A -->|请求key3| M3
    
    Note over A: Redis客户端自动计算key属于哪个槽

连接集群:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 使用redis-cli连接集群
redis-cli -c -p 7001

# -c 表示集群模式

# 测试
127.0.0.1:7001> SET name xiaoming
# 自动跳转到对应节点
-> Redirected to slot [5798] located at 192.168.1.102:7002
OK

# 获取数据
127.0.0.1:7002> GET name
# 自动从对应节点获取
-> Redirected to slot [5798] located at 192.168.1.102:7002
"xiaoming"

高可用方案对比

方案适用场景复杂度特点
主从复制读写分离数据备份,读扩展
Sentinel自动故障转移监控 + 故障转移
Cluster数据分片数据分布 + 高可用

小结

Redis集群模式:

  • 主从复制:一主多从,数据备份,读写分离
  • Sentinel:监控主库,自动故障转移,高可用
  • Cluster:数据分片到多个节点,海量数据 + 高可用

下一节我们将学习Redis的缓存策略!

46.6 缓存策略

Redis最常用的场景就是缓存。但缓存怎么用才能既快又省内存?

这就需要缓存策略

缓存的经典问题

缓存穿透:查询不存在的数据,每次都打到数据库
缓存击穿:热点key过期,瞬间大量请求打到数据库
缓存雪崩:大量key同时过期,瞬间大量请求打到数据库

缓存穿透解决方案

问题: 查询一个不存在的数据,缓存没有,数据库也没有,每次都打到数据库。

解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def get_user(user_id):
    # 1. 先查缓存
    cache_key = f"user:{user_id}"
    user = redis.get(cache_key)
    
    if user:
        return json.loads(user)
    
    # 2. 缓存没有,查数据库
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    
    if user:
        # 3. 数据库有,存入缓存
        redis.setex(cache_key, 3600, json.dumps(user))
    else:
        # 4. 数据库也没有,设置空值缓存(防止穿透)
        redis.setex(cache_key, 300, "NULL")
    
    return user

缓存击穿解决方案

问题: 热点key过期,瞬间大量请求同时打到数据库。

解决方案:互斥锁/分布式锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import time
import hashlib

def get_user_with_lock(user_id):
    cache_key = f"user:{user_id}"
    
    # 1. 先查缓存
    user = redis.get(cache_key)
    if user:
        return json.loads(user)
    
    # 2. 缓存没有,获取锁
    lock_key = f"lock:user:{user_id}"
    lock_id = hashlib.md5(str(time.time()).encode()).hexdigest()
    
    # SETNX + EXPIRE 原子操作
    lock_acquired = redis.set(lock_key, lock_id, nx=True, ex=10)
    
    if lock_acquired:
        try:
            # 3. 获取到锁,查数据库
            user = db.query("SELECT * FROM users WHERE id = ?", user_id)
            
            if user:
                redis.setex(cache_key, 3600, json.dumps(user))
            
            return user
        finally:
            # 4. 释放锁
            if redis.get(lock_key) == lock_id:
                redis.delete(lock_key)
    else:
        # 5. 没获取到锁,等待一下再查缓存
        time.sleep(0.1)
        return get_user_with_lock(user_id)

缓存雪崩解决方案

问题: 大量key同时过期或Redis重启,瞬间大量请求打到数据库。

解决方案1:过期时间加随机值

1
2
3
4
5
6
# 设置过期时间时加随机值
base_ttl = 3600  # 1小时
random_ttl = random.randint(0, 300)  # 0-5分钟随机
ttl = base_ttl + random_ttl

redis.setex(cache_key, ttl, value)

解决方案2:永不过期 + 异步更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def get_user_async(user_id):
    cache_key = f"user:{user_id}"
    
    # 1. 先查缓存
    user = redis.get(cache_key)
    if user:
        return json.loads(user)
    
    # 2. 没有缓存,从数据库查
    user = db.query("SELECT * FROM users WHERE id = ?", user_id)
    
    if user:
        # 设置永不过期
        redis.set(cache_key, json.dumps(user))
        
        # 异步更新缓存(可以发到队列处理)
        queue.put(("update_cache", user_id, user))
    
    return user

缓存淘汰策略

当Redis内存满了,新数据进来怎么办?

这时候Redis就会开始"断舍离"——不是Marie Kondo那种,是根据一定策略把旧数据踢出去!

Redis的内存淘汰策略(maxmemory-policy):

策略说明比喻
noeviction不淘汰,返回错误(默认)仓库满了,暂停进货
allkeys-lru所有key中,淘汰最近最少使用的把好久不用的东西扔掉
volatile-lru有过期时间的key中,淘汰最近最少使用的只在有保质期的东西里挑
allkeys-random所有key中,随机淘汰闭眼随便扔
volatile-random有过期时间的key中,随机淘汰在快过期的东西里随便扔
allkeys-lfu所有key中,淘汰使用频率最低的把使用频率最低的踢出去
volatile-lfu有过期时间的key中,淘汰使用频率最低的挑快过期且不常用的踢
volatile-ttl有过期时间的key中,淘汰剩余TTL最短的先扔快过期的东西

最常用的是 allkeys-lru——想象一下,你衣柜满了,你会先扔那些"去年买的一年都没穿过的衣服",而不是"上周刚买的限量款"。

配置淘汰策略:

1
2
3
4
5
# 设置最大内存
sudo nano /etc/redis/redis.conf

maxmemory 2gb
maxmemory-policy allkeys-lru  # 推荐这个!

缓存使用最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1. 选择合适的缓存粒度
# ❌ 不好:缓存整个用户列表
redis.set("users:all", json.dumps(all_users))

# ✅ 好:缓存单个用户
for user in users:
    redis.setex(f"user:{user.id}", 3600, json.dumps(user))

# 2. 合理设置过期时间
# ❌ 不好:所有数据都是1小时
redis.setex("key", 3600, value)

# ✅ 好:根据数据特性设置
# 配置数据:永不过期,更新时删除
# 用户数据:30分钟过期
# 临时数据:5分钟过期

# 3. 使用缓存预热
def warm_cache():
    # 系统启动时,把热点数据加载到缓存
    hot_users = db.query("SELECT * FROM users WHERE is_vip = 1")
    for user in hot_users:
        redis.setex(f"user:{user.id}", 86400, json.dumps(user))

# 4. 缓存监控
def monitor_cache():
    info = redis.info()
    print(f"内存使用: {info['used_memory_human']}")
    print(f"键数量: {redis.dbsize()}")
    print(f"命中率: {info['keyspace_hits'] / (info['keyspace_hits'] + info['keyspace_misses']) * 100}%")

一图总结缓存策略

flowchart TB
    A[缓存问题] --> B[穿透]
    A --> C[击穿]
    A --> D[雪崩]
    
    B --> B1[布隆过滤器]
    B --> B2[空值缓存]
    
    C --> C1[分布式锁]
    C --> C2[永不过期]
    
    D --> D1[过期时间+随机]
    D --> D2[预热缓存]
    D --> D3[持久化]

小结

缓存策略三剑客:

  • 缓存穿透:布隆过滤器 + 空值缓存
  • 缓存击穿:分布式锁 + 永不过期
  • 缓存雪崩:过期时间随机 + 预热缓存

内存淘汰策略:allkeys-lru 是最常用的!

下一节我们将学习Redis配置!

46.7 Redis 配置

核心配置项

Redis的配置文件位于 /etc/redis/redis.conf

网络配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 监听地址(多个用逗号分隔)
bind 127.0.0.1 192.168.1.100

# 端口
port 6379

# TCP连接队列长度
tcp-backlog 511

# 超时时间(客户端多久没操作就断开)
timeout 300

# 心跳检测频率
tcp-keepalive 300

通用配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 是否守护进程运行
daemonize no

# PID文件
pidfile /var/run/redis/redis-server.pid

# 日志级别
loglevel notice
# debug: 调试信息
# verbose: 详细信息
# notice: 一般信息(生产环境)
# warning: 警告信息

# 日志文件
logfile /var/log/redis/redis-server.log

# 数据库数量(16个)
databases 16

持久化配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# RDB持久化
save 900 1
save 300 10
save 60 10000
dir /var/lib/redis
dbfilename dump.rdb

# AOF持久化
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

内存配置:

1
2
3
4
5
# 最大内存(根据服务器配置)
maxmemory 2gb

# 内存淘汰策略
maxmemory-policy allkeys-lru

安全配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 设置密码
requirepass YourSecurePassword123

# 危险命令重命名
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command DEBUG ""

# 禁止远程连接(bind内网IP)
bind 192.168.1.100

生产环境配置示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# /etc/redis/redis.conf 生产环境配置

# 网络
bind 0.0.0.0
port 6379
tcp-backlog 65535
timeout 60
tcp-keepalive 300

# 安全
requirepass YourSuperSecurePassword123
protected-mode yes

# 通用
daemonize no
supervised systemd
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-server.log
databases 16

# 持久化 - RDB
save 900 1
save 300 10
save 60 10000
dir /var/lib/redis
dbfilename dump.rdb
rdbcompression yes
rdbchecksum yes

# 持久化 - AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 内存
maxmemory 4gb
maxmemory-policy allkeys-lru

# 客户端
maxclients 10000

# 性能优化
tcp-backlog 65535

小结

Redis核心配置:

  • 网络bindportrequirepass
  • 持久化saveappendonly
  • 内存maxmemorymaxmemory-policy
  • 安全:密码、危险命令重命名

下一节我们将学习如何连接Redis!

46.8 Redis 连接

redis-cli 连接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 连接本地Redis
redis-cli

# 连接远程Redis
redis-cli -h 192.168.1.100 -p 6379

# 有密码的Redis
redis-cli -h 192.168.1.100 -p 6379 -a YourPassword
# 或连接后用AUTH命令
redis-cli
AUTH YourPassword

# 选择数据库(默认0-15)
redis-cli -n 1

# 执行命令后退出
redis-cli PING
redis-cli GET key
redis-cli --pipe < commands.txt

Redis连接命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 测试连接
redis-cli PING
# PONG

# 查看服务器信息
redis-cli INFO

# 查看所有键
redis-cli KEYS "*"

# 查看键数量
redis-cli DBSIZE

# 查看键类型
redis-cli TYPE key

# 查看键剩余TTL
redis-cli TTL key

# 删除键
redis-cli DEL key

# 删除所有键(危险!)
redis-cli FLUSHDB    # 清空当前数据库
redis-cli FLUSHALL   # 清空所有数据库

# 切换数据库
redis-cli SELECT 0
redis-cli SELECT 1

# 监视操作(调试用)
redis-cli MONITOR

# 查看客户端连接
redis-cli CLIENT LIST

# 关闭客户端连接
redis-cli CLIENT KILL ip:port

Python连接Redis

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 安装redis库
pip install redis

# 基本使用
import redis

# 创建连接
r = redis.Redis(
    host='localhost',
    port=6379,
    password='YourPassword',
    db=0,
    decode_responses=True  # 自动解码为字符串
)

# 测试连接
r.ping()  # True

# String操作
r.set('name', 'xiaoming')
r.get('name')  # 'xiaoming'

# Hash操作
r.hset('user:1', mapping={'name': 'xiaoming', 'age': 25})
r.hgetall('user:1')  # {'name': 'xiaoming', 'age': '25'}

# List操作
r.lpush('queue', 'task1', 'task2')
r.rpop('queue')  # 'task1'

# Set操作
r.sadd('tags', 'redis', 'database')
r.smembers('tags')  # {'redis', 'database'}

# ZSet操作
r.zadd('leaderboard', {'xiaoming': 100, 'xiaohong': 90})
r.zrevrange('leaderboard', 0, -1, withscores=True)
# [('xiaoming', 100.0), ('xiaohong', 90.0)]

# 连接池
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    max_connections=10
)
r = redis.Redis(connection_pool=pool)

Java连接Redis

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Maven依赖
// <dependency>
//     <groupId>redis.clients</groupId>
//     <artifactId>jedis</artifactId>
//     <version>5.0.0</version>
// </dependency>

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisDemo {
    public static void main(String[] args) {
        // 创建连接池
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(50);
        config.setMinIdle(10);
        
        JedisPool pool = new JedisPool(config, "localhost", 6379, 3000, "password");
        
        // 获取连接
        try (Jedis jedis = pool.getResource()) {
            // String操作
            jedis.set("name", "xiaoming");
            System.out.println(jedis.get("name"));
            
            // Hash操作
            jedis.hset("user:1", "name", "xiaoming");
            jedis.hset("user:1", "age", "25");
            System.out.println(jedis.hgetAll("user:1"));
            
            // List操作
            jedis.lpush("queue", "task1", "task2");
            System.out.println(jedis.rpop("queue"));
            
            // Set操作
            jedis.sadd("tags", "redis", "database");
            System.out.println(jedis.smembers("tags"));
            
            // ZSet操作
            jedis.zadd("leaderboard", 100, "xiaoming");
            jedis.zadd("leaderboard", 90, "xiaohong");
            System.out.println(jedis.zrevrangeWithScores("leaderboard", 0, -1));
        }
        
        pool.close();
    }
}

一图总结连接方式

flowchart LR
    A[连接Redis] --> B[redis-cli]
    A --> C[Python]
    A --> D[Java]
    A --> E[PHP]
    
    B --> B1[本地连接]
    B --> B2[远程连接<br/>-h -p -a]
    
    C --> C1[redis-py库]
    C --> C2[连接池]
    
    D --> D1[Jedis库]
    D --> D2[连接池]
    
    style A fill:#ff9999

本章小结

本章我们学习了Redis,从简介到安装,从数据类型到持久化,从集群到缓存策略!

核心内容回顾

分类内容
数据类型String、Hash、List、Set、ZSet
持久化RDB(快照)、AOF(追加日志)
高可用主从复制、Sentinel哨兵、Cluster集群
缓存策略穿透、击穿、雪崩
连接redis-cli、Python、Java

Redis vs 其他数据库

特点RedisMySQLPostgreSQL
数据存储内存磁盘磁盘
数据结构丰富表格表格+丰富类型
性能极快(10万+ QPS)快(1万 QPS)快(1万 QPS)
适用场景缓存、会话、排行榜普通业务复杂业务

下章预告

下一章我们将学习 消息队列,这是现代分布式系统的核心组件之一。我们会学习RabbitMQ和Kafka,敬请期待!

趣味彩蛋:Redis的作者Antirez曾经说过:“Redis的代码量只有2万行,但功能比你想象的强大得多!”

是的,有时候简单才是王道。一行 SET key value,背后是十几年的精心设计。

记住:用好Redis,性能翻10倍不是梦!但用不好,数据全丢泪两行! 🚀

最后修改 March 24, 2026: 新增JavaScript教程 (37305c4)