08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

1170
数据库/DQL查询练习.md Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

801
数据库/Memcached.md Normal file
View File

@@ -0,0 +1,801 @@
# Memcached分布式缓存系统
# Memcached介绍
## 什么是Memcached缓存数据库
**Memcached是一个自由开源的高性能分布式内存对象缓存系统。**
Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。
Memcached是一种基于**内存的key-value存储**,用来存储**小**块的任意数据字符串、对象。这些数据可以是数据库调用、API调用或者是页面渲染的结果。
Memcached简洁而强大。它的简洁设计便于快速开发减轻开发难度解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。
本质上它是一个简洁的key-value存储系统。
一般的使用目的是通过缓存数据库查询结果减少数据库访问次数以提高动态Web应用的速度、提高可扩展性。
<img src="Memcached/web_6.jpg" alt="web_6" />
Memcached 官网https://memcached.org
## Memcached和Redis对比
我们都知道把一些热数据存到缓存中可以极大的提高速度那么问题来了是用Redis好还是Memcached好呢以下是它们两者之间一些简单的区别与比较
1. Redis不仅支持简单的k/v类型的数据同时还支持list、set、zset(sorted set)、hash等**丰富数据结构**的存储,使得它拥有更广阔的应用场景。
2. Redis最大的亮点是支持**数据持久化**它在运行的时候可以将数据备份在磁盘中断电或重启后缓存数据可以再次加载到内存中只要Redis配置的合理基本上不会丢失数据。
3. Redis支持**主从模式**的应用。
4. Redis单个value的最大限制是1GB而Memcached则只能保存**1MB**内的数据。
5. Memcache在并发场景下能用cas保证一致性而Redis事务支持比较弱只能保证事务中的每个操作连续执行。
6. 性能方面,根据网友提供的测试,**Redis**在读操作和写操作上是略**领先**Memcached的。
7. Memcached的内存管理不像Redis那么复杂元数据metadata更小相对来说额外**开销就很少**。Memcached唯一支持的数据类型是**字符串string**,非常适合缓存只读数据,因为字符串不需要额外的处理。
# 快速开始
## Memcached部署
1. 通过yum安装memcache软件包
```bash
# 先安装一些依赖包
[root@localhost ~]# yum install libevent libevent-devel -y
# 安装memcached
[root@localhost ~]# yum install -y memcached
```
2. 命令行启动测试
```bash
[root@localhost ~]# memcached -d -m 1024 -u memcached -l 127.0.0.1 -p 11211 -c 1024 -P /tmp/memcached.pid
```
- memcached启动参数说明
- -d是启动一个守护进程
- -m是分配给Memcache使用的内存数量单位是MB
- -u是运行Memcache的用户
- -l是监听的服务器IP地址可以有多个地址
- -p是设置Memcache监听的端口最好是1024以上的端口
- -c是最大运行的并发连接数默认是1024
- -P是设置保存Memcache的pid文件。
3. 查看端口号是否启动成功
```bash
[root@localhost ~]# ss -nlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 *:11211 *:*
LISTEN 0 128 *:22 *:*
LISTEN 0 100 127.0.0.1:25 *:*
LISTEN 0 128 :::11211 :::*
LISTEN 0 128 :::22 :::*
LISTEN 0 100 ::1:25 :::*
```
memcached的端口号为**11211**
4. 关闭服务
```bash
[root@localhost ~]# pkill memcached
```
5. 服务自启动(systemd)
```bash
[root@localhost ~]# systemctl start memcached
```
## Memcached连接
对于Memcached的连接这里要使用到一个协议telnet远程连接协议
Telnet 是一种用于远程访问和控制计算机系统的网络协议。它允许用户从一台计算机连接到另一台计算机,并在远程系统上执行命令和操作。
**Telnet 的主要特点如下:**
1. 远程访问:Telnet 协议可以让用户远程登录到其他计算机系统,就像直接在那台机器上工作一样。这对于管理远程服务器或设备非常有用。
2. 文本界面:Telnet 使用纯文本界面进行交互,不需要图形界面。这使它适用于各种类型的终端设备和操作系统。
3. 简单易用:Telnet 客户端通常内置在操作系统中,或者可以很容易地下载安装。建立连接只需输入远程主机的 IP 地址或主机名即可。
4. 功能丰富:Telnet 协议支持各种命令和操作,如文件传输、远程执行命令等。用户可以在 Telnet 会话中执行系统管理任务。
5. 不安全:Telnet 使用明文传输数据,包括用户名和密码,存在严重的安全隐患。因此在现代网络环境下,Telnet 逐步被更安全的 SSH 协议所取代。
**Telnet和ssh对比**
| 特性 | Telnet | SSH |
| :------- | :---------------------------------------- | :------------------------------------------ |
| 安全性 | 使用明文传输数据,非常不安全 | 使用强大的加密技术,提供高度安全性 |
| 加密 | 不提供任何加密功能 | 支持多种加密算法,如 AES、3DES 等 |
| 认证 | 仅依赖用户名和密码进行身份验证 | 支持多种认证方式,如用户名/密码、公钥/私钥等 |
| 端口 | 默认使用 23 号端口 | 默认使用 22 号端口 |
| 应用场景 | 用于简单的远程控制和诊断任务,但已很少使用 | 广泛用于企业和个人的远程管理和数据传输 |
```bash
[root@localhost ~]# yum install telnet -y
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
# 连接以后并不会给出明确提示,我们可以直接输入命令进行测试
```
## 查看Memcached信息
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
```bash
[root@localhost ~]# telnet localhost 11211
Trying ::1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 16509
STAT uptime 8793
STAT time 1723623164
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.219841
STAT rusage_system 0.109920
STAT curr_connections 10
STAT total_connections 12
STAT connection_structures 11
STAT reserved_fds 20
STAT cmd_get 8
STAT cmd_set 2
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 1
STAT get_misses 7
STAT delete_misses 0
STAT delete_hits 2
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 222
STAT bytes_written 1197
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 0
STAT curr_items 0
STAT total_items 2
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END
```
如果可以看到以上输出,说明安装并且连接成功,至于具体解释,后面再说....
# slab存储机制
memcached接收来此客户端的存储请求那么服务端是如何存储来自客户端的存储内容的呢这里就涉及到了slabs存储机制在此之前首先介绍memcached中关于内存管理的几个重要的概念
## item数据存储节点
items客户端传送的键-值包装成items结构体其内存通过slab分配
源码说明:
```c
typedef struct _stritem {
/* Protected by LRU locks */
//一个item的地址, 主要用于LRU链和freelist链
struct _stritem *next;
//下一个item的地址,主要用于LRU链和freelist链
struct _stritem *prev;
/* Rest are protected by an item lock */
//用于记录哈希表槽中下一个item节点的地址
struct _stritem *h_next; /* hash chain next */
//最近访问时间
rel_time_t time; /* least recent access */
//缓存过期时间
rel_time_t exptime; /* expire time */
int nbytes; /* size of data */
//当前item被引用的次数用于判断item是否被其它的线程在操作中
//refcount == 1的情况下该节点才可以被删除
unsigned short refcount;
uint8_t nsuffix; /* length of flags-and-length string */
uint8_t it_flags; /* ITEM_* above */
//记录该item节点位于哪个slabclass_t中
uint8_t slabs_clsid;/* which slab class we're in */
uint8_t nkey; /* key length, w/terminating null and padding */
/* this odd type prevents type-punning issues when we do
* the little shuffle to save space when not using CAS. */
union {
uint64_t cas;
char end;
} data[];
/* if it_flags & ITEM_CAS we have 8 bytes CAS */
/* then null-terminated key */
/* then " flags length\r\n" (no terminating null) */
/* then data with terminating \r\n (no terminating null; it's binary!) */
} item;
```
## slab与chunk
slab是一块内存空间默认大小为1Mmemcached会把一个slab分割成一个个chunk, 这些被切割的小的内存块主要用来存储item
## slabclass
每个item的大小都可能不一样item存储于chunk,如果chunk大小不够则不足以分配给item使用如果chunk过大则太过于浪费内存空间。因此memcached采取的做法是将slab切割成不同大小的chunk这样就满足了不同大小item的存储。被划分不同大小chunk的slab的内存在memcached就是用slabclass这个结构体来表现的
**slabclass结构体源码**
```c
typedef struct {
//chunk大小
unsigned int size; /* sizes of items */
//1M内存大小被分割为多少个chunck
unsigned int perslab; /* how many items per slab */
//空闲chunk链表
void *slots; /* list of item ptrs */
//空闲chunk的个数
unsigned int sl_curr; /* total free items in list */
//当前slabclass已经分配了所少个1M空间的slab
unsigned int slabs; /* how many slabs were allocated for this class */
//slab指针数组
void **slab_list; /* array of slab pointers */
//slab指针数组的大小
unsigned int list_size; /* size of prev array */
size_t requested; /* The number of requested bytes */
} slabclass_t;
```
1. slabclass数组初始化的时候每个slabclass_t都会分配一个1M大小的slabslab会被切分为N个小的内存块这个小的内存块的大小取决于slabclass_t结构上的size的大小
2. 每个slabclass_t都只存储一定大小范围的数据并且下一个slabclass切割的chunk块大于前一个slabclass切割的chunk块大小
3. memcached中slabclass数组默认大小为64slabclass切割块大小的增长因子默认是1.25
例如slabclass[1]切割的chunk块大小为100字节slabclass[2]为125如果需要存储一个110字节的缓存那么就需要到slabclass[2] 的空闲链表中获取一个空闲节点进行存储
## item节点分配流程
1. 根据大小找到合适的slabclass
2. slabclass空闲列表中是否有空闲的item节点如果有直接分配item用于存储内容
3. 空闲列表没有空闲的item可以分配会重新开辟一个slab(默认大小为1M)的内存块然后切割slab并放入到空闲列表中然后从空闲列表中获取节点
## item节点的释放
释放一个item节点并不会free内存空间而是将item节点归还到slabclass的空闲列表中
# Memcached常用操作
## 数据存储
### set命令
语法:
```bash
set key flags exptime bytes [noreply]
value
```
相关参数说明:
- **key**键值 key-value 结构中的 key用于查找缓存值。
- **flags**flags 是一个整型参数,用于存储关于键值对的额外信息。这些信息可以被客户端用来解释或处理存储的数据。通过 flags,客户端可以在存储数据时为该数据打上一些标记,以便后续处理时能够正确识别数据的类型或属性。
- **exptime**在缓存中保存键值对的时间长度以秒为单位0 表示永远)
- **bytes**:在缓存中存储的字节数
- **noreply可选** 该参数告知服务器不需要返回数据
- **value**存储的值始终位于第二行可直接理解为key-value结构中的value
示例:
```bash
set name 0 0 8
zhangsan
STORED
get name
VALUE name 0 8
zhangsan
END
```
### add命令
Memcached add 命令用于将 **value(数据值)** 存储在**不存在的** **key(键)** 中。
如果 add 的 key 已经存在,则不会更新数据(过期的 key 会更新),之前的值将仍然保持相同,并且您将获得响应 **NOT_STORED**
语法:
```bash
add key flags exptime bytes [noreply]
value
```
示例:
```bash
add name 0 0 10
zhangsan
NOT_STORED
get name
VALUE name 0 8
zhangsan
END
# 加入一个不存在的key就可以成功
add age 1 0 10
18
STORED
get age
VALUE age 1 10
18
END
```
### replace命令
Memcached replace 命令用于替换已存在的 **key(键)****value(数据值)**
如果 key 不存在,则替换失败,并且您将获得响应 **NOT_STORED**
语法:
```bash
replace key flags exptime bytes [noreply]
value
```
示例:
```bash
replace name 0 900 8
lisilisi
replace gender 0 900 4
male
```
### append命令
Memcached append 命令用于向已存在 **key(键)****value(数据值)** 后面追加数据 。
示例:
```bash
get key1
END
set key1 0 900 9
memcached
STORED
get key1
VALUE key1 0 9
memcached
END
append key1 0 900 5
redis
STORED
get key1
VALUE key1 0 14
memcachedredis
END
```
### prepend命令
Memcached prepend 命令用于向已存在 **key(键)****value(数据值)** 前面追加数据 。
语法:
```bash
prepend key flags exptime bytes [noreply]
value
```
示例:
```bash
prepend key1 0 900 5
hello
STORED
get key1
VALUE key1 0 19
hellomemcachedredis
END
```
## CAS命令
CAS (Check-And-Set) 是 Memcached 中一个非常有用的原子操作特性。它可以帮助我们解决多客户端并发更新同一数据的问题。
### CAS 命令格式
CAS 命令的完整格式为:
复制
```
cas key flags exptime bytes casunique
```
其中:
- `key`: 缓存数据的键
- `flags`: 缓存数据的标志位
- `exptime`: 缓存数据的过期时间(单位为秒)
- `bytes`: 缓存数据的长度
- `casunique`: 一个唯一标识符,用于检查值是否被修改过
### CAS 操作流程
CAS 操作的流程如下:
1. 客户端先使用 `get` 命令获取某个 key 的值,并记录下返回的 `casunique`
2. 客户端准备更新这个值时,会使用 `cas` 命令,并附带之前获取的 `casunique`
3. Memcached 服务器收到 `cas` 命令后,会先检查当前值的 `casunique` 是否与客户端传来的一致。
4. 如果一致,说明这个值自从客户端获取后就没有被其他人修改过,服务器会接受这次更新。
5. 如果不一致,说明这个值在客户端获取后已经被其他人修改过了,服务器会拒绝这次更新。
### CAS 的应用场景
CAS 命令主要用于解决多客户端并发更新同一缓存数据的问题,避免出现"丢失更新"的情况。
例如,在一个电商网站上,多个用户可能同时操作同一个购物车。这时就可以使用 CAS 来确保只有最后一个更新成功的客户端的修改生效。
假设我们有一个电商网站,需要缓存用户的购物车信息。多个用户可能同时操作同一个购物车,此时就需要使用 CAS 来避免"丢失更新"的问题。
**案例流程如下:**
1. 用户 A 访问网站,获取自己的购物车信息:
- 使用 `get` 命令从 Memcached 中获取购物车数据
- 同时记录下返回的 `casunique`
2. 用户 A 添加一件商品到购物车:
- 使用 `cas` 命令更新购物车数据
- 同时带上之前获取的 `casunique`
3. 与此同时,用户 B 也访问网站,获取自己的购物车信息:
- 同样使用 `get` 命令从 Memcached 中获取购物车数据
- 记录下返回的 `casunique`
4. 用户 B 也想修改购物车中的商品:
- 使用 `cas` 命令尝试更新购物车数据
- 但此时 Memcached 检查发现 `casunique` 已经不一致了
- 因此拒绝了用户 B 的更新请求
5. 最终只有用户 A 的更新生效,用户 B 的更新被拒绝。
**示例:**
```shell
set name 0 0 3
nls
STORED
get name
VALUE name 0 3
nls
END
gets name
VALUE name 0 3 12
nls
END
cas name 0 0 3 12
xls
STORED
get name
VALUE name 0 3
xls
END
cas name 0 0 2 12
cs
EXISTS
```
输出信息说明:
- **STORED**:保存成功后输出。
- **ERROR**:保存出错或语法错误。
- **EXISTS**:在最后一次取值后另外一个用户也在更新该数据。
- **NOT_FOUND**Memcached 服务上不存在该键值。
## 数据查找
### get命令
get 命令的基本语法格式如下:
```shell
get key
```
多个 key 使用空格隔开,如下:
```shell
get key1 key2 key3
```
参数说明如下:
- **key**键值 key-value 结构中的 key用于查找缓存值。
### gets命令
Memcached gets 命令获取带有 CAS 令牌存 的 **value(数据值)** ,如果 key 不存在,则返回空。不带的也可以正常获取
语法
gets 命令的基本语法格式如下:
```shell
gets key
```
多个 key 使用空格隔开,如下:
```shell
gets key1 key2 key3
```
参数说明如下:
- **key**键值 key-value 结构中的 key用于查找缓存值。
### delete命令
Memcached delete 命令用于删除已存在的 key(键)。
语法
delete 命令的基本语法格式如下:
```
delete key [noreply]
```
参数说明如下:
- **key**键值 key-value 结构中的 key用于查找缓存值。
- **noreply可选** 该参数告知服务器不需要返回数据
输出信息说明:
- **DELETED**:删除成功。
- **ERROR**:语法错误或删除失败。
- **NOT_FOUND**key 不存在。
## 统计命令
### stat命令
Memcached stats 命令用于返回统计信息例如 PID(进程号)、版本号、连接数等。
```shell
stats
```
```shell
stats
STAT pid 1162
STAT uptime 5022
STAT time 1415208270
STAT version 1.4.14
STAT libevent 2.0.19-stable
STAT pointer_size 64
STAT rusage_user 0.096006
STAT rusage_system 0.152009
STAT curr_connections 5
STAT total_connections 6
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 6
STAT cmd_set 4
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 4
STAT get_misses 2
STAT delete_misses 1
STAT delete_hits 1
STAT incr_misses 2
STAT incr_hits 1
STAT decr_misses 0
STAT decr_hits 1
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 262
STAT bytes_written 313
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT expired_unfetched 1
STAT evicted_unfetched 0
STAT bytes 142
STAT curr_items 2
STAT total_items 6
STAT evictions 0
STAT reclaimed 1
END
```
这里显示了很多状态信息,下边详细解释每个状态项:
- **pid** memcache服务器进程ID
- **uptime**:服务器已运行秒数
- **time**服务器当前Unix时间戳
- **version**memcache版本
- **pointer_size**:操作系统指针大小
- **rusage_user**:进程累计用户时间
- **rusage_system**:进程累计系统时间
- **curr_connections**:当前连接数量
- **total_connections**Memcached运行以来连接总数
- **connection_structures**Memcached分配的连接结构数量
- **cmd_get**get命令请求次数
- **cmd_set**set命令请求次数
- **cmd_flush**flush命令请求次数
- **get_hits**get命令命中次数
- **get_misses**get命令未命中次数
- **delete_misses**delete命令未命中次数
- **delete_hits**delete命令命中次数
- **incr_misses**incr命令未命中次数
- **incr_hits**incr命令命中次数
- **decr_misses**decr命令未命中次数
- **decr_hits**decr命令命中次数
- **cas_misses**cas命令未命中次数
- **cas_hits**cas命令命中次数
- **cas_badval**:使用擦拭次数
- **auth_cmds**:认证命令处理的次数
- **auth_errors**:认证失败数目
- **bytes_read**:读取总字节数
- **bytes_written**:发送总字节数
- **limit_maxbytes**:分配的内存总大小(字节)
- **accepting_conns**服务器是否达到过最大连接0/1
- **listen_disabled_num**:失效的监听数
- **threads**:当前线程数
- **conn_yields**:连接操作主动放弃数目
- **bytes**:当前存储占用的字节数
- **curr_items**:当前存储的数据总数
- **total_items**:启动以来存储的数据总数
- **evictions**LRU释放的对象数目
- **reclaimed**:已过期的数据条目来存储新数据的数目
### stats items
Memcached stats items 命令用于显示各个 slab 中 item 的数目和存储时长(最后一次访问距离现在的秒数)。
语法
```shell
stats items
```
示例
```shell
stats items
STAT items:1:number 1
STAT items:1:age 7
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0
END
```
### stats slab
Memcached stats slabs 命令用于显示各个slab的信息包括chunk的大小、数目、使用情况等。
```shell
stats slabs
```
示例
```shell
stats slabs
STAT 1:chunk_size 96
STAT 1:chunks_per_page 10922
STAT 1:total_pages 1
STAT 1:total_chunks 10922
STAT 1:used_chunks 1
STAT 1:free_chunks 10921
STAT 1:free_chunks_end 0
STAT 1:mem_requested 71
STAT 1:get_hits 0
STAT 1:cmd_set 1
STAT 1:delete_hits 0
STAT 1:incr_hits 0
STAT 1:decr_hits 0
STAT 1:cas_hits 0
STAT 1:cas_badval 0
STAT 1:touch_hits 0
STAT active_slabs 1
STAT total_malloced 1048512
END
```
### stats sizes
Memcached stats sizes 命令用于显示所有item的大小和个数。
该信息返回两列,第一列是 item 的大小,第二列是 item 的个数。
语法stats sizes 命令的基本语法格式如下:
```shell
stats sizes
```
实例:
```shell
stats sizes
STAT 96 1
END
```
### flush_all命令
Memcached flush_all 命令用于清理缓存中的所有 **key=>value(键=>值)** 对。
该命令提供了一个可选参数 **time**,用于在制定的时间后执行清理缓存操作。
语法:
flush_all 命令的基本语法格式如下:
```shell
flush_all [time] [noreply]
```
实例
清理缓存:
```shell
set runoob 0 900 9
memcached
STORED
get runoob
VALUE runoob 0 9
memcached
END
flush_all
OK
get runoob
END
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

2376
数据库/MongoDB.md Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,3 @@
[MongoDB官网地址](https://www.mongodb.com/)
[MongoDB项目地址](https://github.com/mongodb/mongo)

View File

@@ -0,0 +1,210 @@
# 分片Sharding
当数据量增长到单个副本集无法承载或者写操作的吞吐量达到单台主节点的极限时就需要通过分片Sharding来进行水平扩展。本章节将深入探讨 MongoDB 分片集群的架构、核心组件、分片键的选择策略以及如何部署和管理一个分片集群。
---
## 分片概述
### 什么是分片?
分片是一种将大型数据集水平分区到多个服务器或分片上的数据库架构模式。每个分片都是一个独立的副本集存储着整个数据集的一部分。通过分片MongoDB 可以将读写负载分布到多个服务器上,从而实现近乎无限的水平扩展能力。
### 为什么需要分片?
1. **存储容量扩展**: 当数据量超过单台服务器的磁盘容量时,可以通过增加分片来扩展存储空间。
2. **读写吞吐量提升**: 通过将负载分布到多个分片,可以显著提高整个集群的读写处理能力。
3. **高可用性**: 分片集群的每个分片本身就是一个副本集,因此继承了副本集的高可用性特性。
---
## 分片集群架构
一个 MongoDB 分片集群由以下三个核心组件构成:
1. **分片 (Shard)**
- **作用**: 存储数据的单元。每个分片都是一个独立的 MongoDB 副本集,以保证其高可用性。
- **职责**: 存储集合数据的一个子集Chunk
2. **查询路由 (Query Router / `mongos`)**
- **作用**: 客户端的入口。`mongos` 是一个轻量级的无状态进程,它接收客户端的请求,并将其路由到正确的分片上。
- **职责**: 从配置服务器获取元数据,根据分片键将查询路由到目标分片,并聚合来自多个分片的结果返回给客户端。
3. **配置服务器 (Config Server)**
- **作用**: 存储集群的元数据。这些元数据包含了数据在各个分片上的分布情况(哪个 Chunk 在哪个 Shard
- **职责**: 管理集群的配置信息。从 MongoDB 3.4 开始配置服务器必须部署为副本集CSRS以保证其高可用性。
![Sharded Cluster Architecture](https://docs.mongodb.com/manual/images/sharded-cluster-production-architecture.bakedsvg.svg)
---
## 分片键Shard Key
分片键是决定数据如何在各个分片之间分布的关键。选择一个好的分片键至关重要,它直接影响到分片集群的性能和效率。
### 分片键的选择策略
一个理想的分片键应该具备以下特征:
- **高基数 (High Cardinality)**: 分片键应该有大量可能的值,以便将数据均匀地分布到多个 Chunk 中。
- **低频率 (Low Frequency)**: 分片键的值应该被均匀地访问避免出现热点数据Hot Spot
- **非单调变化 (Non-Monotonic)**: 分片键的值不应随时间单调递增或递减,这会导致所有的写操作都集中在最后一个分片上。
### 分片策略
1. **范围分片 (Ranged Sharding)**
- **描述**: 根据分片键的范围将数据分成不同的块Chunk
- **优点**: 对于基于范围的查询(如 `find({ x: { $gt: 10, $lt: 20 } })`)非常高效,因为 `mongos` 可以直接将查询路由到存储该范围数据的分片。
- **缺点**: 如果分片键是单调变化的(如时间戳),容易导致写操作集中在单个分片上。
2. **哈希分片 (Hashed Sharding)**
- **描述**: 计算分片键的哈希值,并根据哈希值的范围来分片。
- **优点**: 能够将数据在各个分片之间均匀分布,保证了写操作的负载均衡。
- **缺点**: 对于范围查询不友好,因为相邻的分片键值可能被哈希到不同的分片上,导致查询需要广播到所有分片。
3. **标签感知分片 (Tag Aware Sharding)**
- **描述**: 允许管理员通过标签Tag将特定范围的数据块Chunk分配到特定的分片上。例如可以将美国用户的数据放在位于美国的服务器上以降低延迟。
---
## Chunks 和 Balancer
### 数据块 (Chunk)
- Chunk 是分片集合中一段连续的数据范围基于分片键。MongoDB 会试图保持 Chunk 的大小在一个可配置的范围内(默认为 64MB
- 当一个 Chunk 的大小超过配置值时,它会分裂成两个更小的 Chunk。
### 均衡器 (Balancer)
- Balancer 是一个后台进程,它负责在各个分片之间迁移 Chunk以确保数据在整个集群中均匀分布。
- 当某个分片的 Chunk 数量远多于其他分片时Balancer 会自动启动,并将一些 Chunk 从最拥挤的分片迁移到最空闲的分片。
- 均衡过程会消耗 I/O 和网络资源,可以在业务高峰期临时禁用 Balancer。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 分片集群,模拟电商平台的订单数据存储场景。该场景需要处理大量的订单数据,要求系统具备高可用性和水平扩展能力。通过实际操作来理解分片集群的部署、配置和管理过程。
### 实践细节和结果验证
```shell
# 1. 创建数据目录
mkdir -p /data/mongodb-sharding/{config1,config2,config3,shard1,shard2,mongos}
# 2. 启动配置服务器副本集 (CSRS)
# 启动三个配置服务器实例
mongod --configsvr --replSet configReplSet --port 27019 --dbpath /data/mongodb-sharding/config1 --fork --logpath /data/mongodb-sharding/config1.log
mongod --configsvr --replSet configReplSet --port 27020 --dbpath /data/mongodb-sharding/config2 --fork --logpath /data/mongodb-sharding/config2.log
mongod --configsvr --replSet configReplSet --port 27021 --dbpath /data/mongodb-sharding/config3 --fork --logpath /data/mongodb-sharding/config3.log
# 连接到配置服务器并初始化副本集
mongosh --port 27019
# 在 mongosh shell 中执行:
rs.initiate({
_id: "configReplSet",
configsvr: true,
members: [
{ _id: 0, host: "localhost:27019" },
{ _id: 1, host: "localhost:27020" },
{ _id: 2, host: "localhost:27021" }
]
})
# 验证配置服务器状态
rs.status()
# 预期结果:显示三个配置服务器节点,其中一个为 PRIMARY两个为 SECONDARY
# 3. 启动分片副本集
# 启动第一个分片
mongod --shardsvr --replSet shard1ReplSet --port 27022 --dbpath /data/mongodb-sharding/shard1 --fork --logpath /data/mongodb-sharding/shard1.log
# 启动第二个分片
mongod --shardsvr --replSet shard2ReplSet --port 27023 --dbpath /data/mongodb-sharding/shard2 --fork --logpath /data/mongodb-sharding/shard2.log
# 初始化分片副本集
mongosh --port 27022
# 在 mongosh shell 中执行:
rs.initiate({
_id: "shard1ReplSet",
members: [{ _id: 0, host: "localhost:27022" }]
})
mongosh --port 27023
# 在 mongosh shell 中执行:
rs.initiate({
_id: "shard2ReplSet",
members: [{ _id: 0, host: "localhost:27023" }]
})
# 4. 启动 mongos 查询路由
mongos --configdb configReplSet/localhost:27019,localhost:27020,localhost:27021 --port 27017 --fork --logpath /data/mongodb-sharding/mongos.log
# 5. 连接到 mongos 并添加分片
mongosh --port 27017
# 在 mongosh shell 中执行:
sh.addShard("shard1ReplSet/localhost:27022")
sh.addShard("shard2ReplSet/localhost:27023")
# 验证分片状态
sh.status()
# 预期结果:显示两个分片已成功添加到集群中
# 6. 为数据库和集合启用分片
# 启用数据库分片
sh.enableSharding("ecommerce")
# 为订单集合创建分片键并启用分片
sh.shardCollection("ecommerce.orders", { "customerId": 1 })
# 为订单集合创建分片键:哈希策略
# sh.shardCollection("ecommerce.orders", {"customerId": "hashed"})
# 设置新的 chunk 大小单位MB
db.settings.updateOne(
{ _id: "chunksize" },
{ $set: { value: 1 } },
{ upsert: true }
)
# 7. 插入测试数据
use ecommerce
for (let i = 1; i <= 100000; i++) {
db.orders.insertOne({
customerId: Math.floor(Math.random() * 1000) + 1,
orderDate: new Date(2024, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
amount: Math.random() * 1000,
products: ["product" + (Math.floor(Math.random() * 100) + 1)]
})
}
# 8. 等待3min后观察数据分布和均衡过程
## partitioned: false 新版本已默认开启,可以忽略
sh.status()
# 预期结果:显示数据已分布到不同的分片上,可以看到 chunks 的分布情况
# 查看集合的分片信息
db.orders.getShardDistribution()
# 预期结果:显示每个分片上的文档数量和数据大小
sh.getShardedDataDistribution()
# 预期结果:显示每个分片上的数据库和集合的分布情况
# 9. 测试分片键查询性能
db.orders.find({customerId: 123}).explain("executionStats")
# 预期结果:查询只会路由到包含该 customerId 数据的特定分片
# 加速迁移
use config
db.settings.update(
{ "_id": "balancer" },
{ $set:
{
"_waitForDelete": false,
"_secondaryThrottle": false,
"writeConcern": { "w": "1" }
}
},
{ upsert: true }
)
```

View File

@@ -0,0 +1,188 @@
# 副本集Replica Set
为了提供高可用性和数据冗余MongoDB 使用副本集Replica Set的机制。本章节将详细介绍副本集的概念、架构、工作原理以及如何配置和管理一个副本集确保数据库服务能够抵御单点故障。
---
## 副本集概述
### 什么是副本集?
副本集是一组维护相同数据集的 `mongod` 进程。它由一个**主节点 (Primary)** 和多个**从节点 (Secondary)** 组成,共同保证了数据的冗余和高可用性。
- **主节点 (Primary)**: 接收所有的写操作。一个副本集在任何时候最多只能有一个主节点。
- **从节点 (Secondary)**: 从主节点异步复制数据。从节点可以接受读操作,从而分担主节点的读负载。
### 副本集的目标
1. **数据冗余 (Data Redundancy)**: 数据在多个服务器上有副本,防止因单台服务器硬件故障导致的数据丢失。
2. **高可用性 (High Availability)**: 当主节点发生故障时副本集会自动进行故障转移Failover从剩下的从节点中选举出一个新的主节点从而保证服务的持续可用。
3. **读写分离 (Read Scaling)**: 客户端可以将读请求路由到从节点,从而分散读负载,提高读取性能。
---
## 副本集架构与工作原理
### 副本集成员
一个典型的副本集包含以下成员:
- **主节点 (Primary)**: 唯一的写操作入口。
- **从节点 (Secondary)**: 复制主节点的数据,可以处理读请求。从节点也可以被配置为:
- **优先级为 0 的成员 (Priority 0 Member)**: 不能被选举为主节点,适合用于备份或离线分析。
- **隐藏成员 (Hidden Member)**: 对客户端不可见,不能处理读请求,通常用于备份。
- **延迟成员 (Delayed Member)**: 数据会比主节点延迟一段时间,可用于恢复误操作的数据。
- **仲裁者 (Arbiter)**: 只参与选举投票,不存储数据副本。它的存在是为了在成员数量为偶数时,打破平局,确保能够选举出主节点。
### 数据同步(复制)
- 主节点记录其所有的操作到一个特殊的 capped collection 中,称为 **oplog (operations log)**
- 从节点持续地从主节点的 oplog 中拉取新的操作,并在自己的数据集上应用这些操作,从而保持与主节点的数据同步。
- 这个过程是异步的,因此从节点的数据可能会有轻微的延迟。
### 选举过程Failover
当主节点在一定时间内(默认为 10 秒)无法与副本集中的其他成员通信时,会触发一次选举。
1. **触发选举**: 从节点发现主节点不可达。
2. **选举投票**: 剩下的健康成员会进行投票选举一个新的主节点。投票的依据包括成员的优先级、oplog 的新旧程度等。
3. **产生新的主节点**: 获得大多数票数(`(N/2) + 1`,其中 N 是副本集总成员数)的从节点会成为新的主节点。
4. **恢复同步**: 旧的主节点恢复后,会作为从节点重新加入副本集。
---
## 读写关注点Read and Write Concern
### 写关注点 (Write Concern)
写关注点决定了写操作在向客户端确认成功之前,需要被多少个副本集成员确认。
- **`w: 1` (默认)**: 写操作只需在主节点成功即可返回。
- **`w: "majority"`**: 写操作需要被大多数(`(N/2) + 1`)数据承载成员确认后才返回。这是推荐的设置,可以防止在故障转移期间发生数据回滚。
- **`j: true`**: 要求写操作在返回前写入到磁盘日志journal
### 读偏好 (Read Preference)
读偏好决定了客户端从哪个成员读取数据。
- **`primary` (默认)**: 只从主节点读取,保证数据最新。
- **`primaryPreferred`**: 优先从主节点读取,如果主节点不可用,则从从节点读取。
- **`secondary`**: 只从从节点读取,可以分担主节点负载,但可能读到稍有延迟的数据。
- **`secondaryPreferred`**: 优先从从节点读取,如果没有可用的从节点,则从主节点读取。
- **`nearest`**: 从网络延迟最低的成员读取,不关心是主节点还是从节点。
---
## 实践操作
### 需求描述
某电商公司的 MongoDB 数据库需要实现高可用性架构,要求:
1. 数据库服务 24/7 不间断运行
2. 单节点故障时自动故障转移
3. 支持读写分离以提升性能
4. 数据冗余备份防止数据丢失
我们需要搭建一个三成员副本集来满足这些需求,并验证其高可用性和读写分离功能。
### 实践细节和结果验证
```shell
# 1. 创建数据目录
mkdir -p /data/rs{1,2,3}
# 2. 启动三个 mongod 实例
# 实例 1 (Primary 候选)
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 实例 2 (Secondary 候选)
mongod --port 27028 --dbpath /data/rs2 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs2.log
# 实例 3 (Secondary 候选)
mongod --port 27029 --dbpath /data/rs3 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs3.log
# 3. 连接到第一个实例并初始化副本集
mongosh --port 27027
# 在 mongo shell 中执行以下命令
# 初始化副本集配置
config = {
_id: "myReplicaSet",
members: [
{ _id: 0, host: "localhost:27027", priority: 2 },
{ _id: 1, host: "localhost:27028", priority: 1 },
{ _id: 2, host: "localhost:27029", priority: 1 }
]
}
# 执行初始化
rs.initiate(config)
# 等待几秒后验证副本集状态
rs.status()
# 预期结果:显示一个 PRIMARY 节点和两个 SECONDARY 节点
# 4. 测试写操作(在主节点执行)
use testDB
db.products.insertOne({name: "iPhone 14", price: 999, stock: 100})
db.products.insertOne({name: "MacBook Pro", price: 2499, stock: 50})
# 验证写入成功
db.products.find()
# 预期结果:显示刚插入的两条记录
# 5. 测试读写分离
# 连接到副本集不设置读偏好,默认从主节点读取
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet"
# 数据来源一直为 27027
use testDB
myReplicaSet [primary] test> db.products.find().explain().serverInfo
# 连接到副本集并设置读偏好为从节点
mongosh "mongodb://localhost:27027,localhost:27028,localhost:27029/?replicaSet=myReplicaSet&readPreference=secondary"
# readPreference 仅控制查询路由,不改变连接目标,但数据来源为 27028/27029
use testDB
db.products.find().explain().serverInfo
# 6. 模拟故障转移测试
# 在另一个终端中找到主节点进程并停止
ps aux | grep "mongod.*27027"
kill <主节点进程ID>
# 回到 mongo shell 观察选举过程
rs.status()
# 等待 10-30 秒后再次检查
rs.status()
# 预期结果:原来的一个从节点变成新的主节点
# 例如 localhost:27028 变成 PRIMARY 状态
# 7. 验证故障转移后的读写功能
# 在新主节点上执行写操作
mongosh --port 27028
use testDB
db.products.insertOne({name: "iPad Air", price: 599, stock: 75})
# 验证数据写入成功
db.products.find()
# 预期结果:显示包括新插入记录在内的所有数据
# 8. 恢复故障节点
# 重新启动之前停止的节点
mongod --port 27027 --dbpath /data/rs1 --replSet myReplicaSet --fork --logpath /var/log/mongodb/rs1.log
# 验证节点重新加入副本集
rs.status()
# 预期结果localhost:27027 重新出现在成员列表中,状态为 SECONDARY因为 27027 节点优先级配置更高会主动出发选举并抢占主节点角色这是MongoDB副本集设计的正常行为
# 9. 验证数据同步
# 连接到恢复的节点
mongosh --port 27027
use testDB
db.getMongo().setReadPref("secondary")
db.products.find()
# 预期结果:显示所有数据,包括故障期间插入的记录,证明数据同步正常
# [扩展] 10. 性能测试 - 验证读写分离效果
```

View File

@@ -0,0 +1,161 @@
# 基础操作
本章节将引导大家学习 MongoDB 的基础操作,包括如何使用 MongoDB Shell、管理数据库和集合以及对文档进行核心的增删改查CRUD操作。掌握这些基础是进行更高级应用的前提。
---
## MongoDB Shell 使用
MongoDB Shell 是一个功能强大的交互式 JavaScript 接口,用于管理和操作 MongoDB 数据库。
- **连接数据库**:
打开终端,输入 `mongo``mongosh` 命令即可连接到本地默认的 MongoDB 实例 (mongodb://127.0.0.1:27017)。
```shell
mongosh "mongodb://<host>:<port>/<database>" -u <username> -p
```
- **基本命令**:
- `show dbs`: 显示所有数据库列表。
- `use <db_name>`: 切换到指定数据库,如果不存在则在首次插入数据时创建。
- `show collections`: 显示当前数据库中的所有集合。
- `db`: 显示当前所在的数据库。
- `db.stats()`: 显示当前数据库的状态信息。
- `exit` 或 `quit()`: 退出 Shell。
---
## 数据库操作
- **创建数据库**: 无需显式创建,当向一个不存在的数据库中的集合插入第一条数据时,该数据库会自动创建。
- **查看数据库**: `show dbs`
- **切换数据库**: `use myNewDB`
- **删除数据库**: 首先切换到要删除的数据库,然后执行 `db.dropDatabase()`。
---
## 集合操作
- **创建集合**:
- **隐式创建**: 当向一个不存在的集合插入第一条数据时,集合会自动创建。
- **显式创建**: 使用 `db.createCollection()` 方法,可以指定更多选项,如大小限制、验证规则等。
```javascript
db.createCollection("myCollection", { capped: true, size: 100000 })
```
- **查看集合**: `show collections`
- **删除集合**: `db.myCollection.drop()`
---
## 文档 CRUD 操作
CRUD 代表创建 (Create)、读取 (Read)、更新 (Update) 和删除 (Delete) 操作。
### 插入文档 (Create)
- **`insertOne()`**: 插入单个文档。
```javascript
db.inventory.insertOne({ item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } })
```
- **`insertMany()`**: 插入多个文档。
```javascript
db.inventory.insertMany([
{ item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
{ item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } }
])
```
### 查询文档 (Read)
- **`find()`**: 查询集合中所有匹配的文档。
```javascript
// 查询所有文档
db.inventory.find({})
// 查询 qty 大于 50 的文档
db.inventory.find({ qty: { $gt: 50 } })
```
- **`findOne()`**: 只返回匹配的第一个文档。
```javascript
db.inventory.findOne({ item: "journal" })
```
### 更新文档 (Update)
- **`updateOne()`**: 更新匹配的第一个文档。
```javascript
db.inventory.updateOne(
{ item: "journal" },
{ $set: { "size.uom": "in" }, $currentDate: { lastModified: true } }
)
```
- **`updateMany()`**: 更新所有匹配的文档。
```javascript
db.inventory.updateMany(
{ qty: { $gt: 50 } },
{ $set: { "size.uom": "in" }, $currentDate: { lastModified: true } }
)
```
- **`replaceOne()`**: 替换匹配的第一个文档。
### 删除文档 (Delete)
- **`deleteOne()`**: 删除匹配的第一个文档。
```javascript
db.inventory.deleteOne({ item: "journal" })
```
- **`deleteMany()`**: 删除所有匹配的文档。
```javascript
db.inventory.deleteMany({ qty: { $gt: 50 } })
```
---
## 实践操作
### 需求描述
1. **创建与切换**: 创建一个名为 `bookstore` 的数据库并切换过去。
2. **插入数据**: 在 `bookstore` 数据库中创建一个 `books` 集合,并批量插入至少 5 本书的数据,每本书包含 `title`, `author`, `published_year`, `genres` (数组), `stock` (库存) 字段。
3. **查询练习**:
- 查询所有库存量小于 10 本的书。
- 查询所有 `Science Fiction` 类型的书。
3. **更新练习**: 将指定一本书的库存量增加 5。
4. **删除练习**: 删除所有 `published_year` 在 1950 年之前的书。
5. **数据导入导出**: 使用 `mongoexport` 将 `books` 集合导出为 JSON 文件,然后使用 `mongoimport` 将其导入到一个新的集合 `books_backup` 中。
### 实践细节和结果验证
```javascript
// 1. 创建与切换数据库
use bookstore;
// 2. 插入数据
// 参考 data.js 文件中 Data for: MongoDB基础操作.md 下 books 集合的插入数据部分
// 3. 查询练习
// 查询所有库存量小于 10 本的书
db.books.find({ stock: { $lt: 10 } });
// 查询所有 Science Fiction 类型的书
db.books.find({ genres: "Science Fiction" });
// 4. 更新练习
// 将指定一本书的库存量增加 5
db.books.updateOne(
{ title: "Dune" },
{ $inc: { stock: 5 } }
);
// 5. 删除练习
// 删除所有 published_year 在 1950 年之前的书
db.books.deleteMany({ published_year: { $lt: 1950 } });
// 6. 数据导入导出
// 使用 mongoexport 将 books 集合导出为 JSON 文件
// mongoexport --db bookstore --collection books --out books.json --jsonArray
// 使用 mongoimport 将其导入到一个新的集合 books_backup 中
// mongoimport --db bookstore --collection books_backup --file books.json --jsonArray
```

View File

@@ -0,0 +1,113 @@
# 基础概念
本章节将介绍 NoSQL 数据库的基本概念,并深入探讨 MongoDB 的核心概念、架构原理,为后续的学习打下坚实的基础。
---
## NoSQL 数据库概述
NoSQLNot Only SQL泛指非关系型数据库它们在数据结构、可扩展性和事务模型上与传统的关系型数据库如 MySQL有显著不同。
### 关系型数据库 vs NoSQL 数据库
| 特性 | 关系型数据库 (RDBMS) | NoSQL 数据库 |
| :--- | :--- | :--- |
| **数据模型** | 基于表Table和行Row的结构化数据 | 多样化模型(文档、键值、列族、图) |
| **Schema** | 固定Schema-on-Write需预先定义表结构 | 动态Schema-on-Read数据结构灵活 |
| **扩展性** | 主要通过垂直扩展Scale-Up提升单机性能 | 主要通过水平扩展Scale-Out构建分布式集群 |
| **事务** | 遵循 ACID 原则,保证强一致性 | 通常遵循 BASE 原则,保证最终一致性 |
| **适用场景** | 事务性要求高、数据结构稳定的应用 | 大数据、高并发、需要快速迭代的应用 |
### NoSQL 数据库分类
NoSQL 数据库根据其数据模型的不同,主要分为以下几类:
1. **文档型数据库 (Document-Oriented)**
- **特点**:以文档(通常是 JSON 或 BSON 格式)为单位存储数据,结构灵活。
- **代表**MongoDB, CouchDB
2. **键值型数据库 (Key-Value)**
- **特点**:数据以简单的键值对形式存储,查询速度快。
- **代表**Redis, Memcached
3. **列族型数据库 (Column-Family)**
- **特点**:数据按列族存储,适合大规模数据聚合分析。
- **代表**Cassandra, HBase
4. **图形型数据库 (Graph)**
- **特点**:专注于节点和边的关系,适合社交网络、推荐系统等场景。
- **代表**Neo4j, ArangoDB
### MongoDB 在 NoSQL 生态中的定位
MongoDB 是文档型数据库的杰出代表,它凭借其灵活的数据模型、强大的查询语言和高可扩展性,在 NoSQL 生态中占据了重要地位。它特别适用于需要快速开发、数据结构多变、高并发读写的应用场景。
---
## 核心概念
理解 MongoDB 的核心概念是掌握其使用的第一步。
### 文档Document与 BSON 格式
- **文档 (Document)**:是 MongoDB 中数据的基本单元由一组键值对key-value组成类似于 JSON 对象。文档的结构是动态的,同一个集合中的文档可以有不同的字段。
```json
{
"_id": ObjectId("60c72b2f9b1d8b3b8c8b4567"),
"name": "Alice",
"age": 30,
"email": "alice@example.com",
"tags": ["mongodb", "database", "nosql"]
}
```
- **BSON (Binary JSON)**MongoDB 在内部使用 BSON 格式存储文档。BSON 是 JSON 的二进制表示形式,它支持更多的数据类型(如日期、二进制数据),并优化了存储空间和查询性能。
### 集合Collection概念
- **集合 (Collection)**:是 MongoDB 文档的容器可以看作是关系型数据库中的表Table。但与表不同集合不需要预先定义结构schema-less
### 数据库Database结构
- **数据库 (Database)**:是集合的物理容器。一个 MongoDB 实例可以承载多个数据库,每个数据库都有自己独立的权限和文件。
### MongoDB 与关系型数据库术语对比
| MongoDB | 关系型数据库 (RDBMS) |
| :--- | :--- |
| Database | Database |
| Collection | Table |
| Document | Row (or Record) |
| Field | Column (or Attribute) |
| Index | Index |
| Replica Set | Master-Slave Replication |
| Sharding | Partitioning |
---
## 架构原理
了解 MongoDB 的底层架构有助于我们更好地进行性能调优和故障排查。
### 存储引擎WiredTiger
WiredTiger 是 MongoDB 默认的存储引擎,它提供了文档级别的并发控制、数据压缩和快照等高级功能,是 MongoDB 高性能的关键。
### 内存映射文件系统
MongoDB 使用内存映射文件Memory-Mapped Files来处理数据。它将磁盘上的数据文件映射到内存中使得 MongoDB 可以像访问内存一样访问数据,从而将数据管理委托给操作系统的虚拟内存管理器,简化了代码并提升了性能。
### 索引机制
为了提高查询效率MongoDB 支持在任意字段上创建索引。与关系型数据库类似MongoDB 的索引也采用 B-Tree 数据结构,能够极大地加速数据检索过程。
### 查询优化器
MongoDB 内置了一个查询优化器,它会分析查询请求,并从多个可能的查询计划中选择一个最优的执行计划,以确保查询的高效性。
---
## 文档学习
- 阅读 MongoDB 官方文档中关于“核心概念”的部分。

View File

@@ -0,0 +1,215 @@
# MongoDB 备份与恢复
制定可靠的备份和恢复策略是数据库管理中至关重要的一环,它可以帮助在发生数据损坏、误操作或灾难性故障时恢复数据。本章节将介绍 MongoDB 常用的备份方法、恢复流程以及灾难恢复的最佳实践。
---
## 备份策略
选择哪种备份方法取决于具体需求,如恢复时间目标 (RTO)、恢复点目标 (RPO)、数据库规模以及部署架构(独立实例、副本集、分片集群)。
### 备份方法
1. **`mongodump``mongorestore`**
- **描述**: MongoDB 提供的官方命令行工具,用于创建数据库的 BSON 文件备份,并可以从这些文件恢复数据。
- **优点**: 简单易用,适合小型数据集或对备份窗口要求不高的场景。
- **缺点**: 备份期间可能会影响数据库性能。对于大型数据集,备份和恢复过程可能非常耗时。在恢复 `mongodump` 的备份时,它会重建索引,这会增加恢复时间。
2. **文件系统快照 (Filesystem Snapshots)**
- **描述**: 利用文件系统(如 LVM或云存储如 AWS EBS的快照功能对 MongoDB 的数据文件进行即时备份。
- **优点**: 备份速度快,对数据库性能影响小。恢复速度也很快,因为数据和索引都无需重建。
- **缺点**: 必须确保在创建快照时,数据处于一致性状态。这通常需要开启 journaling 并确保在快照前执行 `fsync` 和 lock 操作。
3. **MongoDB Cloud Manager / Ops Manager**
- **描述**: MongoDB 提供的企业级管理工具,提供持续的、时间点恢复的备份服务。
- **优点**: 提供图形化界面,管理方便。支持副本集和分片集群的持续备份和时间点恢复,可以恢复到任意时刻。
- **缺点**: 是商业服务,需要额外成本。
---
## 使用 `mongodump` 和 `mongorestore`
### `mongodump`
`mongodump` 可以导出整个数据库、特定数据库或特定集合。
```shell
# 备份整个 MongoDB 实例
mongodump --out /data/backup/`date +%F`
# 备份指定的数据库
mongodump --db myAppDB --out /data/backup/myAppDB
# 备份指定的集合
mongodump --db myAppDB --collection users --out /data/backup/users_collection
# 备份远程数据库并启用压缩
mongodump --host mongodb.example.com --port 27017 --username myUser --password myPass --authenticationDatabase admin --db myAppDB --gzip --out /data/backup/myAppDB.gz
```
### `mongorestore`
`mongorestore` 用于将 `mongodump` 创建的备份恢复到数据库中。
```shell
# 恢复整个实例的备份
mongorestore /data/backup/2023-10-27/
# 恢复指定的数据库备份
# mongorestore 会将数据恢复到备份时同名的数据库中
mongorestore --db myAppDB /data/backup/myAppDB/
# 恢复并删除现有数据
# 使用 --drop 选项可以在恢复前删除目标集合
mongorestore --db myAppDB --collection users --drop /data/backup/users_collection/users.bson
```
---
## 副本集和分片集群的备份
### 备份副本集
- **使用 `mongodump`**: 建议连接到一个从节点进行备份,以减少对主节点的影响。可以使用 `--oplog` 选项来创建一个包含 oplog 条目的时间点快照,这对于在恢复后与其他副本集成员同步非常重要。
```bash
mongodump --host secondary.example.com --oplog --out /data/backup/repl_set_backup
```
- **使用文件系统快照**: 可以在一个被停止(或锁定)的从节点上进行,以保证数据一致性。
### 备份分片集群
备份分片集群要复杂得多,因为必须保证整个集群的数据是一致的。
- **核心挑战**: 必须在同一时刻捕获所有分片和配置服务器的数据快照。
- **推荐方法**: **强烈建议使用 MongoDB Cloud Manager 或 Ops Manager** 来处理分片集群的备份,因为它们专门设计用于处理这种复杂性。
- **手动备份(不推荐,风险高)**:
1. 禁用 Balancer。
2. 对配置服务器进行 `mongodump`。
3. 对每个分片副本集进行 `mongodump`。
4. 重新启用 Balancer。
恢复过程同样复杂且容易出错。
---
## 恢复策略与灾难恢复
### 恢复误操作的数据
- **使用延迟从节点**: 如果配置了一个延迟从节点,可以停止它的复制,从其数据文件中恢复误删或误改的数据。
- **使用时间点恢复**: 如果使用 Cloud Manager / Ops Manager可以将数据库恢复到误操作发生前的任意时间点。
### 灾难恢复 (Disaster Recovery)
灾难恢复计划旨在应对整个数据中心或区域级别的故障。
- **异地备份**: 将备份数据存储在与生产数据中心物理位置不同的地方。
- **多数据中心部署**: 将副本集的成员分布在不同的地理位置。例如,一个主节点和从节点在主数据中心,另一个从节点在灾备数据中心。当主数据中心发生故障时,可以手动或自动将灾备中心的从节点提升为新的主节点。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 备份与恢复实践环境,掌握不同场景下的备份策略和恢复操作。通过实际操作理解备份工具的使用方法、副本集备份的最佳实践,以及制定符合业务需求的备份恢复策略。
### 实践细节和结果验证
```shell
# 1. 准备测试数据
# 创建测试数据库和集合
use myAppDB
db.users.insertMany([
{name: "Alice", age: 25, email: "alice@example.com"},
{name: "Bob", age: 30, email: "bob@example.com"},
{name: "Charlie", age: 35, email: "charlie@example.com"}
])
db.orders.insertMany([
{orderId: "ORD001", userId: "Alice", amount: 100.50, status: "completed"},
{orderId: "ORD002", userId: "Bob", amount: 250.75, status: "pending"},
{orderId: "ORD003", userId: "Charlie", amount: 89.99, status: "completed"}
])
# 验证数据插入
db.users.countDocuments() # 预期结果: 3
db.orders.countDocuments() # 预期结果: 3
# 2. 使用 mongodump 备份数据库
# 创建备份目录
mkdir -pv /data/mongodb/backup
# 备份整个 myAppDB 数据库
mongodump --db myAppDB --out /data/mongodb/backup/$(date +%F)/dbs/
# 验证备份文件
ls -la /data/mongodb/backup/$(date +%F)/dbs/
# 预期结果: 应该看到 users.bson, users.metadata.json, orders.bson, orders.metadata.json 等文件
# 备份指定集合(带压缩)
mongodump --db myAppDB --collection users --gzip --out /data/mongodb/backup/$(date +%F)/collections/
# 验证压缩备份
ls -la /data/mongodb/backup/$(date +%F)/users_backup/myAppDB/
# 预期结果: 应该看到 users.bson.gz 和 users.metadata.json.gz 文件
# 3. 模拟数据丢失并恢复
# 删除 users 集合模拟数据丢失
use myAppDB
db.users.drop()
# 验证数据已删除
db.users.countDocuments() # 预期结果: 0
show collections # 预期结果: 只显示 orders 集合
# 使用 mongorestore 恢复 users 集合
gzip /data/mongodb/backup/$(date +%F)/collections/myAppDB/users.bson.gz -d /data/mongodb/backup/$(date +%F)/collections/myAppDB/
mongorestore --db myAppDB --collection users /data/mongodb/backup/$(date +%F)/collections/myAppDB/users.bson
# 验证数据恢复
db.users.countDocuments() # 预期结果: 3
db.users.find().pretty() # 预期结果: 显示所有用户数据
# 4. 完整数据库恢复测试
# 删除整个数据库
use myAppDB
db.dropDatabase()
# 验证数据库已删除
show dbs # 预期结果: myAppDB 不在列表中
# 恢复整个数据库
mongorestore /data/mongodb/backup/$(date +%F)/dbs/myAppDB/
# 验证数据库恢复
use myAppDB
show collections # 预期结果: 显示 users 和 orders 集合
db.users.countDocuments() # 预期结果: 3
db.orders.countDocuments() # 预期结果: 3
# 5. 副本集备份实践(如果已配置副本集)
# 连接到副本集的从节点进行备份
mongodump --host secondary.example.com:27017 --oplog --out /data/backup/replica_backup
# 验证副本集备份
ls -la /data/backup/replica_backup/
# 预期结果: 应该看到各个数据库目录和 oplog.bson 文件
# 6.[扩展] 制定生产环境备份策略
# 电商订单数据库备份策略设计
# 业务需求分析:
# - RTO (恢复时间目标): 4小时
# - RPO (恢复点目标): 1小时
# - 数据重要性: 订单数据极其重要,用户数据重要
# - 业务特点: 24/7运行高并发读写
# 推荐备份策略:
# 1. 每日全量备份 (使用文件系统快照)
# 2. 每小时增量备份 (使用 oplog)
# 3. 异地备份存储
# 4. 副本集跨数据中心部署
```

View File

@@ -0,0 +1,273 @@
# 安全管理
确保数据库的安全是任何应用都必须考虑的关键问题。MongoDB 提供了丰富的安全特性,包括认证、授权、加密等,以保护数据免受未经授权的访问。本章节将详细介绍如何配置和管理 MongoDB 的各项安全功能。
---
## 安全概述
MongoDB 的安全模型主要围绕以下几个核心概念构建:
- **认证 (Authentication)**: 验证用户身份,确认“你是谁”。
- **授权 (Authorization)**: 控制经过认证的用户可以执行哪些操作,确认“你能做什么”。
- **加密 (Encryption)**: 保护数据在传输过程TLS/SSL和静态存储时Encryption at Rest的安全。
- **审计 (Auditing)**: 记录数据库上发生的活动,以便进行安全分析和合规性检查。
默认情况下MongoDB 的安全特性是**未启用**的。必须显式地配置和启用它们。
---
## 认证机制 (Authentication)
启用认证是保护数据库的第一步。当认证启用后,所有客户端和数据库节点之间的连接都必须提供有效的凭据。
### 启用认证
`mongod.conf` 配置文件中或通过命令行参数启用认证:
```yaml
# mongod.conf
security:
authorization: enabled
```
或者
```bash
mongod --auth
```
### 认证方法
MongoDB 支持多种认证机制,最常用的是 **SCRAM (Salted Challenge Response Authentication Mechanism)**
- **SCRAM-SHA-1**: 默认机制。
- **SCRAM-SHA-256**: 更强的加密算法,建议在 MongoDB 4.0 及以上版本中使用。
### 创建管理员用户
在启用认证之前,必须先创建一个具有 `userAdminAnyDatabase` 角色的用户管理员。这个用户将用于创建和管理其他用户。
1. **以无认证模式启动 `mongod`**
2. **连接到 `admin` 数据库并创建用户**:
```javascript
use admin
db.createUser({
user: "myUserAdmin",
pwd: passwordPrompt(), // or a plain-text password
roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
})
```
3. **重启 `mongod` 并启用认证**。
---
## 授权与基于角色的访问控制 (RBAC)
MongoDB 使用基于角色的访问控制Role-Based Access Control, RBAC来管理用户权限。权限被定义为**角色 (Role)**,然后将角色分配给用户。
### 内置角色 (Built-In Roles)
MongoDB 提供了一系列预定义的角色,涵盖了常见的管理和操作需求。
- **数据库用户角色**: `read`, `readWrite`
- **数据库管理员角色**: `dbAdmin`, `dbOwner`, `userAdmin`
- **集群管理员角色**: `clusterAdmin`, `clusterManager`, `hostManager`
- **备份与恢复角色**: `backup`, `restore`
- **所有数据库角色**: `readAnyDatabase`, `readWriteAnyDatabase`, `userAdminAnyDatabase`, `dbAdminAnyDatabase`
- **超级用户角色**: `root` (拥有所有权限)
### 自定义角色 (Custom Roles)
如果内置角色无法满足精细化权限控制需求,可以创建自定义角色。
```javascript
use myAppDB
db.createRole({
role: "salesDataViewer",
privileges: [
{ resource: { db: "myAppDB", collection: "sales" }, actions: ["find"] }
],
roles: []
})
```
### 管理用户和角色
- `db.createUser()`: 创建用户。
- `db.updateUser()`: 更新用户信息(如密码、角色)。
- `db.dropUser()`: 删除用户。
- `db.createRole()`: 创建角色。
- `db.grantRolesToUser()`: 为用户授予角色。
- `db.revokeRolesFromUser()`: 撤销用户的角色。
---
## 网络加密 (TLS/SSL)
为了保护数据在网络传输过程中的安全,防止窃听和中间人攻击,应该为 MongoDB 部署启用 TLS/SSL 加密。
### 配置 TLS/SSL
1. **获取 TLS/SSL 证书**: 可以使用自签名证书(用于内部测试)或从证书颁发机构 (CA) 获取证书。
2. **配置 `mongod` 和 `mongos`**: 在配置文件中指定证书文件、私钥文件和 CA 文件。
```yaml
net:
tls:
mode: requireTLS
certificateKeyFile: /path/to/mongodb.pem
CAFile: /path/to/ca.pem
```
3. **客户端连接**: 客户端在连接时也需要指定 TLS/SSL 选项。
```shell
mongo --ssl --sslCAFile /path/to/ca.pem --sslPEMKeyFile /path/to/client.pem ...
```
---
## 静态数据加密 (Encryption at Rest)
对于高度敏感的数据,除了网络加密,还应该考虑对存储在磁盘上的数据文件进行加密。
- **WiredTiger 的原生加密**: MongoDB Enterprise 版本支持使用 WiredTiger 存储引擎的原生加密功能。它使用本地密钥管理器或第三方密钥管理服务(如 KMIP来管理加密密钥。
- **文件系统/磁盘加密**: 也可以在操作系统层面或通过云服务商提供的功能(如 AWS KMS, Azure Key Vault对存储设备进行加密。
---
## 实践操作
### 需求描述
构建一个完整的MongoDB安全管理环境模拟企业级应用的安全需求。该场景需要配置认证机制、创建不同权限的用户角色并验证安全策略的有效性。通过实际操作来理解MongoDB安全管理的核心概念和最佳实践。
### 实践细节和结果验证
```shell
# 1. 启用认证并创建管理员用户
# 首先以无认证模式启动MongoDB
mongod --dbpath /data/mongodb/ins11 --port 27117 --fork --logpath /data/mongodb/logs/ins11.log
# 连接到MongoDB并创建管理员用户
mongosh --port 27117
# 在mongosh中执行
use admin
db.createUser({
user: "admin",
pwd: "ealgeslab123",
roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})
# 验证管理员用户创建成功
db.getUsers()
# 预期结果显示创建的admin用户信息
# 2. 重启MongoDB并启用认证
# 停止MongoDB服务
db.adminCommand("shutdown")
# 以认证模式重新启动
mongod --auth --dbpath /data/mongodb/ins11 --port 27117 --fork --logpath /data/mongodb/logs/ins11.log
# 使用管理员账户登录
mongosh --port 27117 -u "admin" -p "ealgeslab123" --authenticationDatabase "admin"
# 3. 创建业务数据库和用户
# 创建应用数据库
use ecommerce
# 创建具有读写权限的业务用户
db.createUser({
user: "appUser",
pwd: "appPassword123",
roles: [{ role: "readWrite", db: "ecommerce" }]
})
# 验证业务用户创建成功
db.getUsers()
# 预期结果显示appUser用户信息
# 4. 创建自定义角色
# 创建只允许查询和更新特定集合的自定义角色
db.createRole({
role: "productManager",
privileges: [
{
resource: { db: "ecommerce", collection: "products" },
actions: ["find", "update"]
}
],
roles: []
})
# 创建使用自定义角色的用户
db.createUser({
user: "productAdmin",
pwd: "productPassword123",
roles: [{ role: "productManager", db: "ecommerce" }]
})
# 验证自定义角色和用户
db.getRoles({ showPrivileges: true })
db.getUsers()
# 预期结果显示productManager角色和productAdmin用户
# 5. 测试用户权限
# 使用业务用户连接
mongosh --port 27117 -u "appUser" -p "appPassword123" --authenticationDatabase "ecommerce"
# 测试读写权限
use ecommerce
db.products.insertOne({ name: "Laptop", price: 999, category: "Electronics" })
db.products.find()
db.products.updateOne({ name: "Laptop" }, { $set: { price: 899 } })
# 预期结果:所有操作成功执行
# 6. 测试自定义角色权限
# 使用自定义角色用户连接
mongosh --port 27017 -u "productAdmin" -p "productPassword123" --authenticationDatabase "ecommerce"
use ecommerce
# 测试允许的操作
db.products.find()
db.products.updateOne({ name: "Laptop" }, { $set: { description: "High-performance laptop" } })
# 预期结果:查询和更新操作成功
# 测试禁止的操作
db.products.insertOne({ name: "Mouse", price: 25 })
# 预期结果:操作失败,提示权限不足
db.products.deleteOne({ name: "Laptop" })
# 预期结果:操作失败,提示权限不足
# 8. 查看用户和角色信息
# 查看所有用户
use admin
db.system.users.find().pretty()
# 查看所有角色
db.system.roles.find().pretty()
# 查看当前用户权限
db.runCommand({ connectionStatus: 1 })
# 预期结果:显示当前连接的用户信息和权限
# 9. 权限管理操作
use ecommerce
# 为用户添加新角色
db.grantRolesToUser("appUser", [{ role: "dbAdmin", db: "ecommerce" }])
# 撤销用户角色
db.revokeRolesFromUser("appUser", [{ role: "dbAdmin", db: "ecommerce" }])
# 修改用户密码
db.updateUser("appUser", { pwd: "newPassword123" })
# 验证权限变更
db.getUser("appUser")
# 预期结果:显示用户的最新角色信息
```

View File

@@ -0,0 +1,300 @@
# MongoDB 性能监控与调优
为了确保 MongoDB 数据库持续、稳定、高效地运行,必须对其进行有效的性能监控和及时的调优。本章节将介绍 MongoDB 的关键性能指标、常用的监控工具以及针对常见性能问题的调优策略。
---
## 关键性能指标
监控 MongoDB 时,应重点关注以下几类指标:
### 操作计数器 (Operation Counters)
- **`opcounters`**: 显示自 `mongod` 启动以来执行的数据库操作insert, query, update, delete, getmore, command的总数。通过观察其增长率可以了解数据库的负载情况。
### 锁 (Locks)
- **`globalLock`**: 反映全局锁的使用情况。在 WiredTiger 存储引擎中,全局锁的使用已大大减少,但仍需关注。
- **`locks`**: 提供数据库、集合等更细粒度锁的信息。长时间的锁等待(`timeAcquiringMicros`)可能表示存在锁竞争,需要优化查询或索引。
### 网络 (Network)
- **`network.bytesIn` / `network.bytesOut`**: 进出数据库的网络流量。
- **`network.numRequests`**: 接收到的请求总数。
- 监控网络指标有助于发现网络瓶颈或异常的客户端行为。
### 内存 (Memory)
- **`mem.resident`**: 进程占用的物理内存RAM大小。
- **`mem.virtual`**: 进程使用的虚拟内存大小。
- **`mem.mapped`**: 内存映射文件的大小。
- 监控内存使用情况,特别是 WiredTiger 的内部缓存(`wiredTiger.cache`),对于确保性能至关重要。
### Oplog
- **Oplog Window**: Oplog 中记录的操作所覆盖的时间范围。如果 oplog window 太小从节点可能会因为跟不上主节点的更新速度而掉队stale
### 慢查询 (Slow Queries)
- **`system.profile` 集合**: 当开启数据库分析profiling执行时间超过阈值的查询会被记录到该集合中。这是识别和优化慢查询的主要工具。
---
## 监控工具
### `mongostat`
- **描述**: 一个命令行工具,可以实时地、逐秒地显示 MongoDB 实例的主要性能指标,类似于 Linux 的 `vmstat`
- **用途**: 快速了解数据库当前的操作负载和性能状况。
```bash
mongostat
```
### `mongotop`
- **描述**: 一个命令行工具,用于跟踪 MongoDB 实例花费时间最多的读写操作。
- **用途**: 快速定位哪些集合是当前系统的性能热点。
```bash
mongotop 10 # 每 10 秒刷新一次
```
### `db.serverStatus()`
- **描述**: 一个数据库命令,返回一个包含大量服务器状态信息的文档。
- **用途**: 获取详细的、全面的性能指标,是大多数监控系统的主要数据来源。
```javascript
db.serverStatus()
```
### MongoDB Cloud Manager / Ops Manager
- **描述**: 提供了一个功能强大的图形化监控界面,可以收集、可视化并告警各种性能指标。
- **用途**: 长期、系统化的性能监控和趋势分析。
---
## 性能调优策略
### 索引优化
- **识别缺失的索引**: 通过分析 `system.profile` 集合中的慢查询日志,找出那些因为缺少合适索引而导致全集合扫描(`COLLSCAN`)的查询。
- **评估现有索引**: 使用 `$indexStats` 聚合阶段检查索引的使用频率。对于很少使用或从不使用的索引,应考虑删除以减少写操作的开销和存储空间。
- **创建复合索引**: 根据 ESR 法则设计高效的复合索引,使其能够覆盖多种查询模式。
### 查询优化
- **使用投影 (Projection)**: 只返回查询所需的字段,减少网络传输量和客户端处理数据的负担。
- **避免使用 `$where` 和 `$function`**: 这类操作无法使用索引,并且会在每个文档上执行 JavaScript性能极差。
- **优化正则表达式**: 尽量使用前缀表达式(如 `/^prefix/`),这样可以利用索引。避免使用不区分大小写的正则表达式,因为它无法有效利用索引。
### Schema 设计调优
- **遵循数据建模模式**: 根据应用的读写模式选择合适的建模策略(嵌入 vs. 引用),可以从根本上提升性能。
- **避免大文档和无界数组**: 过大的文档会增加内存使用和网络传输,无限制增长的数组会给更新操作带来性能问题。
### 硬件和拓扑结构调优
- **内存**: 确保 WiredTiger 的缓存大小(默认为 `(RAM - 1GB) / 2`足够容纳工作集Working Set即最常访问的数据和索引。
- **磁盘**: 使用 SSD 可以显著提升 I/O 性能,特别是对于写密集型应用。
- **网络**: 确保数据库服务器之间的网络延迟尽可能低,特别是在分片集群和地理分布的副本集中。
- **读写分离**: 在读密集型应用中,通过将读请求路由到从节点来扩展读取能力。
---
## 实践操作
### 需求描述
构建一个完整的 MongoDB 性能监控与调优实践环境,掌握性能指标监控、慢查询分析、索引优化等核心技能。通过实际操作理解如何识别性能瓶颈、分析查询执行计划、创建高效索引,以及使用各种监控工具来持续优化数据库性能。
### 实践细节和结果验证
```shell
# 1. 准备测试数据和环境
# 创建性能测试数据库
use performanceTestDB
# 创建大量测试数据
for (let i = 0; i < 100000; i++) {
db.products.insertOne({
productId: "PROD" + i.toString().padStart(6, '0'),
name: "Product " + i,
category: ["electronics", "books", "clothing", "home"][i % 4],
price: Math.floor(Math.random() * 1000) + 10,
rating: Math.floor(Math.random() * 5) + 1,
inStock: Math.random() > 0.3,
tags: ["popular", "new", "sale", "featured"].slice(0, Math.floor(Math.random() * 3) + 1),
createdAt: new Date(Date.now() - Math.floor(Math.random() * 365 * 24 * 60 * 60 * 1000))
});
}
# 验证数据插入
db.products.countDocuments() # 预期结果: 100000
# 2. 开启数据库性能分析 (Profiling)
# 设置慢查询阈值为100ms记录所有慢查询
db.setProfilingLevel(1, { slowms: 100 })
# 验证profiling设置
db.getProfilingStatus()
# 预期结果: { "was" : 1, "slowms" : 100, "sampleRate" : 1.0 }
# 3. 生成慢查询进行性能分析
# 执行没有索引的复杂查询(故意制造慢查询)
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 },
inStock: true
}).sort({ createdAt: -1 }).limit(10)
# 执行正则表达式查询(通常较慢)
db.products.find({ name: /Product 1.*/ })
# 执行范围查询
db.products.find({
price: { $gte: 100, $lte: 500 },
createdAt: { $gte: new Date("2023-01-01") }
})
# 4. 分析慢查询日志
# 查看最近的慢查询记录
db.system.profile.find().sort({ ts: -1 }).limit(5).pretty()
# 预期结果: 显示最近5条慢查询记录包含执行时间、查询语句等信息
# 查看特定类型的慢查询
db.system.profile.find({
"command.find": "products",
"millis": { $gte: 100 }
}).sort({ ts: -1 })
# 统计慢查询数量
db.system.profile.countDocuments({ "millis": { $gte: 100 } })
# 预期结果: 显示慢查询总数
# 5. 使用 explain() 分析查询执行计划
# 分析复杂查询的执行计划
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 }
}).explain("executionStats")
# 预期结果: 显示详细的执行统计,包括扫描的文档数、执行时间等
# 查看查询是否使用了索引
db.products.find({ category: "electronics" }).explain("queryPlanner")
# 预期结果: winningPlan.stage 应该显示 "COLLSCAN"(全集合扫描)
# 6. 创建索引优化查询性能
# 为常用查询字段创建单字段索引
db.products.createIndex({ category: 1 })
db.products.createIndex({ price: 1 })
db.products.createIndex({ rating: 1 })
db.products.createIndex({ createdAt: -1 })
# 创建复合索引遵循ESR法则Equality, Sort, Range
db.products.createIndex({
category: 1,
createdAt: -1,
price: 1
})
# 创建文本索引用于搜索
db.products.createIndex({ name: "text", tags: "text" })
# 验证索引创建
db.products.getIndexes()
# 预期结果: 显示所有已创建的索引
# 7. 验证索引优化效果
# 重新执行之前的慢查询,观察性能提升
db.products.find({
category: "electronics",
price: { $gte: 500 },
rating: { $gte: 4 }
}).sort({ createdAt: -1 }).explain("executionStats")
# 预期结果: 应该显示 "IXSCAN"(索引扫描),执行时间显著减少
# 比较优化前后的查询性能
var startTime = new Date();
db.products.find({ category: "electronics", price: { $gte: 500 } }).toArray();
var endTime = new Date();
print("查询执行时间: " + (endTime - startTime) + "ms");
# 预期结果: 执行时间应该显著减少
# 8. 使用监控工具进行性能监控
# 在终端中使用 mongostat 监控实时性能
# mongostat --host localhost:27017
# 预期结果: 显示实时的操作统计、内存使用、网络流量等
# 使用 mongotop 监控集合级别的读写活动
# mongotop 10
# 预期结果: 每10秒显示各集合的读写时间统计
# 9. 服务器状态监控
# 获取详细的服务器状态信息
db.serverStatus()
# 预期结果: 返回包含操作计数器、锁信息、内存使用、网络统计等的详细报告
# 监控特定的性能指标
db.serverStatus().opcounters
# 预期结果: 显示各种操作insert、query、update等的计数
db.serverStatus().wiredTiger.cache
# 预期结果: 显示WiredTiger缓存的使用情况
db.serverStatus().locks
# 预期结果: 显示锁的使用统计
# 10. 索引使用情况分析
# 查看索引使用统计
db.products.aggregate([
{ $indexStats: {} }
])
# 预期结果: 显示每个索引的使用次数和访问模式
# 识别未使用的索引
db.products.aggregate([
{ $indexStats: {} },
{ $match: { "accesses.ops": 0 } }
])
# 预期结果: 显示从未被使用的索引(可考虑删除)
# 11. 查询优化最佳实践验证
# 使用投影减少网络传输
var startTime = new Date();
db.products.find(
{ category: "electronics" },
{ name: 1, price: 1, _id: 0 } # 只返回需要的字段
).toArray();
var endTime = new Date();
print("投影查询执行时间: " + (endTime - startTime) + "ms");
# 对比不使用投影的查询时间
var startTime = new Date();
db.products.find({ category: "electronics" }).toArray();
var endTime = new Date();
print("完整文档查询执行时间: " + (endTime - startTime) + "ms");
# 预期结果: 使用投影的查询应该更快
# 12. 清理和总结
# 关闭profiling
db.setProfilingLevel(0)
# 查看profiling总结
db.system.profile.aggregate([
{
$group: {
_id: "$command.find",
avgDuration: { $avg: "$millis" },
maxDuration: { $max: "$millis" },
count: { $sum: 1 }
}
},
{ $sort: { avgDuration: -1 } }
])
# 预期结果: 显示各集合查询的平均执行时间统计
```

View File

@@ -0,0 +1,299 @@
# 数据建模
与关系型数据库不同MongoDB 灵活的文档模型为数据建模提供了更多的选择。本章节将介绍 MongoDB 数据建模的核心思想、常见模式以及如何根据应用需求选择合适的模型,以实现最佳的性能和可扩展性。
---
## 数据建模核心思想
### 嵌入Embedding vs. 引用Referencing
这是 MongoDB 数据建模中最核心的决策点。
- **嵌入 (Embedding / Denormalization)**
- **描述**: 将相关的数据直接嵌入到主文档中,形成一个嵌套的文档结构。
- **优点**:
- **性能好**: 只需一次数据库查询即可获取所有相关数据,减少了读操作的次数。
- **数据原子性**: 对单个文档的更新是原子操作。
- **缺点**:
- **文档体积大**: 可能导致文档超过 16MB 的大小限制。
- **数据冗余**: 如果嵌入的数据在多处被使用,更新时需要修改所有包含它的文档。
- **适用场景**: “一对一”或“一对多”关系,且“多”的一方数据不经常变动,或者总是和“一”的一方一起被查询。
- **引用 (Referencing / Normalization)**
- **描述**: 将数据存储在不同的集合中,通过在文档中存储对另一个文档的引用(通常是 `_id`)来建立关系。
- **优点**:
- **数据一致性**: 更新数据时只需修改一处。
- **文档体积小**: 避免了数据冗余。
- **缺点**:
- **查询性能较低**: 需要多次查询(或使用 `$lookup`)来获取关联数据。
- **适用场景**: “多对多”关系,或者“一对多”关系中“多”的一方数据量巨大、经常变动或经常被独立查询。
### 决策依据
选择嵌入还是引用,主要取决于应用的**数据访问模式**
- **读多写少**: 优先考虑嵌入,优化读取性能。
- **写操作频繁**: 优先考虑引用,避免更新大量冗余数据。
- **数据一致性要求高**: 优先考虑引用。
- **数据关联性强,总是一起访问**: 优先考虑嵌入。
---
## 常见数据建模模式
MongoDB 社区总结了一些行之有效的数据建模模式,可以作为设计的参考。
### 属性模式 (Attribute Pattern)
- **问题**: 当文档有大量字段,但大部分查询只关心其中一小部分时。
- **解决方案**: 将不常用或具有相似特征的字段分组到一个子文档中。
- **示例**: 一个产品文档,将详细规格参数放到 `specs` 子文档中。
```json
{
"product_id": "123",
"name": "Laptop",
"specs": {
"cpu": "Intel i7",
"ram_gb": 16,
"storage_gb": 512
}
}
```
### 扩展引用模式 (Extended Reference Pattern)
- **问题**: 在引用模型中,为了获取被引用文档的某个常用字段,需要执行额外的查询。
- **解决方案**: 在引用文档中,除了存储 `_id` 外,还冗余存储一些经常需要一起显示的字段。
- **示例**: 在文章(`posts`)集合中,除了存储作者的 `author_id`,还冗余存储 `author_name`。
```json
// posts collection
{
"title": "My First Post",
"author_id": "xyz",
"author_name": "John Doe" // Extended Reference
}
```
### 子集模式 (Subset Pattern)
- **问题**: 一个文档中有一个巨大的数组(如评论、日志),导致文档过大,且大部分时间只需要访问数组的最新一部分。
- **解决方案**: 将数组的一个子集(如最新的 10 条评论)与主文档存储在一起,完整的数组存储在另一个集合中。
- **示例**: 产品文档中存储最新的 5 条评论,所有评论存储在 `reviews` 集合。
```json
// products collection
{
"product_id": "abc",
"name": "Super Widget",
"reviews_subset": [
{ "review_id": 1, "text": "Great!" },
{ "review_id": 2, "text": "Awesome!" }
]
}
```
### 计算模式 (Computed Pattern)
- **问题**: 需要频繁计算某些值(如总数、平均值),每次读取时计算会消耗大量资源。
- **解决方案**: 在写操作时预先计算好这些值,并将其存储在文档中。当数据更新时,同步更新计算结果。
- **示例**: 在用户文档中存储其发布的帖子总数 `post_count`。
```json
{
"user_id": "user123",
"name": "Jane Doe",
"post_count": 42 // Computed value
}
```
---
## Schema 验证
虽然 MongoDB 是 schema-less 的,但在应用层面保持数据结构的一致性非常重要。从 MongoDB 3.2 开始,引入了 Schema 验证功能。
- **作用**: 可以在集合级别定义文档必须满足的结构规则(如字段类型、必需字段、范围等)。
- **好处**: 确保写入数据库的数据符合预期格式,提高数据质量。
```javascript
db.createCollection("students", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "major", "gpa"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
gpa: {
bsonType: ["double"],
minimum: 0,
maximum: 4.0,
description: "must be a double in [0, 4] and is required"
}
}
}
}
})
```
---
## 实践环节
### 需求描述
为一个博客系统设计数据模型,包含以下实体:`用户 (User)`、`文章 (Post)`、`评论 (Comment)` 和 `标签 (Tag)`。
1. **关系分析**: 分析这些实体之间的关系(一对一、一对多、多对多)。
2. **模型设计**: 讨论并设计至少两种不同的数据模型方案(例如,一种偏向嵌入,一种偏向引用)。
3. **优劣对比**: 对比不同方案的优缺点,并说明它们分别适用于哪些场景。
4. **Schema 定义**: 选择的最佳方案中的 `posts` 集合编写一个 Schema 验证规则。
### 实践细节和结果验证
```javascript
// 1. 关系分析与模型设计
// 方案一:偏向嵌入式(适合读多写少的场景)
// users 集合示例文档
db.users.insertOne({
"_id": ObjectId(),
"username": "john_doe",
"email": "john@example.com",
"posts": [{
"_id": ObjectId(),
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": ISODate("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"],
"comments": [{
"user_id": ObjectId(),
"username": "alice",
"content": "写得很好!",
"created_at": ISODate("2024-01-16")
}]
}]
});
// 方案二:偏向引用式(适合写多读少的场景)
// users 集合
db.users.insertOne({
"_id": ObjectId(),
"username": "john_doe",
"email": "john@example.com"
});
// posts 集合
db.posts.insertOne({
"_id": ObjectId(),
"author_id": ObjectId(), // 引用 users._id
"author_name": "john_doe", // 扩展引用
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": ISODate("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"]
});
// comments 集合
db.comments.insertOne({
"_id": ObjectId(),
"post_id": ObjectId(), // 引用 posts._id
"user_id": ObjectId(), // 引用 users._id
"username": "alice", // 扩展引用
"content": "写得很好!",
"created_at": ISODate("2024-01-16")
});
// 2. 性能测试
// 方案一:嵌入式查询(一次查询获取所有信息)
db.users.find(
{ "posts.tags": "MongoDB" },
{ "posts.$": 1 }
).explain("executionStats");
// 方案二:引用式查询(需要聚合或多次查询)
db.posts.aggregate([
{ $match: { tags: "MongoDB" } },
{ $lookup: {
from: "comments",
localField: "_id",
foreignField: "post_id",
as: "comments"
}}
]).explain("executionStats");
// 3. Schema 验证规则
// 为 posts 集合创建验证规则
db.createCollection("posts", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["author_id", "author_name", "title", "content", "created_at", "tags"],
properties: {
author_id: {
bsonType: "objectId",
description: "作者ID必须是 ObjectId 类型且不能为空"
},
author_name: {
bsonType: "string",
description: "作者名称,必须是字符串且不能为空"
},
title: {
bsonType: "string",
minLength: 1,
maxLength: 100,
description: "标题长度必须在1-100字符之间"
},
content: {
bsonType: "string",
minLength: 1,
description: "内容不能为空"
},
created_at: {
bsonType: "date",
description: "创建时间,必须是日期类型"
},
tags: {
bsonType: "array",
minItems: 1,
uniqueItems: true,
items: {
bsonType: "string"
},
description: "标签必须是非空字符串数组,且不能重复"
}
}
}
},
validationLevel: "strict",
validationAction: "error"
});
// 4. 验证结果
// 测试有效文档(应该成功)
db.posts.insertOne({
author_id: ObjectId(),
author_name: "john_doe",
title: "测试文章",
content: "这是一篇测试文章",
created_at: new Date(),
tags: ["测试", "MongoDB"]
});
// 测试无效文档(应该失败)
db.posts.insertOne({
author_name: "john_doe", // 缺少 author_id
title: "测试文章",
content: "这是一篇测试文章",
created_at: new Date(),
tags: [] // 空标签数组,违反 minItems 规则
});
```

View File

@@ -0,0 +1,171 @@
# 环境搭建
本章将详细介绍如何在 Rocky Linux 操作系统上安装和配置 MongoDB以及如何管理 MongoDB 服务。正确的环境搭建是后续学习的基础。
---
## 安装部署
基于 Rocky Linux 9 系统上,推荐使用 `yum``dnf` 包管理器通过官方源进行安装,这样可以确保安装的是最新稳定版本,并且便于后续更新。
**步骤 1: 配置 MongoDB 的 YUM/DNF 仓库**
创建一个 `/etc/yum.repos.d/mongodb-org-7.0.repo` 文件,并添加以下内容:
```ini
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el9-7.0/
gpgcheck=0
enabled=1
```
**步骤 2: 安装 MongoDB**
执行以下命令安装 MongoDB 数据库及其相关工具:
```shell
# 清理缓存并安装
yum makecache
yum install -y mongodb-org
```
这个命令会安装以下几个包:
- `mongodb-org-server`: `mongod` 守护进程、配置文件和初始化脚本。
- `mongodb-org-mongos`: `mongos` 守护进程。
- `mongodb-mongosh`: MongoDB Shell (`mongosh`)。
- `mongodb-org-tools`: MongoDB 的命令行工具,如 `mongodump`, `mongorestore`, `mongoimport`, `mongoexport` 等。
---
## 相关配置
MongoDB 的主配置文件位于 `/etc/mongod.conf`,默认的配置文件采用 YAML 格式。以下是一些核心配置项的说明:
```yaml
# mongod.conf
# 存储设置
storage:
dbPath: /var/lib/mongo # 数据文件存放目录
journal:
enabled: true # 是否启用 journal 日志,建议始终开启
# 系统日志设置
systemLog:
destination: file # 日志输出到文件
logAppend: true # 日志以追加方式写入
path: /var/log/mongodb/mongod.log # 日志文件路径
# 网络设置
net:
port: 27017 # 监听端口
bindIp: 127.0.0.1 # 绑定的 IP 地址,默认为本地回环地址。如需远程访问,可设置为 0.0.0.0
# 进程管理
processManagement:
timeZoneInfo: /usr/share/zoneinfo # 时区信息
# 安全设置 (默认禁用)
#security:
# authorization: enabled # 启用访问控制
```
- **`storage.dbPath`**: MongoDB 数据文件的存储路径,需要确保 `mongod` 用户对此目录有读写权限。
- **`systemLog.path`**: 日志文件路径,用于记录数据库的操作和错误信息。
- **`net.bindIp`**: 这是非常重要的安全配置。默认 `127.0.0.1` 只允许本机连接。如果需要从其他服务器连接,应谨慎地将其设置为服务器的内网 IP 或 `0.0.0.0`(监听所有网络接口),并配合防火墙和安全组规则使用。
---
## 服务管理
在现代 Linux 系统中,我们使用 `systemd` 来管理 `mongod` 服务。
```shell
# 启动 MongoDB 服务
systemctl start mongod
# 查看服务状态
systemctl status mongod
# 停止 MongoDB 服务
systemctl stop mongod
# 重启 MongoDB 服务
systemctl restart mongod
# 启用开机自启动
systemctl enable mongod
# 禁用开机自启动
systemctl disable mongod
```
## 生产配置
这是一个更贴近生产环境的配置文件示例,增加了安全和性能相关的配置。
```yaml
# /etc/mongod.conf - Production Example
storage:
dbPath: /var/lib/mongo
journal:
enabled: true
engine: wiredTiger
wiredTiger:
engineConfig:
cacheSizeGB: 1 # 根据服务器内存调整,建议为物理内存的 50% - 60%
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
net:
port: 27017
bindIp: 127.0.0.1,192.168.1.100 # 绑定本地和内网 IP
maxIncomingConnections: 1000 # 最大连接数
processManagement:
fork: true # 后台运行
pidFilePath: /var/run/mongodb/mongod.pid
timeZoneInfo: /usr/share/zoneinfo
security:
authorization: enabled # 开启认证
replication:
replSetName: rs0 # 副本集名称
```
## 实践操作
### 需求描述
1. 使用 `root` 用户安装并正常运行
2. 更改数据目录 `dbPath``/data/mongodb`
3. 更改监听地址 `bindIp``0.0.0.0`
### 实践细节和结果验证
```shell
# 关闭防火墙和 SELINUX
# 安装 Mongodb 并正常启动
tee /etc/yum.repos.d/mongodb-org-7.0.repo << EOF
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://mirrors.tuna.tsinghua.edu.cn/mongodb/yum/el9-7.0/
gpgcheck=0
enabled=1
EOF
yum makecache
yum install -y mongodb-org
mkdir -p /data/mongodb
sed -i 's|dbPath: /var/lib/mongo|dbPath: /data/mongodb|' /etc/mongod.conf
sed -i 's|bindIp: 127.0.0.1|bindIp: 0.0.0.0|' /etc/mongod.conf
cp /lib/systemd/system/mongod.service /etc/systemd/system/mongod.service
sed -i 's/User=mongod/User=root/' /etc/systemd/system/mongod.service
sed -i 's/Group=mongod/Group=root/' /etc/systemd/system/mongod.service
systemctl start mongod && systemctl enable mongod
# 测试验证
mongod --version
```

View File

@@ -0,0 +1,267 @@
# 索引优化
索引是提升 MongoDB 查询性能的关键。本章节将深入探讨索引的类型、创建策略、如何分析查询性能以及索引的维护方法,帮助大家构建高性能的数据库应用。
---
## 索引基础
### 什么是索引?
索引是一种特殊的数据结构它以易于遍历的形式存储了集合中一小部分的数据。通过索引MongoDB 可以直接定位到符合查询条件的文档,而无需扫描整个集合,从而极大地提高了查询速度。
### 索引的类型
MongoDB 支持多种类型的索引,以适应不同的查询需求。
1. **单字段索引 (Single Field Index)**
- 最基础的索引类型,针对单个字段创建。
- 示例: `db.inventory.createIndex({ price: 1 })` (按价格升序)
2. **复合索引 (Compound Index)**
- 在多个字段上创建的索引。字段的顺序非常重要,决定了索引的查询效率。
- 示例: `db.inventory.createIndex({ price: 1, qty: -1 })` (先按价格升序,再按数量降序)
3. **多键索引 (Multikey Index)**
- 为数组字段创建的索引。MongoDB 会为数组中的每个元素创建一条索引项。
- 示例: `db.inventory.createIndex({ tags: 1 })` (为 `tags` 数组中的每个标签创建索引)
4. **文本索引 (Text Index)**
- 用于支持对字符串内容的文本搜索查询。
- 示例: `db.inventory.createIndex({ description: "text" })`
5. **地理空间索引 (Geospatial Index)**
- 用于高效地查询地理空间坐标数据。
- 类型: `2dsphere` (用于球面几何) 和 `2d` (用于平面几何)。
- 示例: `db.places.createIndex({ location: "2dsphere" })`
6. **哈希索引 (Hashed Index)**
- 计算字段值的哈希值并对哈希值建立索引,主要用于哈希分片。
- 示例: `db.inventory.createIndex({ status: "hashed" })`
---
## 索引策略与创建
### 创建索引
使用 `createIndex()` 方法在集合上创建索引。
```javascript
// 在 students 集合的 email 字段上创建一个唯一的升序索引
db.students.createIndex({ student_id: 1 }, { unique: true })
// 创建一个后台索引,避免阻塞数据库操作
db.students.createIndex({ name: 1 }, { background: true })
```
### 索引属性
- **`unique`**: 强制索引字段的值唯一,拒绝包含重复值的文档插入或更新。
- **`sparse`**: 稀疏索引,只为包含索引字段的文档创建索引项,节省空间。
- **`background`**: 在后台创建索引,允许在创建过程中进行读写操作(但会稍慢)。
- **`expireAfterSeconds`**: TTL (Time-To-Live) 索引,自动从集合中删除超过指定时间的文档。
### 复合索引的字段顺序
复合索引的字段顺序遵循 **ESR (Equality, Sort, Range)** 法则:
1. **等值查询 (Equality)** 字段应放在最前面。
2. **排序 (Sort)** 字段其次。
3. **范围查询 (Range)** 字段(如 `$gt`, `$lt`)应放在最后。
正确的字段顺序可以使一个索引服务于多种查询,提高索引的复用率。
**案例分析**:
假设我们有一个 `products` 集合,需要频繁执行一个查询:查找特定 `category` 的商品,按 `brand` 排序,并筛选出价格 `price` 大于某个值的商品。
**查询语句**:
```javascript
db.products.find(
{
category: "electronics", // 等值查询 (Equality)
price: { $gt: 500 } // 范围查询 (Range)
}
).sort({ brand: 1 }) // 排序 (Sort)
```
**根据 ESR 法则创建索引**:
遵循 ESR 法则,我们应该将等值查询字段 `category` 放在最前面,然后是排序字段 `brand`,最后是范围查询字段 `price`
**最佳索引**:
```javascript
db.products.createIndex({ category: 1, brand: 1, price: 1 })
```
**为什么这个顺序是最高效的?**
1. **等值查询优先 (`category`)**: MongoDB 可以立即通过索引定位到所有 `category``"electronics"` 的文档,快速缩小查询范围。
2. **排序字段次之 (`brand`)**: 在已筛选出的 `"electronics"` 索引条目中,数据已经按 `brand` 排好序。因此MongoDB 无需在内存中执行额外的排序操作 (`in-memory sort`),极大地提升了性能。
3. **范围查询最后 (`price`)**: 在已经按 `brand` 排序的索引条目上MongoDB 可以顺序扫描,高效地过滤出 `price` 大于 500 的条目。
如果索引顺序不当,例如 `{ price: 1, brand: 1, category: 1 }`MongoDB 将无法有效利用索引进行排序,可能导致性能下降。
---
## 查询性能分析
### `explain()` 方法
`explain()` 是分析查询性能的利器。它可以显示查询的执行计划,包括是否使用了索引、扫描了多少文档等信息。
```javascript
db.students.find({ age: { $gt: 21 } }).explain("executionStats")
```
### 分析 `explain()` 结果
关注 `executionStats` 中的关键指标:
- **`executionSuccess`**: 查询是否成功执行。
- **`nReturned`**: 查询返回的文档数量。
- **`totalKeysExamined`**: 扫描的索引键数量。
- **`totalDocsExamined`**: 扫描的文档数量。
- **`executionTimeMillis`**: 查询执行时间(毫秒)。
- **`winningPlan.stage`**: 查询计划的阶段。
- `COLLSCAN`: 全集合扫描,性能最低。
- `IXSCAN`: 索引扫描,性能较高。
- `FETCH`: 根据索引指针去获取文档。
**理想情况**: `totalKeysExamined``totalDocsExamined` 应该尽可能接近 `nReturned`
### 覆盖查询 (Covered Query)
当查询所需的所有字段都包含在索引中时MongoDB 可以直接从索引返回结果,而无需访问文档,这称为覆盖查询。覆盖查询性能极高。
**条件**:
1. 查询的所有字段都是索引的一部分。
2. 查询返回的所有字段都在同一个索引中。
3. 查询的字段中不包含 `_id` 字段,或者 `_id` 字段是索引的一部分(默认情况下 `_id` 会被返回,除非显式排除)。
**案例分析**:
假设我们有一个 `students` 集合,并且我们经常需要通过 `student_id` 查找学生的姓名 `name`
1. **创建复合索引**:
为了优化这个查询,我们可以在 `student_id``name` 字段上创建一个复合索引。
```javascript
db.students.createIndex({ student_id: 1, name: 1 })
```
2. **执行覆盖查询**:
现在,我们执行一个只查询 `student_id` 并只返回 `name` 字段的查询。我们使用投影 (projection) 来显式排除 `_id` 字段,并只包含 `name` 字段。
```javascript
db.students.find({ student_id: 'S1001' }, { name: 1, _id: 0 })
```
3. **性能验证**:
使用 `explain()` 方法来查看查询的执行计划。
```javascript
db.students.find({ student_id: 'S1001' }, { name: 1, _id: 0 }).explain('executionStats')
```
在 `executionStats` 的输出中,我们会发现:
- `totalDocsExamined` 的值为 `0`。这表明 MongoDB 没有扫描任何文档。
- `totalKeysExamined` 的值大于 `0`,说明扫描了索引。
- `winningPlan.stage` 会显示为 `IXSCAN`,并且没有 `FETCH` 阶段。
这个结果证明了该查询是一个覆盖查询。MongoDB 仅通过访问索引就获取了所有需要的数据,完全避免了读取文档的磁盘 I/O 操作,从而实现了极高的查询性能。
---
## 索引维护
### 查看索引
使用 `getIndexes()` 方法查看集合上的所有索引。
```javascript
db.students.getIndexes()
```
### 删除索引
使用 `dropIndex()` 方法删除指定的索引。
```javascript
// 按名称删除索引
db.students.dropIndex("email_1")
// 按键模式删除索引
db.students.dropIndex({ last_login: -1 })
```
### 索引大小和使用情况
使用 `$indexStats` 聚合阶段可以查看索引的大小和使用情况(自上次重启以来的操作次数)。
```javascript
db.students.aggregate([{ $indexStats: {} }])
```
---
## 实践操作
在本节中,我们将使用 `products_for_indexing` 集合进行实践,数据已在 `data.js` 中定义。
### 需求描述
假设我们有一个电商平台的 `products02` 集合,需要支持以下查询场景:
1. 频繁按商品类别 (`category`) 查找商品,并按价格 (`price`) 排序。
2. 需要快速获取特定类别和品牌的商品信息,且只关心品牌和价格。
### 实践细节和结果验证
```javascript
// 准备工作:确保 products_for_indexing 集合存在且包含数据
// (数据已在 data.js 中定义,请先加载)
// 1. 创建复合索引以优化排序查询
// 需求:按类别查找并按价格排序
db.products_for_indexing.createIndex({ category: 1, price: 1 });
// 2. 使用 explain() 分析查询性能
// -- 无索引查询 (模拟,假设索引未创建) --
// db.products_for_indexing.find({ category: 'Electronics' }).sort({ price: -1 }).explain('executionStats');
// 结果会显示 COLLSCAN (全表扫描),效率低
// -- 有索引查询 --
db.products_for_indexing.find({ category: 'Electronics' }).sort({ price: -1 }).explain('executionStats');
// **结果验证**:
// winningPlan.stage 应为 IXSCAN (索引扫描),表明使用了索引。
// totalDocsExamined 数量远小于集合总数,性能高。
// 3. 构造并验证覆盖查询
// 需求:只查询电子产品的品牌和价格
// -- 创建一个能覆盖查询的索引 --
db.products_for_indexing.createIndex({ category: 1, brand: 1, price: 1 });
// -- 执行覆盖查询 --
db.products_for_indexing.find(
{ category: 'Electronics', brand: 'Sony' },
{ brand: 1, price: 1, _id: 0 }
).explain('executionStats');
// **结果验证**:
// totalDocsExamined 应为 0表明没有读取文档。
// winningPlan.stage 为 IXSCAN且没有 FETCH 阶段,证明是高效的覆盖查询。
// 4. 索引维护
// -- 查看当前集合上的所有索引 --
db.products_for_indexing.getIndexes();
// -- 删除一个不再需要的索引 --
// 假设 { category: 1, price: 1 } 这个索引不再需要
db.products_for_indexing.dropIndex('category_1_price_1');
// -- 再次查看索引,确认已删除 --
db.products_for_indexing.getIndexes();
```

View File

@@ -0,0 +1,267 @@
# 聚合框架
聚合框架是 MongoDB 提供的一个强大的数据处理工具,它允许对集合中的数据进行一系列的转换和计算,最终得到聚合后的结果。本章节将深入探讨聚合管道、常用的聚合阶段以及如何利用聚合框架进行复杂的数据分析。
---
## 聚合管道Aggregation Pipeline
聚合操作的核心是聚合管道。管道由一个或多个**阶段 (Stage)** 组成,每个阶段都会对输入的文档流进行处理,并将结果传递给下一个阶段。
### 聚合管道的语法
聚合操作使用 `aggregate()` 方法,其参数是一个包含所有阶段的数组。
```javascript
db.collection.aggregate([
{ <stage1> },
{ <stage2> },
...
])
```
### 聚合管道的优势
- **功能强大**: 支持复杂的数据转换、分组、计算和重塑。
- **性能高效**: 许多操作在数据库服务端以原生代码执行,减少了数据在网络中的传输。
- **灵活性高**: 可以通过组合不同的阶段来满足各种复杂的数据处理需求。
---
## 常用聚合阶段
以下是一些最常用的聚合阶段,通过组合它们可以实现强大的数据处理能力。
### `$match`
- **功能**: 过滤文档,只将满足条件的文档传递给下一个阶段。类似于 `find()` 方法的查询条件。
- **建议**: 尽可能将 `$match` 放在管道的开头,以尽早减少需要处理的文档数量,提高效率。
- **示例**: 筛选出状态为 "A" 的订单。
```javascript
{ $match: { status: "A" } }
```
### `$project`
- **功能**: 重塑文档流。可以包含、排除、重命名字段,或者通过表达式计算新字段。
- **示例**: 只保留 `_id`、`name` 字段,并创建一个新的 `bonus` 字段。
```javascript
{ $project: { name: 1, bonus: { $multiply: ["$salary", 0.1] } } }
```
### `$group`
- **功能**: 按指定的 `_id` 表达式对文档进行分组,并对每个分组应用累加器表达式进行计算。
- **核心**: `_id` 字段定义了分组的键。
- **示例**: 按 `cust_id` 分组,并计算每个客户的订单总金额。
```javascript
{
$group: {
_id: "$cust_id",
totalAmount: { $sum: "$amount" }
}
}
```
### `$sort`
- **功能**: 对文档流进行排序,与 `find()` 中的 `sort()` 类似。
- **建议**: 如果需要排序,尽量在管道的早期阶段进行,特别是当排序字段有索引时。
- **示例**: 按 `totalAmount` 降序排序。
```javascript
{ $sort: { totalAmount: -1 } }
```
### `$limit` 和 `$skip`
- **功能**: 分别用于限制和跳过文档数量,实现分页。
- **示例**: 返回排序后的前 5 个结果。
```javascript
{ $limit: 5 }
```
### `$unwind`
- **功能**: 将文档中的数组字段拆分成多个文档,数组中的每个元素都会生成一个新的文档(与其他字段组合)。
- **示例**: 将 `tags` 数组拆分。
```javascript
// 输入: { _id: 1, item: "A", tags: ["x", "y"] }
{ $unwind: "$tags" }
// 输出:
// { _id: 1, item: "A", tags: "x" }
// { _id: 1, item: "A", tags: "y" }
```
### `$lookup`
- **功能**: 实现左外连接Left Outer Join将当前集合与另一个集合的文档进行关联。
- **示例**: 将 `orders` 集合与 `inventory` 集合关联起来。
```javascript
{
$lookup: {
from: "inventory",
localField: "item",
foreignField: "sku",
as: "inventory_docs"
}
}
```
---
## 聚合累加器表达式
累加器主要在 `$group` 阶段使用,用于对分组后的数据进行计算。
| 累加器 | 描述 |
| :--- | :--- |
| `$sum` | 计算总和 |
| `$avg` | 计算平均值 |
| `$min` | 获取最小值 |
| `$max` | 获取最大值 |
| `$first` | 获取每个分组的第一条文档的字段值 |
| `$last` | 获取每个分组的最后一条文档的字段值 |
| `$push` | 将字段值添加到一个数组中 |
| `$addToSet` | 将唯一的字段值添加到一个数组中 |
---
## 聚合管道优化
- **尽早过滤**: 将 `$match` 阶段放在管道的最前面。
- **尽早投影**: 使用 `$project` 移除不需要的字段,减少后续阶段的数据处理量。
- **利用索引**: 如果 `$match` 或 `$sort` 阶段的字段有索引MongoDB 可以利用它来优化性能。
- **避免在分片键上 `$unwind`**: 这可能会导致性能问题。
---
## 实践环节
### 需求描述
假设有一个 `sales` 集合,包含 `product`, `quantity`, `price`, `date` 字段。
1. **计算总销售额**: 计算每个产品的总销售额(`quantity * price`)。
2. **查找最畅销产品**: 按销售额降序排列,找出销售额最高的前 5 个产品。
3. **按月统计销售**: 按月份对所有销售数据进行分组,并计算每月的总销售额和平均订单金额。
4. **关联查询**: 假设还有一个 `products_for_aggregation` 集合(包含 `name`, `category`),使用 `$lookup` 将销售数据与产品类别关联起来,并按类别统计销售额。
5.
### 实践细节和结果验证
```javascript
// 准备工作:确保已在 mongo shell 中加载 data.js 文件
// 1. 计算每个产品的总销售额
db.sales.aggregate([
{
$group: {
_id: "$product",
totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
}
}
])
/*
预期结果:
[
{ _id: 'Mouse', totalRevenue: 125 },
{ _id: 'Keyboard', totalRevenue: 75 },
{ _id: 'Monitor', totalRevenue: 300 },
{ _id: 'Webcam', totalRevenue: 50 },
{ _id: 'Laptop', totalRevenue: 4700 }
]
*/
// 2. 查找最畅销的前 5 个产品(按销售额)
db.sales.aggregate([
{
$group: {
_id: "$product",
totalRevenue: { $sum: { $multiply: ["$quantity", "$price"] } }
}
},
{
$sort: { totalRevenue: -1 }
},
{
$limit: 5
}
])
/*
预期结果:
[
{ _id: 'Laptop', totalRevenue: 4700 },
{ _id: 'Monitor', totalRevenue: 300 },
{ _id: 'Mouse', totalRevenue: 125 },
{ _id: 'Keyboard', totalRevenue: 75 },
{ _id: 'Webcam', totalRevenue: 50 }
]
*/
// 3. 按月统计销售额
db.sales.aggregate([
{
$group: {
_id: { $month: "$date" }, // 按月份分组
totalMonthlyRevenue: { $sum: { $multiply: ["$quantity", "$price"] } },
averageOrderValue: { $avg: { $multiply: ["$quantity", "$price"] } }
}
},
{
$sort: { _id: 1 } // 按月份升序排序
}
])
/*
预期结果:
[
{ _id: 1, totalMonthlyRevenue: 1325, averageOrderValue: 441.666... },
{ _id: 2, totalMonthlyRevenue: 1675, averageOrderValue: 558.333... },
{ _id: 3, totalMonthlyRevenue: 2250, averageOrderValue: 1125 }
]
*/
// 4. 关联查询:按产品类别统计销售额
db.sales.aggregate([
// 阶段一: 计算每笔销售的销售额
{
$project: {
product: 1,
revenue: { $multiply: ["$quantity", "$price"] }
}
},
// 阶段二: 关联 products 集合获取类别信息
{
$lookup: {
from: "products_for_aggregation",
localField: "product",
foreignField: "name",
as: "productDetails"
}
},
// 阶段三: 展开 productDetails 数组
{
$unwind: "$productDetails"
},
// 阶段四: 按类别分组统计总销售额
{
$group: {
_id: "$productDetails.category",
totalCategoryRevenue: { $sum: "$revenue" }
}
}
])
/*
预期结果:
[
{ _id: 'Accessories', totalCategoryRevenue: 50 },
{ _id: 'Electronics', totalCategoryRevenue: 5200 }
]
*/
```

View File

@@ -0,0 +1,167 @@
# 查询进阶
本章节将带大家深入了解 MongoDB 强大的查询功能,包括使用查询操作符、投影、排序、分页以及正则表达式查询,可以更精确、更高效地从数据库中检索数据。
---
## 查询操作符
查询操作符是 MongoDB 查询语言的核心,它们可以帮助大家构建更复杂的查询条件。
### 比较操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$eq` | 等于 (Equal) | `db.inventory.find({ qty: { $eq: 20 } })` |
| `$ne` | 不等于 (Not Equal) | `db.inventory.find({ qty: { $ne: 20 } })` |
| `$gt` | 大于 (Greater Than) | `db.inventory.find({ qty: { $gt: 20 } })` |
| `$gte` | 大于等于 (Greater Than or Equal) | `db.inventory.find({ qty: { $gte: 20 } })` |
| `$lt` | 小于 (Less Than) | `db.inventory.find({ qty: { $lt: 20 } })` |
| `$lte` | 小于等于 (Less Than or Equal) | `db.inventory.find({ qty: { $lte: 20 } })` |
| `$in` | 在指定数组内 (In) | `db.inventory.find({ qty: { $in: [5, 15] } })` |
| `$nin` | 不在指定数组内 (Not In) | `db.inventory.find({ qty: { $nin: [5, 15] } })` |
### 逻辑操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$and` | 逻辑与,连接多个查询条件 | `db.inventory.find({ $and: [{ qty: { $lt: 20 } }, { price: { $gt: 10 } }] })` |
| `$or` | 逻辑或,满足任一查询条件 | `db.inventory.find({ $or: [{ qty: { $lt: 20 } }, { price: { $gt: 50 } }] })` |
| `$nor` | 逻辑非或,所有条件都不满足 | `db.inventory.find({ $nor: [{ price: 1.99 }, { sale: true }] })` |
| `$not` | 逻辑非,对指定条件取反 | `db.inventory.find({ price: { $not: { $gt: 1.99 } } })` |
### 元素操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$exists` | 判断字段是否存在 | `db.inventory.find({ qty: { $exists: true } })` |
| `$type` | 判断字段的数据类型 | `db.inventory.find({ qty: { $type: "number" } })` |
### 数组操作符
| 操作符 | 描述 | 示例 |
| :--- | :--- | :--- |
| `$all` | 匹配包含所有指定元素的数组 | `db.inventory.find({ tags: { $all: ["appliance", "school"] } })` |
| `$elemMatch` | 数组中至少有一个元素满足所有指定条件 | `db.inventory.find({ results: { $elemMatch: { product: "xyz", score: { $gte: 8 } } } })` |
| `$size` | 匹配指定大小的数组 | `db.inventory.find({ tags: { $size: 3 } })` |
---
## 投影Projection
投影用于限制查询结果中返回的字段,可以减少网络传输的数据量,并保护敏感字段。
- **包含字段**: 在 `find()` 方法的第二个参数中,将需要返回的字段设置为 `1`
```javascript
// 只返回 name 和 price 字段_id 默认返回
db.products.find({}, { name: 1, price: 1 })
```
- **排除字段**: 将不需要返回的字段设置为 `0`。
```javascript
// 返回除 description 之外的所有字段
db.products.find({}, { description: 0 })
```
- **排除 `_id` 字段**:
```javascript
db.products.find({}, { name: 1, price: 1, _id: 0 })
```
---
## 排序Sorting
使用 `sort()` 方法对查询结果进行排序。
- **升序 (Ascending)**: 将字段值设置为 `1`。
- **降序 (Descending)**: 将字段值设置为 `-1`。
```javascript
// 按价格升序排序
db.products.find().sort({ price: 1 })
// 按库存降序、名称升序排序
db.products.find().sort({ stock: -1, name: 1 })
```
---
## 分页Pagination
通过组合使用 `limit()` 和 `skip()` 方法,可以实现对查询结果的分页。
- **`limit()`**: 限制返回的文档数量。
- **`skip()`**: 跳过指定数量的文档。
```javascript
// 获取第 2 页数据,每页 10 条 (跳过前 10 条,返回 10 条)
db.products.find().skip(10).limit(10)
```
**注意**: 对于大数据量的集合,使用 `skip()` 进行深度分页可能会有性能问题。在这种情况下,建议使用基于范围的查询(如使用 `_id` 或时间戳)。
---
## 正则表达式查询
MongoDB 支持使用 PCRE (Perl Compatible Regular Expression) 语法的正则表达式进行字符串匹配。
```javascript
// 查询 name 字段以 'A' 开头的文档 (不区分大小写)
db.products.find({ name: { $regex: '^A', $options: 'i' } })
// 查询 name 字段包含 'pro' 的文档
db.products.find({ name: /pro/ })
```
---
## 实践操作
假设有一个 `students` 集合,包含以下字段:`name`, `age`, `major`, `gpa`, `courses` (数组)。
### 需求描述
1. **查询练习**:
- 查询所有主修 'Computer Science' 且 GPA 大于 3.5 的学生。
- 查询年龄在 20 到 22 岁之间(含)的学生。
- 查询选修了 'Database Systems' 和 'Data Structures' 两门课的学生。
2. **投影练习**:
- 只返回学生的姓名和专业。
3. **排序和分页练习**:
- 按 GPA 降序显示前 10 名学生。
4. **正则表达式练习**:
- 查询所有姓 'Li' 的学生。
### 实践细节和结果验证
```javascript
// 1. 查询练习
// 查询所有主修 'Computer Science' 且 GPA 大于 3.5 的学生
db.students.find({ major: 'Computer Science', gpa: { $gt: 3.5 } });
// 查询年龄在 20 到 22 岁之间(含)的学生
db.students.find({ age: { $gte: 20, $lte: 22 } });
// 查询选修了 'Database Systems' 和 'Data Structures' 两门课的学生
db.students.find({ courses: { $all: ['Database Systems', 'Data Structures'] } });
// 2. 投影练习
// 只返回学生的姓名和专业
db.students.find({}, { name: 1, major: 1, _id: 0 });
// 3. 排序和分页练习
// 按 GPA 降序显示前 10 名学生
db.students.find().sort({ gpa: -1 }).limit(10);
// 4. 正则表达式练习
// 查询所有姓 'Li' 的学生
db.students.find({ name: /^Li/ });
```

