08-27-周三_17-09-29
This commit is contained in:
402
数据库/Redis_2025/01_Redis基础概念.md
Normal file
402
数据库/Redis_2025/01_Redis基础概念.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Redis 基础概念
|
||||
|
||||
## NoSQL 数据库概述
|
||||
|
||||
### NoSQL 数据库的特点和分类
|
||||
|
||||
NoSQL(Not Only SQL)数据库是一类非关系型数据库的统称,它们不使用传统的表格关系模型来存储数据。NoSQL 数据库具有以下特点:
|
||||
|
||||
**主要特点:**
|
||||
- **灵活的数据模型**:支持多种数据结构,如键值对、文档、列族、图等
|
||||
- **水平扩展性**:易于在多台服务器间分布数据
|
||||
- **高性能**:针对特定用例优化,读写性能优异
|
||||
- **高可用性**:支持分布式部署,具备容错能力
|
||||
- **最终一致性**:放宽了 ACID 约束,采用 BASE 理论
|
||||
|
||||
**NoSQL 数据库分类:**
|
||||
|
||||
1. **键值存储(Key-Value Store)**
|
||||
- 代表产品:Redis、Amazon DynamoDB、Riak
|
||||
- 特点:简单的键值对存储,查询速度快
|
||||
- 适用场景:缓存、会话存储、购物车
|
||||
|
||||
2. **文档数据库(Document Database)**
|
||||
- 代表产品:MongoDB、CouchDB、Amazon DocumentDB
|
||||
- 特点:存储半结构化文档(如 JSON、XML)
|
||||
- 适用场景:内容管理、用户配置、产品目录
|
||||
|
||||
3. **列族数据库(Column Family)**
|
||||
- 代表产品:Cassandra、HBase、Amazon SimpleDB
|
||||
- 特点:按列存储数据,适合大数据分析
|
||||
- 适用场景:时间序列数据、日志分析、IoT 数据
|
||||
|
||||
4. **图数据库(Graph Database)**
|
||||
- 代表产品:Neo4j、Amazon Neptune、ArangoDB
|
||||
- 特点:存储节点和关系,擅长处理复杂关联
|
||||
- 适用场景:社交网络、推荐系统、知识图谱
|
||||
|
||||
### 关系型数据库 vs NoSQL 数据库
|
||||
|
||||
| 特性 | 关系型数据库 (RDBMS) | NoSQL 数据库 |
|
||||
|------|---------------------|---------------|
|
||||
| **数据模型** | 表格结构,固定模式 | 灵活模式,多种数据结构 |
|
||||
| **扩展性** | 垂直扩展(Scale Up) | 水平扩展(Scale Out) |
|
||||
| **一致性** | 强一致性(ACID) | 最终一致性(BASE) |
|
||||
| **查询语言** | 标准 SQL | 各自专有 API |
|
||||
| **事务支持** | 完整的 ACID 事务 | 有限的事务支持 |
|
||||
| **性能** | 复杂查询性能好 | 简单操作性能优异 |
|
||||
| **成本** | 许可费用高 | 多数开源免费 |
|
||||
| **适用场景** | 复杂业务逻辑,强一致性要求 | 大数据,高并发,快速开发 |
|
||||
|
||||
### NoSQL 数据库的应用场景
|
||||
|
||||
**1. 大数据处理**
|
||||
- 海量数据存储和分析
|
||||
- 实时数据处理
|
||||
- 数据仓库和数据湖
|
||||
|
||||
**2. 高并发 Web 应用**
|
||||
- 社交媒体平台
|
||||
- 电商网站
|
||||
- 在线游戏
|
||||
|
||||
**3. 实时应用**
|
||||
- 实时推荐系统
|
||||
- 实时监控和告警
|
||||
- 实时聊天应用
|
||||
|
||||
**4. 内容管理**
|
||||
- 内容发布系统
|
||||
- 数字资产管理
|
||||
- 多媒体存储
|
||||
|
||||
**5. IoT 和传感器数据**
|
||||
- 设备数据收集
|
||||
- 时间序列数据
|
||||
- 地理位置数据
|
||||
|
||||
## Redis 简介
|
||||
|
||||
### Redis 的定义和特点
|
||||
|
||||
**Redis**(Remote Dictionary Server)是一个开源的、基于内存的键值对数据库,由 Salvatore Sanfilippo 于 2009 年开发。Redis 以其卓越的性能和丰富的数据结构而闻名。
|
||||
|
||||
**核心特点:**
|
||||
|
||||
1. **内存存储**
|
||||
- 所有数据存储在内存中,读写速度极快
|
||||
- 支持数据持久化到磁盘
|
||||
- 内存使用效率高
|
||||
|
||||
2. **丰富的数据结构**
|
||||
- 支持字符串、列表、集合、有序集合、哈希等
|
||||
- 每种数据结构都有专门的操作命令
|
||||
- 支持复杂的数据操作
|
||||
|
||||
3. **高性能**
|
||||
- 单线程模型,避免锁竞争
|
||||
- 基于事件驱动的 I/O 多路复用
|
||||
- 读写性能可达 10 万+ QPS
|
||||
|
||||
4. **持久化支持**
|
||||
- RDB 快照持久化
|
||||
- AOF 日志持久化
|
||||
- 混合持久化模式
|
||||
|
||||
5. **高可用性**
|
||||
- 主从复制
|
||||
- 哨兵模式
|
||||
- 集群模式
|
||||
|
||||
6. **原子性操作**
|
||||
- 所有操作都是原子性的
|
||||
- 支持事务
|
||||
- 支持 Lua 脚本
|
||||
|
||||
### Redis 的数据结构
|
||||
|
||||
Redis 支持五种基本数据结构:
|
||||
|
||||
**1. 字符串(String)**
|
||||
```shell
|
||||
# 基本操作
|
||||
SET key "Hello Redis"
|
||||
GET key
|
||||
INCR counter
|
||||
DECR counter
|
||||
```
|
||||
|
||||
**2. 列表(List)**
|
||||
```shell
|
||||
# 列表操作
|
||||
LPUSH mylist "world"
|
||||
LPUSH mylist "hello"
|
||||
LRANGE mylist 0 -1
|
||||
```
|
||||
|
||||
**3. 集合(Set)**
|
||||
```shell
|
||||
# 集合操作
|
||||
SADD myset "apple"
|
||||
SADD myset "banana"
|
||||
SMEMBERS myset
|
||||
```
|
||||
|
||||
**4. 有序集合(Sorted Set)**
|
||||
```shell
|
||||
# 有序集合操作
|
||||
ZADD leaderboard 100 "player1"
|
||||
ZADD leaderboard 200 "player2"
|
||||
ZRANGE leaderboard 0 -1 WITHSCORES
|
||||
```
|
||||
|
||||
**5. 哈希(Hash)**
|
||||
```shell
|
||||
# 哈希操作
|
||||
HSET user:1 name "John"
|
||||
HSET user:1 age 30
|
||||
HGETALL user:1
|
||||
```
|
||||
|
||||
### Redis 的应用场景
|
||||
|
||||
**1. 缓存系统**
|
||||
- 数据库查询结果缓存
|
||||
- 页面缓存
|
||||
- 对象缓存
|
||||
- 减少数据库压力,提高响应速度
|
||||
|
||||
**2. 会话存储**
|
||||
- Web 应用会话管理
|
||||
- 分布式会话共享
|
||||
- 用户状态保持
|
||||
|
||||
**3. 消息队列**
|
||||
- 发布/订阅模式
|
||||
- 任务队列
|
||||
- 实时消息推送
|
||||
|
||||
**4. 排行榜和计数器**
|
||||
- 游戏排行榜
|
||||
- 网站访问统计
|
||||
- 点赞数、评论数统计
|
||||
|
||||
**5. 分布式锁**
|
||||
- 防止并发操作冲突
|
||||
- 资源访问控制
|
||||
- 分布式系统协调
|
||||
|
||||
**6. 实时分析**
|
||||
- 实时数据统计
|
||||
- 用户行为分析
|
||||
- 业务指标监控
|
||||
|
||||
### Redis 的优势和局限性
|
||||
|
||||
**优势:**
|
||||
|
||||
1. **极高的性能**
|
||||
- 内存操作,读写速度快
|
||||
- 单线程模型,无锁竞争
|
||||
- 支持管道操作,批量处理
|
||||
|
||||
2. **丰富的数据结构**
|
||||
- 多种数据类型满足不同需求
|
||||
- 原生支持复杂操作
|
||||
- 减少应用层代码复杂度
|
||||
|
||||
3. **高可用性**
|
||||
- 多种部署模式
|
||||
- 自动故障转移
|
||||
- 数据冗余保护
|
||||
|
||||
4. **易于使用**
|
||||
- 简单的命令接口
|
||||
- 丰富的客户端库
|
||||
- 详细的文档支持
|
||||
|
||||
5. **活跃的社区**
|
||||
- 开源免费
|
||||
- 持续更新
|
||||
- 广泛的生态系统
|
||||
|
||||
**局限性:**
|
||||
|
||||
1. **内存限制**
|
||||
- 数据量受内存大小限制
|
||||
- 内存成本相对较高
|
||||
- 需要合理的内存管理策略
|
||||
|
||||
2. **单线程模型**
|
||||
- CPU 密集型操作可能阻塞
|
||||
- 无法充分利用多核 CPU
|
||||
- 大数据量操作需要优化
|
||||
|
||||
3. **持久化开销**
|
||||
- RDB 快照可能丢失数据
|
||||
- AOF 日志影响性能
|
||||
- 需要权衡性能和数据安全
|
||||
|
||||
4. **复杂查询支持有限**
|
||||
- 不支持复杂的关联查询
|
||||
- 没有标准的查询语言
|
||||
- 需要在应用层处理复杂逻辑
|
||||
|
||||
## Redis 架构
|
||||
|
||||
### Redis 的内存模型
|
||||
|
||||
Redis 采用内存存储模型,所有数据都保存在内存中,这是其高性能的关键所在。
|
||||
|
||||
**内存布局:**
|
||||
|
||||
1. **数据存储区域**
|
||||
- 用户数据:存储实际的键值对数据
|
||||
- 过期字典:存储键的过期时间信息
|
||||
- 数据库字典:存储不同数据库的数据
|
||||
|
||||
2. **缓冲区域**
|
||||
- 输入缓冲区:存储客户端发送的命令
|
||||
- 输出缓冲区:存储返回给客户端的结果
|
||||
- AOF 缓冲区:存储 AOF 日志数据
|
||||
|
||||
3. **复制缓冲区**
|
||||
- 复制积压缓冲区:主从复制时使用
|
||||
- 从服务器输出缓冲区:向从服务器发送数据
|
||||
|
||||
**内存管理策略:**
|
||||
|
||||
1. **内存分配**
|
||||
- 使用 jemalloc 内存分配器
|
||||
- 减少内存碎片
|
||||
- 提高内存使用效率
|
||||
|
||||
2. **内存回收**
|
||||
- 过期键删除:定期删除和惰性删除
|
||||
- 内存淘汰策略:LRU、LFU、随机等
|
||||
- 内存压缩:对小对象进行压缩存储
|
||||
|
||||
### Redis 的线程模型演进
|
||||
|
||||
Redis 的线程模型在不同版本中有重要演进,从单线程到混合线程模型。
|
||||
|
||||
**Redis 6.0 之前:单线程模型**
|
||||
|
||||
Redis 6.0 之前采用单线程模型处理所有客户端请求:
|
||||
|
||||
1. **单线程特点**
|
||||
- 主线程处理所有网络 I/O 和命令执行
|
||||
- 避免锁竞争和上下文切换
|
||||
- 保证操作的原子性
|
||||
- 简化代码实现和调试
|
||||
|
||||
2. **单线程优势**
|
||||
- 无需考虑线程安全问题
|
||||
- CPU 缓存命中率高
|
||||
- 减少内存访问延迟
|
||||
- 所有操作都是原子性的
|
||||
|
||||
**Redis 6.0+:混合线程模型**
|
||||
|
||||
Redis 6.0 引入了多线程 I/O 处理,但保持命令执行的单线程特性:
|
||||
|
||||
1. **多线程 I/O 处理**
|
||||
- 网络 I/O 读写使用多线程
|
||||
- 提高网络吞吐量
|
||||
- 减少网络延迟
|
||||
- 更好地利用多核 CPU
|
||||
|
||||
2. **单线程命令执行**
|
||||
- 命令解析和执行仍在主线程
|
||||
- 保持数据操作的原子性
|
||||
- 避免复杂的锁机制
|
||||
- 确保数据一致性
|
||||
|
||||
3. **线程模型配置**
|
||||
```bash
|
||||
# 启用多线程 I/O(默认关闭)
|
||||
io-threads-do-reads yes
|
||||
|
||||
# 设置 I/O 线程数量(建议为 CPU 核心数)
|
||||
io-threads 4
|
||||
```
|
||||
|
||||
**为什么 Redis 仍能保持高性能?**
|
||||
|
||||
1. **内存操作优势**
|
||||
- 所有数据在内存中,访问速度快
|
||||
- 避免磁盘 I/O 延迟
|
||||
- 内存带宽充分利用
|
||||
- 高效的内存管理
|
||||
|
||||
2. **优化的数据结构**
|
||||
- 针对不同场景优化的数据结构
|
||||
- 减少不必要的内存拷贝
|
||||
- 高效的算法实现
|
||||
- 内存压缩和优化
|
||||
|
||||
3. **事件驱动架构**
|
||||
- 非阻塞 I/O 模型
|
||||
- 事件循环处理
|
||||
- 高并发连接支持
|
||||
- 异步处理机制
|
||||
|
||||
4. **多线程 I/O 优化**
|
||||
- 网络 I/O 并行处理
|
||||
- 减少网络瓶颈
|
||||
- 提高整体吞吐量
|
||||
- 更好的资源利用
|
||||
|
||||
### Redis 的事件驱动机制
|
||||
|
||||
Redis 使用事件驱动模型来处理客户端请求和内部任务。
|
||||
|
||||
**事件类型:**
|
||||
|
||||
1. **文件事件(File Event)**
|
||||
- 处理客户端连接
|
||||
- 读取客户端命令
|
||||
- 发送响应数据
|
||||
- 基于 I/O 多路复用实现
|
||||
|
||||
2. **时间事件(Time Event)**
|
||||
- 定期执行的任务
|
||||
- 过期键清理
|
||||
- 统计信息更新
|
||||
- 持久化操作
|
||||
|
||||
**事件处理流程:**
|
||||
|
||||
```
|
||||
1. 等待事件发生
|
||||
↓
|
||||
2. 处理文件事件
|
||||
↓
|
||||
3. 处理时间事件
|
||||
↓
|
||||
4. 返回步骤1
|
||||
```
|
||||
|
||||
**I/O 多路复用:**
|
||||
|
||||
Redis 根据不同平台选择最优的 I/O 多路复用实现:
|
||||
- Linux:epoll
|
||||
- macOS/FreeBSD:kqueue
|
||||
- Windows:select
|
||||
|
||||
**事件循环优势:**
|
||||
|
||||
1. **高并发支持**
|
||||
- 单线程处理多个客户端
|
||||
- 避免线程创建开销
|
||||
- 内存使用效率高
|
||||
|
||||
2. **响应及时**
|
||||
- 事件驱动,实时响应
|
||||
- 无阻塞等待
|
||||
- 低延迟处理
|
||||
|
||||
3. **资源利用率高**
|
||||
- CPU 利用率高
|
||||
- 内存占用少
|
||||
- 系统资源消耗低
|
519
数据库/Redis_2025/02_Redis环境搭建.md
Normal file
519
数据库/Redis_2025/02_Redis环境搭建.md
Normal file
@@ -0,0 +1,519 @@
|
||||
# Redis 环境搭建
|
||||
|
||||
## Redis 安装
|
||||
|
||||
Redis 在 Rocky Linux release 9.4 (Blue Onyx) 通过 YUM 安装 redis-6.2.19-1.el9_6.x86_64
|
||||
|
||||
```shell
|
||||
[root@localhost ~]# cat /etc/redhat-release
|
||||
Rocky Linux release 9.4 (Blue Onyx)
|
||||
[root@localhost ~]# yum provides redis
|
||||
Last metadata expiration check: 0:06:10 ago on Sat Aug 2 12:50:25 2025.
|
||||
redis-6.2.19-1.el9_6.x86_64 : A persistent key-value database
|
||||
Repo : appstream
|
||||
Matched from:
|
||||
Provide : redis = 6.2.19-1.el9_6
|
||||
[root@localhost ~]# yum install redis-6.2.19-1.el9_6 -y
|
||||
|
||||
```
|
||||
|
||||
## Redis 启动和连接
|
||||
|
||||
### Redis 服务启动
|
||||
|
||||
**使用 systemd 管理**
|
||||
|
||||
```shell
|
||||
# 服务文件
|
||||
[root@localhost ~]# ls -l /usr/lib/systemd/system/redis.service
|
||||
# 重新加载 systemd
|
||||
systemctl daemon-reload
|
||||
# 启动 Redis 服务
|
||||
systemctl start redis
|
||||
# 设置开机自启
|
||||
systemctl enable redis
|
||||
# 查看服务状态
|
||||
systemctl status redis
|
||||
```
|
||||
|
||||
**手动启动**
|
||||
|
||||
```shell
|
||||
# 前台启动(用于调试)
|
||||
redis-server /etc/redis/redis.conf
|
||||
# 后台启动
|
||||
redis-server /etc/redis/redis.conf --daemonize yes
|
||||
# 指定端口启动
|
||||
redis-server --port 6380
|
||||
# 指定配置参数启动
|
||||
redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
### Redis 客户端连接
|
||||
|
||||
**本地连接**
|
||||
|
||||
```shell
|
||||
# 默认连接
|
||||
redis-cli
|
||||
# 指定主机和端口
|
||||
redis-cli -h 127.0.0.1 -p 6379
|
||||
# 使用密码连接
|
||||
redis-cli -h 127.0.0.1 -p 6379 -a your_password
|
||||
# 连接后认证
|
||||
redis-cli
|
||||
127.0.0.1:6379> AUTH your_password
|
||||
OK
|
||||
# 选择数据库
|
||||
127.0.0.1:6379> SELECT 1
|
||||
OK
|
||||
127.0.0.1:6379[1]>
|
||||
```
|
||||
|
||||
**远程连接配置**
|
||||
|
||||
```shell
|
||||
# 服务器端配置
|
||||
# 修改配置文件
|
||||
vim /etc/redis/redis.conf
|
||||
# 修改绑定地址
|
||||
bind 0.0.0.0
|
||||
# 设置密码
|
||||
requirepass your_strong_password
|
||||
# 重启服务
|
||||
systemctl restart redis
|
||||
|
||||
# 客户端连接
|
||||
# 远程连接
|
||||
redis-cli -h 192.168.1.100 -p 6379 -a your_password
|
||||
|
||||
```
|
||||
|
||||
## Redis 配置
|
||||
|
||||
### 配置文件详解
|
||||
|
||||
Redis 的主配置文件通常位于 `/etc/redis/redis.conf`,包含了所有的配置选项。
|
||||
|
||||
**配置文件结构:**
|
||||
|
||||
```shell
|
||||
# Redis 配置文件主要部分
|
||||
1. 网络配置 (NETWORK)
|
||||
2. 通用配置 (GENERAL)
|
||||
3. 快照配置 (SNAPSHOTTING)
|
||||
4. 复制配置 (REPLICATION)
|
||||
5. 安全配置 (SECURITY)
|
||||
6. 客户端配置 (CLIENTS)
|
||||
7. 内存管理 (MEMORY MANAGEMENT)
|
||||
8. 惰性释放 (LAZY FREEING)
|
||||
9. 线程 I/O (THREADED I/O)
|
||||
10. 内核透明大页 (KERNEL TRANSPARENT HUGEPAGE)
|
||||
11. 追加模式 (APPEND ONLY MODE)
|
||||
12. LUA 脚本 (LUA SCRIPTING)
|
||||
13. Redis 集群 (REDIS CLUSTER)
|
||||
14. 慢日志 (SLOW LOG)
|
||||
15. 延迟监控 (LATENCY MONITOR)
|
||||
16. 事件通知 (EVENT NOTIFICATION)
|
||||
17. 高级配置 (ADVANCED CONFIG)
|
||||
```
|
||||
|
||||
### 常用配置参数
|
||||
|
||||
#### 网络配置
|
||||
|
||||
```shell
|
||||
# 绑定地址
|
||||
bind 127.0.0.1 ::1
|
||||
# 允许所有地址访问(生产环境需谨慎)
|
||||
# bind 0.0.0.0
|
||||
|
||||
# 端口号
|
||||
port 6379
|
||||
|
||||
# TCP 监听队列长度
|
||||
tcp-backlog 511
|
||||
|
||||
# 客户端空闲超时时间(秒)
|
||||
timeout 0
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
```
|
||||
|
||||
#### 通用配置
|
||||
|
||||
```shell
|
||||
# 以守护进程方式运行
|
||||
daemonize yes
|
||||
|
||||
# 进程文件
|
||||
pidfile /var/run/redis_6379.pid
|
||||
|
||||
# 日志级别:debug, verbose, notice, warning
|
||||
loglevel notice
|
||||
|
||||
# 日志文件
|
||||
logfile /var/log/redis/redis-server.log
|
||||
|
||||
# 数据库数量
|
||||
databases 16
|
||||
|
||||
# 显示 Redis logo
|
||||
always-show-logo no
|
||||
```
|
||||
|
||||
#### 内存管理
|
||||
|
||||
```shell
|
||||
# 最大内存限制
|
||||
maxmemory 2gb
|
||||
|
||||
# 内存淘汰策略
|
||||
# noeviction: 不淘汰,内存满时报错
|
||||
# allkeys-lru: 所有键 LRU 淘汰
|
||||
# volatile-lru: 有过期时间的键 LRU 淘汰
|
||||
# allkeys-random: 所有键随机淘汰
|
||||
# volatile-random: 有过期时间的键随机淘汰
|
||||
# volatile-ttl: 有过期时间的键按 TTL 淘汰
|
||||
# allkeys-lfu: 所有键 LFU 淘汰
|
||||
# volatile-lfu: 有过期时间的键 LFU 淘汰
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# LRU 和 LFU 算法样本数量
|
||||
maxmemory-samples 5
|
||||
```
|
||||
|
||||
### 安全配置
|
||||
|
||||
#### 密码认证
|
||||
|
||||
```shell
|
||||
# 设置密码
|
||||
requirepass your_strong_password_here
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command KEYS ""
|
||||
rename-command CONFIG "CONFIG_9a8b7c6d5e4f"
|
||||
```
|
||||
|
||||
#### ACL 用户管理
|
||||
|
||||
```shell
|
||||
# 启用 ACL 日志
|
||||
acllog-max-len 128
|
||||
|
||||
# ACL 配置文件
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# 示例 ACL 配置
|
||||
# user default on nopass ~* &* -@all +@read +@write
|
||||
# user app_user on >app_password ~app:* +@read +@write -@dangerous
|
||||
# user readonly_user on >readonly_password ~* +@read -@write -@dangerous
|
||||
```
|
||||
|
||||
#### 网络安全
|
||||
|
||||
```shell
|
||||
# 保护模式(默认开启)
|
||||
protected-mode yes
|
||||
|
||||
# 绑定到特定接口
|
||||
bind 127.0.0.1 192.168.1.100
|
||||
|
||||
# 禁用某些命令
|
||||
rename-command DEBUG ""
|
||||
rename-command EVAL ""
|
||||
rename-command SCRIPT ""
|
||||
```
|
||||
|
||||
### 性能调优配置
|
||||
|
||||
#### 持久化优化
|
||||
|
||||
```shell
|
||||
# RDB 配置
|
||||
save 900 1 # 900秒内至少1个键变化
|
||||
save 300 10 # 300秒内至少10个键变化
|
||||
save 60 10000 # 60秒内至少10000个键变化
|
||||
|
||||
# RDB 文件压缩
|
||||
rdbcompression yes
|
||||
|
||||
# RDB 文件校验
|
||||
rdbchecksum yes
|
||||
|
||||
# RDB 文件名
|
||||
dbfilename dump.rdb
|
||||
|
||||
# 工作目录
|
||||
dir /var/lib/redis
|
||||
|
||||
# AOF 配置
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
# always: 每个写操作都同步
|
||||
# everysec: 每秒同步一次
|
||||
# no: 由操作系统决定
|
||||
appendfsync everysec
|
||||
|
||||
# AOF 重写配置
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
#### 客户端连接优化
|
||||
|
||||
```shell
|
||||
# 最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# 客户端查询缓冲区限制
|
||||
client-query-buffer-limit 1gb
|
||||
```
|
||||
|
||||
#### 慢查询配置
|
||||
|
||||
```shell
|
||||
# 慢查询阈值(微秒)
|
||||
slowlog-log-slower-than 10000
|
||||
|
||||
# 慢查询日志长度
|
||||
slowlog-max-len 128
|
||||
```
|
||||
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
在生产环境中,我们经常需要在同一台服务器上运行多个Redis实例,用于不同的业务场景或实现数据隔离。本实践将演示如何通过自定义配置文件部署多个Redis实例,包括主实例、缓存实例和会话实例。
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 创建多实例目录结构
|
||||
mkdir -p /etc/redis/instances/{main,cache,session}
|
||||
mkdir -p /var/lib/redis/{main,cache,session}
|
||||
mkdir -p /var/log/redis
|
||||
|
||||
# 2. 创建主实例配置文件 (端口6380)
|
||||
tee /etc/redis/instances/main/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 主实例配置 - 用于核心业务数据
|
||||
port 6380
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-main.pid
|
||||
logfile /var/log/redis/redis-main.log
|
||||
dir /var/lib/redis/main
|
||||
dbfilename dump-main.rdb
|
||||
|
||||
# 内存配置
|
||||
maxmemory 1gb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-main.aof"
|
||||
|
||||
# 安全配置
|
||||
requirepass main_redis_2025
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
|
||||
# 客户端配置
|
||||
maxclients 1000
|
||||
timeout 300
|
||||
EOF
|
||||
|
||||
# 3. 创建缓存实例配置文件 (端口6381)
|
||||
sudo tee /etc/redis/instances/cache/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 缓存实例配置 - 用于应用缓存
|
||||
port 6381
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-cache.pid
|
||||
logfile /var/log/redis/redis-cache.log
|
||||
dir /var/lib/redis/cache
|
||||
dbfilename dump-cache.rdb
|
||||
|
||||
# 内存配置 - 缓存实例分配更多内存
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory-samples 10
|
||||
|
||||
# 持久化配置 - 缓存数据可以不持久化
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 安全配置
|
||||
requirepass cache_redis_2025
|
||||
|
||||
# 客户端配置
|
||||
maxclients 2000
|
||||
timeout 0
|
||||
|
||||
# 过期键删除配置
|
||||
hz 10
|
||||
EOF
|
||||
|
||||
# 4. 创建会话实例配置文件 (端口6382)
|
||||
sudo tee /etc/redis/instances/session/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 会话实例配置 - 用于用户会话存储
|
||||
port 6382
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-session.pid
|
||||
logfile /var/log/redis/redis-session.log
|
||||
dir /var/lib/redis/session
|
||||
dbfilename dump-session.rdb
|
||||
|
||||
# 内存配置
|
||||
maxmemory 512mb
|
||||
maxmemory-policy volatile-lru
|
||||
|
||||
# 持久化配置 - 会话数据需要持久化但频率可以低一些
|
||||
save 1800 1
|
||||
save 300 100
|
||||
appendonly yes
|
||||
appendfilename "appendonly-session.aof"
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 安全配置
|
||||
requirepass session_redis_2025
|
||||
|
||||
# 客户端配置
|
||||
maxclients 500
|
||||
timeout 1800
|
||||
|
||||
# 键空间通知 - 用于会话过期监控
|
||||
notify-keyspace-events Ex
|
||||
EOF
|
||||
|
||||
# 5. 创建运行时目录
|
||||
mkdir -p /var/run/redis
|
||||
chown redis:redis /var/run/redis
|
||||
chown -R redis:redis /var/lib/redis
|
||||
chown -R redis:redis /var/log/redis
|
||||
chown -R redis:redis /etc/redis/instances
|
||||
|
||||
# 6. 创建systemd服务文件
|
||||
# 主实例服务
|
||||
tee /etc/systemd/system/redis-main.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Main Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/main/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6380 -a main_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 缓存实例服务
|
||||
sudo tee /etc/systemd/system/redis-cache.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Cache Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/cache/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6381 -a cache_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 会话实例服务
|
||||
sudo tee /etc/systemd/system/redis-session.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Session Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/session/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6382 -a session_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 7. 重新加载systemd并启动服务
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable redis-main redis-cache redis-session
|
||||
sudo systemctl start redis-main redis-cache redis-session
|
||||
|
||||
# 检查服务状态
|
||||
sudo systemctl status redis-main redis-cache redis-session --no-pager
|
||||
|
||||
# 检查端口监听
|
||||
sudo netstat -tlnp | grep redis-server
|
||||
|
||||
# 检查进程
|
||||
ps aux | grep redis-server | grep -v grep
|
||||
|
||||
# 测试主实例
|
||||
redis-cli -p 6380 -a main_redis_2025 ping
|
||||
redis-cli -p 6380 -a main_redis_2025 set main:test "Main Instance Data"
|
||||
redis-cli -p 6380 -a main_redis_2025 get main:test
|
||||
|
||||
# 测试缓存实例
|
||||
redis-cli -p 6381 -a cache_redis_2025 ping
|
||||
redis-cli -p 6381 -a cache_redis_2025 set cache:user:1001 "User Cache Data" EX 3600
|
||||
redis-cli -p 6381 -a cache_redis_2025 get cache:user:1001
|
||||
redis-cli -p 6381 -a cache_redis_2025 ttl cache:user:1001
|
||||
|
||||
# 测试会话实例
|
||||
redis-cli -p 6382 -a session_redis_2025 ping
|
||||
redis-cli -p 6382 -a session_redis_2025 set session:user:1001 "User Session Data" EX 1800
|
||||
redis-cli -p 6382 -a session_redis_2025 get session:user:1001
|
||||
redis-cli -p 6382 -a session_redis_2025 ttl session:user:1001
|
||||
|
||||
# 检查内存使用情况 - used_memory_human
|
||||
redis-cli -p 6380 -a main_redis_2025 info memory | grep used_memory_human
|
||||
redis-cli -p 6381 -a cache_redis_2025 info memory | grep used_memory_human
|
||||
redis-cli -p 6382 -a session_redis_2025 info memory | grep used_memory_human
|
||||
|
||||
# 检查配置信息 - maxmemory
|
||||
redis-cli -p 6380 -a main_redis_2025 config get maxmemory
|
||||
redis-cli -p 6381 -a cache_redis_2025 config get maxmemory
|
||||
redis-cli -p 6382 -a session_redis_2025 config get maxmemory
|
||||
|
||||
```
|
977
数据库/Redis_2025/03_Redis数据类型.md
Normal file
977
数据库/Redis_2025/03_Redis数据类型.md
Normal file
@@ -0,0 +1,977 @@
|
||||
# Redis 数据类型
|
||||
|
||||
Redis 支持五种基本数据类型,每种数据类型都有其特定的应用场景和操作命令。理解这些数据类型的特点和使用方法是掌握 Redis 的关键。
|
||||
|
||||
## 字符串 (String)
|
||||
|
||||
### 字符串的特点和应用
|
||||
|
||||
字符串是 Redis 最基本的数据类型,也是最常用的类型。在 Redis 中,字符串是二进制安全的,可以存储任何数据,包括数字、文本、序列化对象、甚至是图片。
|
||||
|
||||
**主要特点:**
|
||||
- **二进制安全**:可以存储任何二进制数据
|
||||
- **最大长度**:单个字符串最大 512MB
|
||||
- **编码优化**:Redis 会根据内容自动选择最优编码
|
||||
- **原子操作**:所有字符串操作都是原子性的
|
||||
|
||||
**常见应用场景:**
|
||||
- 缓存数据(JSON、XML、HTML 等)
|
||||
- 计数器(访问量、点赞数等)
|
||||
- 分布式锁
|
||||
- 会话存储
|
||||
- 配置信息存储
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**设置和获取**
|
||||
|
||||
```shell
|
||||
# 设置键值
|
||||
# 用法:SET key value
|
||||
SET name "张三"
|
||||
SET age 25
|
||||
|
||||
# 获取值
|
||||
# 用法:GET key
|
||||
GET name # 返回 "张三"
|
||||
GET age # 返回 "25"
|
||||
GET nonexistent # 返回 (nil)
|
||||
|
||||
# 批量设置
|
||||
# 用法:MSET key1 value1 key2 value2 key3 value3
|
||||
MSET user:1:name "张三" user:1:age 25 user:1:city "北京"
|
||||
|
||||
# 批量获取
|
||||
# 用法:MGET key1 key2 key3
|
||||
MGET key1 key2 key3
|
||||
MGET user:1:name user:1:age user:1:city
|
||||
|
||||
# 设置并返回旧值
|
||||
# 用法:GETSET key newvalue
|
||||
GETSET key newvalue
|
||||
GETSET counter 100 # 返回旧值,设置新值为100
|
||||
```
|
||||
|
||||
**条件设置**
|
||||
|
||||
```shell
|
||||
# 仅当键不存在时设置
|
||||
# 用法:SETNX key value
|
||||
SETNX lock:resource "server1" # 分布式锁应用
|
||||
|
||||
# 设置键值和过期时间
|
||||
# 用法:SETEX key seconds value
|
||||
SETEX session:abc123 3600 "user_data" # 3600秒后过期
|
||||
|
||||
# 仅当键不存在时设置,并指定过期时间
|
||||
# 用法:SET key value EX seconds NX
|
||||
SET lock:order:1001 "processing" EX 30 NX
|
||||
|
||||
# 仅当键存在时设置
|
||||
# 用法:SET key value XX
|
||||
SET existing_key "new_value" XX
|
||||
```
|
||||
|
||||
**字符串操作**
|
||||
|
||||
```shell
|
||||
# 追加字符串
|
||||
# 用法:APPEND key value
|
||||
SET message "Hello"
|
||||
APPEND message " World" # message 现在是 "Hello World"
|
||||
|
||||
# 获取字符串长度
|
||||
# 用法:STRLEN key
|
||||
STRLEN message # 返回 11
|
||||
|
||||
# 获取子字符串
|
||||
# 用法:GETRANGE key start end
|
||||
GETRANGE message 0 4 # 返回 "Hello"
|
||||
GETRANGE message -5 -1 # 返回 "World"
|
||||
|
||||
# 设置子字符串
|
||||
# 用法:SETRANGE key offset value
|
||||
SETRANGE message 6 "Redis" # message 现在是 "Hello Redis"
|
||||
```
|
||||
|
||||
### 数值操作
|
||||
|
||||
Redis 可以将字符串当作数字进行操作,支持整数和浮点数。
|
||||
|
||||
```shell
|
||||
# 整数操作
|
||||
SET counter 10
|
||||
|
||||
# 增加指定值
|
||||
INCR counter # counter = 11
|
||||
INCRBY counter 5 # counter = 16
|
||||
DECR counter # counter = 15
|
||||
DECRBY counter 3 # counter = 12
|
||||
|
||||
# 浮点数操作
|
||||
SET price 19.99
|
||||
INCRBYFLOAT price 5.01 # price = 25.00
|
||||
INCRBYFLOAT price -2.50 # price = 22.50
|
||||
|
||||
# 应用示例:网站访问计数
|
||||
INCR page:home:views
|
||||
INCR user:1001:login_count
|
||||
INCRBY article:123:likes 1
|
||||
```
|
||||
|
||||
### 位操作
|
||||
|
||||
Redis 支持对字符串进行位级操作,适用于布尔值存储和位图应用。
|
||||
|
||||
```shell
|
||||
# 设置位值
|
||||
# 用法:SETBIT key offset value
|
||||
SETBIT user:1001:login 0 1 # 设置第0位为1
|
||||
SETBIT user:1001:login 1 0 # 设置第1位为0
|
||||
SETBIT user:1001:login 2 1 # 设置第2位为1
|
||||
|
||||
# 获取位值
|
||||
# 用法:GETBIT key offset
|
||||
GETBIT user:1001:login 0 # 返回 1
|
||||
GETBIT user:1001:login 1 # 返回 0
|
||||
|
||||
# 统计位为1的数量
|
||||
BITCOUNT key [start end]
|
||||
BITCOUNT user:1001:login # 返回 2
|
||||
|
||||
# 位运算
|
||||
# 用法:BITOP operation destkey key [key ...]
|
||||
SETBIT user:1001 0 1
|
||||
SETBIT user:1001 1 0
|
||||
SETBIT user:1002 0 1
|
||||
SETBIT user:1002 1 1
|
||||
BITOP AND result user:1001 user:1002 # 按位与运算
|
||||
BITOP OR result user:1001 user:1002 # 按位或运算
|
||||
|
||||
# 查找第一个指定位值的位置
|
||||
# 用法:BITPOS key bit [start] [end]
|
||||
BITPOS user:1001:login 1 # 查找第一个1的位置
|
||||
```
|
||||
|
||||
## 列表 (List)
|
||||
|
||||
### 列表的特点和应用
|
||||
|
||||
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表的头部或尾部。
|
||||
|
||||
**主要特点:**
|
||||
- **有序性**:元素按插入顺序排列
|
||||
- **可重复**:允许重复元素
|
||||
- **双端操作**:支持头部和尾部操作
|
||||
- **最大长度**:理论上可包含 2^32 - 1 个元素
|
||||
|
||||
**常见应用场景:**
|
||||
- 消息队列
|
||||
- 最新消息列表
|
||||
- 用户操作历史
|
||||
- 任务队列
|
||||
- 时间线功能
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)添加
|
||||
# 用法:LPUSH key element [element ...]
|
||||
LPUSH messages "消息3" "消息2" "消息1"
|
||||
# 结果:["消息1", "消息2", "消息3"]
|
||||
|
||||
# 从右侧(尾部)添加
|
||||
# 用法:RPUSH key element [element ...]
|
||||
RPUSH messages "消息4" "消息5"
|
||||
# 结果:["消息1", "消息2", "消息3", "消息4", "消息5"]
|
||||
|
||||
# 仅当列表存在时添加
|
||||
# 用法:LPUSHX key element
|
||||
# 用法:RPUSHX key element
|
||||
LPUSHX messages "消息6"
|
||||
RPUSHX messages "消息7"
|
||||
```
|
||||
|
||||
**获取元素**
|
||||
|
||||
```shell
|
||||
# 获取指定范围的元素
|
||||
# 用法:LRANGE key start stop
|
||||
LRANGE messages 0 -1 # 获取所有元素
|
||||
LRANGE messages 0 2 # 获取前3个元素
|
||||
LRANGE messages -3 -1 # 获取最后3个元素
|
||||
```
|
||||
|
||||
**删除元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)删除
|
||||
# 用法:LPOP key
|
||||
LPOP messages # 弹出并返回第一个元素
|
||||
|
||||
# 从右侧(尾部)删除
|
||||
# 用法:RPOP key
|
||||
RPOP messages # 弹出并返回最后一个元素
|
||||
|
||||
# 在指定元素前/后插入
|
||||
# 用法:LINSERT key BEFORE|AFTER pivot element
|
||||
LINSERT messages BEFORE "消息3" "消息2.5"
|
||||
```
|
||||
|
||||
**获取元素**
|
||||
|
||||
```shell
|
||||
# 获取指定范围的元素
|
||||
# 用法:LRANGE key start stop
|
||||
LRANGE messages 0 -1 # 获取所有元素
|
||||
LRANGE messages 0 2 # 获取前3个元素
|
||||
LRANGE messages -3 -1 # 获取最后3个元素
|
||||
|
||||
# 获取指定索引的元素
|
||||
# 用法:LINDEX key index
|
||||
LINDEX messages 0 # 获取第一个元素
|
||||
LINDEX messages -1 # 获取最后一个元素
|
||||
|
||||
# 获取列表长度
|
||||
# 用法:LLEN key
|
||||
LLEN messages # 返回列表长度
|
||||
```
|
||||
|
||||
**删除元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)弹出
|
||||
# 用法:LPOP key
|
||||
LPOP messages # 弹出并返回第一个元素
|
||||
|
||||
# 从右侧(尾部)弹出
|
||||
# 用法:RPOP key
|
||||
RPOP messages # 弹出并返回最后一个元素
|
||||
|
||||
# 删除指定值的元素
|
||||
# 用法:LREM key count element
|
||||
LREM messages 1 "消息2" # 从头开始删除1个"消息2"
|
||||
LREM messages -1 "消息2" # 从尾开始删除1个"消息2"
|
||||
LREM messages 0 "消息2" # 删除所有"消息2"
|
||||
|
||||
# 保留指定范围的元素
|
||||
# 用法:LTRIM key start stop
|
||||
LTRIM messages 0 99 # 只保留前100个元素
|
||||
```
|
||||
|
||||
**修改元素**
|
||||
|
||||
```shell
|
||||
# 设置指定索引的元素值
|
||||
# 用法:LSET key index element
|
||||
LSET messages 0 "新消息1"
|
||||
```
|
||||
|
||||
### 阻塞操作
|
||||
|
||||
阻塞操作是 Redis 列表的重要特性,常用于实现消息队列。
|
||||
|
||||
```shell
|
||||
# 阻塞式左侧弹出
|
||||
# 用法:BLPOP key [key ...] timeout
|
||||
BLPOP task_queue 30 # 等待30秒,如果有元素则弹出
|
||||
|
||||
# 阻塞式右侧弹出
|
||||
# 用法:BRPOP key [key ...] timeout
|
||||
BRPOP task_queue 0 # 无限等待
|
||||
|
||||
# 阻塞式右侧弹出并左侧推入
|
||||
# 用法:BRPOPLPUSH source destination timeout
|
||||
BRPOPLPUSH pending_tasks processing_tasks 60
|
||||
```
|
||||
|
||||
### 列表的应用场景
|
||||
|
||||
**消息队列实现**
|
||||
|
||||
```shell
|
||||
# 生产者:添加任务到队列
|
||||
LPUSH task_queue "发送邮件:user@example.com"
|
||||
LPUSH task_queue "生成报表:monthly_report"
|
||||
LPUSH task_queue "数据备份:database_backup"
|
||||
|
||||
# 消费者:从队列获取任务
|
||||
BRPOP task_queue 30
|
||||
# 返回:1) "task_queue" 2) "发送邮件:user@example.com"
|
||||
```
|
||||
|
||||
**最新消息列表**
|
||||
|
||||
```shell
|
||||
# 添加新消息(保持最新100条)
|
||||
LPUSH latest_news "新闻标题1"
|
||||
LTRIM latest_news 0 99
|
||||
|
||||
# 获取最新10条消息
|
||||
LRANGE latest_news 0 9
|
||||
```
|
||||
|
||||
**用户操作历史**
|
||||
|
||||
```shell
|
||||
# 记录用户操作
|
||||
LPUSH user:1001:actions "登录:2024-01-15 10:30:00"
|
||||
LPUSH user:1001:actions "查看商品:product_123"
|
||||
LPUSH user:1001:actions "加入购物车:product_123"
|
||||
|
||||
# 获取最近操作历史
|
||||
LRANGE user:1001:actions 0 9
|
||||
```
|
||||
|
||||
## 集合 (Set)
|
||||
|
||||
### 集合的特点和应用
|
||||
|
||||
Redis 集合是字符串的无序集合,集合中的元素是唯一的。
|
||||
|
||||
**主要特点:**
|
||||
- **唯一性**:不允许重复元素
|
||||
- **无序性**:元素没有固定顺序
|
||||
- **快速查找**:O(1) 时间复杂度判断元素是否存在
|
||||
- **集合运算**:支持交集、并集、差集运算
|
||||
|
||||
**常见应用场景:**
|
||||
- 标签系统
|
||||
- 好友关系
|
||||
- 权限管理
|
||||
- 去重统计
|
||||
- 抽奖系统
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加和删除元素**
|
||||
|
||||
```shell
|
||||
# 添加元素
|
||||
# 用法:SADD key member [member ...]
|
||||
SADD tags "Redis" "数据库" "缓存" "NoSQL"
|
||||
SADD user:1001:skills "Java" "Python" "Redis"
|
||||
|
||||
# 删除元素
|
||||
# 用法:SREM key member [member ...]
|
||||
SREM tags "缓存"
|
||||
SREM user:1001:skills "Java"
|
||||
|
||||
# 随机删除并返回元素
|
||||
# 用法:SPOP key [count]
|
||||
SPOP tags # 随机删除一个元素
|
||||
SPOP tags 2 # 随机删除两个元素
|
||||
```
|
||||
|
||||
**查询操作**
|
||||
|
||||
```shell
|
||||
# 获取所有元素
|
||||
# 用法:SMEMBERS key
|
||||
SMEMBERS tags
|
||||
|
||||
# 判断元素是否存在
|
||||
# 用法:SISMEMBER key member
|
||||
SISMEMBER tags "Redis" # 返回 1(存在)
|
||||
SISMEMBER tags "MySQL" # 返回 0(不存在)
|
||||
|
||||
# 获取集合大小
|
||||
# 用法:SCARD key
|
||||
SCARD tags # 返回集合元素数量
|
||||
|
||||
# 随机获取元素(不删除)
|
||||
# 用法:SRANDMEMBER key [count]
|
||||
SRANDMEMBER tags # 随机返回一个元素
|
||||
SRANDMEMBER tags 3 # 随机返回三个元素
|
||||
```
|
||||
|
||||
**移动元素**
|
||||
|
||||
```shell
|
||||
# 将元素从一个集合移动到另一个集合
|
||||
# 用法:SMOVE source destination member
|
||||
SMOVE user:1001:skills user:1002:skills "Python"
|
||||
```
|
||||
|
||||
### 集合运算
|
||||
|
||||
Redis 支持集合间的数学运算,这是集合类型的强大特性。
|
||||
|
||||
```shell
|
||||
# 准备测试数据
|
||||
SADD set1 "a" "b" "c" "d"
|
||||
SADD set2 "c" "d" "e" "f"
|
||||
SADD set3 "d" "e" "f" "g"
|
||||
|
||||
# 交集运算
|
||||
# 用法:SINTER key [key ...]
|
||||
SINTER set1 set2 # 返回 {"c", "d"}
|
||||
SINTER set1 set2 set3 # 返回 {"d"}
|
||||
|
||||
# 并集运算
|
||||
# 用法:SUNION key [key ...]
|
||||
SUNION set1 set2 # 返回 {"a", "b", "c", "d", "e", "f"}
|
||||
|
||||
# 差集运算
|
||||
# 用法:SDIFF key [key ...]
|
||||
SDIFF set1 set2 # 返回 {"a", "b"}
|
||||
SDIFF set2 set1 # 返回 {"e", "f"}
|
||||
|
||||
# 将运算结果存储到新集合
|
||||
# 用法:[SINTERSTORE | SUNIONSTORE | SDIFFSTORE] destination key [key ...]
|
||||
|
||||
SINTERSTORE result set1 set2
|
||||
SMEMBERS result # 查看交集结果
|
||||
```
|
||||
|
||||
### 随机操作
|
||||
|
||||
集合的随机操作常用于抽奖、推荐等场景。
|
||||
|
||||
```shell
|
||||
# 抽奖系统示例
|
||||
SADD lottery_pool "用户1" "用户2" "用户3" "用户4" "用户5"
|
||||
|
||||
# 随机抽取一个中奖者(不删除)
|
||||
SRANDMEMBER lottery_pool
|
||||
|
||||
# 随机抽取并移除中奖者
|
||||
SPOP lottery_pool
|
||||
|
||||
# 抽取多个中奖者
|
||||
SPOP lottery_pool 3
|
||||
```
|
||||
|
||||
## 有序集合 (Sorted Set)
|
||||
|
||||
### 有序集合的特点和应用
|
||||
|
||||
有序集合是 Redis 中最复杂的数据类型,它结合了集合和列表的特点。
|
||||
|
||||
**主要特点:**
|
||||
- **唯一性**:成员唯一,不允许重复
|
||||
- **有序性**:按分数(score)排序
|
||||
- **双重索引**:可以按成员或分数查找
|
||||
- **范围查询**:支持按分数或排名范围查询
|
||||
|
||||
**常见应用场景:**
|
||||
- 排行榜系统
|
||||
- 优先级队列
|
||||
- 时间线排序
|
||||
- 范围查询
|
||||
- 权重计算
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加和删除元素**
|
||||
|
||||
```shell
|
||||
# 添加元素(分数 成员)
|
||||
# 用法:ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
|
||||
ZADD leaderboard 1500 "玩家A"
|
||||
ZADD leaderboard 2300 "玩家B" 1800 "玩家C" 2100 "玩家D"
|
||||
# 批量添加
|
||||
ZADD leaderboard 1200 "玩家E" 1900 "玩家F" 2500 "玩家G"
|
||||
|
||||
# 删除元素
|
||||
# 用法:ZREM key member [member ...]
|
||||
ZREM leaderboard "玩家E"
|
||||
|
||||
# 按分数范围删除
|
||||
# 用法:ZREMRANGEBYSCORE key min max
|
||||
ZREMRANGEBYSCORE leaderboard 0 1000
|
||||
|
||||
# 按排名范围删除
|
||||
# 用法:ZREMRANGEBYRANK key start stop
|
||||
ZREMRANGEBYRANK leaderboard 0 2 # 删除前3名
|
||||
```
|
||||
|
||||
**查询操作**
|
||||
|
||||
```shell
|
||||
# 获取元素数量
|
||||
# 用法:ZCARD key
|
||||
ZCARD leaderboard
|
||||
|
||||
# 获取元素分数
|
||||
# 用法:ZSCORE key member
|
||||
ZSCORE leaderboard "玩家B"
|
||||
|
||||
# 获取元素排名
|
||||
# 用法:ZRANK key member # 正序排名(从0开始)
|
||||
# 用法:ZREVRANK key member # 逆序排名(从0开始)
|
||||
ZRANK leaderboard "玩家B"
|
||||
ZREVRANK leaderboard "玩家B"
|
||||
|
||||
# 统计分数范围内的元素数量
|
||||
# 用法:ZCOUNT key min max
|
||||
ZCOUNT leaderboard 1500 2000
|
||||
|
||||
# 获取分数范围内的元素数量(字典序)
|
||||
# 用法:ZLEXCOUNT key min max
|
||||
ZLEXCOUNT leaderboard a c
|
||||
```
|
||||
|
||||
### 范围操作
|
||||
|
||||
有序集合的范围操作是其最强大的功能之一。
|
||||
|
||||
**按排名范围查询**
|
||||
|
||||
```shell
|
||||
# 按排名获取元素(正序)
|
||||
# 用法:ZRANGE key start stop [WITHSCORES]
|
||||
ZRANGE leaderboard 0 2 # 获取前3名
|
||||
ZRANGE leaderboard 0 2 WITHSCORES # 获取前3名及分数
|
||||
ZRANGE leaderboard 0 -1 # 获取所有元素
|
||||
|
||||
# 按排名获取元素(逆序)
|
||||
# 用法:ZREVRANGE key start stop [WITHSCORES]
|
||||
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前3名(高分到低分)
|
||||
```
|
||||
|
||||
**按分数范围查询**
|
||||
|
||||
```shell
|
||||
# 按分数范围获取元素(正序)
|
||||
# 用法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
|
||||
ZRANGEBYSCORE leaderboard 1500 2000
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 WITHSCORES
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 LIMIT 0 5
|
||||
|
||||
# 按分数范围获取元素(逆序)
|
||||
# 用法:ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
|
||||
ZREVRANGEBYSCORE leaderboard 2000 1500 WITHSCORES
|
||||
|
||||
# 使用开区间
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 # 不包含1500和2000
|
||||
ZRANGEBYSCORE leaderboard -inf +inf # 所有元素
|
||||
```
|
||||
|
||||
**按字典序范围查询**
|
||||
|
||||
```shell
|
||||
# 按字典序范围获取元素
|
||||
# 用法:ZRANGEBYLEX key min max [LIMIT offset count]
|
||||
ZRANGEBYLEX leaderboard a c
|
||||
# 用法:ZREVRANGEBYLEX key max min [LIMIT offset count]
|
||||
ZREVRANGEBYLEX leaderboard c a
|
||||
|
||||
# 示例:相同分数的元素按字典序排列
|
||||
ZADD words 0 "apple" 0 "banana" 0 "cherry" 0 "date"
|
||||
ZRANGEBYLEX words a c # 获取a到c之间的单词
|
||||
ZRANGEBYLEX words banana date # 获取banana到date之间的单词
|
||||
```
|
||||
|
||||
### 排行榜应用
|
||||
|
||||
有序集合最典型的应用就是排行榜系统。
|
||||
|
||||
```shell
|
||||
# 游戏排行榜示例
|
||||
# 添加玩家分数
|
||||
ZADD game:leaderboard 15000 "张三"
|
||||
ZADD game:leaderboard 23000 "李四"
|
||||
ZADD game:leaderboard 18000 "王五"
|
||||
ZADD game:leaderboard 21000 "赵六"
|
||||
ZADD game:leaderboard 19000 "钱七"
|
||||
|
||||
# 获取排行榜前10名
|
||||
ZREVRANGE game:leaderboard 0 9 WITHSCORES
|
||||
|
||||
# 获取某玩家的排名
|
||||
ZREVRANK game:leaderboard "张三" # 返回排名(从0开始)
|
||||
|
||||
# 获取某玩家的分数
|
||||
ZSCORE game:leaderboard "张三"
|
||||
|
||||
# 增加玩家分数
|
||||
ZINCRBY game:leaderboard 1000 "张三"
|
||||
|
||||
# 获取分数范围内的玩家
|
||||
ZRANGEBYSCORE game:leaderboard 20000 25000 WITHSCORES
|
||||
|
||||
# 获取排名范围内的玩家
|
||||
ZREVRANGE game:leaderboard 10 19 WITHSCORES # 第11-20名
|
||||
```
|
||||
|
||||
## 哈希 (Hash)
|
||||
|
||||
### 哈希的特点和应用
|
||||
|
||||
Redis 哈希是一个键值对集合,特别适合存储对象。
|
||||
|
||||
**主要特点:**
|
||||
- **结构化存储**:键值对形式存储
|
||||
- **内存优化**:小哈希使用压缩编码
|
||||
- **部分更新**:可以只更新某个字段
|
||||
- **原子操作**:字段级别的原子操作
|
||||
|
||||
**常见应用场景:**
|
||||
- 用户信息存储
|
||||
- 配置信息管理
|
||||
- 购物车实现
|
||||
- 对象缓存
|
||||
- 计数器组合
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**设置和获取字段**
|
||||
|
||||
```shell
|
||||
# 用法:HSET key field value
|
||||
HSET user:1001 name "张三"
|
||||
HSET user:1001 age 28
|
||||
HSET user:1001 city "北京"
|
||||
HSET user:1001 email "zhangsan@example.com"
|
||||
|
||||
# 用法:HMSET key field value [field value ...]
|
||||
HMSET user:1002 name "李四" age 25 city "上海" email "lisi@example.com"
|
||||
|
||||
# 仅当字段不存在时设置
|
||||
# 用法:HSETNX key field value
|
||||
HSETNX user:1001 phone "13800138000"
|
||||
|
||||
# 获取单个字段
|
||||
# 用法:HGET key field
|
||||
HGET user:1001 name
|
||||
HGET user:1001 age
|
||||
|
||||
# 获取多个字段
|
||||
# 用法:HMGET key field [field ...]
|
||||
HMGET user:1001 name age city
|
||||
|
||||
# 获取所有字段和值
|
||||
# 用法:HGETALL key
|
||||
HGETALL user:1001
|
||||
|
||||
# 获取所有字段名
|
||||
# 用法:HKEYS key
|
||||
HKEYS user:1001
|
||||
|
||||
# 获取所有值
|
||||
# 用法:HVALS key
|
||||
HVALS user:1001
|
||||
```
|
||||
|
||||
**删除和检查字段**
|
||||
|
||||
```shell
|
||||
# 删除字段
|
||||
# 用法:HDEL key field [field ...]
|
||||
HDEL user:1001 email
|
||||
HDEL user:1001 phone city
|
||||
|
||||
# 检查字段是否存在
|
||||
# 用法:HEXISTS key field
|
||||
HEXISTS user:1001 name # 返回 1(存在)
|
||||
HEXISTS user:1001 phone # 返回 0(不存在)
|
||||
|
||||
# 获取字段数量
|
||||
# 用法:HLEN key
|
||||
HLEN user:1001
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
哈希支持高效的批量操作,减少网络往返次数。
|
||||
|
||||
```shell
|
||||
# 批量设置用户信息
|
||||
HMSET user:1003 \
|
||||
name "王五" \
|
||||
age 30 \
|
||||
city "广州" \
|
||||
email "wangwu@example.com" \
|
||||
phone "13900139000" \
|
||||
department "技术部" \
|
||||
position "高级工程师"
|
||||
|
||||
# 批量获取用户信息
|
||||
HMGET user:1003 name age city department position
|
||||
|
||||
# 获取完整用户信息
|
||||
HGETALL user:1003
|
||||
```
|
||||
|
||||
### 数值操作
|
||||
|
||||
哈希字段支持数值操作,常用于计数器场景。
|
||||
|
||||
```shell
|
||||
# 增加字段的数值
|
||||
# 用法:HINCRBY key field increment
|
||||
HSET user:1001 login_count 0
|
||||
HINCRBY user:1001 login_count 1 # login_count = 1
|
||||
HINCRBY user:1001 login_count 5 # login_count = 6
|
||||
|
||||
# 增加字段的浮点数值
|
||||
HINCRBYFLOAT key field increment
|
||||
HSET user:1001 balance 100.50
|
||||
HINCRBYFLOAT user:1001 balance 25.30 # balance = 125.80
|
||||
HINCRBYFLOAT user:1001 balance -10.00 # balance = 115.80
|
||||
```
|
||||
|
||||
### 对象存储应用
|
||||
|
||||
哈希非常适合存储对象数据,提供了比字符串更好的结构化存储。
|
||||
|
||||
**用户信息管理**
|
||||
|
||||
```shell
|
||||
# 创建用户信息
|
||||
HMSET user:1001 \
|
||||
name "张三" \
|
||||
age 28 \
|
||||
email "zhangsan@example.com" \
|
||||
city "北京" \
|
||||
department "研发部" \
|
||||
position "软件工程师" \
|
||||
salary 15000 \
|
||||
join_date "2023-01-15" \
|
||||
status "active"
|
||||
|
||||
# 更新用户信息
|
||||
HSET user:1001 age 29
|
||||
HSET user:1001 salary 16000
|
||||
HSET user:1001 position "高级软件工程师"
|
||||
|
||||
# 获取用户基本信息
|
||||
HMGET user:1001 name age email city
|
||||
|
||||
# 获取用户工作信息
|
||||
HMGET user:1001 department position salary
|
||||
|
||||
# 检查用户状态
|
||||
HGET user:1001 status
|
||||
```
|
||||
|
||||
**购物车实现**
|
||||
|
||||
```shell
|
||||
# 用户购物车:cart:user_id,字段为商品ID,值为数量
|
||||
HSET cart:1001 product:123 2
|
||||
HSET cart:1001 product:456 1
|
||||
HSET cart:1001 product:789 3
|
||||
|
||||
# 增加商品数量
|
||||
HINCRBY cart:1001 product:123 1 # 数量变为3
|
||||
|
||||
# 减少商品数量
|
||||
HINCRBY cart:1001 product:456 -1 # 数量变为0
|
||||
|
||||
# 删除商品(数量为0时)
|
||||
HDEL cart:1001 product:456
|
||||
|
||||
# 获取购物车所有商品
|
||||
HGETALL cart:1001
|
||||
|
||||
# 获取购物车商品数量
|
||||
HLEN cart:1001
|
||||
|
||||
# 清空购物车
|
||||
DEL cart:1001
|
||||
```
|
||||
|
||||
**配置信息管理**
|
||||
|
||||
```shell
|
||||
# 应用配置
|
||||
HMSET app:config \
|
||||
database_host "localhost" \
|
||||
database_port 3306 \
|
||||
database_name "myapp" \
|
||||
redis_host "localhost" \
|
||||
redis_port 6379 \
|
||||
cache_ttl 3600 \
|
||||
max_connections 100 \
|
||||
debug_mode "false"
|
||||
|
||||
# 获取数据库配置
|
||||
HMGET app:config database_host database_port database_name
|
||||
|
||||
# 获取缓存配置
|
||||
HMGET app:config redis_host redis_port cache_ttl
|
||||
|
||||
# 更新配置
|
||||
HSET app:config debug_mode "true"
|
||||
HSET app:config max_connections 200
|
||||
|
||||
# 获取所有配置
|
||||
HGETALL app:config
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
**需求描述:**
|
||||
使用 Redis 的不同数据类型构建一个完整的用户信息存储系统,包括用户基本信息、用户标签、用户行为记录等。
|
||||
|
||||
**实践细节和结果验证:**
|
||||
|
||||
```shell
|
||||
|
||||
# 1. 用户基本信息(使用哈希)
|
||||
redis-cli << 'EOF'
|
||||
# 创建用户基本信息
|
||||
HMSET user:1001 \
|
||||
name "张三" \
|
||||
age 28 \
|
||||
email "zhangsan@example.com" \
|
||||
phone "13800138000" \
|
||||
city "北京" \
|
||||
department "技术部" \
|
||||
position "高级工程师" \
|
||||
join_date "2023-01-15" \
|
||||
status "active"
|
||||
|
||||
HMSET user:1002 \
|
||||
name "李四" \
|
||||
age 25 \
|
||||
email "lisi@example.com" \
|
||||
phone "13900139000" \
|
||||
city "上海" \
|
||||
department "产品部" \
|
||||
position "产品经理" \
|
||||
join_date "2023-03-20" \
|
||||
status "active"
|
||||
|
||||
# 获取用户信息
|
||||
HGETALL user:1001
|
||||
HMGET user:1002 name age department position
|
||||
EOF
|
||||
|
||||
# 2. 用户标签系统(使用集合)
|
||||
redis-cli << 'EOF'
|
||||
# 为用户添加标签
|
||||
SADD user:1001:tags "技术专家" "Redis" "Python" "团队领导" "创新者"
|
||||
SADD user:1002:tags "产品专家" "用户体验" "数据分析" "沟通能力" "创新者"
|
||||
SADD user:1003:tags "设计师" "UI/UX" "创意" "用户体验" "创新者"
|
||||
|
||||
# 查看用户标签
|
||||
SMEMBERS user:1001:tags
|
||||
SMEMBERS user:1002:tags
|
||||
|
||||
# 查找共同标签
|
||||
SINTER user:1001:tags user:1002:tags
|
||||
SINTER user:1002:tags user:1003:tags
|
||||
|
||||
# 查找所有标签
|
||||
SUNION user:1001:tags user:1002:tags user:1003:tags
|
||||
EOF
|
||||
|
||||
# 3. 用户行为记录(使用列表)
|
||||
redis-cli << 'EOF'
|
||||
# 记录用户行为(最新的在前面)
|
||||
LPUSH user:1001:actions "登录:2024-01-15 09:00:00"
|
||||
LPUSH user:1001:actions "查看文档:Redis教程"
|
||||
LPUSH user:1001:actions "编辑代码:user_service.py"
|
||||
LPUSH user:1001:actions "提交代码:修复缓存bug"
|
||||
LPUSH user:1001:actions "参加会议:技术分享"
|
||||
|
||||
LPUSH user:1002:actions "登录:2024-01-15 09:30:00"
|
||||
LPUSH user:1002:actions "查看报表:用户增长数据"
|
||||
LPUSH user:1002:actions "创建需求:新功能设计"
|
||||
LPUSH user:1002:actions "审核设计:UI界面"
|
||||
|
||||
# 获取用户最近行为
|
||||
LRANGE user:1001:actions 0 4
|
||||
LRANGE user:1002:actions 0 4
|
||||
|
||||
# 保持行为记录在合理范围内(最多100条)
|
||||
LTRIM user:1001:actions 0 99
|
||||
LTRIM user:1002:actions 0 99
|
||||
EOF
|
||||
|
||||
# 4. 用户积分排行(使用有序集合)
|
||||
redis-cli << 'EOF'
|
||||
# 设置用户积分
|
||||
ZADD user:points 1500 "user:1001"
|
||||
ZADD user:points 2300 "user:1002"
|
||||
ZADD user:points 1800 "user:1003"
|
||||
ZADD user:points 2100 "user:1004"
|
||||
ZADD user:points 1200 "user:1005"
|
||||
|
||||
# 增加用户积分
|
||||
ZINCRBY user:points 200 "user:1001"
|
||||
ZINCRBY user:points 150 "user:1003"
|
||||
|
||||
# 查看积分排行榜
|
||||
ZREVRANGE user:points 0 -1 WITHSCORES
|
||||
|
||||
# 查看用户排名
|
||||
ZREVRANK user:points "user:1001"
|
||||
ZREVRANK user:points "user:1002"
|
||||
|
||||
# 查看用户积分
|
||||
ZSCORE user:points "user:1001"
|
||||
EOF
|
||||
|
||||
# 5. 用户会话管理(使用字符串)
|
||||
redis-cli << 'EOF'
|
||||
# 创建用户会话
|
||||
SET session:abc123 "user:1001" EX 3600
|
||||
SET session:def456 "user:1002" EX 3600
|
||||
|
||||
# 检查会话
|
||||
GET session:abc123
|
||||
TTL session:abc123
|
||||
|
||||
# 延长会话
|
||||
EXPIRE session:abc123 7200
|
||||
TTL session:abc123
|
||||
EOF
|
||||
|
||||
# 6. 用户统计信息(使用哈希)
|
||||
redis-cli << 'EOF'
|
||||
# 用户统计数据
|
||||
HMSET user:1001:stats \
|
||||
login_count 45 \
|
||||
last_login "2024-01-15 09:00:00" \
|
||||
total_actions 156 \
|
||||
articles_read 23 \
|
||||
code_commits 89
|
||||
|
||||
HMSET user:1002:stats \
|
||||
login_count 38 \
|
||||
last_login "2024-01-15 09:30:00" \
|
||||
total_actions 142 \
|
||||
reports_created 15 \
|
||||
meetings_attended 67
|
||||
|
||||
# 更新统计数据
|
||||
HINCRBY user:1001:stats login_count 1
|
||||
HSET user:1001:stats last_login "2024-01-15 14:30:00"
|
||||
HINCRBY user:1001:stats total_actions 1
|
||||
|
||||
# 查看统计数据
|
||||
HGETALL user:1001:stats
|
||||
HMGET user:1002:stats login_count last_login total_actions
|
||||
EOF
|
||||
|
||||
# 查找活跃用户(积分>1500)
|
||||
redis-cli ZRANGEBYSCORE user:points 1500 +inf WITHSCORES
|
||||
|
||||
# 查找技术相关用户
|
||||
redis-cli SISMEMBER user:1001:tags "技术专家"
|
||||
redis-cli SISMEMBER user:1002:tags "技术专家"
|
||||
|
||||
# 获取用户完整信息
|
||||
redis-cli HMGET user:1001 name age department position
|
||||
redis-cli SMEMBERS user:1001:tags
|
||||
redis-cli LRANGE user:1001:actions 0 2
|
||||
redis-cli ZSCORE user:points "user:1001"
|
||||
redis-cli ZREVRANK user:points "user:1001"
|
||||
redis-cli HMGET user:1001:stats login_count total_actions
|
||||
|
||||
# 清理测试数据
|
||||
redis-cli << 'EOF'
|
||||
DEL user:1001 user:1002 user:1003
|
||||
DEL user:1001:tags user:1002:tags user:1003:tags
|
||||
DEL user:1001:actions user:1002:actions
|
||||
DEL user:points
|
||||
DEL session:abc123 session:def456
|
||||
DEL user:1001:stats user:1002:stats
|
||||
EOF
|
||||
```
|
590
数据库/Redis_2025/04_Redis基本命令.md
Normal file
590
数据库/Redis_2025/04_Redis基本命令.md
Normal file
@@ -0,0 +1,590 @@
|
||||
# Redis 基本命令
|
||||
|
||||
Redis 提供了丰富的命令来管理键、数据库、事务和脚本。掌握这些基本命令是高效使用 Redis 的基础。
|
||||
|
||||
## 键操作命令
|
||||
|
||||
### 键的命名规范
|
||||
|
||||
良好的键命名规范是 Redis 应用的重要基础,有助于提高可维护性和性能。
|
||||
|
||||
**推荐的命名规范:**
|
||||
|
||||
1. **使用冒号分隔层级**
|
||||
```shell
|
||||
user:1001:profile
|
||||
product:category:electronics
|
||||
cache:query:user_list
|
||||
session:web:abc123
|
||||
```
|
||||
|
||||
2. **使用有意义的前缀**
|
||||
```shell
|
||||
# 按功能分类
|
||||
cache:* # 缓存数据
|
||||
session:* # 会话数据
|
||||
config:* # 配置信息
|
||||
temp:* # 临时数据
|
||||
|
||||
# 按业务分类
|
||||
user:* # 用户相关
|
||||
order:* # 订单相关
|
||||
product:* # 商品相关
|
||||
```
|
||||
|
||||
3. **避免特殊字符**
|
||||
```shell
|
||||
# 推荐
|
||||
user:1001:name
|
||||
article:2023:01:15
|
||||
|
||||
# 不推荐
|
||||
user 1001 name
|
||||
article@2023#01#15
|
||||
```
|
||||
|
||||
4. **保持一致性**
|
||||
```shell
|
||||
# 统一使用小写
|
||||
user:profile:name
|
||||
user:profile:email
|
||||
|
||||
# 统一日期格式
|
||||
log:2024:01:15
|
||||
report:2024:01:15
|
||||
```
|
||||
|
||||
### 键的查询和遍历
|
||||
|
||||
**基本查询命令**
|
||||
|
||||
```shell
|
||||
# 检查键是否存在
|
||||
# 用法:EXISTS key [key ...]
|
||||
EXISTS user:1001
|
||||
EXISTS user:1001 user:1002 user:1003 # 返回存在的键数量
|
||||
|
||||
# 获取键的类型
|
||||
# 用法:TYPE key
|
||||
TYPE user:1001 # 返回 hash
|
||||
TYPE user:tags # 返回 set
|
||||
TYPE message_queue # 返回 list
|
||||
|
||||
# 获取键的编码方式
|
||||
# 用法:OBJECT ENCODING key
|
||||
OBJECT ENCODING user:1001
|
||||
OBJECT ENCODING counter
|
||||
|
||||
# 获取键的空闲时间(秒)
|
||||
# 用法:OBJECT IDLETIME key
|
||||
# 注意:返回值为键的空闲时间,单位为秒。
|
||||
OBJECT IDLETIME user:1001
|
||||
|
||||
# 获取键的引用计数
|
||||
# 用法:OBJECT REFCOUNT key
|
||||
# 注意:返回值为键的引用计数,单位为秒。
|
||||
OBJECT REFCOUNT user:1001
|
||||
```
|
||||
|
||||
**模式匹配查询**
|
||||
|
||||
```shell
|
||||
# 查找匹配模式的键(谨慎使用)
|
||||
# 用法:KEYS pattern
|
||||
KEYS user:* # 查找所有用户相关的键
|
||||
KEYS cache:* # 查找所有缓存键
|
||||
KEYS *:1001:* # 查找包含1001的键
|
||||
KEYS user:100? # ?匹配单个字符
|
||||
KEYS user:[12]* # []匹配字符集合
|
||||
|
||||
# 更安全的遍历方式(推荐)
|
||||
# 用法:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
|
||||
SCAN 0 MATCH user:* COUNT 10
|
||||
SCAN 0 MATCH cache:* COUNT 20
|
||||
SCAN 0 TYPE string COUNT 10
|
||||
|
||||
# 示例:安全遍历所有用户键
|
||||
redis-cli --scan --pattern "user:*"
|
||||
```
|
||||
|
||||
**随机获取键**
|
||||
|
||||
```shell
|
||||
# 随机返回一个键
|
||||
# 用法:RANDOMKEY
|
||||
|
||||
# 示例:随机获取键进行采样
|
||||
RANDOMKEY
|
||||
RANDOMKEY
|
||||
RANDOMKEY
|
||||
```
|
||||
|
||||
### 键的过期设置
|
||||
|
||||
Redis 支持为键设置过期时间,这是内存管理和缓存实现的重要功能。
|
||||
|
||||
**设置过期时间**
|
||||
|
||||
```shell
|
||||
# 设置键的过期时间(秒)
|
||||
# 用法:EXPIRE key seconds
|
||||
EXPIRE user:1001 3600 # 1小时后过期
|
||||
EXPIRE temp_data 300 # 5分钟后过期
|
||||
|
||||
# 设置键的过期时间(毫秒)
|
||||
# 用法:PEXPIRE key milliseconds
|
||||
PEXPIRE temp_data 30000 # 30秒后过期
|
||||
|
||||
# 设置键的过期时间戳(秒)
|
||||
# 用法:EXPIREAT key timestamp
|
||||
EXPIREAT temp_data 1705123200 # 指定时间戳过期
|
||||
|
||||
# 设置键的过期时间戳(毫秒)
|
||||
# 用法:PEXPIREAT key milliseconds-timestamp
|
||||
PEXPIREAT temp_data 1705123200000
|
||||
|
||||
# 创建键时同时设置过期时间
|
||||
# 用法:SETEX key seconds value
|
||||
SETEX key seconds value
|
||||
SETEX session:abc123 3600 "user_data"
|
||||
|
||||
# 创建键时同时设置过期时间(毫秒)
|
||||
# 用法:PSETEX key milliseconds value
|
||||
PSETEX session:def456 3600000 "user_data"
|
||||
```
|
||||
|
||||
**查询过期时间**
|
||||
|
||||
```shell
|
||||
# 获取键的剩余生存时间(秒)
|
||||
# 用法:TTL key
|
||||
TTL session:abc123 # 返回剩余秒数,-1表示永不过期,-2表示不存在
|
||||
|
||||
# 获取键的剩余生存时间(毫秒)
|
||||
# 用法:PTTL key
|
||||
PTTL session:abc123 # 返回剩余毫秒数
|
||||
|
||||
# 示例:检查多个键的过期时间
|
||||
SETEX key1 100 "value1"
|
||||
SETEX key2 200 "value2"
|
||||
SET key3 "value3" # 永不过期
|
||||
TTL key1
|
||||
TTL key2
|
||||
TTL key3
|
||||
```
|
||||
|
||||
**移除过期时间**
|
||||
|
||||
```shell
|
||||
# 移除键的过期时间,使其永久存在
|
||||
# 用法:PERSIST key
|
||||
PERSIST session:abc123
|
||||
SETEX temp_key 300 "临时数据"
|
||||
TTL temp_key # 查看过期时间
|
||||
PERSIST temp_key # 移除过期时间
|
||||
TTL temp_key # 返回-1,表示永不过期
|
||||
```
|
||||
|
||||
### 键的删除和重命名
|
||||
|
||||
**删除键**
|
||||
|
||||
```shell
|
||||
# 删除一个或多个键
|
||||
# 用法:DEL key [key ...]
|
||||
DEL user:1001
|
||||
DEL cache:query1 cache:query2 cache:query3
|
||||
|
||||
# 异步删除键(适用于大键)
|
||||
# 用法:UNLINK key [key ...]
|
||||
UNLINK large_list large_set large_hash
|
||||
|
||||
# 删除所有键(危险操作)
|
||||
FLUSHDB # 删除当前数据库所有键
|
||||
FLUSHALL # 删除所有数据库所有键
|
||||
```
|
||||
|
||||
**重命名键**
|
||||
|
||||
```shell
|
||||
# 重命名键
|
||||
# 用法:RENAME key newkey
|
||||
RENAME user:1001 user:1002
|
||||
SET old_name "value"
|
||||
RENAME old_name new_name
|
||||
GET new_name
|
||||
|
||||
# 仅当新键不存在时重命名
|
||||
# 用法:RENAMENX key newkey
|
||||
RENAMENX source_key target_key # 成功返回1
|
||||
RENAMENX source_key target_key # 失败返回0(target_key已存在)
|
||||
```
|
||||
|
||||
## 数据库操作
|
||||
|
||||
### 多数据库概念
|
||||
|
||||
Redis 支持多个数据库,默认有16个数据库(编号0-15),可以通过配置文件修改数量。
|
||||
|
||||
**数据库特点:**
|
||||
- 每个数据库都是独立的键空间
|
||||
- 不同数据库间的键名可以相同
|
||||
- 默认连接到数据库0
|
||||
- 数据库间不支持关联查询
|
||||
|
||||
**使用场景:**
|
||||
- 区分不同环境(开发、测试、生产)
|
||||
- 分离不同类型的数据
|
||||
- 临时数据隔离
|
||||
|
||||
### 数据库切换
|
||||
|
||||
```shell
|
||||
# 切换到指定数据库
|
||||
# 用法:SELECT index
|
||||
SELECT 0 # 切换到数据库0
|
||||
SELECT 1 # 切换到数据库1
|
||||
SELECT 15 # 切换到数据库15
|
||||
|
||||
# 示例:在不同数据库中存储数据
|
||||
SELECT 0
|
||||
SET app:env "production"
|
||||
SET app:version "1.0.0"
|
||||
|
||||
SELECT 1
|
||||
SET app:env "development"
|
||||
SET app:version "1.1.0-dev"
|
||||
|
||||
SELECT 2
|
||||
SET temp:data "临时测试数据"
|
||||
|
||||
# 验证数据隔离
|
||||
SELECT 0
|
||||
GET app:env # 返回 "production"
|
||||
|
||||
SELECT 1
|
||||
GET app:env # 返回 "development"
|
||||
```
|
||||
|
||||
### 数据库清空
|
||||
|
||||
```shell
|
||||
# 清空当前数据库
|
||||
FLUSHDB
|
||||
|
||||
# 异步清空当前数据库
|
||||
FLUSHDB ASYNC
|
||||
|
||||
# 清空所有数据库
|
||||
FLUSHALL
|
||||
|
||||
# 异步清空所有数据库
|
||||
FLUSHALL ASYNC
|
||||
|
||||
# 示例:安全的数据库清理
|
||||
SELECT 15 # 切换到测试数据库
|
||||
SET test:key "test_value"
|
||||
DBSIZE # 查看键数量
|
||||
FLUSHDB # 清空测试数据库
|
||||
DBSIZE # 确认清空结果
|
||||
```
|
||||
|
||||
### 数据库信息查看
|
||||
|
||||
```shell
|
||||
# 获取当前数据库键的数量
|
||||
DBSIZE
|
||||
|
||||
# 获取服务器信息
|
||||
INFO [section]
|
||||
INFO # 获取所有信息
|
||||
INFO server # 服务器信息
|
||||
INFO clients # 客户端信息
|
||||
INFO memory # 内存信息
|
||||
INFO keyspace # 键空间信息
|
||||
|
||||
# 获取配置信息
|
||||
CONFIG GET parameter
|
||||
CONFIG GET databases # 查看数据库数量配置
|
||||
CONFIG GET maxmemory # 查看最大内存配置
|
||||
|
||||
# 示例:查看各数据库使用情况
|
||||
INFO keyspace
|
||||
# 输出示例:
|
||||
# db0:keys=100,expires=10,avg_ttl=3600000
|
||||
# db1:keys=50,expires=5,avg_ttl=1800000
|
||||
```
|
||||
|
||||
## 事务操作
|
||||
|
||||
### 事务的概念
|
||||
|
||||
Redis 事务是一组命令的集合,这些命令会被顺序执行,具有以下特点:
|
||||
|
||||
**事务特性:**
|
||||
- **原子性**:事务中的命令要么全部执行,要么全部不执行
|
||||
- **一致性**:事务执行前后,数据保持一致状态
|
||||
- **隔离性**:事务执行过程中不会被其他命令干扰
|
||||
- **持久性**:事务执行结果会被持久化(如果启用了持久化)
|
||||
|
||||
**Redis 事务限制:**
|
||||
- 不支持回滚
|
||||
- 命令错误不会影响其他命令执行
|
||||
- 不支持嵌套事务
|
||||
|
||||
### MULTI/EXEC 命令
|
||||
|
||||
**基本事务操作**
|
||||
|
||||
```shell
|
||||
# 开始事务
|
||||
MULTI
|
||||
|
||||
# 添加命令到事务队列
|
||||
SET account:1001:balance 1000
|
||||
SET account:1002:balance 500
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
|
||||
# 执行事务
|
||||
EXEC
|
||||
|
||||
# 取消事务
|
||||
# DISCARD
|
||||
```
|
||||
|
||||
**完整事务示例**
|
||||
|
||||
```shell
|
||||
# 银行转账事务示例
|
||||
MULTI
|
||||
GET account:1001:balance # 检查余额
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
SET transfer:log "1001->1002:100"
|
||||
EXEC
|
||||
|
||||
# 批量用户创建事务
|
||||
MULTI
|
||||
HMSET user:2001 name "用户A" email "usera@example.com" status "active"
|
||||
HMSET user:2002 name "用户B" email "userb@example.com" status "active"
|
||||
SADD active_users "user:2001" "user:2002"
|
||||
INCR total_users
|
||||
EXEC
|
||||
```
|
||||
|
||||
**事务错误处理**
|
||||
|
||||
```shell
|
||||
# 语法错误示例(事务不会执行)
|
||||
MULTI
|
||||
SET key1 value1
|
||||
INVALID_COMMAND # 语法错误
|
||||
SET key2 value2
|
||||
EXEC # 返回错误,事务不执行
|
||||
|
||||
# 运行时错误示例(其他命令仍会执行)
|
||||
MULTI
|
||||
SET string_key "hello"
|
||||
LPUSH string_key "world" # 类型错误,但不影响其他命令
|
||||
SET another_key "value"
|
||||
EXEC # 部分命令执行成功
|
||||
```
|
||||
|
||||
### WATCH 命令
|
||||
|
||||
WATCH 命令用于实现乐观锁,监视键的变化。
|
||||
|
||||
**基本用法**
|
||||
|
||||
```shell
|
||||
# 监视键
|
||||
WATCH key [key ...]
|
||||
WATCH account:1001:balance account:1002:balance
|
||||
|
||||
# 开始事务
|
||||
MULTI
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
EXEC # 如果被监视的键发生变化,事务不会执行
|
||||
|
||||
# 取消监视
|
||||
UNWATCH
|
||||
```
|
||||
|
||||
**乐观锁实现**
|
||||
|
||||
```shell
|
||||
# 实现安全的计数器增加
|
||||
WATCH counter
|
||||
val=$(redis-cli GET counter)
|
||||
if [ "$val" -lt 100 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli INCR counter
|
||||
redis-cli EXEC
|
||||
else
|
||||
redis-cli UNWATCH
|
||||
echo "计数器已达到上限"
|
||||
fi
|
||||
```
|
||||
|
||||
**复杂事务示例**
|
||||
|
||||
```shell
|
||||
# 库存扣减事务(防止超卖)
|
||||
WATCH product:1001:stock
|
||||
stock=$(redis-cli GET product:1001:stock)
|
||||
if [ "$stock" -ge 1 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli DECR product:1001:stock
|
||||
redis-cli LPUSH order:queue "order:$(date +%s)"
|
||||
redis-cli INCR sales:total
|
||||
result=$(redis-cli EXEC)
|
||||
if [ "$result" != "" ]; then
|
||||
echo "购买成功"
|
||||
else
|
||||
echo "购买失败,请重试"
|
||||
fi
|
||||
else
|
||||
redis-cli UNWATCH
|
||||
echo "库存不足"
|
||||
fi
|
||||
```
|
||||
|
||||
### 事务的特性和限制
|
||||
|
||||
**事务特性验证**
|
||||
|
||||
```shell
|
||||
# 原子性验证
|
||||
SET test:atomic "initial"
|
||||
MULTI
|
||||
SET test:atomic "step1"
|
||||
SET test:atomic "step2"
|
||||
SET test:atomic "final"
|
||||
EXEC
|
||||
GET test:atomic # 返回 "final"
|
||||
|
||||
# 隔离性验证(在事务执行期间,其他客户端的命令不会干扰)
|
||||
# 客户端1:
|
||||
MULTI
|
||||
SET test:isolation "value1"
|
||||
# 暂停,不执行EXEC
|
||||
|
||||
# 客户端2:
|
||||
SET test:isolation "value2" # 这个命令会立即执行
|
||||
|
||||
# 客户端1继续:
|
||||
EXEC
|
||||
GET test:isolation # 返回 "value1"(事务中的值覆盖了客户端2的修改)
|
||||
```
|
||||
|
||||
**事务限制示例**
|
||||
|
||||
```shell
|
||||
# 无回滚特性
|
||||
SET balance 1000
|
||||
MULTI
|
||||
DECRBY balance 100 # 成功
|
||||
LPUSH balance "error" # 类型错误,但不影响前面的命令
|
||||
DECRBY balance 50 # 成功
|
||||
EXEC
|
||||
GET balance # 返回 "850"(前面和后面的命令都执行了)
|
||||
|
||||
# 条件执行限制(Redis事务不支持if-else逻辑)
|
||||
# 需要在应用层实现条件判断
|
||||
balance=$(redis-cli GET account:balance)
|
||||
if [ "$balance" -gt 100 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli DECRBY account:balance 100
|
||||
redis-cli SET last:transaction "withdraw:100"
|
||||
redis-cli EXEC
|
||||
else
|
||||
echo "余额不足"
|
||||
fi
|
||||
```
|
||||
|
||||
## 脚本操作
|
||||
|
||||
### Lua 脚本简介
|
||||
|
||||
Redis 内嵌了 Lua 解释器,支持执行 Lua 脚本。Lua 脚本的优势:
|
||||
|
||||
**主要优势:**
|
||||
- **原子性**:脚本执行过程中不会被其他命令干扰
|
||||
- **减少网络开销**:多个命令在服务器端执行
|
||||
- **复杂逻辑**:支持条件判断、循环等复杂逻辑
|
||||
- **性能优化**:避免多次网络往返
|
||||
|
||||
**使用场景:**
|
||||
- 复杂的原子操作
|
||||
- 条件判断和循环
|
||||
- 批量数据处理
|
||||
- 限流算法实现
|
||||
- 分布式锁
|
||||
|
||||
### EVAL 和 EVALSHA 命令
|
||||
|
||||
**EVAL 命令基本用法**
|
||||
|
||||
```shell
|
||||
# EVAL script numkeys key [key ...] arg [arg ...]
|
||||
# script: Lua脚本
|
||||
# numkeys: 键的数量
|
||||
# key: 键名
|
||||
# arg: 参数
|
||||
|
||||
# 简单示例:设置键值并返回
|
||||
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
|
||||
|
||||
# 获取并增加计数器
|
||||
EVAL "local val = redis.call('GET', KEYS[1]) or 0; return redis.call('INCR', KEYS[1])" 1 counter
|
||||
|
||||
# 条件设置(仅当键不存在时设置)
|
||||
EVAL "if redis.call('EXISTS', KEYS[1]) == 0 then return redis.call('SET', KEYS[1], ARGV[1]) else return nil end" 1 newkey newvalue
|
||||
```
|
||||
|
||||
|
||||
**EVALSHA 命令**
|
||||
|
||||
```shell
|
||||
# 加载脚本并获取SHA1值
|
||||
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
|
||||
# 返回:"6b1bf486c81ceb7edf3c093f4c48582e38c0e791"
|
||||
|
||||
# 使用SHA1值执行脚本
|
||||
EVALSHA 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 1 mykey
|
||||
|
||||
# 检查脚本是否存在
|
||||
SCRIPT EXISTS sha1 [sha1 ...]
|
||||
SCRIPT EXISTS 6b1bf486c81ceb7edf3c093f4c48582e38c0e791
|
||||
|
||||
# 清除脚本缓存
|
||||
SCRIPT FLUSH
|
||||
|
||||
# 杀死正在执行的脚本
|
||||
SCRIPT KILL
|
||||
```
|
||||
|
||||
### 脚本缓存
|
||||
|
||||
Redis 会缓存已执行的脚本,提高重复执行的性能。
|
||||
|
||||
```shell
|
||||
# 预加载常用脚本
|
||||
incr_script_sha=$(redis-cli SCRIPT LOAD "return redis.call('INCR', KEYS[1])")
|
||||
echo "增加计数器脚本SHA: $incr_script_sha"
|
||||
|
||||
# 使用缓存的脚本
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
|
||||
# 检查脚本缓存状态
|
||||
redis-cli SCRIPT EXISTS $incr_script_sha
|
||||
|
||||
# 获取脚本缓存统计
|
||||
redis-cli INFO memory | grep script
|
||||
```
|
||||
|
||||
## 实践操作
|
502
数据库/Redis_2025/05_Redis持久化.md
Normal file
502
数据库/Redis_2025/05_Redis持久化.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Redis 持久化
|
||||
|
||||
Redis 是一个内存数据库,为了保证数据的持久性和可靠性,Redis 提供了多种持久化机制。理解和正确配置持久化是 Redis 生产环境部署的关键。
|
||||
|
||||
## RDB 持久化
|
||||
|
||||
### RDB 概述
|
||||
|
||||
RDB(Redis Database)是 Redis 的一种持久化方式,它将某个时间点的数据集快照保存到磁盘上。
|
||||
|
||||
**RDB 特点:**
|
||||
- **快照方式**:保存某个时间点的完整数据集
|
||||
- **紧凑格式**:二进制格式,文件体积小
|
||||
- **恢复快速**:启动时直接加载,恢复速度快
|
||||
- **性能影响小**:通过 fork 子进程执行,对主进程影响小
|
||||
- **数据丢失风险**:两次快照间的数据可能丢失
|
||||
|
||||
**适用场景:**
|
||||
- 对数据完整性要求不高的场景
|
||||
- 需要定期备份的场景
|
||||
- 主从复制的从节点
|
||||
- 数据恢复速度要求高的场景
|
||||
|
||||
### RDB 配置
|
||||
|
||||
**自动触发配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 配置文件中的 RDB 相关配置
|
||||
|
||||
# 自动保存条件(save <seconds> <changes>)
|
||||
# 在指定时间内,如果至少有指定数量的键发生变化,则执行 BGSAVE
|
||||
save 900 1 # 900秒内至少1个键发生变化
|
||||
save 300 10 # 300秒内至少10个键发生变化
|
||||
save 60 10000 # 60秒内至少10000个键发生变化
|
||||
|
||||
# 禁用自动保存(注释掉所有 save 行或使用空字符串)
|
||||
# save ""
|
||||
|
||||
# RDB 文件名:dump.rdb
|
||||
|
||||
# RDB 文件保存目录:/var/lib/redis
|
||||
|
||||
# 当 RDB 持久化出现错误时,是否停止接受写命令
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
# 是否压缩 RDB 文件
|
||||
rdbcompression yes
|
||||
|
||||
# 是否对 RDB 文件进行校验和检查
|
||||
rdbchecksum yes
|
||||
```
|
||||
|
||||
**手动触发配置**
|
||||
|
||||
```shell
|
||||
# 查看当前 RDB 配置
|
||||
redis-cli CONFIG GET save
|
||||
redis-cli CONFIG GET dbfilename
|
||||
redis-cli CONFIG GET dir
|
||||
|
||||
# 动态修改 RDB 配置
|
||||
redis-cli CONFIG SET save "900 1 300 10 60 10000"
|
||||
redis-cli CONFIG SET dbfilename "backup.rdb"
|
||||
redis-cli CONFIG SET rdbcompression yes
|
||||
|
||||
# 保存配置到文件
|
||||
redis-cli CONFIG REWRITE
|
||||
```
|
||||
|
||||
### RDB 操作命令
|
||||
|
||||
**手动生成 RDB 文件**
|
||||
|
||||
```shell
|
||||
# SAVE 命令(阻塞方式)
|
||||
# 在主进程中执行,会阻塞所有客户端请求
|
||||
redis-cli SAVE
|
||||
|
||||
# BGSAVE 命令(非阻塞方式,推荐)
|
||||
# 在后台子进程中执行,不阻塞主进程
|
||||
redis-cli BGSAVE
|
||||
|
||||
# 检查 BGSAVE 是否正在执行
|
||||
redis-cli LASTSAVE # 返回最后一次成功执行 SAVE/BGSAVE 的时间戳
|
||||
|
||||
# 获取 RDB 相关信息
|
||||
redis-cli INFO persistence
|
||||
```
|
||||
|
||||
**RDB 文件管理**
|
||||
|
||||
```shell
|
||||
# 查看 RDB 文件信息
|
||||
ls -la /var/lib/redis/dump.rdb
|
||||
|
||||
# 备份 RDB 文件
|
||||
cp /var/lib/redis/dump.rdb /backup/redis/dump_$(date +%Y%m%d_%H%M%S).rdb
|
||||
|
||||
# 验证 RDB 文件完整性
|
||||
redis-check-rdb /var/lib/redis/dump.rdb
|
||||
|
||||
# 从 RDB 文件恢复数据
|
||||
# 1. 停止 Redis 服务
|
||||
systemctl stop redis
|
||||
|
||||
# 2. 替换 RDB 文件
|
||||
cp /backup/redis/dump_xxx.rdb /var/lib/redis/dump.rdb
|
||||
chown redis:redis /var/lib/redis/dump.rdb
|
||||
|
||||
# 3. 启动 Redis 服务
|
||||
systemctl start redis
|
||||
|
||||
# 4. 验证数据恢复
|
||||
redis-cli DBSIZE
|
||||
```
|
||||
|
||||
### RDB 文件格式
|
||||
|
||||
**RDB 文件结构**
|
||||
|
||||
```shell
|
||||
RDB 文件结构:
|
||||
+-------+-------------+-----------+-----------------+-----+-----------+
|
||||
| REDIS | RDB-VERSION | SELECT-DB | KEY-VALUE-PAIRS | EOF | CHECK-SUM |
|
||||
+-------+-------------+-----------+-----------------+-----+-----------+
|
||||
|
||||
详细说明:
|
||||
- REDIS: 文件标识符(5字节)
|
||||
- RDB-VERSION: RDB 版本号(4字节)
|
||||
- SELECT-DB: 数据库选择器
|
||||
- KEY-VALUE-PAIRS: 键值对数据
|
||||
- EOF: 文件结束标识(1字节)
|
||||
- CHECK-SUM: 校验和(8字节)
|
||||
```
|
||||
|
||||
**分析 RDB 文件**
|
||||
|
||||
```bash
|
||||
# 使用 redis-rdb-tools 分析 RDB 文件
|
||||
pip install rdbtools python-lzf
|
||||
|
||||
# 将 RDB 转换为 JSON 格式
|
||||
rdb --command json /var/lib/redis/dump.rdb > dump.json
|
||||
|
||||
# 生成内存使用报告
|
||||
rdb --command memory /var/lib/redis/dump.rdb > memory_report.csv
|
||||
|
||||
# 按键类型统计
|
||||
rdb --command memory /var/lib/redis/dump.rdb --bytes 128 --largest 10
|
||||
|
||||
# 查看特定键的信息
|
||||
rdb --command memory /var/lib/redis/dump.rdb | grep "user:"
|
||||
```
|
||||
|
||||
## AOF 持久化
|
||||
|
||||
### AOF 概述
|
||||
|
||||
AOF(Append Only File)是 Redis 的另一种持久化方式,它记录服务器接收到的每个写操作命令。
|
||||
|
||||
**AOF 特点:**
|
||||
- **命令记录**:记录每个写操作命令
|
||||
- **数据安全性高**:可配置每秒或每个命令同步
|
||||
- **文件可读**:文本格式,可以直接查看和编辑
|
||||
- **自动重写**:定期压缩 AOF 文件
|
||||
- **恢复较慢**:需要重放所有命令
|
||||
|
||||
**适用场景:**
|
||||
- 对数据完整性要求高的场景
|
||||
- 需要最小数据丢失的场景
|
||||
- 主节点持久化
|
||||
- 需要审计写操作的场景
|
||||
|
||||
### AOF 配置
|
||||
|
||||
**基本配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 配置文件中的 AOF 相关配置
|
||||
|
||||
# 启用 AOF 持久化
|
||||
appendonly yes
|
||||
|
||||
# AOF 文件名
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
# always: 每个写命令都同步到磁盘(最安全,性能最低)
|
||||
# everysec: 每秒同步一次(推荐,平衡安全性和性能)
|
||||
# no: 由操作系统决定何时同步(性能最高,安全性最低)
|
||||
appendfsync everysec
|
||||
|
||||
# 在 AOF 重写期间是否同步
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# AOF 自动重写配置
|
||||
# 当 AOF 文件大小超过上次重写后大小的指定百分比时触发重写
|
||||
auto-aof-rewrite-percentage 100
|
||||
# AOF 文件最小重写大小
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# AOF 加载时是否忽略最后一个不完整的命令
|
||||
aof-load-truncated yes
|
||||
|
||||
# 是否使用 RDB-AOF 混合持久化
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
**动态配置**
|
||||
|
||||
```shell
|
||||
# 查看当前 AOF 配置
|
||||
redis-cli CONFIG GET appendonly
|
||||
redis-cli CONFIG GET appendfsync
|
||||
redis-cli CONFIG GET auto-aof-rewrite-percentage
|
||||
|
||||
# 动态启用 AOF
|
||||
redis-cli CONFIG SET appendonly yes
|
||||
|
||||
# 修改同步策略
|
||||
redis-cli CONFIG SET appendfsync everysec
|
||||
|
||||
# 修改自动重写配置
|
||||
redis-cli CONFIG SET auto-aof-rewrite-percentage 100
|
||||
redis-cli CONFIG SET auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 保存配置
|
||||
redis-cli CONFIG REWRITE
|
||||
```
|
||||
|
||||
### AOF 操作命令
|
||||
|
||||
**手动重写 AOF**
|
||||
|
||||
```shell
|
||||
# 手动触发 AOF 重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 检查 AOF 重写状态
|
||||
redis-cli INFO persistence | grep aof
|
||||
|
||||
# 查看 AOF 相关统计信息
|
||||
redis-cli INFO persistence
|
||||
```
|
||||
|
||||
**AOF 文件管理**
|
||||
|
||||
```shell
|
||||
# 查看 AOF 文件
|
||||
ls -la /var/lib/redis/appendonly.aof
|
||||
tail -f /var/lib/redis/appendonly.aof
|
||||
|
||||
# 检查 AOF 文件完整性
|
||||
redis-check-aof /var/lib/redis/appendonly.aof
|
||||
|
||||
# 修复损坏的 AOF 文件
|
||||
redis-check-aof --fix /var/lib/redis/appendonly.aof
|
||||
|
||||
# 备份 AOF 文件
|
||||
cp /var/lib/redis/appendonly.aof /backup/redis/appendonly_$(date +%Y%m%d_%H%M%S).aof
|
||||
```
|
||||
|
||||
### AOF 文件格式
|
||||
|
||||
**AOF 命令格式**
|
||||
|
||||
AOF 文件使用 RESP(Redis Serialization Protocol)格式记录命令:
|
||||
|
||||
```shell
|
||||
# AOF 文件内容示例
|
||||
*3 # 数组长度(3个元素)
|
||||
$3 # 字符串长度(3字节)
|
||||
SET # 命令
|
||||
$4 # 字符串长度(4字节)
|
||||
name # 键名
|
||||
$5 # 字符串长度(5字节)
|
||||
Alice # 键值
|
||||
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$3
|
||||
age
|
||||
$2
|
||||
25
|
||||
|
||||
*2
|
||||
$4
|
||||
INCR
|
||||
$7
|
||||
counter
|
||||
```
|
||||
|
||||
**分析 AOF 文件**
|
||||
|
||||
```shell
|
||||
# 查看 AOF 文件内容
|
||||
cat /var/lib/redis/appendonly.aof
|
||||
|
||||
# 统计 AOF 文件中的命令
|
||||
grep -c "^\*" /var/lib/redis/appendonly.aof
|
||||
|
||||
# 查看最近的命令
|
||||
tail -20 /var/lib/redis/appendonly.aof
|
||||
|
||||
# 提取特定命令
|
||||
grep -A 10 "SET" /var/lib/redis/appendonly.aof
|
||||
|
||||
# 使用工具分析 AOF 文件:python 脚本工具
|
||||
```
|
||||
|
||||
### AOF 重写机制
|
||||
|
||||
**重写原理**
|
||||
|
||||
AOF 重写是为了解决 AOF 文件不断增长的问题:
|
||||
|
||||
```shell
|
||||
# 重写前的 AOF 文件可能包含:
|
||||
SET counter 1
|
||||
INCR counter
|
||||
INCR counter
|
||||
INCR counter
|
||||
DEL temp_key
|
||||
SET temp_key value
|
||||
DEL temp_key
|
||||
|
||||
# 重写后的 AOF 文件只包含:
|
||||
SET counter 4
|
||||
# temp_key 相关的命令被完全移除,因为最终结果是键不存在
|
||||
```
|
||||
|
||||
**重写配置和监控**
|
||||
|
||||
```shell
|
||||
# 查看重写相关配置
|
||||
redis-cli CONFIG GET auto-aof-rewrite-*
|
||||
|
||||
# 查看重写统计信息
|
||||
redis-cli INFO persistence | grep -E "aof_rewrite|aof_current_size|aof_base_size"
|
||||
|
||||
# 手动触发重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 监控重写进度
|
||||
watch -n 1 'redis-cli INFO persistence | grep aof_rewrite_in_progress'
|
||||
|
||||
```
|
||||
|
||||
**重写性能优化**
|
||||
|
||||
```shell
|
||||
# 优化重写性能的配置
|
||||
# redis.conf
|
||||
|
||||
# 重写期间不进行 fsync,提高性能
|
||||
no-appendfsync-on-rewrite yes
|
||||
|
||||
# 调整重写触发条件
|
||||
auto-aof-rewrite-percentage 100 # 文件大小翻倍时重写
|
||||
auto-aof-rewrite-min-size 64mb # 最小64MB才考虑重写
|
||||
|
||||
# 使用混合持久化减少重写后的文件大小
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
## 混合持久化
|
||||
|
||||
### 混合持久化概述
|
||||
|
||||
Redis 4.0 引入了混合持久化,结合了 RDB 和 AOF 的优点。
|
||||
|
||||
**混合持久化特点:**
|
||||
- **快速恢复**:RDB 部分快速加载
|
||||
- **数据安全**:AOF 部分保证最新数据
|
||||
- **文件较小**:比纯 AOF 文件小
|
||||
- **兼容性好**:向后兼容
|
||||
|
||||
**文件结构:**
|
||||
```
|
||||
混合 AOF 文件结构:
|
||||
+---------------------+------------------------+
|
||||
| RDB 格式的数据快照 | AOF 格式的增量命令 |
|
||||
+---------------------+------------------------+
|
||||
```
|
||||
|
||||
### 混合持久化配置
|
||||
|
||||
```shell
|
||||
# 启用混合持久化
|
||||
# redis.conf
|
||||
appendonly yes
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# 动态启用
|
||||
redis-cli CONFIG SET aof-use-rdb-preamble yes
|
||||
redis-cli CONFIG REWRITE
|
||||
|
||||
# 验证配置
|
||||
redis-cli CONFIG GET aof-use-rdb-preamble
|
||||
```
|
||||
|
||||
### 混合持久化操作
|
||||
|
||||
```shell
|
||||
# 触发混合持久化重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 检查文件格式
|
||||
file /var/lib/redis/appendonly.aof
|
||||
head -c 20 /var/lib/redis/appendonly.aof | xxd
|
||||
|
||||
# 如果是混合格式,开头应该是 "REDIS" 而不是 "*"
|
||||
```
|
||||
|
||||
## 持久化策略选择
|
||||
|
||||
### 不同场景的持久化策略
|
||||
|
||||
**高性能场景**
|
||||
|
||||
```shell
|
||||
# 配置:仅使用 RDB,较长的保存间隔
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly no
|
||||
|
||||
# 适用场景:
|
||||
# - 缓存系统
|
||||
# - 数据丢失容忍度高
|
||||
# - 性能要求极高
|
||||
```
|
||||
|
||||
**高可靠性场景**
|
||||
|
||||
```shell
|
||||
# 配置:AOF + 每秒同步
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# 可选:同时启用 RDB 作为备份
|
||||
save 900 1
|
||||
|
||||
# 适用场景:
|
||||
# - 金融系统
|
||||
# - 重要业务数据
|
||||
# - 数据丢失容忍度低
|
||||
```
|
||||
|
||||
**平衡场景**
|
||||
|
||||
```shell
|
||||
# 配置:混合持久化
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
save 900 1
|
||||
save 300 10
|
||||
|
||||
# 适用场景:
|
||||
# - 大多数生产环境
|
||||
# - 平衡性能和可靠性
|
||||
# - 中等数据量
|
||||
```
|
||||
|
||||
### 持久化最佳实践
|
||||
|
||||
**生产环境配置建议**
|
||||
|
||||
```shell
|
||||
# 推荐的生产环境配置
|
||||
# redis.conf
|
||||
|
||||
# 启用混合持久化
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# RDB 配置(作为备份)
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
|
||||
# AOF 重写配置
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
no-appendfsync-on-rewrite no
|
||||
aof-load-truncated yes
|
||||
|
||||
# 文件路径配置
|
||||
dir /var/lib/redis
|
||||
dbfilename dump.rdb
|
||||
appendfilename "appendonly.aof"
|
||||
```
|
||||
|
||||
## 实践操作
|
546
数据库/Redis_2025/06_Redis主从复制.md
Normal file
546
数据库/Redis_2025/06_Redis主从复制.md
Normal file
@@ -0,0 +1,546 @@
|
||||
# Redis 主从复制
|
||||
|
||||
Redis 主从复制是 Redis 高可用性架构的基础,通过数据复制实现读写分离、故障转移和数据备份。理解主从复制的原理和配置是构建可靠 Redis 集群的关键。
|
||||
|
||||
## 主从复制概述
|
||||
|
||||
### 主从复制的概念
|
||||
|
||||
主从复制(Master-Slave Replication)是 Redis 提供的数据同步机制,其中一个 Redis 实例作为主节点(Master),其他实例作为从节点(Slave)。
|
||||
|
||||
**基本概念:**
|
||||
- **主节点(Master)**:接受写操作,负责数据的修改
|
||||
- **从节点(Slave)**:从主节点复制数据,通常只处理读操作
|
||||
- **复制流(Replication Stream)**:主节点向从节点发送的数据同步流
|
||||
- **复制偏移量(Replication Offset)**:用于标识复制进度的位置
|
||||
|
||||
### 主从复制的优势
|
||||
|
||||
**数据安全性:**
|
||||
- 数据冗余备份
|
||||
- 防止单点故障
|
||||
- 支持数据恢复
|
||||
|
||||
**性能提升:**
|
||||
- 读写分离
|
||||
- 负载均衡
|
||||
- 减轻主节点压力
|
||||
|
||||
**高可用性:**
|
||||
- 故障转移基础
|
||||
- 服务连续性
|
||||
- 自动故障检测
|
||||
|
||||
**扩展性:**
|
||||
- 水平扩展读能力
|
||||
- 支持多级复制
|
||||
- 灵活的架构设计
|
||||
|
||||
### 主从复制的应用场景
|
||||
|
||||
|
||||
典型应用场景:
|
||||
|
||||
1. 读写分离架构
|
||||
应用 → 写操作 → Master
|
||||
应用 → 读操作 → Slave1, Slave2, Slave3
|
||||
|
||||
2. 数据备份
|
||||
Master → 实时数据
|
||||
Slave → 备份数据(可设置不同的持久化策略)
|
||||
|
||||
3. 故障转移
|
||||
Master 故障 → Slave 提升为新 Master
|
||||
|
||||
4. 数据分析
|
||||
Master → 生产数据
|
||||
Slave → 数据分析和报表生成
|
||||
|
||||
5. 跨地域部署
|
||||
Master → 主数据中心
|
||||
Slave → 异地数据中心
|
||||
|
||||
|
||||
## 主从复制原理
|
||||
|
||||
### 复制过程
|
||||
|
||||
**全量复制(Full Resynchronization)**
|
||||
|
||||
全量复制发生在从节点首次连接主节点或复制中断后无法进行增量复制时。
|
||||
|
||||
全量复制流程:
|
||||
|
||||
1. 从节点发送 PSYNC 命令
|
||||
Slave → Master: PSYNC ? -1
|
||||
|
||||
2. 主节点响应 FULLRESYNC
|
||||
Master → Slave: FULLRESYNC <runid> <offset>
|
||||
|
||||
3. 主节点执行 BGSAVE
|
||||
Master: 生成 RDB 快照文件
|
||||
|
||||
4. 主节点发送 RDB 文件
|
||||
Master → Slave: RDB 文件数据
|
||||
|
||||
5. 主节点发送缓冲区命令
|
||||
Master → Slave: 复制期间的写命令
|
||||
|
||||
6. 从节点加载数据
|
||||
Slave: 清空数据库 → 加载 RDB → 执行缓冲命令
|
||||
|
||||
|
||||
**增量复制(Partial Resynchronization)**
|
||||
|
||||
Redis 2.8+ 支持增量复制,用于处理短暂的网络中断。
|
||||
|
||||
增量复制流程:
|
||||
|
||||
1. 从节点重连并发送 PSYNC
|
||||
Slave → Master: PSYNC <runid> <offset>
|
||||
|
||||
2. 主节点检查复制积压缓冲区
|
||||
Master: 检查 offset 是否在缓冲区范围内
|
||||
|
||||
3. 主节点响应 CONTINUE
|
||||
Master → Slave: CONTINUE
|
||||
|
||||
4. 主节点发送缺失的命令
|
||||
Master → Slave: 缓冲区中 offset 之后的命令
|
||||
|
||||
5. 从节点执行命令
|
||||
Slave: 执行接收到的命令,恢复同步
|
||||
|
||||
### 复制相关的数据结构
|
||||
|
||||
**复制积压缓冲区(Replication Backlog)**
|
||||
|
||||
```shell
|
||||
# 复制积压缓冲区配置
|
||||
# redis.conf
|
||||
|
||||
# 缓冲区大小(默认 1MB)
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 缓冲区超时时间(默认 3600 秒)
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 查看缓冲区状态
|
||||
redis-cli INFO replication | grep backlog
|
||||
```
|
||||
|
||||
**运行 ID(Run ID)**
|
||||
|
||||
```shell
|
||||
# 查看运行 ID
|
||||
redis-cli INFO server | grep run_id
|
||||
|
||||
# 运行 ID 的作用:
|
||||
# 1. 标识 Redis 实例的唯一性
|
||||
# 2. 用于增量复制的验证
|
||||
# 3. 重启后会生成新的 Run ID
|
||||
```
|
||||
|
||||
**复制偏移量(Replication Offset)**
|
||||
|
||||
```shell
|
||||
# 查看复制偏移量
|
||||
redis-cli INFO replication | grep offset
|
||||
|
||||
# 主节点偏移量:master_repl_offset
|
||||
# 从节点偏移量:slave_repl_offset
|
||||
# 偏移量差异表示复制延迟
|
||||
```
|
||||
|
||||
## 主从复制配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**主节点配置**
|
||||
|
||||
```shell
|
||||
# 主节点 redis.conf 配置
|
||||
|
||||
# 绑定地址(允许从节点连接)
|
||||
bind 0.0.0.0
|
||||
|
||||
# 端口
|
||||
port 6379
|
||||
|
||||
# 设置密码(可选)
|
||||
requirepass master_password
|
||||
|
||||
# 主从复制相关配置
|
||||
# 复制积压缓冲区大小
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 复制超时时间
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用 TCP_NODELAY(可选,提高网络效率)
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制优先级(用于故障转移)
|
||||
slave-priority 100
|
||||
|
||||
# 最小从节点数量(可选)
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
```
|
||||
|
||||
**从节点配置**
|
||||
|
||||
```shell
|
||||
# 从节点 redis.conf 配置
|
||||
|
||||
# 绑定地址
|
||||
bind 0.0.0.0
|
||||
|
||||
# 端口(通常使用不同端口)
|
||||
port 6380
|
||||
|
||||
# 指定主节点
|
||||
slaveof 192.168.1.100 6379
|
||||
# 或者使用新的配置项
|
||||
replicaof 192.168.1.100 6379
|
||||
|
||||
# 主节点密码
|
||||
masterauth master_password
|
||||
|
||||
# 从节点密码(可选)
|
||||
requirepass slave_password
|
||||
|
||||
# 从节点只读(推荐)
|
||||
slave-read-only yes
|
||||
|
||||
# 复制相关配置
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 从节点持久化配置(可选)
|
||||
# 通常从节点可以禁用持久化以提高性能
|
||||
save ""
|
||||
appendonly no
|
||||
```
|
||||
|
||||
### 动态配置
|
||||
|
||||
**运行时配置主从关系**
|
||||
|
||||
```shell
|
||||
# 将当前实例设置为从节点
|
||||
redis-cli SLAVEOF 192.168.1.100 6379
|
||||
# 或使用新命令
|
||||
redis-cli REPLICAOF 192.168.1.100 6379
|
||||
|
||||
# 取消主从关系(将从节点提升为主节点)
|
||||
redis-cli SLAVEOF NO ONE
|
||||
# 或
|
||||
redis-cli REPLICAOF NO ONE
|
||||
|
||||
# 设置主节点密码
|
||||
redis-cli CONFIG SET masterauth master_password
|
||||
|
||||
# 查看复制状态
|
||||
redis-cli INFO replication
|
||||
```
|
||||
|
||||
**配置验证**
|
||||
|
||||
```shell
|
||||
# 在主节点上查看从节点信息
|
||||
redis-cli -h 192.168.1.100 -p 6379 INFO replication
|
||||
|
||||
# 在从节点上查看复制状态
|
||||
redis-cli -h 192.168.1.101 -p 6380 INFO replication
|
||||
|
||||
# 测试数据同步
|
||||
# 在主节点写入数据
|
||||
redis-cli -h 192.168.1.100 -p 6379 SET test_key "test_value"
|
||||
|
||||
# 在从节点读取数据
|
||||
redis-cli -h 192.168.1.101 -p 6380 GET test_key
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
**复制安全配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 安全配置
|
||||
|
||||
# 保护模式
|
||||
protected-mode yes
|
||||
|
||||
# 绑定特定网络接口
|
||||
bind 192.168.1.100 127.0.0.1
|
||||
|
||||
# 设置强密码
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_$(openssl rand -hex 8)"
|
||||
|
||||
# 限制客户端连接数
|
||||
maxclients 1000
|
||||
|
||||
# 设置内存限制
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
**网络优化配置**
|
||||
|
||||
```shell
|
||||
# 网络相关优化配置
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 复制超时
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用 Nagle 算法(降低延迟)
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制积压缓冲区大小(根据网络情况调整)
|
||||
repl-backlog-size 10mb
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
```
|
||||
|
||||
## 主从复制管理
|
||||
|
||||
### 监控主从状态
|
||||
|
||||
**复制状态监控**
|
||||
|
||||
```shell
|
||||
# 主节点重点指标
|
||||
INFO server:
|
||||
redis_version:6.2.19
|
||||
uptime_in_seconds:14339
|
||||
INFO replication:
|
||||
role:master
|
||||
connected_slaves:1
|
||||
master_repl_offset:1000000
|
||||
repl_backlog_size:10485760
|
||||
repl_backlog_ttl:3600
|
||||
INFO stats:
|
||||
total_commands_processed:1000000
|
||||
instantaneous_ops_per_sec:1000
|
||||
INFO memory:
|
||||
used_memory_human:100.00M
|
||||
|
||||
# 从节点重点指标
|
||||
INFO server:
|
||||
redis_version:6.2.19
|
||||
uptime_in_seconds:14339
|
||||
INFO replication:
|
||||
role:slave
|
||||
master_host:192.168.1.100
|
||||
master_port:6379
|
||||
master_link_status:up
|
||||
master_last_io_seconds_ago:0
|
||||
master_sync_in_progress:0
|
||||
# lag = master_repl_offset - slave_repl_offset
|
||||
slave_repl_offset:1000000
|
||||
slave_priority:100
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
|
||||
**复制性能调优**
|
||||
|
||||
```shell
|
||||
# 复制性能优化配置
|
||||
# 禁用 TCP_NODELAY 以提高网络效率
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 增大复制积压缓冲区
|
||||
repl-backlog-size 100mb
|
||||
|
||||
# 增大客户端输出缓冲区
|
||||
client-output-buffer-limit slave 512mb 128mb 60
|
||||
|
||||
# 复制超时
|
||||
repl-timeout 60
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 7200
|
||||
|
||||
# 主节点:启用 AOF,禁用 RDB 自动保存
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
save ""
|
||||
|
||||
# 从节点:禁用持久化以提高性能
|
||||
# save ""
|
||||
# appendonly no
|
||||
|
||||
# 设置合适的内存策略
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 增加最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
```
|
||||
|
||||
**读写分离优化**
|
||||
|
||||
```shell
|
||||
# 读写分离连接池配置示例(Python)- redis_pool_example.py
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
**需求描述:**
|
||||
在本地环境搭建一主两从的 Redis 复制架构,学习配置方法和管理技巧。
|
||||
|
||||
**实践细节和结果验证:**
|
||||
|
||||
```shell
|
||||
# 创建工作目录
|
||||
mkdir -p /data/redis_cluster/{master,slave1,slave2}
|
||||
|
||||
# 生成配置文件
|
||||
cat > /data/redis_cluster/master/redis.conf << EOF
|
||||
# 主节点配置
|
||||
port 6679
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/master
|
||||
logfile "redis-master.log"
|
||||
pidfile "redis-master.pid"
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-master.aof"
|
||||
|
||||
# 复制配置
|
||||
repl-backlog-size 10mb
|
||||
repl-backlog-ttl 3600
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
EOF
|
||||
|
||||
# 从节点1配置
|
||||
cat > /data/redis_cluster/slave1/redis.conf << EOF
|
||||
# 从节点1配置
|
||||
port 6680
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/slave1
|
||||
logfile "redis-slave1.log"
|
||||
pidfile "redis-slave1.pid"
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6679
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 持久化配置(从节点可以禁用以提高性能)
|
||||
save ""
|
||||
appendonly no
|
||||
EOF
|
||||
|
||||
# 从节点2配置
|
||||
cat > /data/redis_cluster/slave2/redis.conf << EOF
|
||||
# 从节点2配置
|
||||
port 6681
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/slave2
|
||||
logfile "redis-slave2.log"
|
||||
pidfile "redis-slave2.pid"
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6679
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 90
|
||||
|
||||
# 持久化配置
|
||||
save ""
|
||||
appendonly no
|
||||
EOF
|
||||
|
||||
# 启动主节点
|
||||
redis-server /data/redis_cluster/master/redis.conf --daemonize yes
|
||||
# 验证主节点启动
|
||||
redis-cli -p 6679 ping
|
||||
|
||||
# 启动从节点
|
||||
redis-server /data/redis_cluster/slave1/redis.conf --daemonize yes
|
||||
redis-server /data/redis_cluster/slave2/redis.conf --daemonize yes
|
||||
# 验证从节点启动
|
||||
redis-cli -p 6680 ping
|
||||
redis-cli -p 6681 ping
|
||||
|
||||
# 验证主从复制
|
||||
# 查看主节点状态
|
||||
redis-cli -p 6679 INFO replication
|
||||
|
||||
# 查看从节点1状态
|
||||
redis-cli -p 6680 INFO replication | grep -E "role|master_host|master_port|master_link_status"
|
||||
# 查看从节点2状态
|
||||
redis-cli -p 6681 INFO replication | grep -E "role|master_host|master_port|master_link_status"
|
||||
|
||||
# 测试数据同步
|
||||
# 在主节点写入数据
|
||||
redis-cli -p 6679 << EOF
|
||||
SET test:string "Hello Redis Replication"
|
||||
LPUSH test:list "item1" "item2" "item3"
|
||||
SADD test:set "member1" "member2" "member3"
|
||||
HMSET test:hash field1 "value1" field2 "value2"
|
||||
ZADD test:zset 1 "first" 2 "second" 3 "third"
|
||||
SETEX test:expire 3600 "will expire in 1 hour"
|
||||
EOF
|
||||
|
||||
# 在从节点验证数据
|
||||
redis-cli -p 6680 << EOF
|
||||
GET test:string
|
||||
LRANGE test:list 0 -1
|
||||
SMEMBERS test:set
|
||||
HGETALL test:hash
|
||||
ZRANGE test:zset 0 -1 WITHSCORES
|
||||
TTL test:expire
|
||||
EOF
|
||||
|
||||
# 测试读写分离
|
||||
# 测试从节点只读
|
||||
redis-cli -p 6680 SET readonly_test "should_fail"
|
||||
|
||||
# 监控复制延迟
|
||||
# 获取复制偏移量
|
||||
master_offset=$(redis-cli -p 6679 INFO replication | grep master_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
slave1_offset=$(redis-cli -p 6680 INFO replication | grep slave_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
slave2_offset=$(redis-cli -p 6681 INFO replication | grep slave_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
echo "主节点偏移量: $master_offset"
|
||||
echo "从节点1偏移量: $slave1_offset (延迟: $((master_offset - slave1_offset)) 字节)"
|
||||
echo "从节点2偏移量: $slave2_offset (延迟: $((master_offset - slave2_offset)) 字节)"
|
||||
|
||||
# 故障模拟
|
||||
# 模拟从节点故障
|
||||
redis-cli -p 6680 SHUTDOWN NOSAVE
|
||||
# 检查主节点状态
|
||||
redis-cli -p 6679 INFO replication | grep connected_slaves
|
||||
|
||||
# 恢复从节点1
|
||||
redis-server /data/redis_cluster/slave1/redis.conf --daemonize yes
|
||||
|
||||
# 验证恢复
|
||||
redis-cli -p 6680 ping
|
||||
redis-cli -p 6679 INFO replication | grep connected_slaves
|
||||
|
||||
```
|
671
数据库/Redis_2025/07_Redis哨兵架构.md
Normal file
671
数据库/Redis_2025/07_Redis哨兵架构.md
Normal file
@@ -0,0 +1,671 @@
|
||||
# Redis 哨兵架构
|
||||
|
||||
Redis 哨兵(Sentinel)是 Redis 官方提供的高可用性解决方案,通过监控、通知、自动故障转移和配置提供者等功能,确保 Redis 服务的持续可用性。
|
||||
|
||||
## 哨兵模式概述
|
||||
|
||||
### 哨兵模式的概念
|
||||
|
||||
Redis 哨兵是一个分布式系统,用于管理多个 Redis 实例,提供以下核心功能:
|
||||
|
||||
**核心组件:**
|
||||
- **哨兵节点(Sentinel)**:监控和管理 Redis 实例的独立进程
|
||||
- **主节点(Master)**:处理写操作的 Redis 实例
|
||||
- **从节点(Slave/Replica)**:从主节点复制数据的 Redis 实例
|
||||
- **客户端(Client)**:连接到哨兵系统的应用程序
|
||||
|
||||
**核心功能:**
|
||||
1. **监控(Monitoring)**:持续监控主从节点的健康状态
|
||||
2. **通知(Notification)**:当实例出现问题时发送通知
|
||||
3. **自动故障转移(Automatic Failover)**:主节点故障时自动选举新主节点
|
||||
4. **配置提供者(Configuration Provider)**:为客户端提供当前主节点信息
|
||||
|
||||
### 哨兵模式的优势
|
||||
|
||||
**高可用性:**
|
||||
- 自动故障检测和转移
|
||||
- 无需人工干预
|
||||
- 最小化服务中断时间
|
||||
- 支持多数据中心部署
|
||||
|
||||
**可靠性:**
|
||||
- 分布式决策机制
|
||||
- 避免脑裂问题
|
||||
- 多哨兵节点冗余
|
||||
- 客观下线判断
|
||||
|
||||
**易用性:**
|
||||
- 客户端自动发现主节点
|
||||
- 透明的故障转移
|
||||
- 简化的运维管理
|
||||
- 丰富的监控信息
|
||||
|
||||
**扩展性:**
|
||||
- 支持动态添加哨兵节点
|
||||
- 支持多主从架构
|
||||
- 灵活的配置管理
|
||||
- 可编程的通知机制
|
||||
|
||||
### 哨兵模式的应用场景
|
||||
|
||||
```
|
||||
典型应用场景:
|
||||
|
||||
1. 生产环境高可用
|
||||
应用 → 哨兵集群 → Redis 主从集群
|
||||
自动故障转移,保证服务连续性
|
||||
|
||||
2. 多数据中心部署
|
||||
数据中心A: 主节点 + 哨兵
|
||||
数据中心B: 从节点 + 哨兵
|
||||
数据中心C: 从节点 + 哨兵
|
||||
|
||||
3. 读写分离架构
|
||||
写操作 → 哨兵发现的主节点
|
||||
读操作 → 哨兵管理的从节点
|
||||
|
||||
4. 缓存层高可用
|
||||
Web应用 → 哨兵 → Redis缓存集群
|
||||
缓存故障时自动切换
|
||||
|
||||
5. 会话存储
|
||||
负载均衡器 → 应用服务器 → 哨兵 → Redis会话存储
|
||||
保证会话数据的高可用性
|
||||
```
|
||||
|
||||
## 哨兵模式原理
|
||||
|
||||
### 哨兵工作机制
|
||||
|
||||
**监控机制**
|
||||
|
||||
哨兵通过定期发送命令来监控 Redis 实例的状态:
|
||||
|
||||
监控流程:
|
||||
|
||||
1. 发送 PING 命令
|
||||
哨兵 → Redis实例: PING
|
||||
Redis实例 → 哨兵: PONG
|
||||
|
||||
2. 获取实例信息
|
||||
哨兵 → 主节点: INFO replication
|
||||
主节点 → 哨兵: 从节点列表和状态
|
||||
|
||||
3. 发现新实例
|
||||
哨兵根据主节点信息自动发现从节点
|
||||
哨兵之间通过发布/订阅发现彼此
|
||||
|
||||
4. 状态判断
|
||||
主观下线(SDOWN):单个哨兵认为实例不可用
|
||||
客观下线(ODOWN):多数哨兵认为实例不可用
|
||||
|
||||
**故障检测**
|
||||
|
||||
```shell
|
||||
# 故障检测参数
|
||||
# sentinel.conf
|
||||
|
||||
# 主观下线时间(毫秒)
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# 客观下线需要的哨兵数量
|
||||
sentinel quorum mymaster 2
|
||||
|
||||
# 故障转移超时时间
|
||||
sentinel failover-timeout mymaster 180000
|
||||
|
||||
# 并行同步的从节点数量
|
||||
sentinel parallel-syncs mymaster 1
|
||||
```
|
||||
|
||||
**故障检测流程:**
|
||||
|
||||
1. **主观下线(Subjectively Down, SDOWN)**
|
||||
- 单个哨兵在指定时间内无法与实例通信
|
||||
- 哨兵将实例标记为主观下线
|
||||
- 开始询问其他哨兵的意见
|
||||
|
||||
2. **客观下线(Objectively Down, ODOWN)**
|
||||
- 足够数量的哨兵认为实例主观下线
|
||||
- 达到 quorum 配置的数量要求
|
||||
- 实例被标记为客观下线
|
||||
|
||||
3. **故障转移触发**
|
||||
- 只有主节点的客观下线会触发故障转移
|
||||
- 从节点的客观下线只会影响监控状态
|
||||
|
||||
**故障转移过程**
|
||||
|
||||
故障转移详细流程:
|
||||
|
||||
1. 选举领导者哨兵
|
||||
- 检测到主节点客观下线
|
||||
- 哨兵之间进行领导者选举
|
||||
- 使用 Raft 算法确保只有一个领导者
|
||||
|
||||
2. 选择新主节点
|
||||
领导者哨兵根据以下优先级选择:
|
||||
a. 排除主观下线的从节点
|
||||
b. 排除断线时间超过阈值的从节点
|
||||
c. 选择 slave-priority 最小的从节点
|
||||
d. 选择复制偏移量最大的从节点
|
||||
e. 选择 run_id 最小的从节点
|
||||
|
||||
3. 提升新主节点
|
||||
- 向选中的从节点发送 SLAVEOF NO ONE
|
||||
- 等待从节点变为主节点
|
||||
- 验证新主节点状态
|
||||
|
||||
4. 更新其他从节点
|
||||
- 向其他从节点发送 SLAVEOF 新主节点
|
||||
- 控制并行同步数量(parallel-syncs)
|
||||
- 监控同步进度
|
||||
|
||||
5. 更新配置
|
||||
- 更新哨兵配置文件
|
||||
- 通知客户端新主节点信息
|
||||
- 发布配置变更事件
|
||||
|
||||
### 哨兵通信机制
|
||||
|
||||
**发布/订阅通信**
|
||||
|
||||
哨兵使用 Redis 的发布/订阅功能进行通信:
|
||||
|
||||
```shell
|
||||
# 哨兵通信频道
|
||||
__sentinel__:hello # 哨兵发现和信息交换
|
||||
+switch-master # 主节点切换通知
|
||||
+slave # 从节点发现通知
|
||||
+sentinel # 哨兵发现通知
|
||||
+sdown # 主观下线通知
|
||||
+odown # 客观下线通知
|
||||
+failover-triggered # 故障转移触发通知
|
||||
+failover-state-* # 故障转移状态变更
|
||||
```
|
||||
|
||||
**哨兵发现机制**
|
||||
|
||||
哨兵发现流程:
|
||||
|
||||
1. 主节点发现
|
||||
- 通过配置文件指定初始主节点
|
||||
- 哨兵连接并监控主节点
|
||||
|
||||
2. 从节点发现
|
||||
- 通过 INFO replication 命令获取从节点列表
|
||||
- 自动连接和监控发现的从节点
|
||||
|
||||
3. 哨兵发现
|
||||
- 通过 __sentinel__:hello 频道发布自己的信息
|
||||
- 订阅该频道发现其他哨兵
|
||||
- 建立哨兵之间的连接
|
||||
|
||||
4. 信息同步
|
||||
- 定期交换监控信息
|
||||
- 同步实例状态和配置
|
||||
- 协调故障检测和转移
|
||||
|
||||
|
||||
## 哨兵模式配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**哨兵配置文件**
|
||||
|
||||
```shell
|
||||
# 创建哨兵配置文件 sentinel.conf
|
||||
cat > /tmp/sentinel.conf << EOF
|
||||
# Redis 哨兵配置文件
|
||||
|
||||
# 哨兵端口
|
||||
port 26379
|
||||
|
||||
# 哨兵工作目录
|
||||
dir /tmp
|
||||
|
||||
# 监控的主节点配置
|
||||
# sentinel monitor <master-name> <ip> <port> <quorum>
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# 主节点认证密码
|
||||
sentinel auth-pass mymaster your_password
|
||||
|
||||
# 主观下线时间(毫秒)
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# 故障转移超时时间(毫秒)
|
||||
sentinel failover-timeout mymaster 180000
|
||||
|
||||
# 并行同步的从节点数量
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 哨兵认证(可选)
|
||||
requirepass sentinel_password
|
||||
|
||||
# 日志配置
|
||||
logfile "/var/log/redis/sentinel.log"
|
||||
loglevel notice
|
||||
|
||||
# 通知脚本(可选)
|
||||
# sentinel notification-script mymaster /path/to/notify.sh
|
||||
|
||||
# 客户端重配置脚本(可选)
|
||||
# sentinel client-reconfig-script mymaster /path/to/reconfig.sh
|
||||
|
||||
# 拒绝危险命令
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
```
|
||||
|
||||
**多哨兵配置**
|
||||
|
||||
```shell
|
||||
# 哨兵1配置
|
||||
cat > /tmp/sentinel-1.conf << EOF
|
||||
port 26379
|
||||
dir /tmp/sentinel-1
|
||||
logfile "sentinel-1.log"
|
||||
pidfile "sentinel-1.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
|
||||
# 哨兵2配置
|
||||
cat > /tmp/sentinel-2.conf << EOF
|
||||
port 26380
|
||||
dir /tmp/sentinel-2
|
||||
logfile "sentinel-2.log"
|
||||
pidfile "sentinel-2.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
|
||||
# 哨兵3配置
|
||||
cat > /tmp/sentinel-3.conf << EOF
|
||||
port 26381
|
||||
dir /tmp/sentinel-3
|
||||
logfile "sentinel-3.log"
|
||||
pidfile "sentinel-3.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
```
|
||||
|
||||
**安全配置**
|
||||
|
||||
```shell
|
||||
# 安全增强的哨兵配置
|
||||
cat > /tmp/sentinel_secure.conf << 'EOF'
|
||||
# 安全增强的哨兵配置
|
||||
|
||||
# 基本配置
|
||||
port 26379
|
||||
dir /var/lib/redis/sentinel
|
||||
logfile "/var/log/redis/sentinel.log"
|
||||
pidfile "/var/run/redis/sentinel.pid"
|
||||
|
||||
# 绑定特定接口
|
||||
bind 192.168.1.100 127.0.0.1
|
||||
|
||||
# 保护模式
|
||||
protected-mode yes
|
||||
|
||||
# 哨兵认证
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
|
||||
# 监控配置
|
||||
sentinel monitor mymaster 192.168.1.100 6379 2
|
||||
sentinel auth-pass mymaster "$(openssl rand -base64 32)"
|
||||
|
||||
# 超时配置
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 拒绝脚本重配置
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# 通知脚本(使用绝对路径)
|
||||
sentinel notification-script mymaster /usr/local/bin/sentinel_notify.sh
|
||||
sentinel client-reconfig-script mymaster /usr/local/bin/client_reconfig.sh
|
||||
|
||||
# 日志级别
|
||||
loglevel notice
|
||||
|
||||
# 限制连接数
|
||||
# maxclients 100
|
||||
EOF
|
||||
```
|
||||
|
||||
### 动态配置管理
|
||||
|
||||
**运行时配置修改**
|
||||
|
||||
```shell
|
||||
# 连接到哨兵
|
||||
redis-cli -p 26379
|
||||
|
||||
# 查看监控的主节点
|
||||
SENTINEL masters
|
||||
|
||||
# 查看特定主节点的从节点
|
||||
SENTINEL slaves mymaster
|
||||
|
||||
# 查看哨兵节点
|
||||
SENTINEL sentinels mymaster
|
||||
|
||||
# 获取主节点地址
|
||||
SENTINEL get-master-addr-by-name mymaster
|
||||
|
||||
# 动态修改配置
|
||||
SENTINEL set mymaster down-after-milliseconds 60000
|
||||
SENTINEL set mymaster failover-timeout 300000
|
||||
SENTINEL set mymaster parallel-syncs 2
|
||||
|
||||
# 重置主节点(清除故障状态)
|
||||
SENTINEL reset mymaster
|
||||
|
||||
# 强制故障转移
|
||||
SENTINEL failover mymaster
|
||||
|
||||
# 移除主节点监控
|
||||
SENTINEL remove mymaster
|
||||
|
||||
# 添加新的主节点监控
|
||||
SENTINEL monitor newmaster 192.168.1.200 6379 2
|
||||
```
|
||||
|
||||
**配置持久化**
|
||||
|
||||
```shell
|
||||
# 哨兵配置自动更新机制
|
||||
echo "哨兵配置文件会自动更新以下内容:"
|
||||
echo "1. 发现的从节点信息"
|
||||
echo "2. 发现的其他哨兵信息"
|
||||
echo "3. 故障转移后的新主节点信息"
|
||||
echo "4. 实例状态变更记录"
|
||||
|
||||
# 查看自动更新的配置
|
||||
cat /tmp/sentinel.conf | grep -E "^# Generated by CONFIG REWRITE|^sentinel known-"
|
||||
|
||||
# 手动保存配置
|
||||
redis-cli -p 26379 CONFIG REWRITE
|
||||
```
|
||||
|
||||
## 哨兵模式管理
|
||||
|
||||
### 启动和停止
|
||||
|
||||
**启动哨兵**
|
||||
|
||||
```shell
|
||||
# 方法1:使用 redis-sentinel 命令
|
||||
redis-sentinel /path/to/sentinel.conf
|
||||
|
||||
# 方法2:使用 redis-server 命令
|
||||
redis-server /path/to/sentinel.conf --sentinel
|
||||
|
||||
# 后台启动
|
||||
redis-sentinel /path/to/sentinel.conf --daemonize yes
|
||||
|
||||
# 使用 systemd 管理
|
||||
sudo systemctl start redis-sentinel
|
||||
sudo systemctl enable redis-sentinel
|
||||
|
||||
# 检查启动状态
|
||||
ps aux | grep sentinel
|
||||
netstat -tlnp | grep 26379
|
||||
```
|
||||
|
||||
**停止哨兵**
|
||||
|
||||
```shell
|
||||
# 优雅停止
|
||||
redis-cli -p 26379 SHUTDOWN
|
||||
|
||||
# 使用 systemd 停止
|
||||
sudo systemctl stop redis-sentinel
|
||||
|
||||
# 强制停止
|
||||
kill -TERM $(cat /var/run/redis/sentinel.pid)
|
||||
|
||||
# 检查停止状态
|
||||
ps aux | grep sentinel
|
||||
```
|
||||
|
||||
### 监控和诊断
|
||||
|
||||
**状态监控**
|
||||
|
||||
```shell
|
||||
# 检查哨兵连接
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT ping
|
||||
|
||||
# 获取主节点信息
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT SENTINEL get-master-addr-by-name $MASTER_NAME
|
||||
|
||||
# 获取从节点信息
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT SENTINEL slaves $MASTER_NAME
|
||||
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
搭建一个完整的 Redis 哨兵集群,包括1个主节点、2个从节点和3个哨兵节点,学习哨兵的配置、管理和故障转移机制。
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 环境准备
|
||||
# 创建工作目录
|
||||
mkdir -p /tmp/redis_sentinel_cluster/{redis-master,redis-slave1,redis-slave2,sentinel1,sentinel2,sentinel3}
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
|
||||
# 2. 配置 Redis 实例
|
||||
# 主节点配置
|
||||
cat > redis-master/redis.conf << EOF
|
||||
port 6779
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-master
|
||||
logfile "redis-master.log"
|
||||
pidfile "redis-master.pid"
|
||||
daemonize yes
|
||||
|
||||
# 持久化
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-master.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 复制配置
|
||||
repl-backlog-size 10mb
|
||||
repl-backlog-ttl 3600
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 从节点1配置
|
||||
cat > redis-slave1/redis.conf << EOF
|
||||
port 6780
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-slave1
|
||||
logfile "redis-slave1.log"
|
||||
pidfile "redis-slave1.pid"
|
||||
daemonize yes
|
||||
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6779
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 禁用持久化以提高性能
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 从节点2配置
|
||||
cat > redis-slave2/redis.conf << EOF
|
||||
port 6781
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-slave2
|
||||
logfile "redis-slave2.log"
|
||||
pidfile "redis-slave2.pid"
|
||||
daemonize yes
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6779
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 90
|
||||
|
||||
# 禁用持久化
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 3. 配置哨兵节点
|
||||
# 哨兵1配置
|
||||
cat > sentinel1/sentinel.conf << EOF
|
||||
port 26779
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel1
|
||||
logfile "sentinel1.log"
|
||||
pidfile "sentinel1.pid"
|
||||
daemonize yes
|
||||
|
||||
# 监控主节点
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
|
||||
# 故障检测配置
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 通知脚本
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
|
||||
# 安全配置
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 哨兵2配置
|
||||
cat > sentinel2/sentinel.conf << EOF
|
||||
port 26780
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel2
|
||||
logfile "sentinel2.log"
|
||||
pidfile "sentinel2.pid"
|
||||
daemonize yes
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 哨兵3配置
|
||||
cat > sentinel3/sentinel.conf << EOF
|
||||
port 26781
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel3
|
||||
logfile "sentinel3.log"
|
||||
pidfile "sentinel3.pid"
|
||||
daemonize yes
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 4. 创建通知脚本
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
cat > notify.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "$(date): 哨兵事件 - $*" >> /tmp/redis_sentinel_cluster/sentinel_events.log
|
||||
EOF
|
||||
cat > reconfig.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "$(date): 客户端重配置 - $*" >> /tmp/redis_sentinel_cluster/reconfig_events.log
|
||||
EOF
|
||||
chmod +x notify.sh reconfig.sh
|
||||
|
||||
# 5. 启动 Redis 实例
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
redis-server redis-master/redis.conf
|
||||
redis-server redis-slave1/redis.conf
|
||||
redis-server redis-slave2/redis.conf
|
||||
|
||||
|
||||
# 6. 启动哨兵节点
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
redis-sentinel sentinel1/sentinel.conf
|
||||
redis-sentinel sentinel2/sentinel.conf
|
||||
redis-sentinel sentinel3/sentinel.conf
|
||||
|
||||
# 7. 验证集群状态
|
||||
# 检查主从复制
|
||||
redis-cli -p 6779 INFO replication | grep -E "role|connected_slaves"
|
||||
redis-cli -p 6780 INFO replication | grep -E "role|master_host|master_link_status"
|
||||
redis-cli -p 6781 INFO replication | grep -E "role|master_host|master_link_status"
|
||||
# 检查哨兵状态
|
||||
for port in 26779 26780 26781; do redis-cli -p $port SENTINEL masters | grep -E "name|ip|port|num-slaves|num-other-sentinels" | head -5;done
|
||||
|
||||
# 8. [可选]测试数据同步
|
||||
|
||||
# 9. 测试故障转移
|
||||
# 停止主节点
|
||||
redis-cli -p 6779 SHUTDOWN NOSAVE
|
||||
# 获取新主节点
|
||||
redis-cli -p 26779 SENTINEL get-master-addr-by-name mymaster
|
||||
# 验证新主节点
|
||||
redis-cli -p 6781 INFO replication | grep -E "role|connected_slaves"
|
||||
# 测试写入新主节点
|
||||
redis-cli -p 6781 SET failover:test "success"
|
||||
# 查看哨兵事件日志
|
||||
tail -10 sentinel_events.log
|
||||
|
||||
```
|
684
数据库/Redis_2025/08_Redis集群架构.md
Normal file
684
数据库/Redis_2025/08_Redis集群架构.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# Redis 集群架构
|
||||
|
||||
Redis 集群(Redis Cluster)是 Redis 官方提供的分布式解决方案,通过数据分片和自动故障转移实现水平扩展和高可用性。它是构建大规模 Redis 应用的核心技术。
|
||||
|
||||
## Redis 集群概述
|
||||
|
||||
### 集群模式的概念
|
||||
|
||||
Redis 集群是一个分布式、去中心化的 Redis 实现,具有以下特点:
|
||||
|
||||
**核心特性:**
|
||||
- **数据分片(Sharding)**:自动将数据分布到多个节点
|
||||
- **高可用性(High Availability)**:支持主从复制和自动故障转移
|
||||
- **水平扩展(Horizontal Scaling)**:支持动态添加和删除节点
|
||||
- **去中心化(Decentralized)**:没有单点故障,所有节点地位平等
|
||||
|
||||
**架构组件:**
|
||||
- **主节点(Master)**:处理读写请求,负责数据分片
|
||||
- **从节点(Slave)**:复制主节点数据,提供读服务和故障转移
|
||||
- **哈希槽(Hash Slot)**:数据分片的基本单位,共16384个槽
|
||||
- **集群总线(Cluster Bus)**:节点间通信的专用通道
|
||||
|
||||
### 集群模式的优势
|
||||
|
||||
**性能优势:**
|
||||
- 数据分片提高并发处理能力
|
||||
- 多节点并行处理请求
|
||||
- 减少单节点内存压力
|
||||
- 支持大数据量存储
|
||||
|
||||
**可用性优势:**
|
||||
- 自动故障检测和转移
|
||||
- 主从复制保证数据安全
|
||||
- 部分节点故障不影响整体服务
|
||||
- 支持在线扩容和缩容
|
||||
|
||||
**扩展性优势:**
|
||||
- 线性扩展存储容量
|
||||
- 动态调整集群规模
|
||||
- 支持跨数据中心部署
|
||||
- 灵活的数据迁移机制
|
||||
|
||||
**管理优势:**
|
||||
- 自动数据分布和负载均衡
|
||||
- 简化的集群管理工具
|
||||
- 丰富的监控和诊断功能
|
||||
- 标准化的客户端支持
|
||||
|
||||
### 集群模式的应用场景
|
||||
|
||||
典型应用场景:
|
||||
|
||||
1. 大规模缓存系统
|
||||
应用层 → 负载均衡 → Redis集群
|
||||
支持TB级别的缓存数据
|
||||
|
||||
2. 分布式会话存储
|
||||
Web应用 → 会话管理 → Redis集群
|
||||
支持大量并发用户会话
|
||||
|
||||
3. 实时数据分析
|
||||
数据采集 → 实时计算 → Redis集群
|
||||
支持高频数据写入和查询
|
||||
|
||||
4. 消息队列系统
|
||||
生产者 → Redis集群 → 消费者
|
||||
支持大规模消息处理
|
||||
|
||||
5. 游戏排行榜
|
||||
游戏服务器 → Redis集群 → 排行榜系统
|
||||
支持全球用户排行榜
|
||||
|
||||
6. 电商购物车
|
||||
电商平台 → Redis集群 → 购物车服务
|
||||
支持海量用户购物车数据
|
||||
|
||||
|
||||
## Redis 集群原理
|
||||
|
||||
### 数据分片机制
|
||||
|
||||
**哈希槽(Hash Slot)**
|
||||
|
||||
Redis 集群使用哈希槽来实现数据分片:
|
||||
|
||||
哈希槽机制:
|
||||
|
||||
1. 槽位总数:16384 个(0-16383)
|
||||
2. 槽位分配:平均分配给各个主节点
|
||||
3. 数据映射:key → CRC16(key) % 16384 → 槽位 → 节点
|
||||
4. 槽位迁移:支持在线重新分配槽位
|
||||
|
||||
示例分配(3个主节点):
|
||||
节点A:槽位 0-5460 (5461个槽)
|
||||
节点B:槽位 5461-10922 (5462个槽)
|
||||
节点C:槽位 10923-16383(5461个槽)
|
||||
|
||||
**数据路由**
|
||||
|
||||
```shell
|
||||
# 数据路由过程
|
||||
|
||||
# 1. 客户端计算槽位
|
||||
key = "user:1001"
|
||||
slot = CRC16(key) % 16384
|
||||
# 假设 slot = 8000
|
||||
|
||||
# 2. 查找负责的节点
|
||||
# 槽位 8000 属于节点B(5461-10922)
|
||||
|
||||
# 3. 重定向机制
|
||||
# 如果客户端连接到错误的节点:
|
||||
redis-cli -c -p 7001 GET user:1001
|
||||
# 节点A响应:(error) MOVED 8000 192.168.1.102:7002
|
||||
# 客户端自动重定向到节点B
|
||||
|
||||
# 4. ASK重定向(槽位迁移中)
|
||||
# 如果槽位正在迁移:
|
||||
# 源节点响应:(error) ASK 8000 192.168.1.103:7003
|
||||
# 客户端发送 ASKING 命令后重试
|
||||
```
|
||||
|
||||
### 集群通信机制
|
||||
|
||||
**集群总线(Cluster Bus)**
|
||||
|
||||
集群总线特性:
|
||||
|
||||
1. 端口:Redis端口 + 10000
|
||||
Redis端口:7001 → 集群总线端口:17001
|
||||
|
||||
2. 协议:二进制协议,效率更高
|
||||
|
||||
3. 通信内容:
|
||||
- 节点状态信息
|
||||
- 槽位分配信息
|
||||
- 故障检测信息
|
||||
- 配置更新信息
|
||||
|
||||
4. 通信频率:
|
||||
- 心跳:每秒1次
|
||||
- Gossip:随机选择节点交换信息
|
||||
- 故障检测:实时
|
||||
|
||||
|
||||
### Gossip 协议
|
||||
|
||||
Gossip 协议(流言协议)是一种分布式系统中的信息传播协议,类似于现实生活中流言的传播方式。在Redis集群中,Gossip协议用于维护集群状态的一致性。
|
||||
|
||||
**Gossip 协议原理:**
|
||||
|
||||
1. **去中心化设计**
|
||||
- 没有中央协调节点
|
||||
- 每个节点都是平等的
|
||||
- 信息通过节点间的随机通信传播
|
||||
|
||||
2. **最终一致性**
|
||||
- 不保证强一致性
|
||||
- 通过多轮传播达到最终一致
|
||||
- 容忍网络分区和节点故障
|
||||
|
||||
3. **概率性传播**
|
||||
- 随机选择通信节点
|
||||
- 降低网络负载
|
||||
- 提高系统可扩展性
|
||||
|
||||
**Gossip 协议特点:**
|
||||
|
||||
1. **高可用性**
|
||||
- 单点故障不影响整体运行
|
||||
- 网络分区时仍能部分工作
|
||||
- 自动故障恢复能力
|
||||
|
||||
2. **可扩展性**
|
||||
- 通信复杂度为 O(log N)
|
||||
- 支持大规模集群
|
||||
- 动态添加/删除节点
|
||||
|
||||
3. **容错性**
|
||||
- 容忍节点故障
|
||||
- 容忍消息丢失
|
||||
- 容忍网络延迟
|
||||
|
||||
**Redis 中的 Gossip 实现:**
|
||||
|
||||
**Gossip 协议工作流程**:
|
||||
|
||||
1. **节点选择**:每次随机选择几个节点进行通信
|
||||
2. **信息交换**:发送自己已知的集群状态信息
|
||||
3. **信息合并**:接收并更新集群状态信息
|
||||
4. **信息传播**:将新信息传播给其他节点
|
||||
|
||||
**消息类型:**
|
||||
|
||||
- **PING**:心跳消息,包含发送者状态和已知的其他节点信息
|
||||
- **PONG**:心跳响应,包含接收者状态和集群视图
|
||||
- **MEET**:新节点加入集群时的握手消息
|
||||
- **FAIL**:节点故障通知,标记节点为失效状态
|
||||
- **PUBLISH**:发布/订阅消息在集群间的传播
|
||||
|
||||
**Gossip 消息结构:**
|
||||
|
||||
Gossip 消息头:
|
||||
- 消息类型 (PING/PONG/MEET/FAIL)
|
||||
- 发送者节点ID
|
||||
- 消息序列号
|
||||
- 集群配置版本
|
||||
|
||||
Gossip 消息体:
|
||||
- 节点状态信息
|
||||
- 槽位分配信息
|
||||
- 其他节点的状态摘要
|
||||
- 故障检测信息
|
||||
|
||||
|
||||
**传播机制:**
|
||||
|
||||
1. **主动传播**
|
||||
- 每个节点定期发送PING消息
|
||||
- 频率:每秒选择随机节点发送
|
||||
- 目标:维持集群连通性
|
||||
|
||||
2. **被动传播**
|
||||
- 接收到消息后回复PONG
|
||||
- 携带本地集群状态信息
|
||||
- 实现双向信息交换
|
||||
|
||||
3. **故障传播**
|
||||
- 检测到节点故障时发送FAIL消息
|
||||
- 快速传播故障信息
|
||||
- 触发故障转移流程
|
||||
|
||||
**Gossip 协议优势:**
|
||||
|
||||
1. **网络效率**
|
||||
- 避免广播风暴
|
||||
- 减少网络带宽消耗
|
||||
- 适合大规模集群
|
||||
|
||||
2. **故障隔离**
|
||||
- 局部故障不影响全局
|
||||
- 自动绕过故障节点
|
||||
- 提高系统稳定性
|
||||
|
||||
3. **动态适应**
|
||||
- 自动发现新节点
|
||||
- 自动移除故障节点
|
||||
- 支持集群拓扑变化
|
||||
|
||||
|
||||
### 故障检测和转移
|
||||
|
||||
**故障检测机制**
|
||||
|
||||
```shell
|
||||
# 故障检测配置
|
||||
# redis.conf
|
||||
|
||||
# 集群节点超时时间(毫秒)
|
||||
cluster-node-timeout 15000
|
||||
|
||||
# 故障转移投票有效时间
|
||||
cluster-slave-validity-factor 10
|
||||
|
||||
# 从节点迁移屏障
|
||||
cluster-migration-barrier 1
|
||||
|
||||
# 集群要求槽位完整覆盖
|
||||
cluster-require-full-coverage yes
|
||||
```
|
||||
|
||||
**故障检测流程:**
|
||||
|
||||
1. **主观下线(PFAIL)**
|
||||
- 节点在超时时间内无响应
|
||||
- 标记为主观下线状态
|
||||
- 开始收集其他节点意见
|
||||
|
||||
2. **客观下线(FAIL)**
|
||||
- 超过半数主节点认为故障
|
||||
- 标记为客观下线状态
|
||||
- 触发故障转移流程
|
||||
|
||||
3. **故障转移**
|
||||
- 从节点发起选举
|
||||
- 获得多数票的从节点成为新主节点
|
||||
- 更新槽位分配信息
|
||||
|
||||
**故障转移过程**
|
||||
|
||||
故障转移详细流程:
|
||||
|
||||
1. 故障检测
|
||||
- 主节点A无响应超过cluster-node-timeout
|
||||
- 其他节点标记A为PFAIL
|
||||
- 收集到足够PFAIL报告后标记为FAIL
|
||||
|
||||
2. 从节点选举
|
||||
- A的从节点们开始选举
|
||||
- 计算选举延迟:rank * 1000 + random(0,1000)
|
||||
- 延迟最小的从节点首先发起选举
|
||||
|
||||
3. 投票过程
|
||||
- 候选从节点向所有主节点请求投票
|
||||
- 主节点在一个配置纪元内只能投一票
|
||||
- 获得多数票(N/2+1)的从节点胜出
|
||||
|
||||
4. 角色切换
|
||||
- 胜出的从节点执行CLUSTER FAILOVER
|
||||
- 接管原主节点的槽位
|
||||
- 更新集群配置并广播
|
||||
|
||||
5. 配置传播
|
||||
- 新主节点广播配置更新
|
||||
- 其他节点更新路由表
|
||||
- 客户端更新连接信息
|
||||
|
||||
|
||||
## Redis 集群配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**节点配置文件**
|
||||
|
||||
```shell
|
||||
# Redis 集群节点配置模板
|
||||
|
||||
# 基本配置
|
||||
port 7001
|
||||
bind 127.0.0.1
|
||||
dir ./
|
||||
logfile "redis-7001.log"
|
||||
pidfile "redis-7001.pid"
|
||||
daemonize yes
|
||||
|
||||
# 集群配置
|
||||
cluster-enabled yes
|
||||
cluster-config-file nodes-7001.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7001.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 内存配置
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 网络配置
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
|
||||
# 性能优化
|
||||
tcp-backlog 511
|
||||
databases 1 # 集群模式只支持数据库0
|
||||
```
|
||||
|
||||
**多节点配置生成**
|
||||
|
||||
1. 复制节点配置模板
|
||||
2. 修改端口号和配置文件名
|
||||
3. 生成节点配置文件
|
||||
|
||||
### 集群创建
|
||||
|
||||
**使用 redis-cli 创建集群**
|
||||
|
||||
1. 启动所有节点
|
||||
2. 连接其中一个节点(如 7001)
|
||||
3. 执行集群创建命令
|
||||
```shell
|
||||
redis-cli --cluster create 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 127.0.0.1:7006 --cluster-replicas 1
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
**集群安全配置**
|
||||
|
||||
```shell
|
||||
# 安全配置
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
masterauth "$(openssl rand -base64 32)"
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_$(openssl rand -hex 8)"
|
||||
rename-command EVAL "EVAL_$(openssl rand -hex 8)"
|
||||
|
||||
# 连接限制
|
||||
maxclients 10000
|
||||
tcp-backlog 511
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
```
|
||||
|
||||
**性能优化配置**
|
||||
|
||||
```shell
|
||||
# 内存优化
|
||||
maxmemory 8gb
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory-samples 10
|
||||
|
||||
# 持久化优化
|
||||
# 禁用 RDB 以提高性能
|
||||
save ""
|
||||
stop-writes-on-bgsave-error no
|
||||
|
||||
# AOF 优化
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7001.aof"
|
||||
appendfsync no # 由操作系统决定何时同步
|
||||
no-appendfsync-on-rewrite yes
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 1gb
|
||||
|
||||
# 网络优化
|
||||
tcp-keepalive 60
|
||||
tcp-backlog 2048
|
||||
timeout 0
|
||||
|
||||
# 连接优化
|
||||
maxclients 50000
|
||||
|
||||
# 客户端输出缓冲区优化
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 1gb 256mb 60
|
||||
client-output-buffer-limit pubsub 128mb 32mb 60
|
||||
|
||||
# 性能调优
|
||||
databases 1
|
||||
lua-time-limit 5000
|
||||
slowlog-log-slower-than 1000
|
||||
slowlog-max-len 1000
|
||||
|
||||
# 哈希表优化
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
list-max-ziplist-size -2
|
||||
list-compress-depth 0
|
||||
set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# HyperLogLog 优化
|
||||
hll-sparse-max-bytes 3000
|
||||
|
||||
# 流优化
|
||||
stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
```
|
||||
|
||||
## Redis 集群管理
|
||||
|
||||
### 集群操作命令
|
||||
|
||||
**基本管理命令**
|
||||
|
||||
```shell
|
||||
# 集群管理命令大全
|
||||
# 基本连接
|
||||
redis-cli -c -h 127.0.0.1 -p 7001
|
||||
|
||||
# 查看集群基本信息
|
||||
127.0.0.1:7001> CLUSTER INFO
|
||||
# 查看集群节点信息
|
||||
127.0.0.1:7001> CLUSTER NODES
|
||||
# 查看槽位分配
|
||||
127.0.0.1:7001> CLUSTER SLOTS
|
||||
|
||||
# 槽位管理
|
||||
# 查看键所在的槽位
|
||||
127.0.0.1:7001> CLUSTER KEYSLOT key_name
|
||||
# 查看槽位中的键数量
|
||||
127.0.0.1:7001> CLUSTER COUNTKEYSINSLOT 1000
|
||||
# 获取槽位中的键,最多返回10个
|
||||
127.0.0.1:7001> CLUSTER GETKEYSINSLOT 1000 10
|
||||
# 删除槽位分配
|
||||
127.0.0.1:7001> CLUSTER DELSLOTS 7000 7001 7002
|
||||
# 手动分配槽位
|
||||
127.0.0.1:7001> CLUSTER ADDSLOTS 7000 7001 7002
|
||||
|
||||
# 节点管理
|
||||
# 添加节点
|
||||
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7007
|
||||
# 忘记节点
|
||||
127.0.0.1:7001> CLUSTER FORGET node_id
|
||||
# 设置从节点
|
||||
127.0.0.1:7001> CLUSTER REPLICATE 127.0.0.1 7007
|
||||
# 故障转移
|
||||
127.0.0.1:7001> CLUSTER FAILOVER
|
||||
127.0.0.1:7001> CLUSTER FAILOVER FORCE
|
||||
127.0.0.1:7001> CLUSTER FAILOVER TAKEOVER
|
||||
|
||||
# 槽位迁移
|
||||
# 设置槽位为迁移状态
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 MIGRATING target_node_id
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 IMPORTING source_node_id
|
||||
# 迁移键
|
||||
127.0.0.1:7001> MIGRATE 127.0.0.1 7002 key_name 0 5000
|
||||
# 完成槽位迁移
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 NODE target_node_id
|
||||
# 检查槽位迁移状态
|
||||
127.0.0.1:7001> CLUSTER SLOTS
|
||||
# 检查键是否迁移完成
|
||||
127.0.0.1:7001> EXISTS key_name
|
||||
|
||||
# 调试命令
|
||||
# 保存集群配置
|
||||
127.0.0.1:7001> CLUSTER SAVECONFIG
|
||||
# 设置配置纪元
|
||||
127.0.0.1:7001> CLUSTER SET-CONFIG-EPOCH 1
|
||||
# 获取节点ID
|
||||
127.0.0.1:7001> CLUSTER MYID
|
||||
|
||||
```
|
||||
|
||||
### 集群扩容和缩容
|
||||
|
||||
**添加节点流程**
|
||||
1. 复制节点配置模板
|
||||
2. 修改端口号和配置文件名
|
||||
3. 生成节点配置文件
|
||||
4. 启动新节点
|
||||
5. 连接其中一个节点(如 7001)
|
||||
6. 执行集群添加节点命令
|
||||
```shell
|
||||
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
|
||||
```
|
||||
7. 重新分配槽位
|
||||
```shell
|
||||
# NEW_MASTER_ID
|
||||
redis-cli -h 127.0.0.1 -p 7007 CLUSTER MYID
|
||||
# SLOTS_TO_MIGRATE
|
||||
16384 / 4 = 4096
|
||||
|
||||
redis-cli --cluster reshard 127.0.0.1:7001 \
|
||||
--cluster-from all \
|
||||
--cluster-to <NEW_MASTER_ID> \
|
||||
--cluster-slots <SLOTS_TO_MIGRATE> \
|
||||
--cluster-yes
|
||||
```
|
||||
8. 添加新从节点
|
||||
```shell
|
||||
redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 --cluster-slave --cluster-master-id <NEW_MASTER_ID>
|
||||
```
|
||||
9. 验证扩容结果
|
||||
10. 测试新节点 & 验证数据读写
|
||||
|
||||
|
||||
**删除节点流程**
|
||||
1. 确认要删除的主节点
|
||||
2. 迁移该主节点上的所有槽位:注意整个集群槽位分配是否平均
|
||||
```shell
|
||||
# REMOVE_MASTER_ID:删除节点
|
||||
# TARGET_ID:选择一个节点作为目标节点
|
||||
# RANGE_COUNT:槽位迁移数量
|
||||
redis-cli --cluster reshard 127.0.0.1:7001 \
|
||||
--cluster-from <REMOVE_MASTER_ID> \
|
||||
--cluster-to <TARGET_ID> \
|
||||
--cluster-slots <RANGE_COUNT> \
|
||||
--cluster-yes > /dev/null 2>&1
|
||||
```
|
||||
3. 删除从节点 & 主节点
|
||||
```shell
|
||||
redis-cli --cluster del-node 127.0.0.1:7001 <REMOVE_SLAVE_ID>
|
||||
redis-cli --cluster del-node 127.0.0.1:7001 <REMOVE_MASTER_ID>
|
||||
```
|
||||
4. 停掉相关进程
|
||||
|
||||
### 故障处理
|
||||
|
||||
**节点故障恢复流程**
|
||||
1. 检测故障节点:ping 不通
|
||||
2. 分析故障类型:通过 `cluster info` 查看集群状态 `cluster_state` 字段 && `cluster nodes` 查看节点状态
|
||||
- `cluster_state: ok`:可能是从节点故障
|
||||
- `cluster_state: fail`:可能是主节点故障
|
||||
- `cluster_state: unknown`:未知故障
|
||||
3. 执行故障恢复:
|
||||
- 尝试重启相关 Redis 进程:如果需要可以重置节点集群状态 `CLUSTER RESET SOFT` 后重新加入集群 `CLUSTER MEET $failed_ip $failed_port`
|
||||
- 手动故障转移:如果节点故障持续存在,可能需要手动触发故障转移,从节点上执行 `CLUSTER FAILOVER FORCE`
|
||||
4. 检查恢复结果:通过 `cluster nodes` 查看节点状态,确认故障节点已恢复
|
||||
|
||||
**数据一致性检查**
|
||||
|
||||
数据一致性检查是确保Redis集群数据完整性和可靠性的重要环节。
|
||||
|
||||
**1. 槽位分配一致性检查**
|
||||
```shell
|
||||
# 检查所有槽位是否完整分配
|
||||
redis-cli --cluster check 127.0.0.1:7001
|
||||
|
||||
# 查看槽位分配详情
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER SLOTS
|
||||
|
||||
# 检查特定槽位的分配情况
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER KEYSLOT mykey
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER NODES | grep "0-5460"
|
||||
```
|
||||
|
||||
**2. 主从复制一致性检查**
|
||||
```shell
|
||||
# 检查主从节点数据同步状态
|
||||
redis-cli -h 127.0.0.1 -p 7001 INFO replication
|
||||
redis-cli -h 127.0.0.1 -p 7004 INFO replication
|
||||
|
||||
# 比较主从节点的数据量
|
||||
redis-cli -h 127.0.0.1 -p 7001 DBSIZE
|
||||
redis-cli -h 127.0.0.1 -p 7004 DBSIZE
|
||||
|
||||
# 检查复制延迟
|
||||
redis-cli -h 127.0.0.1 -p 7004 LASTSAVE
|
||||
```
|
||||
|
||||
**3. 数据完整性验证**
|
||||
- 写入足够分散的测试数据
|
||||
- 随机从集群中的节点读取数据进行验证
|
||||
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
完成一个完整的 Redis Cluster 集群搭建、测试和管理过程:
|
||||
1. 搭建3主3从节点的Redis Cluster 集群
|
||||
2. 尝试扩容集群节点,添加一个主节点和一个从节点
|
||||
3. 尝试缩容集群节点,删除一个主节点和一个从节点
|
||||
4. 整个过程中,集群状态符合预期
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 搭建3主3从节点的Redis Cluster 集群
|
||||
# 准备目录
|
||||
[root@localhost ~]# mkdir -pv /data/redis_cluster_cluster/{7101,7102,7103,7104,7105,7106}
|
||||
# 为每个节点生成配置:参考 genrate_cluster_configs.sh 脚本
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
# 修改 BASE_PORT 为 7101
|
||||
# 查看生成的目录文件结构
|
||||
[root@localhost ~]# tree /data/redis_cluster_cluster/
|
||||
/data/redis_cluster_cluster/
|
||||
├── node-7101
|
||||
│ └── redis.conf
|
||||
├── node-7102
|
||||
│ └── redis.conf
|
||||
├── node-7103
|
||||
│ └── redis.conf
|
||||
├── node-7104
|
||||
│ └── redis.conf
|
||||
├── node-7105
|
||||
│ └── redis.conf
|
||||
└── node-7106
|
||||
└── redis.conf
|
||||
# 启动所有节点并创建集群: 参考 create_redis_cluster.sh 脚本
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
# 修改 BASE_PORT 为 7101
|
||||
# 手动执行集群管理命令:集群状态符合预期
|
||||
|
||||
# 2. 尝试扩容集群节点,添加一个主节点和一个从节点
|
||||
# 参考 cluster_scale_out.sh 脚本
|
||||
# 修改 EXISTING_NODE 为 127.0.0.1:7101
|
||||
# 修改 NEW_MASTER 为 127.0.0.1:7107
|
||||
# 修改 NEW_SLAVE 为 127.0.0.1:7108
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
|
||||
# 3. 尝试缩容集群节点,删除一个主节点和一个从节点
|
||||
# 参考 cluster_scale_in.sh 脚本
|
||||
# 修改 EXISTING_NODE 为 127.0.0.1:7101
|
||||
# 修改 REMOVE_MASTER_ID 为 7107
|
||||
# 修改 REMOVE_SLAVE_ID 为 7108
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
|
||||
```
|
456
数据库/Redis_2025/09_Redis性能优化.md
Normal file
456
数据库/Redis_2025/09_Redis性能优化.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# Redis 性能优化
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 性能指标分析
|
||||
|
||||
Redis 性能监控需要关注以下关键指标:
|
||||
|
||||
**内存指标**:
|
||||
- `used_memory`:Redis 使用的内存总量
|
||||
- `used_memory_rss`:Redis 进程占用的物理内存
|
||||
- `used_memory_peak`:Redis 使用内存的峰值
|
||||
- `mem_fragmentation_ratio`:内存碎片率
|
||||
|
||||
**性能指标**:
|
||||
- `instantaneous_ops_per_sec`:每秒操作数
|
||||
- `keyspace_hits`:键空间命中次数
|
||||
- `keyspace_misses`:键空间未命中次数
|
||||
- `hit_rate`:缓存命中率
|
||||
|
||||
**连接指标**:
|
||||
- `connected_clients`:当前连接的客户端数量
|
||||
- `blocked_clients`:被阻塞的客户端数量
|
||||
- `rejected_connections`:被拒绝的连接数
|
||||
|
||||
### 监控工具使用
|
||||
|
||||
**Redis 内置监控命令**:
|
||||
|
||||
```shell
|
||||
# 查看服务器信息
|
||||
redis-cli info
|
||||
|
||||
# 查看特定分类信息
|
||||
redis-cli info memory
|
||||
redis-cli info stats
|
||||
redis-cli info clients
|
||||
|
||||
# 实时监控命令执行
|
||||
redis-cli monitor
|
||||
|
||||
# 查看慢查询日志
|
||||
redis-cli slowlog get 10
|
||||
```
|
||||
|
||||
**性能测试工具**:
|
||||
|
||||
```shell
|
||||
# Redis 基准测试
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
|
||||
|
||||
# 测试特定命令性能
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -t set,get -n 10000 -q
|
||||
|
||||
# 测试管道性能:Pepeline模式
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -n 10000 -P 16
|
||||
|
||||
```
|
||||
|
||||
### 慢查询日志
|
||||
|
||||
**配置慢查询**:
|
||||
|
||||
```shell
|
||||
# 设置慢查询阈值(微秒)
|
||||
CONFIG SET slowlog-log-slower-than 10000
|
||||
|
||||
# 设置慢查询日志长度
|
||||
CONFIG SET slowlog-max-len 128
|
||||
|
||||
# 查看慢查询配置
|
||||
CONFIG GET slowlog*
|
||||
```
|
||||
|
||||
**分析慢查询**:
|
||||
|
||||
```shell
|
||||
# 获取慢查询日志
|
||||
SLOWLOG GET 10
|
||||
|
||||
# 获取慢查询日志长度
|
||||
SLOWLOG LEN
|
||||
|
||||
# 清空慢查询日志
|
||||
SLOWLOG RESET
|
||||
```
|
||||
|
||||
### 内存使用分析
|
||||
|
||||
**内存分析命令**:
|
||||
|
||||
```shell
|
||||
# 分析内存使用情况
|
||||
MEMORY USAGE key_name
|
||||
|
||||
# 获取内存统计信息
|
||||
MEMORY STATS
|
||||
|
||||
# 分析键空间
|
||||
MEMORY DOCTOR
|
||||
|
||||
# 查看大键
|
||||
redis-cli --bigkeys
|
||||
```
|
||||
|
||||
## 内存优化
|
||||
|
||||
### 内存使用策略
|
||||
|
||||
**过期策略配置**:
|
||||
|
||||
```shell
|
||||
# 设置最大内存限制
|
||||
maxmemory 2gb
|
||||
|
||||
# 设置内存淘汰策略
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 可选的淘汰策略:
|
||||
# noeviction:不淘汰,返回错误
|
||||
# allkeys-lru:所有键中淘汰最近最少使用
|
||||
# allkeys-lfu:所有键中淘汰最少使用频率
|
||||
# volatile-lru:过期键中淘汰最近最少使用
|
||||
# volatile-lfu:过期键中淘汰最少使用频率
|
||||
# allkeys-random:所有键中随机淘汰
|
||||
# volatile-random:过期键中随机淘汰
|
||||
# volatile-ttl:过期键中淘汰即将过期的
|
||||
```
|
||||
|
||||
### 数据结构优化
|
||||
|
||||
**字符串优化**:
|
||||
|
||||
```shell
|
||||
# 使用整数编码
|
||||
SET counter 100 # 使用 int 编码
|
||||
SET counter "100" # 使用 raw 编码
|
||||
|
||||
# 小字符串使用 embstr 编码(<=44字节)
|
||||
SET small_string "hello world"
|
||||
|
||||
# 大字符串使用 raw 编码(>44字节)
|
||||
SET large_string "very long string content..."
|
||||
```
|
||||
|
||||
**哈希优化**:
|
||||
|
||||
```shell
|
||||
# 配置哈希压缩列表阈值
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
|
||||
# 小哈希使用压缩列表
|
||||
HSET user:1 name "john" age 25
|
||||
|
||||
# 大哈希使用哈希表
|
||||
for i in {1..1000}; do
|
||||
redis-cli HSET large_hash field$i value$i
|
||||
done
|
||||
```
|
||||
|
||||
**列表优化**:
|
||||
|
||||
```shell
|
||||
# 配置列表压缩参数
|
||||
list-max-ziplist-size -2
|
||||
list-compress-depth 0
|
||||
|
||||
# 使用压缩列表的小列表
|
||||
LPUSH small_list item1 item2 item3
|
||||
|
||||
# 使用快速列表的大列表
|
||||
for i in {1..10000}; do
|
||||
redis-cli LPUSH large_list item$i
|
||||
done
|
||||
```
|
||||
|
||||
### 过期策略配置
|
||||
|
||||
**过期策略参数**:
|
||||
|
||||
```shell
|
||||
# 设置过期扫描频率
|
||||
hz 10
|
||||
|
||||
# 设置过期删除的CPU时间比例
|
||||
maxmemory-samples 5
|
||||
|
||||
# 配置惰性删除
|
||||
lazyfree-lazy-eviction yes
|
||||
lazyfree-lazy-expire yes
|
||||
lazyfree-lazy-server-del yes
|
||||
```
|
||||
|
||||
### 内存碎片处理
|
||||
|
||||
**内存碎片分析**:
|
||||
|
||||
```shell
|
||||
# 查看内存碎片率
|
||||
INFO memory | grep mem_fragmentation_ratio
|
||||
|
||||
# 内存碎片率计算
|
||||
# mem_fragmentation_ratio = used_memory_rss / used_memory
|
||||
# 正常范围:1.0 - 1.5
|
||||
# > 1.5:内存碎片较多
|
||||
# < 1.0:可能发生了内存交换
|
||||
```
|
||||
|
||||
**内存整理**:
|
||||
|
||||
```shell
|
||||
# 主动内存整理(Redis 4.0+)
|
||||
MEMORY PURGE
|
||||
|
||||
# 配置自动内存整理
|
||||
activedefrag yes
|
||||
active-defrag-ignore-bytes 100mb
|
||||
active-defrag-threshold-lower 10
|
||||
active-defrag-threshold-upper 100
|
||||
```
|
||||
|
||||
## 网络优化
|
||||
|
||||
### 连接池配置
|
||||
|
||||
**连接池参数优化**:
|
||||
|
||||
```shell
|
||||
# 设置最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
# 设置客户端超时时间
|
||||
timeout 300
|
||||
|
||||
# 设置TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 设置TCP backlog
|
||||
tcp-backlog 511
|
||||
```
|
||||
|
||||
### 管道技术
|
||||
|
||||
**技术原理**
|
||||
1. 客户端批量发送命令 → 服务器批量处理 → 批量返回结果
|
||||
2. 管道技术可以减少网络往返次数,提高批量操作的效率
|
||||
|
||||
|
||||
**管道批量操作**:
|
||||
|
||||
```shell
|
||||
# 传统模式 - 逐个执行命令(每个命令都需要等待响应)
|
||||
redis-cli SET key1 value1
|
||||
redis-cli SET key2 value2
|
||||
redis-cli SET key3 value3
|
||||
redis-cli GET key1
|
||||
redis-cli GET key2
|
||||
redis-cli GET key3
|
||||
|
||||
# Pipeline模式 - 批量发送命令(减少网络往返)
|
||||
# 方法1:使用管道符
|
||||
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3\nGET key1\nGET key2\nGET key3" | redis-cli --pipe
|
||||
|
||||
# 方法2:使用文件批量执行
|
||||
cat > commands.txt << EOF
|
||||
SET key1 value1
|
||||
SET key2 value2
|
||||
SET key3 value3
|
||||
GET key1
|
||||
GET key2
|
||||
GET key3
|
||||
EOF
|
||||
redis-cli --pipe < commands.txt
|
||||
|
||||
|
||||
# 性能对比测试
|
||||
# 传统模式:100个SET命令
|
||||
time for i in {1..100}; do redis-cli SET test_key_$i value_$i > /dev/null; done
|
||||
|
||||
# Pipeline模式:100个SET命令
|
||||
time (for i in {1..100}; do echo "SET test_key_$i value_$i"; done | redis-cli --pipe > /dev/null)
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
**批量命令优化**:
|
||||
|
||||
```shell
|
||||
# 传统方式 - 多个单独命令
|
||||
SET key1 value1
|
||||
SET key2 value2
|
||||
SET key3 value3
|
||||
GET key1
|
||||
GET key2
|
||||
GET key3
|
||||
|
||||
# 优化方式 - 使用批量命令
|
||||
# 使用 MSET 代替多个 SET
|
||||
MSET key1 value1 key2 value2 key3 value3
|
||||
|
||||
# 使用 MGET 代替多个 GET
|
||||
MGET key1 key2 key3
|
||||
|
||||
# 使用 HMSET 批量设置哈希字段
|
||||
HMSET user:1 name john age 25 email john@example.com
|
||||
|
||||
# 使用 HMGET 批量获取哈希字段
|
||||
HMGET user:1 name age email
|
||||
|
||||
# 使用 redis-benchmark 对比批量操作和单个操作的性能
|
||||
# 对比 SET vs MSET 性能
|
||||
redis-benchmark -t set -n 100000 -q
|
||||
# 结果示例: SET: 28352.71 requests per second, p50=0.871 msec
|
||||
redis-benchmark -t mset -n 100000 -q
|
||||
# 结果示例: MSET (10 keys): 26860.06 requests per second, p50=0.927 msec
|
||||
|
||||
# 性能分析:
|
||||
# - SET 单个操作: 28,352 ops/sec,平均延迟 0.871ms
|
||||
# - MSET 批量操作: 26,860 ops/sec,平均延迟 0.927ms
|
||||
# - 注意:MSET 测试的是每次设置10个键值对,实际吞吐量为 26,860 * 10 = 268,600 键/秒
|
||||
# - 批量操作的真实性能提升约为: 268,600 / 28,352 ≈ 9.5倍
|
||||
|
||||
```
|
||||
|
||||
### 网络延迟优化
|
||||
|
||||
**网络参数调优**:
|
||||
|
||||
```shell
|
||||
# 禁用 Nagle 算法
|
||||
tcp-nodelay yes
|
||||
|
||||
# 设置发送缓冲区大小
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
```
|
||||
|
||||
## 配置优化
|
||||
|
||||
### 持久化优化
|
||||
|
||||
**RDB 优化配置**:
|
||||
|
||||
```shell
|
||||
# RDB 保存策略
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# RDB 文件压缩
|
||||
rdbcompression yes
|
||||
|
||||
# RDB 文件校验
|
||||
rdbchecksum yes
|
||||
|
||||
# RDB 文件名
|
||||
dbfilename dump.rdb
|
||||
|
||||
# 后台保存出错时停止写入
|
||||
stop-writes-on-bgsave-error yes
|
||||
```
|
||||
|
||||
**AOF 优化配置**:
|
||||
|
||||
```shell
|
||||
# 启用 AOF
|
||||
appendonly yes
|
||||
|
||||
# AOF 文件名
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
appendfsync everysec
|
||||
|
||||
# AOF 重写优化
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# AOF 重写时不同步
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# AOF 加载时忽略错误
|
||||
aof-load-truncated yes
|
||||
|
||||
# 混合持久化
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
### 复制优化
|
||||
|
||||
**主从复制优化**:
|
||||
|
||||
```shell
|
||||
# 复制积压缓冲区大小
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 复制超时时间
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用TCP_NODELAY
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制ping周期
|
||||
repl-ping-replica-period 10
|
||||
```
|
||||
|
||||
### 系统级优化
|
||||
|
||||
**操作系统参数调优**:
|
||||
|
||||
```shell
|
||||
# 内存过量分配
|
||||
echo 1 > /proc/sys/vm/overcommit_memory
|
||||
|
||||
# 禁用透明大页
|
||||
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
||||
|
||||
# 设置文件描述符限制
|
||||
ulimit -n 65535
|
||||
|
||||
# 设置内存映射限制
|
||||
echo 262144 > /proc/sys/vm/max_map_count
|
||||
|
||||
# TCP 参数优化
|
||||
echo 'net.core.somaxconn = 65535' >> /etc/sysctl.conf
|
||||
echo 'net.ipv4.tcp_max_syn_backlog = 65535' >> /etc/sysctl.conf
|
||||
sysctl -p
|
||||
```
|
||||
|
||||
**文件系统优化**:
|
||||
|
||||
```shell
|
||||
# 使用高性能文件系统
|
||||
# 推荐:ext4, xfs
|
||||
|
||||
# 挂载选项优化
|
||||
# /etc/fstab
|
||||
/dev/sdb1 /var/lib/redis ext4 defaults,noatime,nodiratime 0 2
|
||||
|
||||
# SSD 优化
|
||||
echo deadline > /sys/block/sdb/queue/scheduler
|
||||
echo 1 > /sys/block/sdb/queue/iosched/fifo_batch
|
||||
```
|
||||
|
||||
## [扩展] 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
通过实际操作来监控 Redis 性能、优化内存使用,并测试性能提升效果。
|
||||
|
||||
### 实践细节和结果验证
|
784
数据库/Redis_2025/10_Redis安全管理.md
Normal file
784
数据库/Redis_2025/10_Redis安全管理.md
Normal file
@@ -0,0 +1,784 @@
|
||||
# Redis 安全管理
|
||||
|
||||
## 访问控制
|
||||
|
||||
### 密码认证
|
||||
|
||||
Redis 提供了基本的密码认证机制来保护数据安全。
|
||||
|
||||
**配置密码认证**:
|
||||
|
||||
```shell
|
||||
# 在配置文件中设置密码
|
||||
# /etc/redis/redis.conf
|
||||
requirepass your_strong_password
|
||||
|
||||
# 动态设置密码
|
||||
redis-cli CONFIG SET requirepass "your_strong_password"
|
||||
|
||||
# 使用密码连接
|
||||
redis-cli -a your_strong_password
|
||||
|
||||
# 或者连接后认证
|
||||
redis-cli
|
||||
127.0.0.1:6379> AUTH your_strong_password
|
||||
OK
|
||||
```
|
||||
|
||||
**密码安全最佳实践**:
|
||||
|
||||
```shell
|
||||
# 生成强密码
|
||||
openssl rand -base64 32
|
||||
|
||||
# 密码复杂度要求:
|
||||
# - 长度至少16位
|
||||
# - 包含大小写字母、数字、特殊字符
|
||||
# - 避免使用字典词汇
|
||||
# - 定期更换密码
|
||||
|
||||
# 示例强密码
|
||||
requirepass "Rd!s@2024#Str0ng&P@ssw0rd"
|
||||
```
|
||||
|
||||
### 用户管理 (ACL)
|
||||
|
||||
Redis 6.0+ 引入了 ACL(Access Control List)功能,提供更细粒度的权限控制。
|
||||
|
||||
**ACL 基本概念**:
|
||||
|
||||
```shell
|
||||
# 查看当前用户
|
||||
ACL WHOAMI
|
||||
|
||||
# 列出所有用户
|
||||
ACL LIST
|
||||
|
||||
# 查看用户详细信息
|
||||
ACL GETUSER username
|
||||
|
||||
# 查看当前用户权限
|
||||
ACL GETUSER default
|
||||
```
|
||||
|
||||
**创建和管理用户**:
|
||||
|
||||
```shell
|
||||
# 创建只读用户
|
||||
ACL SETUSER readonly on >readonly_password ~* &* -@all +@read
|
||||
|
||||
# 创建读写用户(限制特定键模式)
|
||||
ACL SETUSER readwrite on >readwrite_password ~app:* &* -@all +@read +@write
|
||||
|
||||
# 创建管理员用户
|
||||
ACL SETUSER admin on >admin_password ~* &* +@all
|
||||
|
||||
# 创建应用用户(限制命令)
|
||||
ACL SETUSER appuser on >app_password ~app:* &* -@all +get +set +del +exists +expire
|
||||
|
||||
# 删除用户
|
||||
ACL DELUSER username
|
||||
```
|
||||
|
||||
**ACL 规则详解**:
|
||||
|
||||
```shell
|
||||
# ACL 规则语法:
|
||||
# on/off:启用/禁用用户
|
||||
# >password:设置密码
|
||||
# ~pattern:允许访问的键模式
|
||||
# &pattern:允许访问的发布订阅频道模式
|
||||
# +command:允许的命令
|
||||
# -command:禁止的命令
|
||||
# +@category:允许的命令分类
|
||||
# -@category:禁止的命令分类
|
||||
|
||||
# 常用命令分类:
|
||||
# @read:读命令
|
||||
# @write:写命令
|
||||
# @admin:管理命令
|
||||
# @dangerous:危险命令
|
||||
# @keyspace:键空间命令
|
||||
# @string:字符串命令
|
||||
# @list:列表命令
|
||||
# @set:集合命令
|
||||
# @hash:哈希命令
|
||||
# @sortedset:有序集合命令
|
||||
```
|
||||
|
||||
### 权限控制
|
||||
|
||||
**细粒度权限配置**:
|
||||
|
||||
```shell
|
||||
# 数据库管理员
|
||||
ACL SETUSER dba on >dba_password ~* &* +@all
|
||||
|
||||
# 应用开发者
|
||||
ACL SETUSER developer on >dev_password ~dev:* &dev:* -@all +@read +@write -flushdb -flushall -shutdown
|
||||
|
||||
# 监控用户
|
||||
ACL SETUSER monitor on >monitor_password ~* &* -@all +info +ping +client +config|get
|
||||
|
||||
# 备份用户
|
||||
ACL SETUSER backup on >backup_password ~* &* -@all +@read +bgsave +lastsave
|
||||
|
||||
# 只读分析用户
|
||||
ACL SETUSER analyst on >analyst_password ~analytics:* &* -@all +@read +scan +keys
|
||||
```
|
||||
|
||||
**权限验证测试**:
|
||||
|
||||
```shell
|
||||
# 测试用户权限
|
||||
redis-cli --user readonly --pass readonly_password
|
||||
127.0.0.1:6379> GET some_key # 应该成功
|
||||
127.0.0.1:6379> SET some_key value # 应该失败
|
||||
|
||||
# 测试键模式限制
|
||||
redis-cli --user developer --pass dev_password
|
||||
127.0.0.1:6379> GET dev:config # 应该成功
|
||||
127.0.0.1:6379> GET prod:config # 应该失败
|
||||
```
|
||||
|
||||
### IP 白名单
|
||||
|
||||
**网络访问控制**:
|
||||
|
||||
```shell
|
||||
# 绑定特定IP地址
|
||||
# /etc/redis/redis.conf
|
||||
bind 127.0.0.1 192.168.1.100 10.0.0.50
|
||||
|
||||
# 禁用保护模式(仅在安全网络环境中)
|
||||
protected-mode no
|
||||
|
||||
# 使用防火墙限制访问
|
||||
sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.0/24' port protocol='tcp' port='6379' accept"
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
## 网络安全
|
||||
|
||||
### 端口安全
|
||||
|
||||
**端口配置和保护**:
|
||||
|
||||
```shell
|
||||
# 更改默认端口
|
||||
# /etc/redis/redis.conf
|
||||
port 16379 # 使用非标准端口
|
||||
|
||||
# 禁用端口(仅使用Unix套接字)
|
||||
port 0
|
||||
unixsocket /var/run/redis/redis.sock
|
||||
unixsocketperm 700
|
||||
|
||||
# 连接Unix套接字
|
||||
redis-cli -s /var/run/redis/redis.sock
|
||||
```
|
||||
|
||||
**网络接口绑定**:
|
||||
|
||||
```shell
|
||||
# 仅绑定内网接口
|
||||
bind 127.0.0.1 192.168.1.100
|
||||
|
||||
# 绑定多个接口
|
||||
bind 127.0.0.1 10.0.0.100 172.16.0.100
|
||||
|
||||
# 监听所有接口(不推荐)
|
||||
# bind 0.0.0.0
|
||||
```
|
||||
|
||||
### SSL/TLS 加密
|
||||
|
||||
Redis 6.0+ 支持 SSL/TLS 加密传输。
|
||||
|
||||
**生成SSL证书**:
|
||||
|
||||
```shell
|
||||
# 创建证书目录
|
||||
sudo mkdir -p /etc/redis/ssl
|
||||
cd /etc/redis/ssl
|
||||
|
||||
# 生成私钥
|
||||
sudo openssl genrsa -out redis.key 2048
|
||||
|
||||
# 生成证书签名请求
|
||||
sudo openssl req -new -key redis.key -out redis.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=redis.example.com"
|
||||
|
||||
# 生成自签名证书
|
||||
sudo openssl x509 -req -days 365 -in redis.csr -signkey redis.key -out redis.crt
|
||||
|
||||
# 生成DH参数文件
|
||||
sudo openssl dhparam -out redis.dh 2048
|
||||
|
||||
# 设置权限
|
||||
sudo chown redis:redis /etc/redis/ssl/*
|
||||
sudo chmod 600 /etc/redis/ssl/redis.key
|
||||
sudo chmod 644 /etc/redis/ssl/redis.crt
|
||||
sudo chmod 644 /etc/redis/ssl/redis.dh
|
||||
```
|
||||
|
||||
**配置SSL/TLS**:
|
||||
|
||||
```shell
|
||||
# Redis 配置文件
|
||||
# /etc/redis/redis.conf
|
||||
|
||||
# 启用TLS端口
|
||||
port 0
|
||||
tls-port 6380
|
||||
|
||||
# 证书文件路径
|
||||
tls-cert-file /etc/redis/ssl/redis.crt
|
||||
tls-key-file /etc/redis/ssl/redis.key
|
||||
tls-dh-params-file /etc/redis/ssl/redis.dh
|
||||
|
||||
# CA证书(如果使用)
|
||||
# tls-ca-cert-file /etc/redis/ssl/ca.crt
|
||||
|
||||
# 客户端证书验证
|
||||
tls-auth-clients yes
|
||||
|
||||
# TLS协议版本
|
||||
tls-protocols "TLSv1.2 TLSv1.3"
|
||||
|
||||
# 密码套件
|
||||
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||
tls-ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
|
||||
|
||||
# 会话缓存
|
||||
tls-session-caching no
|
||||
tls-session-cache-size 5000
|
||||
tls-session-cache-timeout 60
|
||||
```
|
||||
|
||||
**SSL客户端连接**:
|
||||
|
||||
```shell
|
||||
# 使用SSL连接
|
||||
redis-cli --tls --cert /etc/redis/ssl/client.crt --key /etc/redis/ssl/client.key --cacert /etc/redis/ssl/ca.crt -p 6380
|
||||
|
||||
# 跳过证书验证(仅测试环境)
|
||||
redis-cli --tls --insecure -p 6380
|
||||
```
|
||||
|
||||
### 防火墙配置
|
||||
|
||||
**iptables 配置**:
|
||||
|
||||
```shell
|
||||
# 允许特定IP访问Redis
|
||||
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 6379 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
|
||||
|
||||
# 限制连接频率
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m connlimit --connlimit-above 10 -j DROP
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --set --name redis
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --update --seconds 60 --hitcount 20 --name redis -j DROP
|
||||
|
||||
# 保存规则
|
||||
sudo iptables-save > /etc/iptables/rules.v4
|
||||
```
|
||||
|
||||
**UFW 配置**:
|
||||
|
||||
```shell
|
||||
# 启用UFW
|
||||
sudo ufw enable
|
||||
|
||||
# 允许SSH
|
||||
sudo ufw allow ssh
|
||||
|
||||
# 允许特定网络访问Redis
|
||||
sudo ufw allow from 192.168.1.0/24 to any port 6379
|
||||
|
||||
# 拒绝其他Redis连接
|
||||
sudo ufw deny 6379
|
||||
|
||||
# 查看规则
|
||||
sudo ufw status numbered
|
||||
```
|
||||
|
||||
### VPN 访问
|
||||
|
||||
**OpenVPN 配置示例**:
|
||||
|
||||
```shell
|
||||
# 安装OpenVPN
|
||||
sudo apt update
|
||||
sudo apt install openvpn easy-rsa
|
||||
|
||||
# 配置VPN服务器
|
||||
sudo make-cadir /etc/openvpn/easy-rsa
|
||||
cd /etc/openvpn/easy-rsa
|
||||
|
||||
# 初始化PKI
|
||||
./easyrsa init-pki
|
||||
./easyrsa build-ca
|
||||
./easyrsa gen-req server nopass
|
||||
./easyrsa sign-req server server
|
||||
./easyrsa gen-dh
|
||||
|
||||
# 生成客户端证书
|
||||
./easyrsa gen-req client1 nopass
|
||||
./easyrsa sign-req client client1
|
||||
```
|
||||
|
||||
## 数据安全
|
||||
|
||||
### 数据加密
|
||||
|
||||
**应用层加密**:
|
||||
|
||||
```python
|
||||
# Python 数据加密示例
|
||||
import redis
|
||||
import json
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
class EncryptedRedis:
|
||||
def __init__(self, host='localhost', port=6379, password=None, key=None):
|
||||
self.redis = redis.Redis(host=host, port=port, password=password)
|
||||
self.cipher = Fernet(key or Fernet.generate_key())
|
||||
|
||||
def set(self, name, value, ex=None):
|
||||
"""加密存储数据"""
|
||||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
encrypted_value = self.cipher.encrypt(value.encode())
|
||||
return self.redis.set(name, encrypted_value, ex=ex)
|
||||
|
||||
def get(self, name):
|
||||
"""解密获取数据"""
|
||||
encrypted_value = self.redis.get(name)
|
||||
if encrypted_value:
|
||||
decrypted_value = self.cipher.decrypt(encrypted_value)
|
||||
return decrypted_value.decode()
|
||||
return None
|
||||
|
||||
def hset(self, name, key, value):
|
||||
"""加密存储哈希字段"""
|
||||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
encrypted_value = self.cipher.encrypt(value.encode())
|
||||
return self.redis.hset(name, key, encrypted_value)
|
||||
|
||||
def hget(self, name, key):
|
||||
"""解密获取哈希字段"""
|
||||
encrypted_value = self.redis.hget(name, key)
|
||||
if encrypted_value:
|
||||
decrypted_value = self.cipher.decrypt(encrypted_value)
|
||||
return decrypted_value.decode()
|
||||
return None
|
||||
|
||||
# 使用示例
|
||||
key = Fernet.generate_key()
|
||||
encrypted_redis = EncryptedRedis(password='your_password', key=key)
|
||||
|
||||
# 存储加密数据
|
||||
user_data = {'name': 'John', 'email': 'john@example.com', 'phone': '123-456-7890'}
|
||||
encrypted_redis.set('user:1', json.dumps(user_data))
|
||||
|
||||
# 获取解密数据
|
||||
data = encrypted_redis.get('user:1')
|
||||
user_info = json.loads(data)
|
||||
print(user_info)
|
||||
```
|
||||
|
||||
### 敏感数据处理
|
||||
|
||||
**敏感数据脱敏**:
|
||||
|
||||
```python
|
||||
# 数据脱敏工具
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
class DataMasking:
|
||||
@staticmethod
|
||||
def mask_phone(phone):
|
||||
"""手机号脱敏"""
|
||||
if len(phone) == 11:
|
||||
return phone[:3] + '****' + phone[7:]
|
||||
return phone
|
||||
|
||||
@staticmethod
|
||||
def mask_email(email):
|
||||
"""邮箱脱敏"""
|
||||
if '@' in email:
|
||||
local, domain = email.split('@')
|
||||
if len(local) > 2:
|
||||
masked_local = local[0] + '*' * (len(local) - 2) + local[-1]
|
||||
else:
|
||||
masked_local = '*' * len(local)
|
||||
return f"{masked_local}@{domain}"
|
||||
return email
|
||||
|
||||
@staticmethod
|
||||
def mask_id_card(id_card):
|
||||
"""身份证脱敏"""
|
||||
if len(id_card) == 18:
|
||||
return id_card[:6] + '********' + id_card[14:]
|
||||
return id_card
|
||||
|
||||
@staticmethod
|
||||
def hash_sensitive_data(data, salt=''):
|
||||
"""敏感数据哈希"""
|
||||
return hashlib.sha256((str(data) + salt).encode()).hexdigest()
|
||||
|
||||
# 使用示例
|
||||
masker = DataMasking()
|
||||
|
||||
# 存储脱敏数据
|
||||
user_data = {
|
||||
'name': 'John Doe',
|
||||
'phone': masker.mask_phone('13812345678'),
|
||||
'email': masker.mask_email('john.doe@example.com'),
|
||||
'id_card': masker.mask_id_card('110101199001011234')
|
||||
}
|
||||
|
||||
redis_client = redis.Redis(password='your_password')
|
||||
redis_client.hset('user:masked:1', mapping=user_data)
|
||||
```
|
||||
|
||||
### 备份安全
|
||||
|
||||
**安全备份策略**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# 安全备份脚本
|
||||
|
||||
BACKUP_DIR="/secure/backup/redis"
|
||||
ENCRYPTION_KEY="/secure/keys/backup.key"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="redis_backup_$DATE.rdb"
|
||||
ENCRYPTED_FILE="$BACKUP_FILE.enc"
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# 生成备份
|
||||
redis-cli --rdb $BACKUP_DIR/$BACKUP_FILE
|
||||
|
||||
# 加密备份文件
|
||||
openssl enc -aes-256-cbc -salt -in $BACKUP_DIR/$BACKUP_FILE -out $BACKUP_DIR/$ENCRYPTED_FILE -pass file:$ENCRYPTION_KEY
|
||||
|
||||
# 删除未加密文件
|
||||
rm $BACKUP_DIR/$BACKUP_FILE
|
||||
|
||||
# 计算校验和
|
||||
sha256sum $BACKUP_DIR/$ENCRYPTED_FILE > $BACKUP_DIR/$ENCRYPTED_FILE.sha256
|
||||
|
||||
# 设置权限
|
||||
chmod 600 $BACKUP_DIR/$ENCRYPTED_FILE
|
||||
chown backup:backup $BACKUP_DIR/$ENCRYPTED_FILE
|
||||
|
||||
echo "备份完成:$BACKUP_DIR/$ENCRYPTED_FILE"
|
||||
|
||||
# 清理旧备份(保留30天)
|
||||
find $BACKUP_DIR -name "*.enc" -mtime +30 -delete
|
||||
find $BACKUP_DIR -name "*.sha256" -mtime +30 -delete
|
||||
```
|
||||
|
||||
**备份恢复脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# 安全恢复脚本
|
||||
|
||||
BACKUP_FILE="$1"
|
||||
ENCRYPTION_KEY="/secure/keys/backup.key"
|
||||
TEMP_DIR="/tmp/redis_restore"
|
||||
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
echo "用法: $0 <encrypted_backup_file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证文件存在
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "备份文件不存在: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证校验和
|
||||
if [ -f "$BACKUP_FILE.sha256" ]; then
|
||||
echo "验证文件完整性..."
|
||||
sha256sum -c "$BACKUP_FILE.sha256"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "文件完整性验证失败"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建临时目录
|
||||
mkdir -p $TEMP_DIR
|
||||
|
||||
# 解密备份文件
|
||||
echo "解密备份文件..."
|
||||
DECRYPTED_FILE="$TEMP_DIR/$(basename $BACKUP_FILE .enc)"
|
||||
openssl enc -aes-256-cbc -d -in "$BACKUP_FILE" -out "$DECRYPTED_FILE" -pass file:$ENCRYPTION_KEY
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "备份文件已解密到: $DECRYPTED_FILE"
|
||||
echo "请手动将文件复制到Redis数据目录并重启服务"
|
||||
else
|
||||
echo "解密失败"
|
||||
rm -rf $TEMP_DIR
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 审计日志
|
||||
|
||||
**审计日志配置**:
|
||||
|
||||
```shell
|
||||
# Redis 配置文件
|
||||
# /etc/redis/redis.conf
|
||||
|
||||
# 启用命令日志
|
||||
logfile /var/log/redis/redis-server.log
|
||||
loglevel notice
|
||||
|
||||
# 启用慢查询日志
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# 客户端连接日志
|
||||
# 通过监控脚本实现
|
||||
```
|
||||
|
||||
**审计日志脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 审计日志脚本
|
||||
|
||||
LOG_FILE="/var/log/redis/audit.log"
|
||||
REDIS_LOG="/var/log/redis/redis-server.log"
|
||||
|
||||
# 监控Redis连接
|
||||
tail -f $REDIS_LOG | while read line; do
|
||||
if echo "$line" | grep -q "Accepted\|Client closed connection"; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $line" >> $LOG_FILE
|
||||
fi
|
||||
done &
|
||||
|
||||
# 监控命令执行(需要启用monitor)
|
||||
redis-cli monitor | while read line; do
|
||||
# 过滤敏感命令
|
||||
if echo "$line" | grep -qE "AUTH|CONFIG|EVAL|FLUSHDB|FLUSHALL|SHUTDOWN"; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') SENSITIVE: $line" >> $LOG_FILE
|
||||
fi
|
||||
done &
|
||||
|
||||
echo "审计日志监控已启动"
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 安全配置检查
|
||||
|
||||
**安全配置检查清单**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全配置检查脚本
|
||||
|
||||
echo "=== Redis 安全配置检查 ==="
|
||||
|
||||
# 检查密码配置
|
||||
echo "1. 检查密码配置"
|
||||
if redis-cli CONFIG GET requirepass | grep -q "requirepass"; then
|
||||
echo "✓ 已配置密码认证"
|
||||
else
|
||||
echo "✗ 未配置密码认证"
|
||||
fi
|
||||
|
||||
# 检查绑定地址
|
||||
echo "2. 检查绑定地址"
|
||||
BIND_ADDR=$(redis-cli CONFIG GET bind | tail -1)
|
||||
if [ "$BIND_ADDR" != "" ] && [ "$BIND_ADDR" != "0.0.0.0" ]; then
|
||||
echo "✓ 绑定地址配置安全: $BIND_ADDR"
|
||||
else
|
||||
echo "✗ 绑定地址不安全: $BIND_ADDR"
|
||||
fi
|
||||
|
||||
# 检查保护模式
|
||||
echo "3. 检查保护模式"
|
||||
PROTECTED_MODE=$(redis-cli CONFIG GET protected-mode | tail -1)
|
||||
if [ "$PROTECTED_MODE" = "yes" ]; then
|
||||
echo "✓ 保护模式已启用"
|
||||
else
|
||||
echo "✗ 保护模式未启用"
|
||||
fi
|
||||
|
||||
# 检查危险命令
|
||||
echo "4. 检查危险命令"
|
||||
DANGEROUS_COMMANDS=("FLUSHDB" "FLUSHALL" "CONFIG" "EVAL" "SHUTDOWN" "DEBUG")
|
||||
for cmd in "${DANGEROUS_COMMANDS[@]}"; do
|
||||
if redis-cli CONFIG GET "rename-command" | grep -q "$cmd"; then
|
||||
echo "✓ 危险命令 $cmd 已重命名或禁用"
|
||||
else
|
||||
echo "✗ 危险命令 $cmd 未处理"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查文件权限
|
||||
echo "5. 检查文件权限"
|
||||
REDIS_CONF="/etc/redis/redis.conf"
|
||||
if [ -f "$REDIS_CONF" ]; then
|
||||
PERM=$(stat -c "%a" "$REDIS_CONF")
|
||||
if [ "$PERM" = "640" ] || [ "$PERM" = "600" ]; then
|
||||
echo "✓ 配置文件权限安全: $PERM"
|
||||
else
|
||||
echo "✗ 配置文件权限不安全: $PERM"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查日志配置
|
||||
echo "6. 检查日志配置"
|
||||
LOGFILE=$(redis-cli CONFIG GET logfile | tail -1)
|
||||
if [ "$LOGFILE" != "" ]; then
|
||||
echo "✓ 已配置日志文件: $LOGFILE"
|
||||
else
|
||||
echo "✗ 未配置日志文件"
|
||||
fi
|
||||
|
||||
echo "=== 检查完成 ==="
|
||||
```
|
||||
|
||||
### 漏洞防护
|
||||
|
||||
**常见漏洞防护措施**:
|
||||
|
||||
```shell
|
||||
# 1. 禁用或重命名危险命令
|
||||
# /etc/redis/redis.conf
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_b840fc02d524045429941cc15f59e41cb7be6c52"
|
||||
rename-command EVAL ""
|
||||
rename-command DEBUG ""
|
||||
rename-command SHUTDOWN "SHUTDOWN_b840fc02d524045429941cc15f59e41cb7be6c52"
|
||||
|
||||
# 2. 限制客户端连接
|
||||
maxclients 1000
|
||||
timeout 300
|
||||
tcp-keepalive 300
|
||||
|
||||
# 3. 禁用Lua脚本调试
|
||||
lua-replicate-commands yes
|
||||
|
||||
# 4. 设置内存限制
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
### 安全更新
|
||||
|
||||
**安全更新策略**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全更新脚本
|
||||
|
||||
echo "检查Redis版本和安全更新"
|
||||
|
||||
# 获取当前版本
|
||||
CURRENT_VERSION=$(redis-server --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
||||
echo "当前Redis版本: $CURRENT_VERSION"
|
||||
|
||||
# 检查是否有安全更新
|
||||
echo "检查安全公告..."
|
||||
echo "请访问以下链接查看最新安全公告:"
|
||||
echo "- https://redis.io/topics/security"
|
||||
echo "- https://github.com/redis/redis/security/advisories"
|
||||
|
||||
# 备份当前配置
|
||||
echo "备份当前配置..."
|
||||
cp /etc/redis/redis.conf /etc/redis/redis.conf.backup.$(date +%Y%m%d)
|
||||
|
||||
# 更新前检查
|
||||
echo "更新前安全检查:"
|
||||
redis-cli CONFIG GET '*' > /tmp/redis_config_before_update.txt
|
||||
|
||||
echo "请手动执行以下步骤:"
|
||||
echo "1. 下载最新稳定版本"
|
||||
echo "2. 测试环境验证"
|
||||
echo "3. 制定回滚计划"
|
||||
echo "4. 执行更新"
|
||||
echo "5. 验证功能和安全配置"
|
||||
```
|
||||
|
||||
### 应急响应
|
||||
|
||||
**安全事件应急响应**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全事件应急响应脚本
|
||||
|
||||
INCIDENT_LOG="/var/log/redis/security_incident.log"
|
||||
DATE=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
echo "=== Redis 安全事件应急响应 ==="
|
||||
echo "事件时间: $DATE" | tee -a $INCIDENT_LOG
|
||||
|
||||
# 1. 立即隔离
|
||||
echo "1. 立即隔离Redis服务"
|
||||
echo "停止Redis服务? (y/n)"
|
||||
read -r response
|
||||
if [ "$response" = "y" ]; then
|
||||
sudo systemctl stop redis
|
||||
echo "$DATE: Redis服务已停止" | tee -a $INCIDENT_LOG
|
||||
fi
|
||||
|
||||
# 2. 收集证据
|
||||
echo "2. 收集安全证据"
|
||||
EVIDENCE_DIR="/tmp/redis_incident_$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p $EVIDENCE_DIR
|
||||
|
||||
# 收集日志
|
||||
cp /var/log/redis/* $EVIDENCE_DIR/ 2>/dev/null
|
||||
|
||||
# 收集配置
|
||||
cp /etc/redis/redis.conf $EVIDENCE_DIR/
|
||||
|
||||
# 收集进程信息
|
||||
ps aux | grep redis > $EVIDENCE_DIR/processes.txt
|
||||
|
||||
# 收集网络连接
|
||||
netstat -tulpn | grep :6379 > $EVIDENCE_DIR/connections.txt
|
||||
|
||||
# 收集系统信息
|
||||
uname -a > $EVIDENCE_DIR/system_info.txt
|
||||
whoami > $EVIDENCE_DIR/current_user.txt
|
||||
|
||||
echo "证据已收集到: $EVIDENCE_DIR"
|
||||
echo "$DATE: 证据收集完成 - $EVIDENCE_DIR" | tee -a $INCIDENT_LOG
|
||||
|
||||
# 3. 分析威胁
|
||||
echo "3. 分析潜在威胁"
|
||||
echo "检查可疑连接..."
|
||||
redis-cli CLIENT LIST > $EVIDENCE_DIR/client_list.txt 2>/dev/null
|
||||
|
||||
echo "检查慢查询日志..."
|
||||
redis-cli SLOWLOG GET 100 > $EVIDENCE_DIR/slowlog.txt 2>/dev/null
|
||||
|
||||
# 4. 修复建议
|
||||
echo "4. 安全修复建议:"
|
||||
echo "- 更改Redis密码"
|
||||
echo "- 检查ACL配置"
|
||||
echo "- 更新防火墙规则"
|
||||
echo "- 检查系统用户账户"
|
||||
echo "- 扫描恶意软件"
|
||||
echo "- 更新Redis到最新版本"
|
||||
|
||||
echo "应急响应完成,详细日志: $INCIDENT_LOG"
|
||||
```
|
2838
数据库/Redis_2025/11_Redis应用实战.md
Normal file
2838
数据库/Redis_2025/11_Redis应用实战.md
Normal file
File diff suppressed because it is too large
Load Diff
625
数据库/Redis_2025/12_Redis运维管理.md
Normal file
625
数据库/Redis_2025/12_Redis运维管理.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# 第十二章:Redis 运维管理
|
||||
|
||||
## 概述
|
||||
|
||||
Redis运维管理是确保Redis服务稳定运行的关键环节。本章将深入介绍Redis的日常运维管理,包括监控告警、备份恢复、故障处理、容量规划等核心内容,帮助运维人员建立完善的Redis运维体系。
|
||||
|
||||
### 学习目标
|
||||
|
||||
- 掌握Redis监控指标和告警策略
|
||||
- 学会Redis备份恢复的最佳实践
|
||||
- 了解Redis故障诊断和处理方法
|
||||
- 掌握Redis容量规划和扩容策略
|
||||
- 学会Redis运维自动化工具的使用
|
||||
|
||||
## 监控告警
|
||||
|
||||
### 核心监控指标
|
||||
|
||||
**性能指标**:
|
||||
|
||||
```shell
|
||||
# Redis性能监控指标
|
||||
# 1. 连接数
|
||||
redis-cli info clients | grep connected_clients
|
||||
|
||||
# 2. 内存使用
|
||||
redis-cli info memory | grep used_memory_human
|
||||
|
||||
# 3. 命令执行统计
|
||||
redis-cli info commandstats
|
||||
|
||||
# 4. 键空间统计
|
||||
redis-cli info keyspace
|
||||
|
||||
# 5. 复制延迟
|
||||
redis-cli info replication | grep master_repl_offset
|
||||
|
||||
# 6. 慢查询
|
||||
redis-cli slowlog get 10
|
||||
```
|
||||
|
||||
**系统指标**:
|
||||
|
||||
```shell
|
||||
# 系统资源监控
|
||||
# CPU使用率
|
||||
top -p $(pgrep redis-server)
|
||||
|
||||
# 内存使用
|
||||
ps aux | grep redis-server
|
||||
|
||||
# 网络连接
|
||||
netstat -an | grep :6379
|
||||
|
||||
# 磁盘I/O
|
||||
iostat -x 1
|
||||
|
||||
# 文件描述符
|
||||
lsof -p $(pgrep redis-server) | wc -l
|
||||
```
|
||||
|
||||
### 监控脚本实现
|
||||
|
||||
**Redis监控脚本**:`参考 redis_monitor.py 工具`
|
||||
|
||||
### 告警配置
|
||||
|
||||
**告警规则配置**: `参考 redis_alerts.yml 工具`
|
||||
|
||||
**告警处理脚本**: `参考 redis_alert_handler.py 工具`
|
||||
|
||||
## 备份恢复
|
||||
|
||||
### 备份策略
|
||||
|
||||
**自动备份脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# redis_backup.sh - Redis自动备份脚本
|
||||
|
||||
# 配置参数
|
||||
REDIS_HOST="localhost"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_PASSWORD=""
|
||||
BACKUP_DIR="/data/redis_backup"
|
||||
RETENTION_DAYS=7
|
||||
LOG_FILE="/var/log/redis_backup.log"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
|
||||
}
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# 备份函数
|
||||
backup_redis() {
|
||||
local backup_type=$1
|
||||
local timestamp=$(date '+%Y%m%d_%H%M%S')
|
||||
local backup_name="redis_${backup_type}_${timestamp}"
|
||||
local backup_path="$BACKUP_DIR/$backup_name"
|
||||
|
||||
log "开始 $backup_type 备份: $backup_name"
|
||||
|
||||
case $backup_type in
|
||||
"rdb")
|
||||
# RDB备份
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD BGSAVE
|
||||
else
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT BGSAVE
|
||||
fi
|
||||
|
||||
# 等待备份完成
|
||||
while true; do
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD LASTSAVE)
|
||||
else
|
||||
result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT LASTSAVE)
|
||||
fi
|
||||
|
||||
if [ "$result" != "$last_save" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 复制RDB文件
|
||||
redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
|
||||
rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename | tail -1)
|
||||
|
||||
if [ -f "$redis_data_dir/$rdb_filename" ]; then
|
||||
cp "$redis_data_dir/$rdb_filename" "$backup_path.rdb"
|
||||
log "RDB备份完成: $backup_path.rdb"
|
||||
else
|
||||
log "错误: RDB文件不存在"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
|
||||
"aof")
|
||||
# AOF备份
|
||||
redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
|
||||
aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename | tail -1)
|
||||
|
||||
if [ -f "$redis_data_dir/$aof_filename" ]; then
|
||||
cp "$redis_data_dir/$aof_filename" "$backup_path.aof"
|
||||
log "AOF备份完成: $backup_path.aof"
|
||||
else
|
||||
log "警告: AOF文件不存在"
|
||||
fi
|
||||
;;
|
||||
|
||||
"full")
|
||||
# 全量备份(包含配置文件)
|
||||
mkdir -p "$backup_path"
|
||||
|
||||
# 备份RDB
|
||||
backup_redis "rdb"
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$BACKUP_DIR/redis_rdb_"*.rdb "$backup_path/"
|
||||
fi
|
||||
|
||||
# 备份AOF
|
||||
backup_redis "aof"
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$BACKUP_DIR/redis_aof_"*.aof "$backup_path/"
|
||||
fi
|
||||
|
||||
# 备份配置文件
|
||||
redis_config=$(ps aux | grep redis-server | grep -v grep | awk '{for(i=1;i<=NF;i++) if($i ~ /\.conf$/) print $i}')
|
||||
if [ -n "$redis_config" ] && [ -f "$redis_config" ]; then
|
||||
cp "$redis_config" "$backup_path/redis.conf"
|
||||
log "配置文件备份完成: $backup_path/redis.conf"
|
||||
fi
|
||||
|
||||
# 创建备份信息文件
|
||||
cat > "$backup_path/backup_info.txt" << EOF
|
||||
备份时间: $(date)
|
||||
备份类型: 全量备份
|
||||
Redis版本: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO server | grep redis_version | cut -d: -f2 | tr -d '\r')
|
||||
数据库大小: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
|
||||
内存使用: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
|
||||
EOF
|
||||
|
||||
# 压缩备份
|
||||
cd $BACKUP_DIR
|
||||
tar -czf "${backup_name}.tar.gz" "$backup_name"
|
||||
rm -rf "$backup_name"
|
||||
|
||||
log "全量备份完成: ${backup_path}.tar.gz"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_old_backups() {
|
||||
log "清理 $RETENTION_DAYS 天前的备份文件"
|
||||
find $BACKUP_DIR -name "redis_*" -type f -mtime +$RETENTION_DAYS -delete
|
||||
log "清理完成"
|
||||
}
|
||||
|
||||
# 验证备份
|
||||
verify_backup() {
|
||||
local backup_file=$1
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
log "错误: 备份文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_size=$(stat -c%s "$backup_file")
|
||||
if [ $file_size -eq 0 ]; then
|
||||
log "错误: 备份文件为空: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "备份验证通过: $backup_file (大小: $file_size 字节)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local backup_type=${1:-"full"}
|
||||
|
||||
log "=== Redis备份开始 ==="
|
||||
log "备份类型: $backup_type"
|
||||
|
||||
# 检查Redis连接
|
||||
if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "错误: 无法连接到Redis服务器"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行备份
|
||||
backup_redis $backup_type
|
||||
|
||||
# 验证备份
|
||||
latest_backup=$(ls -t $BACKUP_DIR/redis_${backup_type}_* 2>/dev/null | head -1)
|
||||
if [ -n "$latest_backup" ]; then
|
||||
verify_backup "$latest_backup"
|
||||
fi
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_old_backups
|
||||
|
||||
log "=== Redis备份完成 ==="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main $@
|
||||
```
|
||||
|
||||
### 恢复策略
|
||||
|
||||
**数据恢复脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# redis_restore.sh - Redis数据恢复脚本
|
||||
|
||||
# 配置参数
|
||||
REDIS_HOST="localhost"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_PASSWORD=""
|
||||
BACKUP_DIR="/data/redis_backup"
|
||||
LOG_FILE="/var/log/redis_restore.log"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
|
||||
}
|
||||
|
||||
# 停止Redis服务
|
||||
stop_redis() {
|
||||
log "停止Redis服务"
|
||||
|
||||
# 尝试优雅关闭
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD SHUTDOWN SAVE
|
||||
else
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT SHUTDOWN SAVE
|
||||
fi
|
||||
|
||||
# 等待进程结束
|
||||
sleep 5
|
||||
|
||||
# 强制杀死进程(如果仍在运行)
|
||||
pkill -f redis-server
|
||||
|
||||
log "Redis服务已停止"
|
||||
}
|
||||
|
||||
# 启动Redis服务
|
||||
start_redis() {
|
||||
log "启动Redis服务"
|
||||
|
||||
# 查找Redis配置文件
|
||||
local config_file="/etc/redis/redis.conf"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
config_file="/usr/local/etc/redis.conf"
|
||||
fi
|
||||
|
||||
if [ -f "$config_file" ]; then
|
||||
redis-server "$config_file" &
|
||||
else
|
||||
redis-server &
|
||||
fi
|
||||
|
||||
# 等待服务启动
|
||||
local retry_count=0
|
||||
while [ $retry_count -lt 30 ]; do
|
||||
if redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "Redis服务启动成功"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
retry_count=$((retry_count + 1))
|
||||
done
|
||||
|
||||
log "错误: Redis服务启动失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 恢复RDB文件
|
||||
restore_rdb() {
|
||||
local rdb_file=$1
|
||||
|
||||
if [ ! -f "$rdb_file" ]; then
|
||||
log "错误: RDB文件不存在: $rdb_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复RDB文件: $rdb_file"
|
||||
|
||||
# 获取Redis数据目录
|
||||
local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
|
||||
if [ -z "$redis_data_dir" ]; then
|
||||
redis_data_dir="/var/lib/redis"
|
||||
fi
|
||||
|
||||
local rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename 2>/dev/null | tail -1)
|
||||
if [ -z "$rdb_filename" ]; then
|
||||
rdb_filename="dump.rdb"
|
||||
fi
|
||||
|
||||
# 停止Redis
|
||||
stop_redis
|
||||
|
||||
# 备份现有RDB文件
|
||||
if [ -f "$redis_data_dir/$rdb_filename" ]; then
|
||||
mv "$redis_data_dir/$rdb_filename" "$redis_data_dir/${rdb_filename}.backup.$(date +%s)"
|
||||
log "现有RDB文件已备份"
|
||||
fi
|
||||
|
||||
# 复制新的RDB文件
|
||||
cp "$rdb_file" "$redis_data_dir/$rdb_filename"
|
||||
chown redis:redis "$redis_data_dir/$rdb_filename" 2>/dev/null
|
||||
|
||||
# 启动Redis
|
||||
start_redis
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "RDB恢复完成"
|
||||
return 0
|
||||
else
|
||||
log "错误: RDB恢复失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 恢复AOF文件
|
||||
restore_aof() {
|
||||
local aof_file=$1
|
||||
|
||||
if [ ! -f "$aof_file" ]; then
|
||||
log "错误: AOF文件不存在: $aof_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复AOF文件: $aof_file"
|
||||
|
||||
# 验证AOF文件
|
||||
redis-check-aof --fix "$aof_file"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "警告: AOF文件可能有问题,已尝试修复"
|
||||
fi
|
||||
|
||||
# 获取Redis数据目录
|
||||
local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
|
||||
if [ -z "$redis_data_dir" ]; then
|
||||
redis_data_dir="/var/lib/redis"
|
||||
fi
|
||||
|
||||
local aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename 2>/dev/null | tail -1)
|
||||
if [ -z "$aof_filename" ]; then
|
||||
aof_filename="appendonly.aof"
|
||||
fi
|
||||
|
||||
# 停止Redis
|
||||
stop_redis
|
||||
|
||||
# 备份现有AOF文件
|
||||
if [ -f "$redis_data_dir/$aof_filename" ]; then
|
||||
mv "$redis_data_dir/$aof_filename" "$redis_data_dir/${aof_filename}.backup.$(date +%s)"
|
||||
log "现有AOF文件已备份"
|
||||
fi
|
||||
|
||||
# 复制新的AOF文件
|
||||
cp "$aof_file" "$redis_data_dir/$aof_filename"
|
||||
chown redis:redis "$redis_data_dir/$aof_filename" 2>/dev/null
|
||||
|
||||
# 启动Redis
|
||||
start_redis
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "AOF恢复完成"
|
||||
return 0
|
||||
else
|
||||
log "错误: AOF恢复失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 恢复全量备份
|
||||
restore_full() {
|
||||
local backup_file=$1
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
log "错误: 备份文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复全量备份: $backup_file"
|
||||
|
||||
# 创建临时目录
|
||||
local temp_dir="/tmp/redis_restore_$(date +%s)"
|
||||
mkdir -p "$temp_dir"
|
||||
|
||||
# 解压备份文件
|
||||
if [[ "$backup_file" == *.tar.gz ]]; then
|
||||
tar -xzf "$backup_file" -C "$temp_dir"
|
||||
elif [[ "$backup_file" == *.zip ]]; then
|
||||
unzip "$backup_file" -d "$temp_dir"
|
||||
else
|
||||
log "错误: 不支持的备份文件格式"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 查找解压后的目录
|
||||
local extract_dir=$(find "$temp_dir" -maxdepth 1 -type d | grep -v "^$temp_dir$" | head -1)
|
||||
if [ -z "$extract_dir" ]; then
|
||||
extract_dir="$temp_dir"
|
||||
fi
|
||||
|
||||
# 恢复配置文件
|
||||
if [ -f "$extract_dir/redis.conf" ]; then
|
||||
log "发现配置文件,请手动检查是否需要恢复"
|
||||
log "配置文件位置: $extract_dir/redis.conf"
|
||||
fi
|
||||
|
||||
# 恢复数据文件
|
||||
local rdb_file=$(find "$extract_dir" -name "*.rdb" | head -1)
|
||||
local aof_file=$(find "$extract_dir" -name "*.aof" | head -1)
|
||||
|
||||
if [ -n "$rdb_file" ]; then
|
||||
restore_rdb "$rdb_file"
|
||||
elif [ -n "$aof_file" ]; then
|
||||
restore_aof "$aof_file"
|
||||
else
|
||||
log "错误: 未找到数据文件"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
log "全量恢复完成"
|
||||
}
|
||||
|
||||
# 列出可用备份
|
||||
list_backups() {
|
||||
log "可用的备份文件:"
|
||||
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log "备份目录不存在: $BACKUP_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backup_files=$(find "$BACKUP_DIR" -name "redis_*" -type f | sort -r)
|
||||
|
||||
if [ -z "$backup_files" ]; then
|
||||
log "未找到备份文件"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local index=1
|
||||
echo "$backup_files" | while read file; do
|
||||
local size=$(stat -c%s "$file" 2>/dev/null || echo "unknown")
|
||||
local date=$(stat -c%y "$file" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
|
||||
printf "%2d. %s (大小: %s, 日期: %s)\n" $index "$(basename "$file")" "$size" "$date"
|
||||
index=$((index + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# 验证恢复结果
|
||||
verify_restore() {
|
||||
log "验证恢复结果"
|
||||
|
||||
# 检查Redis连接
|
||||
if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "错误: Redis服务未正常运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 获取基本信息
|
||||
local db_size=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
|
||||
local memory_usage=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
|
||||
|
||||
log "恢复验证结果:"
|
||||
log "- 数据库大小: $db_size 个键"
|
||||
log "- 内存使用: $memory_usage"
|
||||
log "- Redis状态: 正常"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local action=$1
|
||||
local backup_file=$2
|
||||
|
||||
log "=== Redis恢复开始 ==="
|
||||
|
||||
case $action in
|
||||
"list")
|
||||
list_backups
|
||||
;;
|
||||
"rdb")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定RDB备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_rdb "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
"aof")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定AOF备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_aof "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
"full")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定全量备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_full "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {list|rdb|aof|full} [backup_file]"
|
||||
echo " list - 列出可用备份"
|
||||
echo " rdb - 恢复RDB备份"
|
||||
echo " aof - 恢复AOF备份"
|
||||
echo " full - 恢复全量备份"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "=== Redis恢复完成 ==="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main $@
|
||||
```
|
||||
|
||||
## 故障处理
|
||||
|
||||
### 常见故障诊断
|
||||
|
||||
**故障诊断脚本**: `待补充`
|
||||
|
||||
### 故障处理手册
|
||||
|
||||
**常见故障及解决方案**:
|
||||
|
||||
```shell
|
||||
# 网络问题:
|
||||
- 客户端无法连接Redis
|
||||
- 连接超时
|
||||
- 连接被拒绝
|
||||
|
||||
# 内存问题:
|
||||
- 内存使用率过高
|
||||
- OOM错误
|
||||
- 性能下降
|
||||
|
||||
# 性能问题
|
||||
- 响应时间慢
|
||||
- QPS下降
|
||||
- 慢查询增多
|
||||
|
||||
# 持久化问题
|
||||
- RDB保存失败
|
||||
- AOF文件损坏
|
||||
- 数据丢失
|
||||
|
||||
# 主从复制问题
|
||||
- 主从同步失败
|
||||
- 复制延迟过大
|
||||
- 从节点数据不一致
|
||||
|
||||
```
|
||||
|
||||
## 容量规划
|
||||
|
||||
### 容量评估脚本
|
||||
|
||||
**Redis容量分析工具**:`参考 redis_capacity_planner.py 工具`
|
3
数据库/Redis_2025/Redis.md
Normal file
3
数据库/Redis_2025/Redis.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[Redis官网地址](https://redis.io/)
|
||||
|
||||
[Redis项目地址](https://github.com/redis/redis)
|
262
数据库/Redis_2025/cluster_delete.sh
Normal file
262
数据库/Redis_2025/cluster_delete.sh
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Redis集群删除脚本
|
||||
# 功能:删除指定Redis集群,包括停止服务、销毁集群、清理配置文件
|
||||
# 作者:EaglesLab
|
||||
# 日期:2025
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 显示使用说明
|
||||
show_usage() {
|
||||
echo "使用方法: $0 <集群配置目录>"
|
||||
echo "示例: $0 /path/to/redis-cluster"
|
||||
echo ""
|
||||
echo "说明:"
|
||||
echo " 集群配置目录应包含Redis节点的配置文件和数据文件"
|
||||
echo " 脚本会自动检测目录中的Redis实例并进行删除操作"
|
||||
}
|
||||
|
||||
# 检查参数
|
||||
if [ $# -ne 1 ]; then
|
||||
print_error "参数错误!"
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLUSTER_DIR="$1"
|
||||
|
||||
# 检查目录是否存在
|
||||
if [ ! -d "$CLUSTER_DIR" ]; then
|
||||
print_error "目录不存在: $CLUSTER_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 转换为绝对路径
|
||||
CLUSTER_DIR=$(cd "$CLUSTER_DIR" && pwd)
|
||||
|
||||
print_info "准备删除Redis集群: $CLUSTER_DIR"
|
||||
|
||||
# 检查目录中是否有Redis配置文件
|
||||
redis_configs=$(find "$CLUSTER_DIR" -name "redis.conf" -o -name "redis_*.conf" 2>/dev/null || true)
|
||||
if [ -z "$redis_configs" ]; then
|
||||
print_warning "在目录 $CLUSTER_DIR 中未找到Redis配置文件"
|
||||
print_info "继续执行将删除整个目录及其内容"
|
||||
else
|
||||
print_info "发现以下Redis配置文件:"
|
||||
echo "$redis_configs" | while read -r config; do
|
||||
echo " - $config"
|
||||
done
|
||||
fi
|
||||
|
||||
# 获取运行中的Redis进程信息
|
||||
get_redis_processes() {
|
||||
local cluster_dir="$1"
|
||||
# 查找与集群目录相关的Redis进程
|
||||
ps aux | grep redis-server | grep -v grep | grep "$cluster_dir" || true
|
||||
}
|
||||
|
||||
# 显示将要删除的内容
|
||||
print_warning "=== 删除操作概览 ==="
|
||||
print_warning "将要删除的目录: $CLUSTER_DIR"
|
||||
print_warning "目录大小: $(du -sh "$CLUSTER_DIR" 2>/dev/null | cut -f1 || echo '未知')"
|
||||
|
||||
# 检查运行中的Redis进程
|
||||
running_processes=$(get_redis_processes "$CLUSTER_DIR")
|
||||
if [ -n "$running_processes" ]; then
|
||||
print_warning "发现运行中的Redis进程:"
|
||||
echo "$running_processes" | while read -r process; do
|
||||
echo " $process"
|
||||
done
|
||||
else
|
||||
print_info "未发现运行中的Redis进程"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_warning "警告: 此操作将永久删除集群数据,无法恢复!"
|
||||
echo ""
|
||||
|
||||
# 第一次确认
|
||||
read -p "$(echo -e "${YELLOW}确认要删除Redis集群吗?请输入 'yes' 继续: ${NC}")" first_confirm
|
||||
if [ "$first_confirm" != "yes" ]; then
|
||||
print_info "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_error "最后确认: 即将删除 $CLUSTER_DIR 及其所有内容!"
|
||||
read -p "$(echo -e "${RED}请再次输入 'DELETE' 确认删除操作: ${NC}")" second_confirm
|
||||
if [ "$second_confirm" != "DELETE" ]; then
|
||||
print_info "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "开始删除Redis集群..."
|
||||
|
||||
# 停止Redis服务
|
||||
stop_redis_services() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在停止Redis服务..."
|
||||
|
||||
# 方法1: 通过配置文件查找并停止Redis进程
|
||||
find "$cluster_dir" -name "*.conf" | while read -r config_file; do
|
||||
if grep -q "^port" "$config_file" 2>/dev/null; then
|
||||
port=$(grep "^port" "$config_file" | awk '{print $2}')
|
||||
if [ -n "$port" ]; then
|
||||
print_info "尝试停止端口 $port 上的Redis服务..."
|
||||
redis-cli -p "$port" shutdown nosave 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 方法2: 强制终止与集群目录相关的Redis进程
|
||||
local pids=$(ps aux | grep redis-server | grep "$cluster_dir" | grep -v grep | awk '{print $2}' || true)
|
||||
if [ -n "$pids" ]; then
|
||||
print_info "强制终止剩余的Redis进程..."
|
||||
echo "$pids" | while read -r pid; do
|
||||
if [ -n "$pid" ]; then
|
||||
print_info "终止进程 PID: $pid"
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
# 如果进程仍然存在,使用KILL信号
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
print_warning "强制杀死进程 PID: $pid"
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 等待进程完全停止
|
||||
sleep 3
|
||||
|
||||
# 验证是否还有运行中的进程
|
||||
remaining_processes=$(get_redis_processes "$cluster_dir")
|
||||
if [ -n "$remaining_processes" ]; then
|
||||
print_warning "仍有Redis进程在运行:"
|
||||
echo "$remaining_processes"
|
||||
print_warning "请手动停止这些进程后重新运行脚本"
|
||||
return 1
|
||||
else
|
||||
print_success "所有Redis服务已停止"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 销毁集群配置
|
||||
destroy_cluster() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在销毁集群配置..."
|
||||
|
||||
# 尝试通过任意一个节点重置集群
|
||||
find "$cluster_dir" -name "*.conf" | head -1 | while read -r config_file; do
|
||||
if [ -n "$config_file" ] && grep -q "^port" "$config_file" 2>/dev/null; then
|
||||
port=$(grep "^port" "$config_file" | awk '{print $2}')
|
||||
if [ -n "$port" ]; then
|
||||
print_info "尝试通过端口 $port 重置集群..."
|
||||
timeout 10 redis-cli -p "$port" cluster reset hard 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
print_success "集群配置销毁完成"
|
||||
}
|
||||
|
||||
# 清理配置文件和数据
|
||||
cleanup_files() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在清理配置文件和数据..."
|
||||
|
||||
# 删除集群相关文件
|
||||
find "$cluster_dir" -name "nodes-*.conf" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "dump.rdb" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "appendonly.aof" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "*.log" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "*.pid" -delete 2>/dev/null || true
|
||||
|
||||
print_success "临时文件清理完成"
|
||||
}
|
||||
|
||||
# 删除整个目录
|
||||
delete_directory() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在删除集群目录: $cluster_dir"
|
||||
|
||||
if rm -rf "$cluster_dir"; then
|
||||
print_success "集群目录删除成功"
|
||||
else
|
||||
print_error "删除集群目录失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行删除操作
|
||||
main() {
|
||||
local cluster_dir="$1"
|
||||
local success=true
|
||||
|
||||
# 停止Redis服务
|
||||
if ! stop_redis_services "$cluster_dir"; then
|
||||
print_error "停止Redis服务失败"
|
||||
success=false
|
||||
fi
|
||||
|
||||
# 销毁集群(即使停止服务失败也尝试销毁)
|
||||
destroy_cluster "$cluster_dir"
|
||||
|
||||
# 清理文件
|
||||
cleanup_files "$cluster_dir"
|
||||
|
||||
# 删除目录
|
||||
if ! delete_directory "$cluster_dir"; then
|
||||
success=false
|
||||
fi
|
||||
|
||||
if [ "$success" = true ]; then
|
||||
echo ""
|
||||
print_success "=== Redis集群删除完成 ==="
|
||||
print_success "集群目录 $cluster_dir 已被完全删除"
|
||||
print_info "所有Redis进程已停止,配置文件和数据已清理"
|
||||
else
|
||||
echo ""
|
||||
print_error "=== 删除过程中出现错误 ==="
|
||||
print_warning "请检查上述错误信息并手动处理剩余问题"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$CLUSTER_DIR"
|
||||
|
||||
echo ""
|
||||
print_info "脚本执行完成"
|
173
数据库/Redis_2025/cluster_scale_in.sh
Normal file
173
数据库/Redis_2025/cluster_scale_in.sh
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== Redis 集群缩容操作 ==="
|
||||
|
||||
# 配置参数
|
||||
EXISTING_NODE="127.0.0.1:7001"
|
||||
REMOVE_MASTER="127.0.0.1:7007"
|
||||
REMOVE_SLAVE="127.0.0.1:7008"
|
||||
|
||||
# 1. 检查要删除的节点
|
||||
echo "1. 检查要删除的节点..."
|
||||
|
||||
# 获取要删除的主节点ID
|
||||
REMOVE_MASTER_ID=$(redis-cli -h ${REMOVE_MASTER%:*} -p ${REMOVE_MASTER#*:} CLUSTER MYID 2>/dev/null)
|
||||
REMOVE_SLAVE_ID=$(redis-cli -h ${REMOVE_SLAVE%:*} -p ${REMOVE_SLAVE#*:} CLUSTER MYID 2>/dev/null)
|
||||
|
||||
echo "要删除的主节点: $REMOVE_MASTER (ID: $REMOVE_MASTER_ID)"
|
||||
echo "要删除的从节点: $REMOVE_SLAVE (ID: $REMOVE_SLAVE_ID)"
|
||||
|
||||
# 检查节点状态
|
||||
if [ -z "$REMOVE_MASTER_ID" ]; then
|
||||
echo "❌ 无法连接到要删除的主节点"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 迁移槽位
|
||||
echo "2. 迁移槽位..."
|
||||
|
||||
# 获取要删除节点的槽位
|
||||
slots_info=$(redis-cli -h ${REMOVE_MASTER%:*} -p ${REMOVE_MASTER#*:} CLUSTER NODES | grep $REMOVE_MASTER_ID)
|
||||
slots_range=$(echo "$slots_info" | awk '{for(i=9;i<=NF;i++) printf "%s%s", $i, (i==NF?"":" ")}')
|
||||
|
||||
echo "要迁移的槽位: $slots_range"
|
||||
|
||||
if [ -n "$slots_range" ] && [ "$slots_range" != "-" ]; then
|
||||
# 解析多个槽位范围(用空格分隔)
|
||||
# 例如: 179-1364 5461-6826 10923-12287
|
||||
slot_ranges_array=()
|
||||
total_slot_count=0
|
||||
|
||||
# 将槽位范围字符串分割成数组
|
||||
IFS=' ' read -ra SLOT_RANGES <<< "$slots_range"
|
||||
|
||||
echo "解析到的槽位范围:"
|
||||
for range in "${SLOT_RANGES[@]}"; do
|
||||
if [[ $range =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||
start_slot=${BASH_REMATCH[1]}
|
||||
end_slot=${BASH_REMATCH[2]}
|
||||
range_count=$((end_slot - start_slot + 1))
|
||||
slot_ranges_array+=("$start_slot:$end_slot:$range_count")
|
||||
total_slot_count=$((total_slot_count + range_count))
|
||||
echo " 范围 $start_slot-$end_slot: $range_count 个槽位"
|
||||
elif [[ $range =~ ^[0-9]+$ ]]; then
|
||||
# 单个槽位
|
||||
slot_ranges_array+=("$range:$range:1")
|
||||
total_slot_count=$((total_slot_count + 1))
|
||||
echo " 单个槽位 $range: 1 个槽位"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "总共需要迁移 $total_slot_count 个槽位"
|
||||
|
||||
# 获取目标节点(选择第一个其他主节点)
|
||||
target_nodes=$(redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | \
|
||||
grep "master" | grep -v $REMOVE_MASTER_ID | head -3)
|
||||
|
||||
echo "目标节点:"
|
||||
echo "$target_nodes"
|
||||
|
||||
# 将目标节点信息存储到数组中
|
||||
target_nodes_array=()
|
||||
while IFS= read -r target_line; do
|
||||
if [ -n "$target_line" ]; then
|
||||
target_nodes_array+=("$target_line")
|
||||
fi
|
||||
done <<< "$target_nodes"
|
||||
|
||||
target_count=${#target_nodes_array[@]}
|
||||
echo "找到 $target_count 个目标节点"
|
||||
|
||||
# 执行槽位迁移 - 处理每个槽位范围
|
||||
echo "开始槽位迁移..."
|
||||
|
||||
range_index=0
|
||||
target_index=0
|
||||
|
||||
# 遍历每个槽位范围
|
||||
for slot_range_info in "${slot_ranges_array[@]}"; do
|
||||
# 解析槽位范围信息 (格式: start:end:count)
|
||||
IFS=':' read -ra RANGE_INFO <<< "$slot_range_info"
|
||||
range_start=${RANGE_INFO[0]}
|
||||
range_end=${RANGE_INFO[1]}
|
||||
range_count=${RANGE_INFO[2]}
|
||||
|
||||
# 选择目标节点(轮询分配)
|
||||
target_line="${target_nodes_array[$target_index]}"
|
||||
target_id=$(echo $target_line | awk '{print $1}')
|
||||
target_addr=$(echo $target_line | awk '{print $2}')
|
||||
|
||||
echo "迁移槽位范围 $range_start-$range_end ($range_count 个槽位) 到 $target_addr"
|
||||
|
||||
# 使用 redis-cli 迁移整个槽位范围
|
||||
redis-cli --cluster reshard ${EXISTING_NODE} \
|
||||
--cluster-from $REMOVE_MASTER_ID \
|
||||
--cluster-to $target_id \
|
||||
--cluster-slots $range_count \
|
||||
--cluster-yes > /dev/null 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 槽位范围 $range_start-$range_end 迁移成功"
|
||||
else
|
||||
echo "❌ 槽位范围 $range_start-$range_end 迁移失败"
|
||||
fi
|
||||
|
||||
# 验证槽位迁移结果
|
||||
echo "验证槽位范围 $range_start-$range_end 的迁移状态..."
|
||||
sleep 5
|
||||
|
||||
# 检查槽位是否已成功迁移
|
||||
migrated_slots=$(redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | \
|
||||
grep $target_id | awk '{print $9}')
|
||||
echo "目标节点 $target_addr 当前槽位: $migrated_slots"
|
||||
|
||||
# 轮询到下一个目标节点
|
||||
target_index=$(((target_index + 1) % target_count))
|
||||
range_index=$((range_index + 1))
|
||||
done
|
||||
echo "所有槽位范围迁移操作完成"
|
||||
|
||||
else
|
||||
echo "该节点没有分配槽位,跳过迁移"
|
||||
fi
|
||||
|
||||
# 3. 删除从节点
|
||||
echo "3. 删除从节点..."
|
||||
|
||||
echo "删除从节点 $REMOVE_SLAVE..."
|
||||
redis-cli --cluster del-node ${EXISTING_NODE} $REMOVE_SLAVE_ID
|
||||
|
||||
# 4. 删除主节点
|
||||
echo "4. 删除主节点..."
|
||||
|
||||
echo "删除主节点 $REMOVE_MASTER..."
|
||||
redis-cli --cluster del-node ${EXISTING_NODE} $REMOVE_MASTER_ID
|
||||
|
||||
|
||||
# 5. 停止已删除的节点
|
||||
echo "5. 停止已删除的节点..."
|
||||
|
||||
for port in ${REMOVE_MASTER#*:} ${REMOVE_SLAVE#*:}; do
|
||||
echo "停止节点 $port..."
|
||||
redis-cli -p $port SHUTDOWN NOSAVE 2>/dev/null || echo "节点 $port 已停止或不可用"
|
||||
done
|
||||
|
||||
# 6. 验证缩容结果
|
||||
echo "6. 验证缩容结果..."
|
||||
|
||||
echo "剩余集群节点:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER INFO
|
||||
|
||||
echo
|
||||
echo "槽位分配验证:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER SLOTS
|
||||
|
||||
echo
|
||||
echo "=== 集群缩容完成 ==="
|
||||
echo "已删除主节点: $REMOVE_MASTER"
|
||||
echo "已删除从节点: $REMOVE_SLAVE"
|
||||
echo "集群现有节点数: 6 (3主3从)"
|
153
数据库/Redis_2025/cluster_scale_out.sh
Normal file
153
数据库/Redis_2025/cluster_scale_out.sh
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== Redis 集群扩容操作 ==="
|
||||
|
||||
# 配置参数
|
||||
EXISTING_NODE="127.0.0.1:7001"
|
||||
NEW_MASTER="127.0.0.1:7007"
|
||||
NEW_SLAVE="127.0.0.1:7008"
|
||||
BASE_DIR="/tmp/redis_cluster"
|
||||
|
||||
# 1. 准备新节点
|
||||
echo "1. 准备新节点配置..."
|
||||
|
||||
# 创建新节点目录
|
||||
mkdir -p ${BASE_DIR}/node-${NEW_MASTER#*:}
|
||||
mkdir -p ${BASE_DIR}/node-${NEW_SLAVE#*:}
|
||||
|
||||
# 生成新主节点配置
|
||||
cat > ${BASE_DIR}/node-${NEW_MASTER#*:}/redis.conf << EOF
|
||||
port ${NEW_MASTER#*:}
|
||||
bind 127.0.0.1
|
||||
dir ${BASE_DIR}/node-${NEW_MASTER#*:}
|
||||
logfile ${BASE_DIR}/node-${NEW_MASTER#*:}/redis-${NEW_MASTER#*:}.log
|
||||
pidfile ${BASE_DIR}/node-${NEW_MASTER#*:}/redis-${NEW_MASTER#*:}.pid
|
||||
daemonize yes
|
||||
|
||||
cluster-enabled yes
|
||||
cluster-config-file ${BASE_DIR}/node-${NEW_MASTER#*:}/nodes-${NEW_MASTER#*:}.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7007.aof"
|
||||
appendfsync everysec
|
||||
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
# 生成新从节点配置
|
||||
cat > ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis.conf << EOF
|
||||
port ${NEW_SLAVE#*:}
|
||||
bind 127.0.0.1
|
||||
dir ${BASE_DIR}/node-${NEW_SLAVE#*:}
|
||||
logfile ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis-${NEW_SLAVE#*:}.log
|
||||
pidfile ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis-${NEW_SLAVE#*:}.pid
|
||||
daemonize yes
|
||||
|
||||
cluster-enabled yes
|
||||
cluster-config-file ${BASE_DIR}/node-${NEW_SLAVE#*:}/nodes-${NEW_SLAVE#*:}.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
# 2. 启动新节点
|
||||
echo "2. 启动新节点..."
|
||||
|
||||
redis-server ${BASE_DIR}/node-${NEW_MASTER#*:}/redis.conf
|
||||
redis-server ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis.conf
|
||||
|
||||
sleep 3
|
||||
|
||||
# 验证新节点启动
|
||||
for port in ${NEW_MASTER#*:} ${NEW_SLAVE#*:}; do
|
||||
if redis-cli -p $port ping > /dev/null 2>&1; then
|
||||
echo "✅ 新节点 $port 启动成功"
|
||||
else
|
||||
echo "❌ 新节点 $port 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 添加新主节点到集群
|
||||
echo "3. 添加新主节点到集群..."
|
||||
|
||||
echo "使用 redis-cli 添加新主节点:"
|
||||
redis-cli --cluster add-node ${NEW_MASTER} ${EXISTING_NODE}
|
||||
|
||||
# 等待节点加入
|
||||
sleep 5
|
||||
|
||||
# 验证节点加入
|
||||
echo "验证新主节点加入:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | grep ${NEW_MASTER%:*}:${NEW_MASTER#*:}
|
||||
|
||||
# 4. 重新分配槽位
|
||||
echo "4. 重新分配槽位..."
|
||||
|
||||
# 获取新主节点ID
|
||||
NEW_MASTER_ID=$(redis-cli -h ${NEW_MASTER%:*} -p ${NEW_MASTER#*:} CLUSTER MYID)
|
||||
echo "新主节点ID: $NEW_MASTER_ID"
|
||||
|
||||
# 计算要迁移的槽位数量(假设平均分配)
|
||||
CURRENT_MASTERS=3
|
||||
NEW_MASTERS=4
|
||||
SLOTS_PER_MASTER=$((16384 / NEW_MASTERS))
|
||||
SLOTS_TO_MIGRATE=$((16384 / NEW_MASTERS))
|
||||
|
||||
echo "每个主节点应分配槽位数: $SLOTS_PER_MASTER"
|
||||
echo "需要迁移的槽位数: $SLOTS_TO_MIGRATE"
|
||||
|
||||
# 使用 redis-cli 重新分片
|
||||
echo "执行重新分片..."
|
||||
redis-cli --cluster reshard ${EXISTING_NODE} \
|
||||
--cluster-from all \
|
||||
--cluster-to $NEW_MASTER_ID \
|
||||
--cluster-slots $SLOTS_TO_MIGRATE \
|
||||
--cluster-yes
|
||||
sleep 5
|
||||
|
||||
# 5. 添加新从节点
|
||||
echo "5. 添加新从节点..."
|
||||
redis-cli --cluster add-node ${NEW_SLAVE} ${EXISTING_NODE} --cluster-slave --cluster-master-id ${NEW_MASTER_ID}
|
||||
|
||||
# 等待从节点加入
|
||||
sleep 5
|
||||
|
||||
# 6. 验证扩容结果
|
||||
echo "集群节点信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER INFO
|
||||
|
||||
echo
|
||||
echo "槽位分配验证:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER SLOTS | grep -A 2 -B 2 ${NEW_MASTER%:*}
|
||||
|
||||
# 7. 测试新节点 - 写入数据至新主节点并验证
|
||||
|
||||
echo
|
||||
echo "=== 集群扩容完成 ==="
|
||||
echo "新主节点: $NEW_MASTER (ID: $NEW_MASTER_ID)"
|
||||
echo "新从节点: $NEW_SLAVE"
|
||||
echo "集群现有节点数: 8 (4主4从)"
|
70
数据库/Redis_2025/create_redis_cluster.sh
Normal file
70
数据库/Redis_2025/create_redis_cluster.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 配置参数
|
||||
BASE_PORT=7001
|
||||
NODE_COUNT=6
|
||||
REPLICAS=1
|
||||
BASE_DIR=/tmp/redis_cluster_cluster/
|
||||
|
||||
# 1. 生成配置文件
|
||||
echo "1. 生成配置文件... generate_cluster_configs.sh"
|
||||
|
||||
# 2. 启动所有节点
|
||||
echo "2. 启动所有节点..."
|
||||
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_DIR="node-$PORT"
|
||||
|
||||
echo "启动节点 $PORT..."
|
||||
redis-server $BASE_DIR/$NODE_DIR/redis.conf
|
||||
|
||||
# 等待节点启动
|
||||
sleep 1
|
||||
|
||||
# 验证节点启动
|
||||
if redis-cli -p $PORT ping > /dev/null 2>&1; then
|
||||
echo "✅ 节点 $PORT 启动成功"
|
||||
else
|
||||
echo "❌ 节点 $PORT 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "3. 创建集群..."
|
||||
|
||||
# 构建节点列表
|
||||
NODE_LIST=""
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_LIST="$NODE_LIST 127.0.0.1:$PORT"
|
||||
done
|
||||
|
||||
echo "节点列表: $NODE_LIST"
|
||||
echo "副本数量: $REPLICAS"
|
||||
echo
|
||||
|
||||
# 创建集群
|
||||
echo "执行集群创建命令..."
|
||||
redis-cli --cluster create $NODE_LIST --cluster-replicas $REPLICAS --cluster-yes
|
||||
|
||||
# 4. 验证集群状态
|
||||
echo "4. 验证集群状态..."
|
||||
sleep 3
|
||||
|
||||
echo "集群节点信息:"
|
||||
redis-cli -p $BASE_PORT cluster nodes
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -p $BASE_PORT cluster info
|
||||
|
||||
echo
|
||||
echo "槽位分配信息:"
|
||||
redis-cli -p $BASE_PORT cluster slots
|
||||
|
||||
echo
|
||||
echo "=== 集群创建完成 ==="
|
||||
echo "主节点端口: $BASE_PORT, $((BASE_PORT+1)), $((BASE_PORT+2))"
|
||||
echo "从节点端口: $((BASE_PORT+3)), $((BASE_PORT+4)), $((BASE_PORT+5))"
|
62
数据库/Redis_2025/generate_cluster_configs.sh
Normal file
62
数据库/Redis_2025/generate_cluster_configs.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 集群配置参数
|
||||
BASE_PORT=7001
|
||||
NODE_COUNT=6
|
||||
BASE_DIR=/tmp/redis_cluster_cluster/
|
||||
|
||||
# 创建工作目录
|
||||
mkdir -pv $BASE_DIR
|
||||
|
||||
# 为每个节点生成配置
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_DIR="node-$PORT"
|
||||
|
||||
echo "生成节点 $PORT 配置..."
|
||||
mkdir -p $BASE_DIR/$NODE_DIR
|
||||
|
||||
cat > $BASE_DIR/$NODE_DIR/redis.conf << EOF
|
||||
# Redis 集群节点 $PORT 配置
|
||||
|
||||
# 基本配置
|
||||
port $PORT
|
||||
bind 127.0.0.1
|
||||
dir $BASE_DIR/$NODE_DIR
|
||||
logfile "$BASE_DIR/$NODE_DIR/redis-$PORT.log"
|
||||
pidfile "$BASE_DIR/$NODE_DIR/redis-$PORT.pid"
|
||||
daemonize yes
|
||||
|
||||
# 集群配置
|
||||
cluster-enabled yes
|
||||
cluster-config-file nodes-$PORT.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-$PORT.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 内存配置
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 网络配置
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
|
||||
# 性能优化
|
||||
tcp-backlog 511
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
done
|
||||
|
||||
echo "配置文件生成完成!"
|
||||
echo "节点目录: $BASE_DIR/node-*"
|
98
数据库/Redis_2025/redis_alert_handler.py
Normal file
98
数据库/Redis_2025/redis_alert_handler.py
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_alert_handler.py - Redis告警处理
|
||||
|
||||
import smtplib
|
||||
import requests
|
||||
import yaml
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from datetime import datetime
|
||||
|
||||
class AlertHandler:
|
||||
def __init__(self, config_file='redis_alerts.yml'):
|
||||
with open(config_file, 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
def send_email(self, subject, message, recipients=None):
|
||||
"""发送邮件告警"""
|
||||
email_config = self.config['notifications']['email']
|
||||
if not email_config.get('enabled'):
|
||||
return
|
||||
|
||||
recipients = recipients or email_config['recipients']
|
||||
|
||||
try:
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = email_config['username']
|
||||
msg['To'] = ', '.join(recipients)
|
||||
msg['Subject'] = subject
|
||||
|
||||
msg.attach(MIMEText(message, 'plain'))
|
||||
|
||||
server = smtplib.SMTP(email_config['smtp_server'], email_config['smtp_port'])
|
||||
server.starttls()
|
||||
server.login(email_config['username'], email_config['password'])
|
||||
|
||||
text = msg.as_string()
|
||||
server.sendmail(email_config['username'], recipients, text)
|
||||
server.quit()
|
||||
|
||||
print(f"邮件告警已发送: {subject}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送邮件失败: {e}")
|
||||
|
||||
def send_webhook(self, message):
|
||||
"""发送Webhook告警"""
|
||||
webhook_config = self.config['notifications']['webhook']
|
||||
if not webhook_config.get('enabled'):
|
||||
return
|
||||
|
||||
try:
|
||||
payload = {
|
||||
'text': message,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
response = requests.post(webhook_config['url'], json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
print(f"Webhook告警已发送: {message}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送Webhook失败: {e}")
|
||||
|
||||
def process_alert(self, alert):
|
||||
"""处理告警"""
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
subject = f"Redis告警 - {alert['type']}"
|
||||
message = f"""
|
||||
时间: {timestamp}
|
||||
级别: {alert['level'].upper()}
|
||||
类型: {alert['type']}
|
||||
消息: {alert['message']}
|
||||
|
||||
请及时处理!
|
||||
"""
|
||||
|
||||
# 发送邮件
|
||||
self.send_email(subject, message)
|
||||
|
||||
# 发送Webhook
|
||||
self.send_webhook(f"[{alert['level'].upper()}] {alert['message']}")
|
||||
|
||||
# 记录到文件
|
||||
with open('processed_alerts.log', 'a') as f:
|
||||
f.write(f"{timestamp} - {alert['level']} - {alert['message']}\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
handler = AlertHandler()
|
||||
|
||||
# 测试告警
|
||||
test_alert = {
|
||||
'level': 'warning',
|
||||
'type': 'memory_fragmentation',
|
||||
'message': '内存碎片率过高: 1.8'
|
||||
}
|
||||
|
||||
handler.process_alert(test_alert)
|
62
数据库/Redis_2025/redis_alerts.yml
Normal file
62
数据库/Redis_2025/redis_alerts.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# redis_alerts.yml - Redis告警配置
|
||||
|
||||
alerts:
|
||||
memory:
|
||||
fragmentation_ratio:
|
||||
threshold: 1.5
|
||||
level: warning
|
||||
message: "内存碎片率过高"
|
||||
|
||||
used_memory_ratio:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "内存使用率过高"
|
||||
|
||||
performance:
|
||||
hit_rate:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "缓存命中率过低"
|
||||
|
||||
slow_queries:
|
||||
threshold: 10
|
||||
level: warning
|
||||
message: "慢查询过多"
|
||||
|
||||
connections:
|
||||
connected_clients_ratio:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "连接数过高"
|
||||
|
||||
persistence:
|
||||
rdb_changes:
|
||||
threshold: 10000
|
||||
level: warning
|
||||
message: "RDB未保存变更过多"
|
||||
|
||||
aof_rewrite:
|
||||
threshold: 3600 # 1小时
|
||||
level: warning
|
||||
message: "AOF重写时间过长"
|
||||
|
||||
notifications:
|
||||
email:
|
||||
enabled: true
|
||||
smtp_server: "smtp.example.com"
|
||||
smtp_port: 587
|
||||
username: "alert@example.com"
|
||||
password: "password"
|
||||
recipients:
|
||||
- "admin@example.com"
|
||||
- "ops@example.com"
|
||||
|
||||
webhook:
|
||||
enabled: true
|
||||
url: "https://hooks.slack.com/services/xxx"
|
||||
|
||||
sms:
|
||||
enabled: false
|
||||
api_key: "your_sms_api_key"
|
||||
recipients:
|
||||
- "+86138xxxxxxxx"
|
310
数据库/Redis_2025/redis_capacity_planner.py
Normal file
310
数据库/Redis_2025/redis_capacity_planner.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_capacity_planner.py - Redis容量规划工具
|
||||
|
||||
import redis
|
||||
import json
|
||||
import time
|
||||
import statistics
|
||||
from datetime import datetime, timedelta
|
||||
from collections import defaultdict
|
||||
|
||||
class RedisCapacityPlanner:
|
||||
def __init__(self, host='localhost', port=6379, password=None):
|
||||
self.redis_client = redis.Redis(
|
||||
host=host, port=port, password=password, decode_responses=True
|
||||
)
|
||||
self.metrics_history = []
|
||||
|
||||
def collect_metrics(self):
|
||||
"""收集当前指标"""
|
||||
try:
|
||||
info = self.redis_client.info()
|
||||
|
||||
metrics = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'memory': {
|
||||
'used_memory': info.get('used_memory', 0),
|
||||
'used_memory_peak': info.get('used_memory_peak', 0),
|
||||
'used_memory_rss': info.get('used_memory_rss', 0),
|
||||
'mem_fragmentation_ratio': info.get('mem_fragmentation_ratio', 1.0)
|
||||
},
|
||||
'clients': {
|
||||
'connected_clients': info.get('connected_clients', 0),
|
||||
'blocked_clients': info.get('blocked_clients', 0)
|
||||
},
|
||||
'stats': {
|
||||
'total_commands_processed': info.get('total_commands_processed', 0),
|
||||
'instantaneous_ops_per_sec': info.get('instantaneous_ops_per_sec', 0),
|
||||
'keyspace_hits': info.get('keyspace_hits', 0),
|
||||
'keyspace_misses': info.get('keyspace_misses', 0)
|
||||
},
|
||||
'keyspace': self.get_keyspace_info(),
|
||||
'persistence': {
|
||||
'rdb_changes_since_last_save': info.get('rdb_changes_since_last_save', 0),
|
||||
'aof_current_size': info.get('aof_current_size', 0)
|
||||
}
|
||||
}
|
||||
|
||||
return metrics
|
||||
except Exception as e:
|
||||
print(f"收集指标失败: {e}")
|
||||
return None
|
||||
|
||||
def get_keyspace_info(self):
|
||||
"""获取键空间信息"""
|
||||
try:
|
||||
keyspace_info = {}
|
||||
info = self.redis_client.info('keyspace')
|
||||
|
||||
for db_key, db_info in info.items():
|
||||
if db_key.startswith('db'):
|
||||
# 解析db信息: keys=xxx,expires=xxx,avg_ttl=xxx
|
||||
db_stats = {}
|
||||
for item in db_info.split(','):
|
||||
key, value = item.split('=')
|
||||
db_stats[key] = int(value)
|
||||
keyspace_info[db_key] = db_stats
|
||||
|
||||
return keyspace_info
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def analyze_key_patterns(self, sample_size=1000):
|
||||
"""分析键模式"""
|
||||
try:
|
||||
print("分析键模式...")
|
||||
|
||||
# 使用SCAN获取键样本
|
||||
keys_sample = []
|
||||
cursor = 0
|
||||
|
||||
while len(keys_sample) < sample_size:
|
||||
cursor, keys = self.redis_client.scan(cursor, count=100)
|
||||
keys_sample.extend(keys)
|
||||
|
||||
if cursor == 0: # 扫描完成
|
||||
break
|
||||
|
||||
# 分析键模式
|
||||
pattern_stats = defaultdict(lambda: {'count': 0, 'total_size': 0, 'avg_ttl': 0})
|
||||
|
||||
for key in keys_sample[:sample_size]:
|
||||
try:
|
||||
# 提取键模式(前缀)
|
||||
pattern = key.split(':')[0] if ':' in key else 'no_pattern'
|
||||
|
||||
# 获取键信息
|
||||
key_type = self.redis_client.type(key)
|
||||
memory_usage = self.redis_client.memory_usage(key) or 0
|
||||
ttl = self.redis_client.ttl(key)
|
||||
|
||||
pattern_stats[pattern]['count'] += 1
|
||||
pattern_stats[pattern]['total_size'] += memory_usage
|
||||
|
||||
if ttl > 0:
|
||||
pattern_stats[pattern]['avg_ttl'] = (
|
||||
pattern_stats[pattern]['avg_ttl'] + ttl
|
||||
) / 2
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# 计算平均大小
|
||||
for pattern, stats in pattern_stats.items():
|
||||
if stats['count'] > 0:
|
||||
stats['avg_size'] = stats['total_size'] / stats['count']
|
||||
|
||||
return dict(pattern_stats)
|
||||
|
||||
except Exception as e:
|
||||
print(f"键模式分析失败: {e}")
|
||||
return {}
|
||||
|
||||
def predict_growth(self, days=30):
|
||||
"""预测增长趋势"""
|
||||
if len(self.metrics_history) < 2:
|
||||
return None
|
||||
|
||||
# 计算内存增长率
|
||||
memory_values = [m['memory']['used_memory'] for m in self.metrics_history]
|
||||
time_points = [datetime.fromisoformat(m['timestamp']) for m in self.metrics_history]
|
||||
|
||||
if len(memory_values) < 2:
|
||||
return None
|
||||
|
||||
# 简单线性增长预测
|
||||
time_diffs = [(t - time_points[0]).total_seconds() / 3600 for t in time_points] # 小时
|
||||
|
||||
# 计算增长率(每小时)
|
||||
growth_rates = []
|
||||
for i in range(1, len(memory_values)):
|
||||
if time_diffs[i] > 0:
|
||||
rate = (memory_values[i] - memory_values[i-1]) / time_diffs[i]
|
||||
growth_rates.append(rate)
|
||||
|
||||
if not growth_rates:
|
||||
return None
|
||||
|
||||
avg_growth_rate = statistics.mean(growth_rates) # 字节/小时
|
||||
current_memory = memory_values[-1]
|
||||
|
||||
# 预测未来内存使用
|
||||
future_memory = current_memory + (avg_growth_rate * 24 * days)
|
||||
|
||||
return {
|
||||
'current_memory': current_memory,
|
||||
'predicted_memory': future_memory,
|
||||
'growth_rate_per_hour': avg_growth_rate,
|
||||
'growth_rate_per_day': avg_growth_rate * 24,
|
||||
'prediction_days': days
|
||||
}
|
||||
|
||||
def generate_capacity_report(self):
|
||||
"""生成容量规划报告"""
|
||||
print("\n=== Redis容量规划报告 ===")
|
||||
print(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
|
||||
# 当前状态
|
||||
current_metrics = self.collect_metrics()
|
||||
if not current_metrics:
|
||||
print("无法获取当前指标")
|
||||
return
|
||||
|
||||
print("1. 当前状态")
|
||||
print("-" * 40)
|
||||
memory_mb = current_metrics['memory']['used_memory'] / 1024 / 1024
|
||||
print(f"内存使用: {memory_mb:.2f} MB")
|
||||
print(f"连接数: {current_metrics['clients']['connected_clients']}")
|
||||
print(f"QPS: {current_metrics['stats']['instantaneous_ops_per_sec']}")
|
||||
|
||||
# 键空间分析
|
||||
total_keys = sum(
|
||||
db_info.get('keys', 0)
|
||||
for db_info in current_metrics['keyspace'].values()
|
||||
)
|
||||
print(f"总键数: {total_keys}")
|
||||
|
||||
# 键模式分析
|
||||
print("\n2. 键模式分析")
|
||||
print("-" * 40)
|
||||
key_patterns = self.analyze_key_patterns()
|
||||
|
||||
if key_patterns:
|
||||
# 按内存使用排序
|
||||
sorted_patterns = sorted(
|
||||
key_patterns.items(),
|
||||
key=lambda x: x[1]['total_size'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
print(f"{'模式':<20} {'数量':<10} {'总大小(KB)':<15} {'平均大小(B)':<15}")
|
||||
print("-" * 65)
|
||||
|
||||
for pattern, stats in sorted_patterns[:10]:
|
||||
avg_size = stats.get('avg_size', 0)
|
||||
total_kb = stats['total_size'] / 1024
|
||||
print(f"{pattern:<20} {stats['count']:<10} {total_kb:<15.2f} {avg_size:<15.0f}")
|
||||
|
||||
# 增长预测
|
||||
print("\n3. 增长预测")
|
||||
print("-" * 40)
|
||||
|
||||
if len(self.metrics_history) >= 2:
|
||||
prediction = self.predict_growth(30)
|
||||
if prediction:
|
||||
current_gb = prediction['current_memory'] / 1024 / 1024 / 1024
|
||||
predicted_gb = prediction['predicted_memory'] / 1024 / 1024 / 1024
|
||||
growth_mb_day = prediction['growth_rate_per_day'] / 1024 / 1024
|
||||
|
||||
print(f"当前内存: {current_gb:.2f} GB")
|
||||
print(f"30天后预测: {predicted_gb:.2f} GB")
|
||||
print(f"日增长率: {growth_mb_day:.2f} MB/天")
|
||||
|
||||
# 容量建议
|
||||
print("\n4. 容量建议")
|
||||
print("-" * 40)
|
||||
|
||||
if predicted_gb > 8:
|
||||
print("⚠ 建议: 考虑增加内存或优化数据结构")
|
||||
elif predicted_gb > 4:
|
||||
print("⚠ 建议: 监控内存使用,准备扩容计划")
|
||||
else:
|
||||
print("✓ 当前容量充足")
|
||||
|
||||
# 扩容时间点预测
|
||||
if growth_mb_day > 0:
|
||||
# 假设8GB为容量上限
|
||||
max_capacity = 8 * 1024 * 1024 * 1024
|
||||
remaining_capacity = max_capacity - prediction['current_memory']
|
||||
days_to_full = remaining_capacity / prediction['growth_rate_per_day']
|
||||
|
||||
if days_to_full > 0:
|
||||
full_date = datetime.now() + timedelta(days=days_to_full)
|
||||
print(f"预计容量耗尽时间: {full_date.strftime('%Y-%m-%d')} ({days_to_full:.0f}天后)")
|
||||
else:
|
||||
print("数据不足,无法进行增长预测")
|
||||
else:
|
||||
print("需要更多历史数据进行预测")
|
||||
|
||||
# 性能建议
|
||||
print("\n5. 性能优化建议")
|
||||
print("-" * 40)
|
||||
|
||||
fragmentation = current_metrics['memory']['mem_fragmentation_ratio']
|
||||
if fragmentation > 1.5:
|
||||
print("⚠ 内存碎片率过高,建议重启Redis或使用MEMORY PURGE")
|
||||
|
||||
hit_rate = 0
|
||||
hits = current_metrics['stats']['keyspace_hits']
|
||||
misses = current_metrics['stats']['keyspace_misses']
|
||||
if hits + misses > 0:
|
||||
hit_rate = hits / (hits + misses)
|
||||
if hit_rate < 0.8:
|
||||
print(f"⚠ 缓存命中率较低({hit_rate:.2%}),建议优化缓存策略")
|
||||
|
||||
if current_metrics['clients']['connected_clients'] > 1000:
|
||||
print("⚠ 连接数较多,建议使用连接池")
|
||||
|
||||
# 保存报告
|
||||
report_data = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'current_metrics': current_metrics,
|
||||
'key_patterns': key_patterns,
|
||||
'prediction': prediction if len(self.metrics_history) >= 2 else None,
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
report_file = f"redis_capacity_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
with open(report_file, 'w') as f:
|
||||
json.dump(report_data, f, indent=2, default=str)
|
||||
|
||||
print(f"\n详细报告已保存到: {report_file}")
|
||||
|
||||
def monitor_and_collect(self, duration_hours=24, interval_minutes=10):
|
||||
"""监控并收集数据"""
|
||||
print(f"开始收集数据,持续{duration_hours}小时,间隔{interval_minutes}分钟")
|
||||
|
||||
end_time = datetime.now() + timedelta(hours=duration_hours)
|
||||
|
||||
while datetime.now() < end_time:
|
||||
metrics = self.collect_metrics()
|
||||
if metrics:
|
||||
self.metrics_history.append(metrics)
|
||||
print(f"已收集数据点: {len(self.metrics_history)}")
|
||||
|
||||
time.sleep(interval_minutes * 60)
|
||||
|
||||
print(f"数据收集完成,共收集{len(self.metrics_history)}个数据点")
|
||||
|
||||
if __name__ == '__main__':
|
||||
planner = RedisCapacityPlanner()
|
||||
|
||||
# 收集一些历史数据(演示用)
|
||||
for i in range(5):
|
||||
metrics = planner.collect_metrics()
|
||||
if metrics:
|
||||
planner.metrics_history.append(metrics)
|
||||
time.sleep(1)
|
||||
|
||||
# 生成报告
|
||||
planner.generate_capacity_report()
|
65
数据库/Redis_2025/redis_doc.md
Normal file
65
数据库/Redis_2025/redis_doc.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Redis 课程文档规范
|
||||
|
||||
本文档旨在定义 Redis 课程相关文件的用途、格式风格和编写原则,以确保课程内容的一致性、专业性和易读性。
|
||||
|
||||
---
|
||||
|
||||
## 1. 文件定义
|
||||
|
||||
### 1.1. 课程大纲 (`redis_tpl.md`)
|
||||
|
||||
- **用途**:作为 Redis 课程的整体教学计划和结构指南。它定义了课程的章节、知识点、学时分配和实践环节。
|
||||
- **目标读者**:课程开发者、上课教师。
|
||||
- **核心要求**:结构清晰,逻辑性强,全面覆盖 Redis 的核心知识体系。
|
||||
|
||||
### 1.2. 课程内容
|
||||
|
||||
- **用途**:根据课程大纲,详细阐述每个知识点的具体内容,包括理论讲解、代码示例、实践操作和练习题。
|
||||
- **目标读者**:学生。
|
||||
- **核心要求**:内容详实,通俗易懂,理论与实践相结合。
|
||||
|
||||
---
|
||||
|
||||
## 2. 编写原则
|
||||
|
||||
### 2.1. 内容组织:从浅入深
|
||||
|
||||
课程内容应遵循认知规律,从基础概念开始,逐步深入到高级主题和实战应用。
|
||||
|
||||
- **基础先行**:首先介绍 NoSQL 和 Redis 的基本概念、架构和安装部署。
|
||||
- **核心操作**:然后讲解数据类型、基本命令和持久化等核心技能。
|
||||
- **高级进阶**:接着深入主从复制、哨兵模式、集群部署和性能优化等高级主题。
|
||||
- **实战应用**:最后通过项目实战,巩固所学知识,培养解决实际问题的能力。
|
||||
|
||||
### 2.2. 语言风格:通俗易懂
|
||||
|
||||
- **简化复杂概念**:使用简单的语言和比喻来解释复杂的技术概念。
|
||||
- **图文并茂**:适当使用图表、流程图和示意图,帮助理解抽象的知识。
|
||||
- **代码注释**:所有代码示例都应有清晰的注释,解释代码的功能和逻辑。
|
||||
|
||||
### 2.3. 理论与实践融合
|
||||
|
||||
- **理论指导实践**:每个理论知识点都应配有相应的实践案例或代码示例。
|
||||
- **实践巩固理论**:通过实验、练习和项目,加深对理论知识的理解和应用。
|
||||
- **场景驱动**:结合真实的应用场景(如缓存、会话管理、消息队列、排行榜等)进行讲解,激发学习兴趣。
|
||||
|
||||
### 2.4. 格式风格:统一规范
|
||||
|
||||
- **Markdown 语法**:统一使用 Markdown 进行文档编写。
|
||||
- **标题层级**:遵循清晰的标题层级结构(`#`、`##`、`###`),与课程大纲保持一致,从 Redis 基础概念开始作为 `#` 标题,其他平级标题也用 `#` 格式,不需要 x.x.x 格式。
|
||||
- **代码块**:使用代码块来格式化代码示例,并注明语言类型。(bash/shell 统一用 shell)
|
||||
- **术语规范**:专业术语首次出现时应予以解释,并保持中英文术语的统一性。
|
||||
- **实践操作**:每一章内容最后有一个实践环节,标题为 **实践操作**,有需求描述、实践细节和结果验证。并将实践细节和结果验证放到同一个代码块中。
|
||||
|
||||
---
|
||||
|
||||
## 3. 协作流程
|
||||
|
||||
1. **大纲评审**:首先共同评审和确定 `redis_tpl.md` 中的课程大纲。
|
||||
2. **内容开发**:根据大纲,分工协作编写具体内容。
|
||||
3. **内容评审**:定期进行内容评审(Peer Review),确保内容质量和风格统一。
|
||||
4. **持续迭代**:根据教学反馈和技术发展,持续更新和完善课程内容。
|
||||
|
||||
## 4. 软件版本
|
||||
|
||||
1. **Redis 版本**:Redis 6.2.19
|
274
数据库/Redis_2025/redis_monitor.py
Normal file
274
数据库/Redis_2025/redis_monitor.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_monitor.py - Redis监控脚本
|
||||
|
||||
import redis
|
||||
import time
|
||||
import json
|
||||
import psutil
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any
|
||||
|
||||
class RedisMonitor:
|
||||
def __init__(self, host='localhost', port=6379, password=None):
|
||||
self.redis_client = redis.Redis(
|
||||
host=host, port=port, password=password, decode_responses=True
|
||||
)
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self):
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('redis_monitor.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def get_redis_info(self) -> Dict[str, Any]:
|
||||
"""获取Redis信息"""
|
||||
try:
|
||||
info = self.redis_client.info()
|
||||
return {
|
||||
'server': {
|
||||
'version': info.get('redis_version'),
|
||||
'uptime': info.get('uptime_in_seconds'),
|
||||
'role': info.get('role')
|
||||
},
|
||||
'clients': {
|
||||
'connected': info.get('connected_clients'),
|
||||
'blocked': info.get('blocked_clients'),
|
||||
'max_clients': info.get('maxclients')
|
||||
},
|
||||
'memory': {
|
||||
'used': info.get('used_memory'),
|
||||
'used_human': info.get('used_memory_human'),
|
||||
'peak': info.get('used_memory_peak'),
|
||||
'peak_human': info.get('used_memory_peak_human'),
|
||||
'fragmentation_ratio': info.get('mem_fragmentation_ratio')
|
||||
},
|
||||
'stats': {
|
||||
'total_commands': info.get('total_commands_processed'),
|
||||
'ops_per_sec': info.get('instantaneous_ops_per_sec'),
|
||||
'keyspace_hits': info.get('keyspace_hits'),
|
||||
'keyspace_misses': info.get('keyspace_misses'),
|
||||
'expired_keys': info.get('expired_keys'),
|
||||
'evicted_keys': info.get('evicted_keys')
|
||||
},
|
||||
'persistence': {
|
||||
'rdb_last_save': info.get('rdb_last_save_time'),
|
||||
'rdb_changes_since_save': info.get('rdb_changes_since_last_save'),
|
||||
'aof_enabled': info.get('aof_enabled'),
|
||||
'aof_rewrite_in_progress': info.get('aof_rewrite_in_progress')
|
||||
},
|
||||
'replication': {
|
||||
'role': info.get('role'),
|
||||
'connected_slaves': info.get('connected_slaves'),
|
||||
'master_repl_offset': info.get('master_repl_offset'),
|
||||
'repl_backlog_size': info.get('repl_backlog_size')
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取Redis信息失败: {e}")
|
||||
return {}
|
||||
|
||||
def get_system_info(self) -> Dict[str, Any]:
|
||||
"""获取系统信息"""
|
||||
try:
|
||||
# 查找Redis进程
|
||||
redis_processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
if 'redis-server' in proc.info['name']:
|
||||
redis_processes.append(proc)
|
||||
|
||||
if not redis_processes:
|
||||
return {}
|
||||
|
||||
redis_proc = redis_processes[0]
|
||||
|
||||
return {
|
||||
'cpu': {
|
||||
'percent': redis_proc.cpu_percent(),
|
||||
'times': redis_proc.cpu_times()._asdict()
|
||||
},
|
||||
'memory': {
|
||||
'rss': redis_proc.memory_info().rss,
|
||||
'vms': redis_proc.memory_info().vms,
|
||||
'percent': redis_proc.memory_percent()
|
||||
},
|
||||
'io': {
|
||||
'read_count': redis_proc.io_counters().read_count,
|
||||
'write_count': redis_proc.io_counters().write_count,
|
||||
'read_bytes': redis_proc.io_counters().read_bytes,
|
||||
'write_bytes': redis_proc.io_counters().write_bytes
|
||||
},
|
||||
'net_connections': len(redis_proc.net_connections()),
|
||||
'open_files': len(redis_proc.open_files()),
|
||||
'threads': redis_proc.num_threads()
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取系统信息失败: {e}")
|
||||
return {}
|
||||
|
||||
def get_slow_queries(self, count=10) -> list:
|
||||
"""获取慢查询"""
|
||||
try:
|
||||
slow_log = self.redis_client.slowlog_get(count)
|
||||
return [
|
||||
{
|
||||
'id': entry['id'],
|
||||
'start_time': entry['start_time'],
|
||||
'duration': entry['duration'],
|
||||
'command': ' '.join(str(cmd) for cmd in entry['command'])
|
||||
}
|
||||
for entry in slow_log
|
||||
]
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取慢查询失败: {e}")
|
||||
return []
|
||||
|
||||
def check_alerts(self, metrics: Dict[str, Any]) -> list:
|
||||
"""检查告警条件"""
|
||||
alerts = []
|
||||
|
||||
# 内存使用率告警
|
||||
if metrics.get('memory', {}).get('fragmentation_ratio', 0) > 1.5:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'memory_fragmentation',
|
||||
'message': f"内存碎片率过高: {metrics['memory']['fragmentation_ratio']}"
|
||||
})
|
||||
|
||||
# 连接数告警
|
||||
connected_clients = metrics.get('clients', {}).get('connected', 0)
|
||||
max_clients = metrics.get('clients', {}).get('max_clients', 10000)
|
||||
if connected_clients > max_clients * 0.8:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'high_connections',
|
||||
'message': f"连接数过高: {connected_clients}/{max_clients}"
|
||||
})
|
||||
|
||||
# 命中率告警
|
||||
hits = metrics.get('stats', {}).get('keyspace_hits', 0)
|
||||
misses = metrics.get('stats', {}).get('keyspace_misses', 0)
|
||||
if hits + misses > 0:
|
||||
hit_rate = hits / (hits + misses)
|
||||
if hit_rate < 0.8:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'low_hit_rate',
|
||||
'message': f"缓存命中率过低: {hit_rate:.2%}"
|
||||
})
|
||||
|
||||
# 持久化告警
|
||||
rdb_changes = metrics.get('persistence', {}).get('rdb_changes_since_save', 0)
|
||||
if rdb_changes > 10000:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'rdb_not_saved',
|
||||
'message': f"RDB未保存变更过多: {rdb_changes}"
|
||||
})
|
||||
|
||||
return alerts
|
||||
|
||||
def collect_metrics(self) -> Dict[str, Any]:
|
||||
"""收集所有监控指标"""
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
metrics = {
|
||||
'timestamp': timestamp,
|
||||
'redis': self.get_redis_info(),
|
||||
'system': self.get_system_info(),
|
||||
'slow_queries': self.get_slow_queries()
|
||||
}
|
||||
|
||||
# 检查告警
|
||||
alerts = self.check_alerts(metrics['redis'])
|
||||
metrics['alerts'] = alerts
|
||||
|
||||
return metrics
|
||||
|
||||
def save_metrics(self, metrics: Dict[str, Any]):
|
||||
"""保存监控数据"""
|
||||
try:
|
||||
filename = f"redis_metrics_{datetime.now().strftime('%Y%m%d')}.json"
|
||||
|
||||
# 读取现有数据
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
data = []
|
||||
|
||||
# 添加新数据
|
||||
data.append(metrics)
|
||||
|
||||
# 保持最近1000条记录
|
||||
if len(data) > 1000:
|
||||
data = data[-1000:]
|
||||
|
||||
# 保存数据
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(data, f, indent=2, default=str)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存监控数据失败: {e}")
|
||||
|
||||
def send_alerts(self, alerts: list):
|
||||
"""发送告警"""
|
||||
for alert in alerts:
|
||||
message = f"[{alert['level'].upper()}] {alert['type']}: {alert['message']}"
|
||||
self.logger.warning(message)
|
||||
|
||||
# 这里可以集成邮件、短信、钉钉等告警方式
|
||||
# 示例:发送到日志文件
|
||||
with open('redis_alerts.log', 'a') as f:
|
||||
f.write(f"{datetime.now().isoformat()} - {message}\n")
|
||||
|
||||
def run_monitor(self, interval=60):
|
||||
"""运行监控"""
|
||||
self.logger.info("开始Redis监控")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 收集指标
|
||||
metrics = self.collect_metrics()
|
||||
|
||||
# 保存数据
|
||||
self.save_metrics(metrics)
|
||||
|
||||
# 处理告警
|
||||
if metrics.get('alerts'):
|
||||
self.send_alerts(metrics['alerts'])
|
||||
|
||||
# 输出关键指标
|
||||
redis_info = metrics.get('redis', {})
|
||||
self.logger.info(
|
||||
f"连接数: {redis_info.get('clients', {}).get('connected', 0)}, "
|
||||
f"内存: {redis_info.get('memory', {}).get('used_human', 'N/A')}, "
|
||||
f"QPS: {redis_info.get('stats', {}).get('ops_per_sec', 0)}"
|
||||
)
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("监控已停止")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"监控异常: {e}")
|
||||
time.sleep(interval)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
host = sys.argv[1] if len(sys.argv) > 1 else 'localhost'
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 6379
|
||||
password = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
interval = int(sys.argv[4]) if len(sys.argv) > 4 else 60
|
||||
|
||||
monitor = RedisMonitor(host, port, password)
|
||||
monitor.run_monitor(interval)
|
70
数据库/Redis_2025/redis_pool_example.py
Normal file
70
数据库/Redis_2025/redis_pool_example.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import redis
|
||||
import random
|
||||
from redis.sentinel import Sentinel
|
||||
|
||||
class RedisCluster:
|
||||
def __init__(self, master_host, master_port, slave_hosts, password=None):
|
||||
# 主节点连接池(写操作)
|
||||
self.master_pool = redis.ConnectionPool(
|
||||
host=master_host,
|
||||
port=master_port,
|
||||
password=password,
|
||||
max_connections=20,
|
||||
retry_on_timeout=True
|
||||
)
|
||||
|
||||
# 从节点连接池(读操作)
|
||||
self.slave_pools = []
|
||||
for host, port in slave_hosts:
|
||||
pool = redis.ConnectionPool(
|
||||
host=host,
|
||||
port=port,
|
||||
password=password,
|
||||
max_connections=10,
|
||||
retry_on_timeout=True
|
||||
)
|
||||
self.slave_pools.append(pool)
|
||||
|
||||
def get_master_client(self):
|
||||
"""获取主节点客户端(写操作)"""
|
||||
return redis.Redis(connection_pool=self.master_pool)
|
||||
|
||||
def get_slave_client(self):
|
||||
"""获取从节点客户端(读操作)"""
|
||||
if not self.slave_pools:
|
||||
return self.get_master_client()
|
||||
|
||||
# 随机选择一个从节点
|
||||
pool = random.choice(self.slave_pools)
|
||||
return redis.Redis(connection_pool=pool)
|
||||
|
||||
def set(self, key, value, **kwargs):
|
||||
"""写操作"""
|
||||
client = self.get_master_client()
|
||||
return client.set(key, value, **kwargs)
|
||||
|
||||
def get(self, key):
|
||||
"""读操作"""
|
||||
client = self.get_slave_client()
|
||||
return client.get(key)
|
||||
|
||||
def delete(self, *keys):
|
||||
"""删除操作"""
|
||||
client = self.get_master_client()
|
||||
return client.delete(*keys)
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
cluster = RedisCluster(
|
||||
master_host="192.168.1.100",
|
||||
master_port=6379,
|
||||
slave_hosts=[("192.168.1.101", 6380), ("192.168.1.102", 6381)],
|
||||
password="your_password"
|
||||
)
|
||||
|
||||
# 写操作(发送到主节点)
|
||||
cluster.set("test_key", "test_value")
|
||||
|
||||
# 读操作(发送到从节点)
|
||||
value = cluster.get("test_key")
|
||||
print(f"读取到的值: {value}")
|
410
数据库/Redis_2025/redis_tpl.md
Normal file
410
数据库/Redis_2025/redis_tpl.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# Redis 基础概念
|
||||
|
||||
## NoSQL 数据库概述
|
||||
|
||||
- NoSQL 数据库的特点和分类
|
||||
- 关系型数据库 vs NoSQL 数据库
|
||||
- NoSQL 数据库的应用场景
|
||||
|
||||
## Redis 简介
|
||||
|
||||
- Redis 的定义和特点
|
||||
- Redis 的数据结构
|
||||
- Redis 的应用场景
|
||||
- Redis 的优势和局限性
|
||||
|
||||
## Redis 架构
|
||||
|
||||
- Redis 的内存模型
|
||||
- Redis 的单线程模型
|
||||
- Redis 的事件驱动机制
|
||||
|
||||
## 实践操作
|
||||
- 了解 NoSQL 数据库的分类和特点
|
||||
- 分析 Redis 在不同场景下的应用优势
|
||||
|
||||
---
|
||||
|
||||
# Redis 环境搭建
|
||||
|
||||
## Redis 安装
|
||||
|
||||
- Linux 环境下的安装
|
||||
|
||||
## Redis 配置
|
||||
|
||||
- 配置文件详解
|
||||
- 常用配置参数
|
||||
- 安全配置
|
||||
- 性能调优配置
|
||||
|
||||
## Redis 启动和连接
|
||||
- Redis 服务启动
|
||||
- Redis 客户端连接
|
||||
- 远程连接配置
|
||||
- 连接池配置
|
||||
|
||||
## 实践操作
|
||||
- 在本地环境安装 Redis
|
||||
- 配置 Redis 服务
|
||||
- 使用客户端连接 Redis
|
||||
|
||||
---
|
||||
|
||||
# Redis 数据类型
|
||||
|
||||
## 字符串 (String)
|
||||
- 字符串的特点和应用
|
||||
- 基本操作命令
|
||||
- 数值操作
|
||||
- 位操作
|
||||
|
||||
## 列表 (List)
|
||||
- 列表的特点和应用
|
||||
- 基本操作命令
|
||||
- 阻塞操作
|
||||
- 列表的应用场景
|
||||
|
||||
## 集合 (Set)
|
||||
- 集合的特点和应用
|
||||
- 基本操作命令
|
||||
- 集合运算
|
||||
- 随机操作
|
||||
|
||||
## 有序集合 (Sorted Set)
|
||||
- 有序集合的特点和应用
|
||||
- 基本操作命令
|
||||
- 范围操作
|
||||
- 排行榜应用
|
||||
|
||||
## 哈希 (Hash)
|
||||
- 哈希的特点和应用
|
||||
- 基本操作命令
|
||||
- 批量操作
|
||||
- 对象存储应用
|
||||
|
||||
## 实践操作
|
||||
- 使用不同数据类型存储数据
|
||||
- 实现简单的缓存功能
|
||||
- 构建用户信息存储系统
|
||||
|
||||
---
|
||||
|
||||
# Redis 基本命令
|
||||
|
||||
## 键操作命令
|
||||
- 键的命名规范
|
||||
- 键的查询和遍历
|
||||
- 键的过期设置
|
||||
- 键的删除和重命名
|
||||
|
||||
## 数据库操作
|
||||
- 多数据库概念
|
||||
- 数据库切换
|
||||
- 数据库清空
|
||||
- 数据库信息查看
|
||||
|
||||
## 事务操作
|
||||
- 事务的概念
|
||||
- MULTI/EXEC 命令
|
||||
- WATCH 命令
|
||||
- 事务的特性和限制
|
||||
|
||||
## 脚本操作
|
||||
- Lua 脚本简介
|
||||
- EVAL 和 EVALSHA 命令
|
||||
- 脚本缓存
|
||||
- 脚本的应用场景
|
||||
|
||||
## 实践操作
|
||||
- 管理 Redis 键空间
|
||||
- 使用事务保证操作原子性
|
||||
- 编写简单的 Lua 脚本
|
||||
|
||||
---
|
||||
|
||||
# Redis 持久化
|
||||
|
||||
## RDB 持久化
|
||||
- RDB 的工作原理
|
||||
- RDB 配置参数
|
||||
- 手动触发 RDB
|
||||
- RDB 文件的恢复
|
||||
|
||||
## AOF 持久化
|
||||
- AOF 的工作原理
|
||||
- AOF 配置参数
|
||||
- AOF 重写机制
|
||||
- AOF 文件的恢复
|
||||
|
||||
## 混合持久化
|
||||
- 混合持久化的优势
|
||||
- 配置混合持久化
|
||||
- 数据恢复策略
|
||||
|
||||
## 持久化策略选择
|
||||
- RDB vs AOF 对比
|
||||
- 不同场景下的选择
|
||||
- 性能影响分析
|
||||
|
||||
## 实践操作
|
||||
- 配置 RDB 持久化
|
||||
- 配置 AOF 持久化
|
||||
- 测试数据恢复功能
|
||||
|
||||
---
|
||||
|
||||
# Redis 主从复制
|
||||
|
||||
## 主从复制概述
|
||||
- 主从复制的概念
|
||||
- 主从复制的优势
|
||||
- 复制的工作原理
|
||||
|
||||
## 主从复制配置
|
||||
- 主服务器配置
|
||||
- 从服务器配置
|
||||
- 复制参数调优
|
||||
|
||||
## 复制的特性
|
||||
- 异步复制
|
||||
- 部分重同步
|
||||
- 无磁盘复制
|
||||
- 复制偏移量
|
||||
|
||||
## 主从切换
|
||||
- 手动主从切换
|
||||
- 故障处理
|
||||
- 数据一致性
|
||||
|
||||
## 实践操作
|
||||
- 搭建主从复制环境
|
||||
- 测试数据同步
|
||||
- 模拟故障切换
|
||||
|
||||
---
|
||||
|
||||
# Redis 哨兵模式
|
||||
|
||||
## 哨兵模式概述
|
||||
- 哨兵的作用
|
||||
- 哨兵的工作原理
|
||||
- 哨兵集群的优势
|
||||
|
||||
## 哨兵配置
|
||||
- 哨兵配置文件
|
||||
- 监控配置
|
||||
- 故障转移配置
|
||||
- 通知配置
|
||||
|
||||
## 故障检测和转移
|
||||
- 主观下线和客观下线
|
||||
- 故障转移流程
|
||||
- 新主服务器选举
|
||||
- 配置传播
|
||||
|
||||
## 哨兵集群管理
|
||||
- 哨兵节点管理
|
||||
- 配置更新
|
||||
- 监控和日志
|
||||
|
||||
## 实践操作
|
||||
- 部署哨兵集群
|
||||
- 配置故障转移
|
||||
- 测试高可用性
|
||||
|
||||
---
|
||||
|
||||
# Redis 集群
|
||||
|
||||
## 集群概述
|
||||
- 集群的概念和优势
|
||||
- 集群 vs 主从复制
|
||||
- 集群的适用场景
|
||||
|
||||
## 集群架构
|
||||
- 槽位分配机制
|
||||
- 节点通信协议
|
||||
- 集群拓扑结构
|
||||
|
||||
## 集群搭建
|
||||
- 集群节点配置
|
||||
- 集群初始化
|
||||
- 节点加入和移除
|
||||
- 槽位迁移
|
||||
|
||||
## 集群管理
|
||||
- 集群状态监控
|
||||
- 故障检测和恢复
|
||||
- 集群扩容和缩容
|
||||
- 数据迁移
|
||||
|
||||
## 实践操作
|
||||
- 搭建 Redis 集群
|
||||
- 测试数据分片
|
||||
- 进行集群扩容
|
||||
|
||||
---
|
||||
|
||||
# Redis 性能优化
|
||||
|
||||
## 性能监控
|
||||
- 性能指标分析
|
||||
- 监控工具使用
|
||||
- 慢查询日志
|
||||
- 内存使用分析
|
||||
|
||||
## 内存优化
|
||||
- 内存使用策略
|
||||
- 数据结构优化
|
||||
- 过期策略配置
|
||||
- 内存碎片处理
|
||||
|
||||
## 网络优化
|
||||
- 连接池配置
|
||||
- 管道技术
|
||||
- 批量操作
|
||||
- 网络延迟优化
|
||||
|
||||
## 配置优化
|
||||
- 核心参数调优
|
||||
- 持久化优化
|
||||
- 复制优化
|
||||
- 系统级优化
|
||||
|
||||
## 实践操作
|
||||
- 监控 Redis 性能
|
||||
- 优化内存使用
|
||||
- 测试性能提升效果
|
||||
|
||||
---
|
||||
|
||||
# Redis 安全管理
|
||||
|
||||
## 访问控制
|
||||
- 密码认证
|
||||
- 用户管理 (ACL)
|
||||
- 权限控制
|
||||
- IP 白名单
|
||||
|
||||
## 网络安全
|
||||
- 端口安全
|
||||
- SSL/TLS 加密
|
||||
- 防火墙配置
|
||||
- VPN 访问
|
||||
|
||||
## 数据安全
|
||||
- 数据加密
|
||||
- 敏感数据处理
|
||||
- 备份安全
|
||||
- 审计日志
|
||||
|
||||
## 安全最佳实践
|
||||
- 安全配置检查
|
||||
- 漏洞防护
|
||||
- 安全更新
|
||||
- 应急响应
|
||||
|
||||
## 实践操作
|
||||
- 配置访问控制
|
||||
- 启用 SSL 加密
|
||||
- 实施安全策略
|
||||
|
||||
---
|
||||
|
||||
# Redis 应用实战
|
||||
|
||||
## 缓存应用
|
||||
- 缓存策略
|
||||
- 缓存穿透、击穿、雪崩
|
||||
- 缓存更新策略
|
||||
- 分布式缓存
|
||||
|
||||
## 会话管理
|
||||
- 会话存储
|
||||
- 分布式会话
|
||||
- 会话过期处理
|
||||
- 安全考虑
|
||||
|
||||
## 消息队列
|
||||
- 发布订阅模式
|
||||
- 消息队列实现
|
||||
- 延时队列
|
||||
- 可靠性保证
|
||||
|
||||
## 排行榜系统
|
||||
- 实时排行榜
|
||||
- 历史排行榜
|
||||
- 分页查询
|
||||
- 性能优化
|
||||
|
||||
## 分布式锁
|
||||
- 锁的实现原理
|
||||
- 锁的获取和释放
|
||||
- 锁的超时处理
|
||||
- 可重入锁
|
||||
|
||||
## 实践操作
|
||||
- 构建缓存系统
|
||||
- 实现会话管理
|
||||
- 开发消息队列
|
||||
- 创建排行榜功能
|
||||
- 实现分布式锁
|
||||
|
||||
---
|
||||
|
||||
# Redis 运维管理
|
||||
|
||||
## 备份和恢复
|
||||
- 备份策略
|
||||
- 自动备份
|
||||
- 数据恢复
|
||||
- 灾难恢复
|
||||
|
||||
## 监控和告警
|
||||
- 监控指标
|
||||
- 告警规则
|
||||
- 监控工具
|
||||
- 性能分析
|
||||
|
||||
## 日志管理
|
||||
- 日志配置
|
||||
- 日志分析
|
||||
- 问题排查
|
||||
- 日志轮转
|
||||
|
||||
## 版本升级
|
||||
- 升级策略
|
||||
- 兼容性检查
|
||||
- 滚动升级
|
||||
- 回滚方案
|
||||
|
||||
## 实践操作
|
||||
- 制定备份策略
|
||||
- 配置监控系统
|
||||
- 进行版本升级
|
||||
|
||||
---
|
||||
|
||||
# Redis 生态工具
|
||||
|
||||
## 客户端工具
|
||||
- Redis CLI
|
||||
- 图形化管理工具
|
||||
- 编程语言客户端
|
||||
- 连接池工具
|
||||
|
||||
## 集群管理工具
|
||||
- Redis Cluster Manager
|
||||
- 第三方集群工具
|
||||
- 自动化部署工具
|
||||
|
||||
## 监控工具
|
||||
- Redis Monitor
|
||||
- Prometheus + Grafana
|
||||
- 第三方监控方案
|
||||
|
||||
## 实践操作
|
||||
- 使用管理工具
|
||||
- 集成开发框架
|
||||
- 配置监控方案
|
Reference in New Issue
Block a user