View File

@@ -0,0 +1,145 @@
// This file contains sample data for all MongoDB practice sessions.
// Load this file into your mongo shell using: load('/path/to/this/file/data.js')
// --- Data for: MongoDB基础操作.md ---
db.inventory.drop();
db.inventory.insertMany([
{ item: "journal", qty: 25, size: { h: 14, w: 21, uom: "cm" }, status: "A", tags: ["blank", "red"], price: 15, sale: false },
{ item: "notebook", qty: 50, size: { h: 8.5, w: 11, uom: "in" }, status: "A", tags: ["school", "office"], price: 20, sale: true },
{ item: "paper", qty: 100, size: { h: 8.5, w: 11, uom: "in" }, status: "D", tags: ["office", "storage"], price: 10, sale: false },
{ item: "planner", qty: 75, size: { h: 22.85, w: 30, uom: "cm" }, status: "D", tags: ["school", "organization"], price: 12, sale: true },
{ item: "postcard", qty: 45, size: { h: 10, w: 15.25, uom: "cm" }, status: "A", tags: ["appliance", "school", "storage"], price: 5, sale: false },
{ item: "mousepad", qty: 20, size: { h: 19, w: 22, uom: "cm" }, status: "P", price: 25, sale: true, results: [{ product: "xyz", score: 10 }, { product: "abc", score: 8 }] },
{ item: "keyboard", qty: 5, price: 55, sale: false, results: [{ product: "xyz", score: 7 }, { product: "abc", score: 6 }] },
{ item: "stapler", qty: 15, price: 1.99, sale: true, results: [{ product: "xyz", score: 9 }, { product: "abc", score: 4 }] }
]);
db.books.drop();
db.books.insertMany([
{
"title": "Dune",
"author": "Frank Herbert",
"published_year": 1965,
"genres": ["Science Fiction", "Fantasy"],
"stock": 8
},
{
"title": "Foundation",
"author": "Isaac Asimov",
"published_year": 1951,
"genres": ["Science Fiction"],
"stock": 15
},
{
"title": "1984",
"author": "George Orwell",
"published_year": 1949,
"genres": ["Science Fiction", "Dystopian"],
"stock": 5
},
{
"title": "Pride and Prejudice",
"author": "Jane Austen",
"published_year": 1813,
"genres": ["Romance", "Classic"],
"stock": 12
},
{
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"published_year": 1937,
"genres": ["Fantasy", "Adventure"],
"stock": 20
}
]);
// --- Data for: MongoDB进阶查询.md ---
db.students.drop();
db.students.insertMany([
{ student_id: 'S1001', name: 'Li Wei', age: 21, major: 'Computer Science', gpa: 3.8, courses: ['Database Systems', 'Data Structures', 'Operating Systems'] },
{ student_id: 'S1002', name: 'Zhang Min', age: 20, major: 'Computer Science', gpa: 3.6, courses: ['Algorithms', 'Computer Networks'] },
{ student_id: 'S1003', name: 'Wang Fang', age: 22, major: 'Mathematics', gpa: 3.9, courses: ['Calculus', 'Linear Algebra'] },
{ student_id: 'S1004', name: 'Li Juan', age: 20, major: 'Computer Science', gpa: 3.4, courses: ['Database Systems', 'Data Structures'] },
{ student_id: 'S1005', name: 'Chen Hao', age: 23, major: 'Physics', gpa: 3.2, courses: ['Mechanics', 'Electromagnetism'] },
{ student_id: 'S1006', name: 'Liu Yang', age: 21, major: 'Computer Science', gpa: 3.7, courses: ['Database Systems', 'Artificial Intelligence'] }
]);
// --- Data for: MongoDB索引优化.md ---
db.products_for_indexing.drop();
db.products_for_indexing.insertMany([
{ "category": "Electronics", "brand": "Sony", "price": 1200 },
{ "category": "Electronics", "brand": "Samsung", "price": 950 },
{ "category": "Electronics", "brand": "Apple", "price": 1500 },
{ "category": "Clothing", "brand": "Nike", "price": 150 },
{ "category": "Clothing", "brand": "Adidas", "price": 120 },
{ "category": "Books", "brand": "PublisherA", "price": 25 },
{ "category": "Books", "brand": "PublisherB", "price": 30 },
{ "category": "Electronics", "brand": "Sony", "price": 800 }
]);
// --- Data for: MongoDB聚合框架.md ---
db.sales.drop();
db.sales.insertMany([
{ "product": "Laptop", "quantity": 1, "price": 1200, "date": new Date("2023-01-15") },
{ "product": "Mouse", "quantity": 2, "price": 25, "date": new Date("2023-01-15") },
{ "product": "Keyboard", "quantity": 1, "price": 75, "date": new Date("2023-01-16") },
{ "product": "Laptop", "quantity": 1, "price": 1300, "date": new Date("2023-02-10") },
{ "product": "Monitor", "quantity": 1, "price": 300, "date": new Date("2023-02-12") },
{ "product": "Mouse", "quantity": 3, "price": 25, "date": new Date("2023-02-20") },
{ "product": "Laptop", "quantity": 2, "price": 1100, "date": new Date("2023-03-05") },
{ "product": "Webcam", "quantity": 1, "price": 50, "date": new Date("2023-03-07") }
]);
db.products_for_aggregation.drop();
db.products_for_aggregation.insertMany([
{ "name": "Laptop", "category": "Electronics" },
{ "name": "Mouse", "category": "Electronics" },
{ "name": "Keyboard", "category": "Electronics" },
{ "name": "Monitor", "category": "Electronics" },
{ "name": "Webcam", "category": "Accessories" },
{ "name": "T-Shirt", "category": "Apparel" }
]);
// --- Data for: MongoDB数据建模.md ---
// Note: The following commands use shell-specific functions like ObjectId() and ISODate().
// They are intended to be run directly in the mongo shell.
db.users.drop();
db.posts.drop();
db.comments.drop();
const userJohnId = new ObjectId();
db.users.insertOne({
"_id": userJohnId,
"username": "john_doe",
"email": "john@example.com"
});
const postMongoId = new ObjectId();
db.posts.insertOne({
"_id": postMongoId,
"author_id": userJohnId,
"author_name": "john_doe",
"title": "MongoDB 入门",
"content": "MongoDB 是一个强大的 NoSQL 数据库...",
"created_at": new Date("2024-01-15"),
"tags": ["MongoDB", "Database", "NoSQL"]
});
const userAliceId = new ObjectId();
db.users.insertOne({
"_id": userAliceId,
"username": "alice",
"email": "alice@example.com"
});
db.comments.insertOne({
"_id": new ObjectId(),
"post_id": postMongoId,
"user_id": userAliceId,
"username": "alice",
"content": "写得很好!",
"created_at": new Date("2024-01-16")
});
print("All sample data have been loaded.");

View File

@@ -0,0 +1,65 @@
# MongoDB 课程文档规范
本文档旨在定义 MongoDB 课程相关文件的用途、格式风格和编写原则,以确保课程内容的一致性、专业性和易读性。
---
## 1. 文件定义
### 1.1. 课程大纲 (`mongodb_tpl.md`)
- **用途**:作为 MongoDB 课程的整体教学计划和结构指南。它定义了课程的章节、知识点、学时分配和实践环节。
- **目标读者**:课程开发者、上课教师。
- **核心要求**:结构清晰,逻辑性强,全面覆盖 MongoDB 的核心知识体系。
### 1.2. 课程内容 (`n_MongoDB.md`)
- **用途**:根据课程大纲,详细阐述每个知识点的具体内容,包括理论讲解、代码示例、实践操作和练习题。
- **目标读者**:学生。
- **核心要求**:内容详实,通俗易懂,理论与实践相结合。
---
## 2. 编写原则
### 2.1. 内容组织:从浅入深
课程内容应遵循认知规律,从基础概念开始,逐步深入到高级主题和实战应用。
- **基础先行**:首先介绍 NoSQL 和 MongoDB 的基本概念、架构和安装部署。
- **核心操作**:然后讲解 CRUD 操作、查询和索引等核心技能。
- **高级进阶**:接着深入数据建模、聚合框架、副本集和分片等高级主题。
- **实战应用**:最后通过项目实战,巩固所学知识,培养解决实际问题的能力。
### 2.2. 语言风格:通俗易懂
- **简化复杂概念**:使用简单的语言和比喻来解释复杂的技术概念。
- **图文并茂**:适当使用图表、流程图和示意图,帮助理解抽象的知识。
- **代码注释**:所有代码示例都应有清晰的注释,解释代码的功能和逻辑。
### 2.3. 理论与实践融合
- **理论指导实践**:每个理论知识点都应配有相应的实践案例或代码示例。
- **实践巩固理论**:通过实验、练习和项目,加深对理论知识的理解和应用。
- **场景驱动**:结合真实的应用场景(如电商、社交、物联网等)进行讲解,激发学习兴趣。
### 2.4. 格式风格:统一规范
- **Markdown 语法**:统一使用 Markdown 进行文档编写。
- **标题层级**:遵循清晰的标题层级结构(`#``##``###`),与课程大纲保持一致
- **代码块**:使用代码块来格式化代码示例,并注明语言类型。(bash/shell 统一用 shell)
- **术语规范**:专业术语首次出现时应予以解释,并保持中英文术语的统一性。
- **实践操作**:每一章内容最后有一个实践环节,标题为 **实践操作**,有需求描述、实践细节和结果验证。并将实践细节和结果验证放到同一个代码块中。
- **其他规范**
- 从 MongoDB 基础概念开始作为 `#` 标题,其他平级标题也用 `#` 格式
- 不需要课程概述
---
## 3. 协作流程
1. **大纲评审**:首先共同评审和确定 `mongodb_tpl.md` 中的课程大纲。
2. **内容开发**:根据大纲,分工协作编写 `n_MongoDB.md` 的具体内容。
3. **内容评审**定期进行内容评审Peer Review确保内容质量和风格统一。
4. **持续迭代**:根据教学反馈和技术发展,持续更新和完善课程内容。

View File

@@ -0,0 +1,373 @@
# 基础概念
## NoSQL 数据库概述
- 关系型数据库 vs NoSQL 数据库
- NoSQL 数据库分类(文档型、键值型、列族型、图形型)
- MongoDB 在 NoSQL 生态中的定位
## 核心概念
- 文档Document与 BSON 格式
- 集合Collection概念
- 数据库Database结构
- MongoDB 与关系型数据库术语对比
## 架构原理
- MongoDB 存储引擎WiredTiger
- 内存映射文件系统
- 索引机制
- 查询优化器
## 文档学习
---
# 环境搭建
## 安装部署
- Linux 环境安装RockyLinux9
## 相关配置
- 配置文件详解
- 数据目录设置
- 日志配置
- 网络绑定设置
## 服务管理
- systemctl 服务管理
- 开机自启动配置
- 服务状态监控
## 实践环节
- 生产环境配置文件模板
---
# 基础操作
## MongoDB Shell 使用
- mongo shell 连接
- 基本命令操作
- 脚本执行
## 数据库操作
- 创建和删除数据库
- 数据库列表查看
- 数据库切换
## 集合操作
- 创建和删除集合
- 集合属性设置
- 集合统计信息
## 文档 CRUD 操作
- 插入文档insert、insertOne、insertMany
- 查询文档find、findOne
- 更新文档update、updateOne、updateMany
- 删除文档remove、deleteOne、deleteMany
## 实践环节
- 基础 CRUD 操作练习
- 批量数据导入导出
- 数据迁移实验
---
# 进阶查询
## 查询操作符
- 比较操作符($eq、$ne、$gt、$gte、$lt、$lte
- 逻辑操作符($and、$or、$not、$nor
- 元素操作符($exists、$type
- 数组操作符($in、$nin、$all、$size
## 查询修饰符
- 排序sort
- 限制limit
- 跳过skip
- 投影projection
## 正则表达式查询
- 正则表达式语法
- 模糊查询实现
- 性能考虑
## 聚合框架基础
- 聚合管道概念
- 常用聚合操作符
- 分组和统计
## 实践环节
- 复杂查询练习
- 聚合查询实例
- 查询性能测试
---
# 索引优化
## 索引基础
- 索引原理和作用
- B-Tree 索引结构
- 索引的优缺点
## 索引类型
- 单字段索引
- 复合索引
- 多键索引
- 文本索引
- 地理空间索引
- 哈希索引
## 索引管理
- 创建索引createIndex
- 删除索引dropIndex
- 查看索引getIndexes
- 重建索引reIndex
## 查询性能优化
- 执行计划分析explain
- 索引选择策略
- 查询优化技巧
## 实践环节
- 索引创建和管理
- 性能测试对比
- 查询优化案例
---
# 数据建模
## 文档设计原则
- 嵌入式文档 vs 引用
- 数据冗余策略
- 原子性考虑
## Schema 设计模式
- 一对一关系建模
- 一对多关系建模
- 多对多关系建模
- 树形结构建模
## 数据验证
- Schema 验证规则
- 数据类型约束
- 自定义验证器
## 实践环节
- 电商系统数据建模
- 社交网络数据建模
- 内容管理系统建模
---
# 聚合框架
## 聚合管道详解
- $match 阶段
- $group 阶段
- $sort 阶段
- $project 阶段
- $limit 和 $skip 阶段
## 高级聚合操作
- $lookup关联查询
- $unwind数组展开
- $facet多维聚合
- $bucket分桶聚合
## 聚合表达式
- 算术表达式
- 字符串表达式
- 日期表达式
- 条件表达式
## MapReduce
- MapReduce 概念
- 编写 Map 和 Reduce 函数
- 性能对比
## 实践环节
- 数据分析报表
- 实时统计系统
- 复杂业务逻辑实现
---
# 副本集Replica Set
## 副本集概念
- 高可用性需求
- 副本集架构
- 主从复制原理
## 副本集架构
- Primary 节点
- Secondary 节点
- Arbiter 节点
- 选举机制
## 副本集部署
- 单机多实例部署
- 多机部署
- 配置文件设置
- 初始化副本集
## 副本集管理
- 添加和删除节点
- 优先级设置
- 故障转移测试
- 数据同步监控
## 实践环节
- 副本集搭建
- 故障模拟和恢复
- 性能测试
---
# 分片Sharding
## 分片概念
- 水平扩展需求
- 分片架构组件
- 分片键选择
## 分片架构
- Config Server
- Shard Server
- mongos 路由
- 数据分布策略
## 分片部署
- 分片集群搭建
- 配置服务器部署
- 路由服务器配置
- 分片初始化
## 分片管理
- 分片键管理
- 数据均衡
- 分片添加和删除
- 性能监控
## 实践环节
- 分片集群搭建
- 数据分片测试
- 扩容演练
---
# 安全管理
## 认证机制
- 用户认证
- 角色权限管理
- LDAP 集成
## 授权控制
- 基于角色的访问控制RBAC
- 数据库级权限
- 集合级权限
## 审计日志
- 审计功能配置
- 日志分析
- 合规性要求
## 实践环节
- 安全配置实施
- 权限测试
- 安全审计
---
# 备份与恢复
## 备份策略
- 全量备份
- 增量备份
- 实时备份
## 备份工具
- mongodump/mongorestore
- 文件系统快照
- 第三方备份工具
## 恢复策略
- 完全恢复
- 时间点恢复
- 选择性恢复
## 备份自动化
- 备份脚本编写
- 定时任务配置
- 备份验证
## 实践环节
- 备份恢复演练
- 自动化脚本开发
- 灾难恢复测试
---
# 性能监控与调优
## 性能监控
- mongostat 工具
- mongotop 工具
- 第三方监控工具
## 性能指标
- 连接数监控
- 操作延迟
- 内存使用
- 磁盘 I/O
## 性能调优
- 查询优化
- 索引优化
- 硬件优化
- 配置优化
## 故障诊断
- 慢查询分析
- 锁竞争分析
- 内存泄漏排查
## 实践环节
- 性能基准测试
- 瓶颈分析
- 调优实施
---

3995
数据库/MySQL.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
```shell
# install mysql 8.0.43
yum install mysql84-community-release-el9-2.noarch.rpm
dnf config-manager --disable mysql-8.4-lts-community
dnf config-manager --disable mysql-tools-8.4-lts-community
dnf config-manager --enable mysql80-community
dnf config-manager --enable mysql-tools-community
yum repolist enabled | grep mysql
yum install mysql-community-server
mysqld -V
grep 'temporary password' /var/log/mysqld.log
mysql -uroot -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass4!';
show databases;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

3619
数据库/Redis.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
# Redis 基础概念
## NoSQL 数据库概述
### NoSQL 数据库的特点和分类
NoSQLNot 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 多路复用实现:
- Linuxepoll
- macOS/FreeBSDkqueue
- Windowsselect
**事件循环优势:**
1. **高并发支持**
- 单线程处理多个客户端
- 避免线程创建开销
- 内存使用效率高
2. **响应及时**
- 事件驱动,实时响应
- 无阻塞等待
- 低延迟处理
3. **资源利用率高**
- CPU 利用率高
- 内存占用少
- 系统资源消耗低

View 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
```

View 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
```

View 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 # 失败返回0target_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
```
## 实践操作

View File

@@ -0,0 +1,502 @@
# Redis 持久化
Redis 是一个内存数据库为了保证数据的持久性和可靠性Redis 提供了多种持久化机制。理解和正确配置持久化是 Redis 生产环境部署的关键。
## RDB 持久化
### RDB 概述
RDBRedis 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 概述
AOFAppend 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 文件使用 RESPRedis 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"
```
## 实践操作

View 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
```
**运行 IDRun 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
```

View 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
```

View 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-163835461个槽
**数据路由**
```shell
# 数据路由过程
# 1. 客户端计算槽位
key = "user:1001"
slot = CRC16(key) % 16384
# 假设 slot = 8000
# 2. 查找负责的节点
# 槽位 8000 属于节点B5461-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/
```

View 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 性能、优化内存使用,并测试性能提升效果。
### 实践细节和结果验证

View 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+ 引入了 ACLAccess 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"
```

File diff suppressed because it is too large Load Diff

View 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 工具`

View File

@@ -0,0 +1,3 @@
[Redis官网地址](https://redis.io/)
[Redis项目地址](https://github.com/redis/redis)

View 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 "脚本执行完成"

View 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从)"

View 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从)"

View 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))"

View 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-*"

View 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)

View 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"

View 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()

View 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

View 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)

View 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}")

View 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
- 第三方监控方案
## 实践操作
- 使用管理工具
- 集成开发框架
- 配置监控方案

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
数据库/redis/RDB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Some files were not shown because too many files have changed in this diff Show More