08-27-周三_17-09-29
1170
数据库/DQL查询练习.md
Normal file
BIN
数据库/DQL查询练习/sql_joins.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
801
数据库/Memcached.md
Normal 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是一块内存空间,默认大小为1M,memcached会把一个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大小的slab,slab会被切分为N个小的内存块,这个小的内存块的大小取决于slabclass_t结构上的size的大小
|
||||
2. 每个slabclass_t都只存储一定大小范围的数据,并且下一个slabclass切割的chunk块大于前一个slabclass切割的chunk块大小
|
||||
3. memcached中slabclass数组默认大小为64,slabclass切割块大小的增长因子默认是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
|
||||
```
|
||||
|
||||
|
||||
|
||||
BIN
数据库/Memcached/web_6.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
2376
数据库/MongoDB.md
Normal file
BIN
数据库/MongoDB/MongoDB_Logos.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
数据库/MongoDB/MongoDB对比关系型.png
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
数据库/MongoDB/MongoDB支持语言.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
数据库/MongoDB/MongoDB简介01.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
数据库/MongoDB/MongoDB简介02.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
数据库/MongoDB/下载二进制包.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
数据库/MongoDB/分片集群01.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
数据库/MongoDB/分片集群架构.png
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
数据库/MongoDB/副本集01.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
数据库/MongoDB/副本集02.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
数据库/MongoDB/哈希分片.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
数据库/MongoDB/涵盖查询.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
数据库/MongoDB/范围分片.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
数据库/MongoDB/范围策略.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
3
数据库/MongoDB_2025/MongoDB.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[MongoDB官网地址](https://www.mongodb.com/)
|
||||
|
||||
[MongoDB项目地址](https://github.com/mongodb/mongo)
|
||||
210
数据库/MongoDB_2025/MongoDB分片.md
Normal 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),以保证其高可用性。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 分片键(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 }
|
||||
)
|
||||
```
|
||||
188
数据库/MongoDB_2025/MongoDB副本集.md
Normal 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. 性能测试 - 验证读写分离效果
|
||||
```
|
||||
161
数据库/MongoDB_2025/MongoDB基础操作.md
Normal 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
|
||||
```
|
||||
113
数据库/MongoDB_2025/MongoDB基础概念.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# 基础概念
|
||||
|
||||
本章节将介绍 NoSQL 数据库的基本概念,并深入探讨 MongoDB 的核心概念、架构原理,为后续的学习打下坚实的基础。
|
||||
|
||||
---
|
||||
|
||||
## NoSQL 数据库概述
|
||||
|
||||
NoSQL(Not 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 官方文档中关于“核心概念”的部分。
|
||||
215
数据库/MongoDB_2025/MongoDB备份恢复.md
Normal 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. 副本集跨数据中心部署
|
||||
|
||||
```
|
||||
273
数据库/MongoDB_2025/MongoDB安全管理.md
Normal 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")
|
||||
# 预期结果:显示用户的最新角色信息
|
||||
```
|
||||
300
数据库/MongoDB_2025/MongoDB性能优化.md
Normal 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 } }
|
||||
])
|
||||
# 预期结果: 显示各集合查询的平均执行时间统计
|
||||
```
|
||||
299
数据库/MongoDB_2025/MongoDB数据建模.md
Normal 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 规则
|
||||
});
|
||||
```
|
||||
171
数据库/MongoDB_2025/MongoDB环境搭建.md
Normal 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
|
||||
```
|
||||
|
||||
267
数据库/MongoDB_2025/MongoDB索引优化.md
Normal 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();
|
||||
```
|
||||
267
数据库/MongoDB_2025/MongoDB聚合框架.md
Normal 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 }
|
||||
]
|
||||
*/
|
||||
```
|
||||
167
数据库/MongoDB_2025/MongoDB进阶查询.md
Normal 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/ });
|
||||
```
|
||||
145
数据库/MongoDB_2025/data.js
Normal 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.");
|
||||
65
数据库/MongoDB_2025/mongodb_doc.md
Normal 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. **持续迭代**:根据教学反馈和技术发展,持续更新和完善课程内容。
|
||||
373
数据库/MongoDB_2025/mongodb_tpl.md
Normal 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
17
数据库/MySQL_2025/ops.md
Normal 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;
|
||||
```
|
||||
BIN
数据库/MySQL数据库/157ueBBseL6MvenH.png!thumbnail
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
数据库/MySQL数据库/5TsFS9HElaslP5ss.png!thumbnail
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
数据库/MySQL数据库/DsbxGtxBZzy6d1kN.png!thumbnail
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
数据库/MySQL数据库/G1WukGpm4kWFCtKy.png!thumbnail
Normal file
|
After Width: | Height: | Size: 576 KiB |
BIN
数据库/MySQL数据库/HET6YsqLtNayuI6Q.png!thumbnail
Normal file
|
After Width: | Height: | Size: 615 KiB |
BIN
数据库/MySQL数据库/JnL2YEThjuHERQPZ.png!thumbnail
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
数据库/MySQL数据库/KkaagjFIBB8C0CCF.png!thumbnail
Normal file
|
After Width: | Height: | Size: 315 KiB |
BIN
数据库/MySQL数据库/L8Ndjn2BuNL9IUl2.png!thumbnail
Normal file
|
After Width: | Height: | Size: 980 KiB |
BIN
数据库/MySQL数据库/MHA_process.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
数据库/MySQL数据库/MHA_topo.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
数据库/MySQL数据库/R5j17V2j8h153YXZ.png!thumbnail
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
数据库/MySQL数据库/Tw0AJl9FjrygD1S4.png!thumbnail
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
数据库/MySQL数据库/WCtzPCFRsaTUTp3A.png!thumbnail
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
数据库/MySQL数据库/agMqU13UrbmuyFz0.png!thumbnail
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
数据库/MySQL数据库/check_done.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
数据库/MySQL数据库/k1P7hTxOM3vKCIG2.png!thumbnail
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
数据库/MySQL数据库/lK3mQW6m9VMwIgVD.png!thumbnail
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
数据库/MySQL数据库/mysql_goal.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
数据库/MySQL数据库/remote_mem_larger_local_disk.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
数据库/MySQL数据库/semi_synchronized_dual_master.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
数据库/MySQL数据库/sql_MS.jpg
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
数据库/MySQL数据库/tL76EP1rBEQKcpeU.png!thumbnail
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
数据库/MySQL数据库/uCoOhC6Rn5HuTfxg.png!thumbnail
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
数据库/MySQL数据库/uxH9iBpInyy8pwrk.png!thumbnail
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
数据库/MySQL数据库/yhWTJJz8GZkilbZN.png!thumbnail
Normal file
|
After Width: | Height: | Size: 384 KiB |
3619
数据库/Redis.md
Normal file
402
数据库/Redis_2025/01_Redis基础概念.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Redis 基础概念
|
||||
|
||||
## NoSQL 数据库概述
|
||||
|
||||
### NoSQL 数据库的特点和分类
|
||||
|
||||
NoSQL(Not Only SQL)数据库是一类非关系型数据库的统称,它们不使用传统的表格关系模型来存储数据。NoSQL 数据库具有以下特点:
|
||||
|
||||
**主要特点:**
|
||||
- **灵活的数据模型**:支持多种数据结构,如键值对、文档、列族、图等
|
||||
- **水平扩展性**:易于在多台服务器间分布数据
|
||||
- **高性能**:针对特定用例优化,读写性能优异
|
||||
- **高可用性**:支持分布式部署,具备容错能力
|
||||
- **最终一致性**:放宽了 ACID 约束,采用 BASE 理论
|
||||
|
||||
**NoSQL 数据库分类:**
|
||||
|
||||
1. **键值存储(Key-Value Store)**
|
||||
- 代表产品:Redis、Amazon DynamoDB、Riak
|
||||
- 特点:简单的键值对存储,查询速度快
|
||||
- 适用场景:缓存、会话存储、购物车
|
||||
|
||||
2. **文档数据库(Document Database)**
|
||||
- 代表产品:MongoDB、CouchDB、Amazon DocumentDB
|
||||
- 特点:存储半结构化文档(如 JSON、XML)
|
||||
- 适用场景:内容管理、用户配置、产品目录
|
||||
|
||||
3. **列族数据库(Column Family)**
|
||||
- 代表产品:Cassandra、HBase、Amazon SimpleDB
|
||||
- 特点:按列存储数据,适合大数据分析
|
||||
- 适用场景:时间序列数据、日志分析、IoT 数据
|
||||
|
||||
4. **图数据库(Graph Database)**
|
||||
- 代表产品:Neo4j、Amazon Neptune、ArangoDB
|
||||
- 特点:存储节点和关系,擅长处理复杂关联
|
||||
- 适用场景:社交网络、推荐系统、知识图谱
|
||||
|
||||
### 关系型数据库 vs NoSQL 数据库
|
||||
|
||||
| 特性 | 关系型数据库 (RDBMS) | NoSQL 数据库 |
|
||||
|------|---------------------|---------------|
|
||||
| **数据模型** | 表格结构,固定模式 | 灵活模式,多种数据结构 |
|
||||
| **扩展性** | 垂直扩展(Scale Up) | 水平扩展(Scale Out) |
|
||||
| **一致性** | 强一致性(ACID) | 最终一致性(BASE) |
|
||||
| **查询语言** | 标准 SQL | 各自专有 API |
|
||||
| **事务支持** | 完整的 ACID 事务 | 有限的事务支持 |
|
||||
| **性能** | 复杂查询性能好 | 简单操作性能优异 |
|
||||
| **成本** | 许可费用高 | 多数开源免费 |
|
||||
| **适用场景** | 复杂业务逻辑,强一致性要求 | 大数据,高并发,快速开发 |
|
||||
|
||||
### NoSQL 数据库的应用场景
|
||||
|
||||
**1. 大数据处理**
|
||||
- 海量数据存储和分析
|
||||
- 实时数据处理
|
||||
- 数据仓库和数据湖
|
||||
|
||||
**2. 高并发 Web 应用**
|
||||
- 社交媒体平台
|
||||
- 电商网站
|
||||
- 在线游戏
|
||||
|
||||
**3. 实时应用**
|
||||
- 实时推荐系统
|
||||
- 实时监控和告警
|
||||
- 实时聊天应用
|
||||
|
||||
**4. 内容管理**
|
||||
- 内容发布系统
|
||||
- 数字资产管理
|
||||
- 多媒体存储
|
||||
|
||||
**5. IoT 和传感器数据**
|
||||
- 设备数据收集
|
||||
- 时间序列数据
|
||||
- 地理位置数据
|
||||
|
||||
## Redis 简介
|
||||
|
||||
### Redis 的定义和特点
|
||||
|
||||
**Redis**(Remote Dictionary Server)是一个开源的、基于内存的键值对数据库,由 Salvatore Sanfilippo 于 2009 年开发。Redis 以其卓越的性能和丰富的数据结构而闻名。
|
||||
|
||||
**核心特点:**
|
||||
|
||||
1. **内存存储**
|
||||
- 所有数据存储在内存中,读写速度极快
|
||||
- 支持数据持久化到磁盘
|
||||
- 内存使用效率高
|
||||
|
||||
2. **丰富的数据结构**
|
||||
- 支持字符串、列表、集合、有序集合、哈希等
|
||||
- 每种数据结构都有专门的操作命令
|
||||
- 支持复杂的数据操作
|
||||
|
||||
3. **高性能**
|
||||
- 单线程模型,避免锁竞争
|
||||
- 基于事件驱动的 I/O 多路复用
|
||||
- 读写性能可达 10 万+ QPS
|
||||
|
||||
4. **持久化支持**
|
||||
- RDB 快照持久化
|
||||
- AOF 日志持久化
|
||||
- 混合持久化模式
|
||||
|
||||
5. **高可用性**
|
||||
- 主从复制
|
||||
- 哨兵模式
|
||||
- 集群模式
|
||||
|
||||
6. **原子性操作**
|
||||
- 所有操作都是原子性的
|
||||
- 支持事务
|
||||
- 支持 Lua 脚本
|
||||
|
||||
### Redis 的数据结构
|
||||
|
||||
Redis 支持五种基本数据结构:
|
||||
|
||||
**1. 字符串(String)**
|
||||
```shell
|
||||
# 基本操作
|
||||
SET key "Hello Redis"
|
||||
GET key
|
||||
INCR counter
|
||||
DECR counter
|
||||
```
|
||||
|
||||
**2. 列表(List)**
|
||||
```shell
|
||||
# 列表操作
|
||||
LPUSH mylist "world"
|
||||
LPUSH mylist "hello"
|
||||
LRANGE mylist 0 -1
|
||||
```
|
||||
|
||||
**3. 集合(Set)**
|
||||
```shell
|
||||
# 集合操作
|
||||
SADD myset "apple"
|
||||
SADD myset "banana"
|
||||
SMEMBERS myset
|
||||
```
|
||||
|
||||
**4. 有序集合(Sorted Set)**
|
||||
```shell
|
||||
# 有序集合操作
|
||||
ZADD leaderboard 100 "player1"
|
||||
ZADD leaderboard 200 "player2"
|
||||
ZRANGE leaderboard 0 -1 WITHSCORES
|
||||
```
|
||||
|
||||
**5. 哈希(Hash)**
|
||||
```shell
|
||||
# 哈希操作
|
||||
HSET user:1 name "John"
|
||||
HSET user:1 age 30
|
||||
HGETALL user:1
|
||||
```
|
||||
|
||||
### Redis 的应用场景
|
||||
|
||||
**1. 缓存系统**
|
||||
- 数据库查询结果缓存
|
||||
- 页面缓存
|
||||
- 对象缓存
|
||||
- 减少数据库压力,提高响应速度
|
||||
|
||||
**2. 会话存储**
|
||||
- Web 应用会话管理
|
||||
- 分布式会话共享
|
||||
- 用户状态保持
|
||||
|
||||
**3. 消息队列**
|
||||
- 发布/订阅模式
|
||||
- 任务队列
|
||||
- 实时消息推送
|
||||
|
||||
**4. 排行榜和计数器**
|
||||
- 游戏排行榜
|
||||
- 网站访问统计
|
||||
- 点赞数、评论数统计
|
||||
|
||||
**5. 分布式锁**
|
||||
- 防止并发操作冲突
|
||||
- 资源访问控制
|
||||
- 分布式系统协调
|
||||
|
||||
**6. 实时分析**
|
||||
- 实时数据统计
|
||||
- 用户行为分析
|
||||
- 业务指标监控
|
||||
|
||||
### Redis 的优势和局限性
|
||||
|
||||
**优势:**
|
||||
|
||||
1. **极高的性能**
|
||||
- 内存操作,读写速度快
|
||||
- 单线程模型,无锁竞争
|
||||
- 支持管道操作,批量处理
|
||||
|
||||
2. **丰富的数据结构**
|
||||
- 多种数据类型满足不同需求
|
||||
- 原生支持复杂操作
|
||||
- 减少应用层代码复杂度
|
||||
|
||||
3. **高可用性**
|
||||
- 多种部署模式
|
||||
- 自动故障转移
|
||||
- 数据冗余保护
|
||||
|
||||
4. **易于使用**
|
||||
- 简单的命令接口
|
||||
- 丰富的客户端库
|
||||
- 详细的文档支持
|
||||
|
||||
5. **活跃的社区**
|
||||
- 开源免费
|
||||
- 持续更新
|
||||
- 广泛的生态系统
|
||||
|
||||
**局限性:**
|
||||
|
||||
1. **内存限制**
|
||||
- 数据量受内存大小限制
|
||||
- 内存成本相对较高
|
||||
- 需要合理的内存管理策略
|
||||
|
||||
2. **单线程模型**
|
||||
- CPU 密集型操作可能阻塞
|
||||
- 无法充分利用多核 CPU
|
||||
- 大数据量操作需要优化
|
||||
|
||||
3. **持久化开销**
|
||||
- RDB 快照可能丢失数据
|
||||
- AOF 日志影响性能
|
||||
- 需要权衡性能和数据安全
|
||||
|
||||
4. **复杂查询支持有限**
|
||||
- 不支持复杂的关联查询
|
||||
- 没有标准的查询语言
|
||||
- 需要在应用层处理复杂逻辑
|
||||
|
||||
## Redis 架构
|
||||
|
||||
### Redis 的内存模型
|
||||
|
||||
Redis 采用内存存储模型,所有数据都保存在内存中,这是其高性能的关键所在。
|
||||
|
||||
**内存布局:**
|
||||
|
||||
1. **数据存储区域**
|
||||
- 用户数据:存储实际的键值对数据
|
||||
- 过期字典:存储键的过期时间信息
|
||||
- 数据库字典:存储不同数据库的数据
|
||||
|
||||
2. **缓冲区域**
|
||||
- 输入缓冲区:存储客户端发送的命令
|
||||
- 输出缓冲区:存储返回给客户端的结果
|
||||
- AOF 缓冲区:存储 AOF 日志数据
|
||||
|
||||
3. **复制缓冲区**
|
||||
- 复制积压缓冲区:主从复制时使用
|
||||
- 从服务器输出缓冲区:向从服务器发送数据
|
||||
|
||||
**内存管理策略:**
|
||||
|
||||
1. **内存分配**
|
||||
- 使用 jemalloc 内存分配器
|
||||
- 减少内存碎片
|
||||
- 提高内存使用效率
|
||||
|
||||
2. **内存回收**
|
||||
- 过期键删除:定期删除和惰性删除
|
||||
- 内存淘汰策略:LRU、LFU、随机等
|
||||
- 内存压缩:对小对象进行压缩存储
|
||||
|
||||
### Redis 的线程模型演进
|
||||
|
||||
Redis 的线程模型在不同版本中有重要演进,从单线程到混合线程模型。
|
||||
|
||||
**Redis 6.0 之前:单线程模型**
|
||||
|
||||
Redis 6.0 之前采用单线程模型处理所有客户端请求:
|
||||
|
||||
1. **单线程特点**
|
||||
- 主线程处理所有网络 I/O 和命令执行
|
||||
- 避免锁竞争和上下文切换
|
||||
- 保证操作的原子性
|
||||
- 简化代码实现和调试
|
||||
|
||||
2. **单线程优势**
|
||||
- 无需考虑线程安全问题
|
||||
- CPU 缓存命中率高
|
||||
- 减少内存访问延迟
|
||||
- 所有操作都是原子性的
|
||||
|
||||
**Redis 6.0+:混合线程模型**
|
||||
|
||||
Redis 6.0 引入了多线程 I/O 处理,但保持命令执行的单线程特性:
|
||||
|
||||
1. **多线程 I/O 处理**
|
||||
- 网络 I/O 读写使用多线程
|
||||
- 提高网络吞吐量
|
||||
- 减少网络延迟
|
||||
- 更好地利用多核 CPU
|
||||
|
||||
2. **单线程命令执行**
|
||||
- 命令解析和执行仍在主线程
|
||||
- 保持数据操作的原子性
|
||||
- 避免复杂的锁机制
|
||||
- 确保数据一致性
|
||||
|
||||
3. **线程模型配置**
|
||||
```bash
|
||||
# 启用多线程 I/O(默认关闭)
|
||||
io-threads-do-reads yes
|
||||
|
||||
# 设置 I/O 线程数量(建议为 CPU 核心数)
|
||||
io-threads 4
|
||||
```
|
||||
|
||||
**为什么 Redis 仍能保持高性能?**
|
||||
|
||||
1. **内存操作优势**
|
||||
- 所有数据在内存中,访问速度快
|
||||
- 避免磁盘 I/O 延迟
|
||||
- 内存带宽充分利用
|
||||
- 高效的内存管理
|
||||
|
||||
2. **优化的数据结构**
|
||||
- 针对不同场景优化的数据结构
|
||||
- 减少不必要的内存拷贝
|
||||
- 高效的算法实现
|
||||
- 内存压缩和优化
|
||||
|
||||
3. **事件驱动架构**
|
||||
- 非阻塞 I/O 模型
|
||||
- 事件循环处理
|
||||
- 高并发连接支持
|
||||
- 异步处理机制
|
||||
|
||||
4. **多线程 I/O 优化**
|
||||
- 网络 I/O 并行处理
|
||||
- 减少网络瓶颈
|
||||
- 提高整体吞吐量
|
||||
- 更好的资源利用
|
||||
|
||||
### Redis 的事件驱动机制
|
||||
|
||||
Redis 使用事件驱动模型来处理客户端请求和内部任务。
|
||||
|
||||
**事件类型:**
|
||||
|
||||
1. **文件事件(File Event)**
|
||||
- 处理客户端连接
|
||||
- 读取客户端命令
|
||||
- 发送响应数据
|
||||
- 基于 I/O 多路复用实现
|
||||
|
||||
2. **时间事件(Time Event)**
|
||||
- 定期执行的任务
|
||||
- 过期键清理
|
||||
- 统计信息更新
|
||||
- 持久化操作
|
||||
|
||||
**事件处理流程:**
|
||||
|
||||
```
|
||||
1. 等待事件发生
|
||||
↓
|
||||
2. 处理文件事件
|
||||
↓
|
||||
3. 处理时间事件
|
||||
↓
|
||||
4. 返回步骤1
|
||||
```
|
||||
|
||||
**I/O 多路复用:**
|
||||
|
||||
Redis 根据不同平台选择最优的 I/O 多路复用实现:
|
||||
- Linux:epoll
|
||||
- macOS/FreeBSD:kqueue
|
||||
- Windows:select
|
||||
|
||||
**事件循环优势:**
|
||||
|
||||
1. **高并发支持**
|
||||
- 单线程处理多个客户端
|
||||
- 避免线程创建开销
|
||||
- 内存使用效率高
|
||||
|
||||
2. **响应及时**
|
||||
- 事件驱动,实时响应
|
||||
- 无阻塞等待
|
||||
- 低延迟处理
|
||||
|
||||
3. **资源利用率高**
|
||||
- CPU 利用率高
|
||||
- 内存占用少
|
||||
- 系统资源消耗低
|
||||
519
数据库/Redis_2025/02_Redis环境搭建.md
Normal file
@@ -0,0 +1,519 @@
|
||||
# Redis 环境搭建
|
||||
|
||||
## Redis 安装
|
||||
|
||||
Redis 在 Rocky Linux release 9.4 (Blue Onyx) 通过 YUM 安装 redis-6.2.19-1.el9_6.x86_64
|
||||
|
||||
```shell
|
||||
[root@localhost ~]# cat /etc/redhat-release
|
||||
Rocky Linux release 9.4 (Blue Onyx)
|
||||
[root@localhost ~]# yum provides redis
|
||||
Last metadata expiration check: 0:06:10 ago on Sat Aug 2 12:50:25 2025.
|
||||
redis-6.2.19-1.el9_6.x86_64 : A persistent key-value database
|
||||
Repo : appstream
|
||||
Matched from:
|
||||
Provide : redis = 6.2.19-1.el9_6
|
||||
[root@localhost ~]# yum install redis-6.2.19-1.el9_6 -y
|
||||
|
||||
```
|
||||
|
||||
## Redis 启动和连接
|
||||
|
||||
### Redis 服务启动
|
||||
|
||||
**使用 systemd 管理**
|
||||
|
||||
```shell
|
||||
# 服务文件
|
||||
[root@localhost ~]# ls -l /usr/lib/systemd/system/redis.service
|
||||
# 重新加载 systemd
|
||||
systemctl daemon-reload
|
||||
# 启动 Redis 服务
|
||||
systemctl start redis
|
||||
# 设置开机自启
|
||||
systemctl enable redis
|
||||
# 查看服务状态
|
||||
systemctl status redis
|
||||
```
|
||||
|
||||
**手动启动**
|
||||
|
||||
```shell
|
||||
# 前台启动(用于调试)
|
||||
redis-server /etc/redis/redis.conf
|
||||
# 后台启动
|
||||
redis-server /etc/redis/redis.conf --daemonize yes
|
||||
# 指定端口启动
|
||||
redis-server --port 6380
|
||||
# 指定配置参数启动
|
||||
redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
### Redis 客户端连接
|
||||
|
||||
**本地连接**
|
||||
|
||||
```shell
|
||||
# 默认连接
|
||||
redis-cli
|
||||
# 指定主机和端口
|
||||
redis-cli -h 127.0.0.1 -p 6379
|
||||
# 使用密码连接
|
||||
redis-cli -h 127.0.0.1 -p 6379 -a your_password
|
||||
# 连接后认证
|
||||
redis-cli
|
||||
127.0.0.1:6379> AUTH your_password
|
||||
OK
|
||||
# 选择数据库
|
||||
127.0.0.1:6379> SELECT 1
|
||||
OK
|
||||
127.0.0.1:6379[1]>
|
||||
```
|
||||
|
||||
**远程连接配置**
|
||||
|
||||
```shell
|
||||
# 服务器端配置
|
||||
# 修改配置文件
|
||||
vim /etc/redis/redis.conf
|
||||
# 修改绑定地址
|
||||
bind 0.0.0.0
|
||||
# 设置密码
|
||||
requirepass your_strong_password
|
||||
# 重启服务
|
||||
systemctl restart redis
|
||||
|
||||
# 客户端连接
|
||||
# 远程连接
|
||||
redis-cli -h 192.168.1.100 -p 6379 -a your_password
|
||||
|
||||
```
|
||||
|
||||
## Redis 配置
|
||||
|
||||
### 配置文件详解
|
||||
|
||||
Redis 的主配置文件通常位于 `/etc/redis/redis.conf`,包含了所有的配置选项。
|
||||
|
||||
**配置文件结构:**
|
||||
|
||||
```shell
|
||||
# Redis 配置文件主要部分
|
||||
1. 网络配置 (NETWORK)
|
||||
2. 通用配置 (GENERAL)
|
||||
3. 快照配置 (SNAPSHOTTING)
|
||||
4. 复制配置 (REPLICATION)
|
||||
5. 安全配置 (SECURITY)
|
||||
6. 客户端配置 (CLIENTS)
|
||||
7. 内存管理 (MEMORY MANAGEMENT)
|
||||
8. 惰性释放 (LAZY FREEING)
|
||||
9. 线程 I/O (THREADED I/O)
|
||||
10. 内核透明大页 (KERNEL TRANSPARENT HUGEPAGE)
|
||||
11. 追加模式 (APPEND ONLY MODE)
|
||||
12. LUA 脚本 (LUA SCRIPTING)
|
||||
13. Redis 集群 (REDIS CLUSTER)
|
||||
14. 慢日志 (SLOW LOG)
|
||||
15. 延迟监控 (LATENCY MONITOR)
|
||||
16. 事件通知 (EVENT NOTIFICATION)
|
||||
17. 高级配置 (ADVANCED CONFIG)
|
||||
```
|
||||
|
||||
### 常用配置参数
|
||||
|
||||
#### 网络配置
|
||||
|
||||
```shell
|
||||
# 绑定地址
|
||||
bind 127.0.0.1 ::1
|
||||
# 允许所有地址访问(生产环境需谨慎)
|
||||
# bind 0.0.0.0
|
||||
|
||||
# 端口号
|
||||
port 6379
|
||||
|
||||
# TCP 监听队列长度
|
||||
tcp-backlog 511
|
||||
|
||||
# 客户端空闲超时时间(秒)
|
||||
timeout 0
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
```
|
||||
|
||||
#### 通用配置
|
||||
|
||||
```shell
|
||||
# 以守护进程方式运行
|
||||
daemonize yes
|
||||
|
||||
# 进程文件
|
||||
pidfile /var/run/redis_6379.pid
|
||||
|
||||
# 日志级别:debug, verbose, notice, warning
|
||||
loglevel notice
|
||||
|
||||
# 日志文件
|
||||
logfile /var/log/redis/redis-server.log
|
||||
|
||||
# 数据库数量
|
||||
databases 16
|
||||
|
||||
# 显示 Redis logo
|
||||
always-show-logo no
|
||||
```
|
||||
|
||||
#### 内存管理
|
||||
|
||||
```shell
|
||||
# 最大内存限制
|
||||
maxmemory 2gb
|
||||
|
||||
# 内存淘汰策略
|
||||
# noeviction: 不淘汰,内存满时报错
|
||||
# allkeys-lru: 所有键 LRU 淘汰
|
||||
# volatile-lru: 有过期时间的键 LRU 淘汰
|
||||
# allkeys-random: 所有键随机淘汰
|
||||
# volatile-random: 有过期时间的键随机淘汰
|
||||
# volatile-ttl: 有过期时间的键按 TTL 淘汰
|
||||
# allkeys-lfu: 所有键 LFU 淘汰
|
||||
# volatile-lfu: 有过期时间的键 LFU 淘汰
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# LRU 和 LFU 算法样本数量
|
||||
maxmemory-samples 5
|
||||
```
|
||||
|
||||
### 安全配置
|
||||
|
||||
#### 密码认证
|
||||
|
||||
```shell
|
||||
# 设置密码
|
||||
requirepass your_strong_password_here
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command KEYS ""
|
||||
rename-command CONFIG "CONFIG_9a8b7c6d5e4f"
|
||||
```
|
||||
|
||||
#### ACL 用户管理
|
||||
|
||||
```shell
|
||||
# 启用 ACL 日志
|
||||
acllog-max-len 128
|
||||
|
||||
# ACL 配置文件
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# 示例 ACL 配置
|
||||
# user default on nopass ~* &* -@all +@read +@write
|
||||
# user app_user on >app_password ~app:* +@read +@write -@dangerous
|
||||
# user readonly_user on >readonly_password ~* +@read -@write -@dangerous
|
||||
```
|
||||
|
||||
#### 网络安全
|
||||
|
||||
```shell
|
||||
# 保护模式(默认开启)
|
||||
protected-mode yes
|
||||
|
||||
# 绑定到特定接口
|
||||
bind 127.0.0.1 192.168.1.100
|
||||
|
||||
# 禁用某些命令
|
||||
rename-command DEBUG ""
|
||||
rename-command EVAL ""
|
||||
rename-command SCRIPT ""
|
||||
```
|
||||
|
||||
### 性能调优配置
|
||||
|
||||
#### 持久化优化
|
||||
|
||||
```shell
|
||||
# RDB 配置
|
||||
save 900 1 # 900秒内至少1个键变化
|
||||
save 300 10 # 300秒内至少10个键变化
|
||||
save 60 10000 # 60秒内至少10000个键变化
|
||||
|
||||
# RDB 文件压缩
|
||||
rdbcompression yes
|
||||
|
||||
# RDB 文件校验
|
||||
rdbchecksum yes
|
||||
|
||||
# RDB 文件名
|
||||
dbfilename dump.rdb
|
||||
|
||||
# 工作目录
|
||||
dir /var/lib/redis
|
||||
|
||||
# AOF 配置
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
# always: 每个写操作都同步
|
||||
# everysec: 每秒同步一次
|
||||
# no: 由操作系统决定
|
||||
appendfsync everysec
|
||||
|
||||
# AOF 重写配置
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
```
|
||||
|
||||
#### 客户端连接优化
|
||||
|
||||
```shell
|
||||
# 最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
# client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
|
||||
# 客户端查询缓冲区限制
|
||||
client-query-buffer-limit 1gb
|
||||
```
|
||||
|
||||
#### 慢查询配置
|
||||
|
||||
```shell
|
||||
# 慢查询阈值(微秒)
|
||||
slowlog-log-slower-than 10000
|
||||
|
||||
# 慢查询日志长度
|
||||
slowlog-max-len 128
|
||||
```
|
||||
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
在生产环境中,我们经常需要在同一台服务器上运行多个Redis实例,用于不同的业务场景或实现数据隔离。本实践将演示如何通过自定义配置文件部署多个Redis实例,包括主实例、缓存实例和会话实例。
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 创建多实例目录结构
|
||||
mkdir -p /etc/redis/instances/{main,cache,session}
|
||||
mkdir -p /var/lib/redis/{main,cache,session}
|
||||
mkdir -p /var/log/redis
|
||||
|
||||
# 2. 创建主实例配置文件 (端口6380)
|
||||
tee /etc/redis/instances/main/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 主实例配置 - 用于核心业务数据
|
||||
port 6380
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-main.pid
|
||||
logfile /var/log/redis/redis-main.log
|
||||
dir /var/lib/redis/main
|
||||
dbfilename dump-main.rdb
|
||||
|
||||
# 内存配置
|
||||
maxmemory 1gb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-main.aof"
|
||||
|
||||
# 安全配置
|
||||
requirepass main_redis_2025
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
|
||||
# 客户端配置
|
||||
maxclients 1000
|
||||
timeout 300
|
||||
EOF
|
||||
|
||||
# 3. 创建缓存实例配置文件 (端口6381)
|
||||
sudo tee /etc/redis/instances/cache/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 缓存实例配置 - 用于应用缓存
|
||||
port 6381
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-cache.pid
|
||||
logfile /var/log/redis/redis-cache.log
|
||||
dir /var/lib/redis/cache
|
||||
dbfilename dump-cache.rdb
|
||||
|
||||
# 内存配置 - 缓存实例分配更多内存
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory-samples 10
|
||||
|
||||
# 持久化配置 - 缓存数据可以不持久化
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 安全配置
|
||||
requirepass cache_redis_2025
|
||||
|
||||
# 客户端配置
|
||||
maxclients 2000
|
||||
timeout 0
|
||||
|
||||
# 过期键删除配置
|
||||
hz 10
|
||||
EOF
|
||||
|
||||
# 4. 创建会话实例配置文件 (端口6382)
|
||||
sudo tee /etc/redis/instances/session/redis.conf > /dev/null << 'EOF'
|
||||
# Redis 会话实例配置 - 用于用户会话存储
|
||||
port 6382
|
||||
bind 127.0.0.1
|
||||
daemonize yes
|
||||
pidfile /var/run/redis/redis-session.pid
|
||||
logfile /var/log/redis/redis-session.log
|
||||
dir /var/lib/redis/session
|
||||
dbfilename dump-session.rdb
|
||||
|
||||
# 内存配置
|
||||
maxmemory 512mb
|
||||
maxmemory-policy volatile-lru
|
||||
|
||||
# 持久化配置 - 会话数据需要持久化但频率可以低一些
|
||||
save 1800 1
|
||||
save 300 100
|
||||
appendonly yes
|
||||
appendfilename "appendonly-session.aof"
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 安全配置
|
||||
requirepass session_redis_2025
|
||||
|
||||
# 客户端配置
|
||||
maxclients 500
|
||||
timeout 1800
|
||||
|
||||
# 键空间通知 - 用于会话过期监控
|
||||
notify-keyspace-events Ex
|
||||
EOF
|
||||
|
||||
# 5. 创建运行时目录
|
||||
mkdir -p /var/run/redis
|
||||
chown redis:redis /var/run/redis
|
||||
chown -R redis:redis /var/lib/redis
|
||||
chown -R redis:redis /var/log/redis
|
||||
chown -R redis:redis /etc/redis/instances
|
||||
|
||||
# 6. 创建systemd服务文件
|
||||
# 主实例服务
|
||||
tee /etc/systemd/system/redis-main.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Main Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/main/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6380 -a main_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 缓存实例服务
|
||||
sudo tee /etc/systemd/system/redis-cache.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Cache Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/cache/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6381 -a cache_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 会话实例服务
|
||||
sudo tee /etc/systemd/system/redis-session.service > /dev/null << 'EOF'
|
||||
[Unit]
|
||||
Description=Redis Session Instance
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/usr/bin/redis-server /etc/redis/instances/session/redis.conf --supervised systemd
|
||||
ExecStop=/usr/bin/redis-cli -p 6382 -a session_redis_2025 shutdown
|
||||
TimeoutStopSec=0
|
||||
Restart=always
|
||||
User=redis
|
||||
Group=redis
|
||||
RuntimeDirectory=redis
|
||||
RuntimeDirectoryMode=0755
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# 7. 重新加载systemd并启动服务
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable redis-main redis-cache redis-session
|
||||
sudo systemctl start redis-main redis-cache redis-session
|
||||
|
||||
# 检查服务状态
|
||||
sudo systemctl status redis-main redis-cache redis-session --no-pager
|
||||
|
||||
# 检查端口监听
|
||||
sudo netstat -tlnp | grep redis-server
|
||||
|
||||
# 检查进程
|
||||
ps aux | grep redis-server | grep -v grep
|
||||
|
||||
# 测试主实例
|
||||
redis-cli -p 6380 -a main_redis_2025 ping
|
||||
redis-cli -p 6380 -a main_redis_2025 set main:test "Main Instance Data"
|
||||
redis-cli -p 6380 -a main_redis_2025 get main:test
|
||||
|
||||
# 测试缓存实例
|
||||
redis-cli -p 6381 -a cache_redis_2025 ping
|
||||
redis-cli -p 6381 -a cache_redis_2025 set cache:user:1001 "User Cache Data" EX 3600
|
||||
redis-cli -p 6381 -a cache_redis_2025 get cache:user:1001
|
||||
redis-cli -p 6381 -a cache_redis_2025 ttl cache:user:1001
|
||||
|
||||
# 测试会话实例
|
||||
redis-cli -p 6382 -a session_redis_2025 ping
|
||||
redis-cli -p 6382 -a session_redis_2025 set session:user:1001 "User Session Data" EX 1800
|
||||
redis-cli -p 6382 -a session_redis_2025 get session:user:1001
|
||||
redis-cli -p 6382 -a session_redis_2025 ttl session:user:1001
|
||||
|
||||
# 检查内存使用情况 - used_memory_human
|
||||
redis-cli -p 6380 -a main_redis_2025 info memory | grep used_memory_human
|
||||
redis-cli -p 6381 -a cache_redis_2025 info memory | grep used_memory_human
|
||||
redis-cli -p 6382 -a session_redis_2025 info memory | grep used_memory_human
|
||||
|
||||
# 检查配置信息 - maxmemory
|
||||
redis-cli -p 6380 -a main_redis_2025 config get maxmemory
|
||||
redis-cli -p 6381 -a cache_redis_2025 config get maxmemory
|
||||
redis-cli -p 6382 -a session_redis_2025 config get maxmemory
|
||||
|
||||
```
|
||||
977
数据库/Redis_2025/03_Redis数据类型.md
Normal file
@@ -0,0 +1,977 @@
|
||||
# Redis 数据类型
|
||||
|
||||
Redis 支持五种基本数据类型,每种数据类型都有其特定的应用场景和操作命令。理解这些数据类型的特点和使用方法是掌握 Redis 的关键。
|
||||
|
||||
## 字符串 (String)
|
||||
|
||||
### 字符串的特点和应用
|
||||
|
||||
字符串是 Redis 最基本的数据类型,也是最常用的类型。在 Redis 中,字符串是二进制安全的,可以存储任何数据,包括数字、文本、序列化对象、甚至是图片。
|
||||
|
||||
**主要特点:**
|
||||
- **二进制安全**:可以存储任何二进制数据
|
||||
- **最大长度**:单个字符串最大 512MB
|
||||
- **编码优化**:Redis 会根据内容自动选择最优编码
|
||||
- **原子操作**:所有字符串操作都是原子性的
|
||||
|
||||
**常见应用场景:**
|
||||
- 缓存数据(JSON、XML、HTML 等)
|
||||
- 计数器(访问量、点赞数等)
|
||||
- 分布式锁
|
||||
- 会话存储
|
||||
- 配置信息存储
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**设置和获取**
|
||||
|
||||
```shell
|
||||
# 设置键值
|
||||
# 用法:SET key value
|
||||
SET name "张三"
|
||||
SET age 25
|
||||
|
||||
# 获取值
|
||||
# 用法:GET key
|
||||
GET name # 返回 "张三"
|
||||
GET age # 返回 "25"
|
||||
GET nonexistent # 返回 (nil)
|
||||
|
||||
# 批量设置
|
||||
# 用法:MSET key1 value1 key2 value2 key3 value3
|
||||
MSET user:1:name "张三" user:1:age 25 user:1:city "北京"
|
||||
|
||||
# 批量获取
|
||||
# 用法:MGET key1 key2 key3
|
||||
MGET key1 key2 key3
|
||||
MGET user:1:name user:1:age user:1:city
|
||||
|
||||
# 设置并返回旧值
|
||||
# 用法:GETSET key newvalue
|
||||
GETSET key newvalue
|
||||
GETSET counter 100 # 返回旧值,设置新值为100
|
||||
```
|
||||
|
||||
**条件设置**
|
||||
|
||||
```shell
|
||||
# 仅当键不存在时设置
|
||||
# 用法:SETNX key value
|
||||
SETNX lock:resource "server1" # 分布式锁应用
|
||||
|
||||
# 设置键值和过期时间
|
||||
# 用法:SETEX key seconds value
|
||||
SETEX session:abc123 3600 "user_data" # 3600秒后过期
|
||||
|
||||
# 仅当键不存在时设置,并指定过期时间
|
||||
# 用法:SET key value EX seconds NX
|
||||
SET lock:order:1001 "processing" EX 30 NX
|
||||
|
||||
# 仅当键存在时设置
|
||||
# 用法:SET key value XX
|
||||
SET existing_key "new_value" XX
|
||||
```
|
||||
|
||||
**字符串操作**
|
||||
|
||||
```shell
|
||||
# 追加字符串
|
||||
# 用法:APPEND key value
|
||||
SET message "Hello"
|
||||
APPEND message " World" # message 现在是 "Hello World"
|
||||
|
||||
# 获取字符串长度
|
||||
# 用法:STRLEN key
|
||||
STRLEN message # 返回 11
|
||||
|
||||
# 获取子字符串
|
||||
# 用法:GETRANGE key start end
|
||||
GETRANGE message 0 4 # 返回 "Hello"
|
||||
GETRANGE message -5 -1 # 返回 "World"
|
||||
|
||||
# 设置子字符串
|
||||
# 用法:SETRANGE key offset value
|
||||
SETRANGE message 6 "Redis" # message 现在是 "Hello Redis"
|
||||
```
|
||||
|
||||
### 数值操作
|
||||
|
||||
Redis 可以将字符串当作数字进行操作,支持整数和浮点数。
|
||||
|
||||
```shell
|
||||
# 整数操作
|
||||
SET counter 10
|
||||
|
||||
# 增加指定值
|
||||
INCR counter # counter = 11
|
||||
INCRBY counter 5 # counter = 16
|
||||
DECR counter # counter = 15
|
||||
DECRBY counter 3 # counter = 12
|
||||
|
||||
# 浮点数操作
|
||||
SET price 19.99
|
||||
INCRBYFLOAT price 5.01 # price = 25.00
|
||||
INCRBYFLOAT price -2.50 # price = 22.50
|
||||
|
||||
# 应用示例:网站访问计数
|
||||
INCR page:home:views
|
||||
INCR user:1001:login_count
|
||||
INCRBY article:123:likes 1
|
||||
```
|
||||
|
||||
### 位操作
|
||||
|
||||
Redis 支持对字符串进行位级操作,适用于布尔值存储和位图应用。
|
||||
|
||||
```shell
|
||||
# 设置位值
|
||||
# 用法:SETBIT key offset value
|
||||
SETBIT user:1001:login 0 1 # 设置第0位为1
|
||||
SETBIT user:1001:login 1 0 # 设置第1位为0
|
||||
SETBIT user:1001:login 2 1 # 设置第2位为1
|
||||
|
||||
# 获取位值
|
||||
# 用法:GETBIT key offset
|
||||
GETBIT user:1001:login 0 # 返回 1
|
||||
GETBIT user:1001:login 1 # 返回 0
|
||||
|
||||
# 统计位为1的数量
|
||||
BITCOUNT key [start end]
|
||||
BITCOUNT user:1001:login # 返回 2
|
||||
|
||||
# 位运算
|
||||
# 用法:BITOP operation destkey key [key ...]
|
||||
SETBIT user:1001 0 1
|
||||
SETBIT user:1001 1 0
|
||||
SETBIT user:1002 0 1
|
||||
SETBIT user:1002 1 1
|
||||
BITOP AND result user:1001 user:1002 # 按位与运算
|
||||
BITOP OR result user:1001 user:1002 # 按位或运算
|
||||
|
||||
# 查找第一个指定位值的位置
|
||||
# 用法:BITPOS key bit [start] [end]
|
||||
BITPOS user:1001:login 1 # 查找第一个1的位置
|
||||
```
|
||||
|
||||
## 列表 (List)
|
||||
|
||||
### 列表的特点和应用
|
||||
|
||||
Redis 列表是简单的字符串列表,按照插入顺序排序。可以添加元素到列表的头部或尾部。
|
||||
|
||||
**主要特点:**
|
||||
- **有序性**:元素按插入顺序排列
|
||||
- **可重复**:允许重复元素
|
||||
- **双端操作**:支持头部和尾部操作
|
||||
- **最大长度**:理论上可包含 2^32 - 1 个元素
|
||||
|
||||
**常见应用场景:**
|
||||
- 消息队列
|
||||
- 最新消息列表
|
||||
- 用户操作历史
|
||||
- 任务队列
|
||||
- 时间线功能
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)添加
|
||||
# 用法:LPUSH key element [element ...]
|
||||
LPUSH messages "消息3" "消息2" "消息1"
|
||||
# 结果:["消息1", "消息2", "消息3"]
|
||||
|
||||
# 从右侧(尾部)添加
|
||||
# 用法:RPUSH key element [element ...]
|
||||
RPUSH messages "消息4" "消息5"
|
||||
# 结果:["消息1", "消息2", "消息3", "消息4", "消息5"]
|
||||
|
||||
# 仅当列表存在时添加
|
||||
# 用法:LPUSHX key element
|
||||
# 用法:RPUSHX key element
|
||||
LPUSHX messages "消息6"
|
||||
RPUSHX messages "消息7"
|
||||
```
|
||||
|
||||
**获取元素**
|
||||
|
||||
```shell
|
||||
# 获取指定范围的元素
|
||||
# 用法:LRANGE key start stop
|
||||
LRANGE messages 0 -1 # 获取所有元素
|
||||
LRANGE messages 0 2 # 获取前3个元素
|
||||
LRANGE messages -3 -1 # 获取最后3个元素
|
||||
```
|
||||
|
||||
**删除元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)删除
|
||||
# 用法:LPOP key
|
||||
LPOP messages # 弹出并返回第一个元素
|
||||
|
||||
# 从右侧(尾部)删除
|
||||
# 用法:RPOP key
|
||||
RPOP messages # 弹出并返回最后一个元素
|
||||
|
||||
# 在指定元素前/后插入
|
||||
# 用法:LINSERT key BEFORE|AFTER pivot element
|
||||
LINSERT messages BEFORE "消息3" "消息2.5"
|
||||
```
|
||||
|
||||
**获取元素**
|
||||
|
||||
```shell
|
||||
# 获取指定范围的元素
|
||||
# 用法:LRANGE key start stop
|
||||
LRANGE messages 0 -1 # 获取所有元素
|
||||
LRANGE messages 0 2 # 获取前3个元素
|
||||
LRANGE messages -3 -1 # 获取最后3个元素
|
||||
|
||||
# 获取指定索引的元素
|
||||
# 用法:LINDEX key index
|
||||
LINDEX messages 0 # 获取第一个元素
|
||||
LINDEX messages -1 # 获取最后一个元素
|
||||
|
||||
# 获取列表长度
|
||||
# 用法:LLEN key
|
||||
LLEN messages # 返回列表长度
|
||||
```
|
||||
|
||||
**删除元素**
|
||||
|
||||
```shell
|
||||
# 从左侧(头部)弹出
|
||||
# 用法:LPOP key
|
||||
LPOP messages # 弹出并返回第一个元素
|
||||
|
||||
# 从右侧(尾部)弹出
|
||||
# 用法:RPOP key
|
||||
RPOP messages # 弹出并返回最后一个元素
|
||||
|
||||
# 删除指定值的元素
|
||||
# 用法:LREM key count element
|
||||
LREM messages 1 "消息2" # 从头开始删除1个"消息2"
|
||||
LREM messages -1 "消息2" # 从尾开始删除1个"消息2"
|
||||
LREM messages 0 "消息2" # 删除所有"消息2"
|
||||
|
||||
# 保留指定范围的元素
|
||||
# 用法:LTRIM key start stop
|
||||
LTRIM messages 0 99 # 只保留前100个元素
|
||||
```
|
||||
|
||||
**修改元素**
|
||||
|
||||
```shell
|
||||
# 设置指定索引的元素值
|
||||
# 用法:LSET key index element
|
||||
LSET messages 0 "新消息1"
|
||||
```
|
||||
|
||||
### 阻塞操作
|
||||
|
||||
阻塞操作是 Redis 列表的重要特性,常用于实现消息队列。
|
||||
|
||||
```shell
|
||||
# 阻塞式左侧弹出
|
||||
# 用法:BLPOP key [key ...] timeout
|
||||
BLPOP task_queue 30 # 等待30秒,如果有元素则弹出
|
||||
|
||||
# 阻塞式右侧弹出
|
||||
# 用法:BRPOP key [key ...] timeout
|
||||
BRPOP task_queue 0 # 无限等待
|
||||
|
||||
# 阻塞式右侧弹出并左侧推入
|
||||
# 用法:BRPOPLPUSH source destination timeout
|
||||
BRPOPLPUSH pending_tasks processing_tasks 60
|
||||
```
|
||||
|
||||
### 列表的应用场景
|
||||
|
||||
**消息队列实现**
|
||||
|
||||
```shell
|
||||
# 生产者:添加任务到队列
|
||||
LPUSH task_queue "发送邮件:user@example.com"
|
||||
LPUSH task_queue "生成报表:monthly_report"
|
||||
LPUSH task_queue "数据备份:database_backup"
|
||||
|
||||
# 消费者:从队列获取任务
|
||||
BRPOP task_queue 30
|
||||
# 返回:1) "task_queue" 2) "发送邮件:user@example.com"
|
||||
```
|
||||
|
||||
**最新消息列表**
|
||||
|
||||
```shell
|
||||
# 添加新消息(保持最新100条)
|
||||
LPUSH latest_news "新闻标题1"
|
||||
LTRIM latest_news 0 99
|
||||
|
||||
# 获取最新10条消息
|
||||
LRANGE latest_news 0 9
|
||||
```
|
||||
|
||||
**用户操作历史**
|
||||
|
||||
```shell
|
||||
# 记录用户操作
|
||||
LPUSH user:1001:actions "登录:2024-01-15 10:30:00"
|
||||
LPUSH user:1001:actions "查看商品:product_123"
|
||||
LPUSH user:1001:actions "加入购物车:product_123"
|
||||
|
||||
# 获取最近操作历史
|
||||
LRANGE user:1001:actions 0 9
|
||||
```
|
||||
|
||||
## 集合 (Set)
|
||||
|
||||
### 集合的特点和应用
|
||||
|
||||
Redis 集合是字符串的无序集合,集合中的元素是唯一的。
|
||||
|
||||
**主要特点:**
|
||||
- **唯一性**:不允许重复元素
|
||||
- **无序性**:元素没有固定顺序
|
||||
- **快速查找**:O(1) 时间复杂度判断元素是否存在
|
||||
- **集合运算**:支持交集、并集、差集运算
|
||||
|
||||
**常见应用场景:**
|
||||
- 标签系统
|
||||
- 好友关系
|
||||
- 权限管理
|
||||
- 去重统计
|
||||
- 抽奖系统
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加和删除元素**
|
||||
|
||||
```shell
|
||||
# 添加元素
|
||||
# 用法:SADD key member [member ...]
|
||||
SADD tags "Redis" "数据库" "缓存" "NoSQL"
|
||||
SADD user:1001:skills "Java" "Python" "Redis"
|
||||
|
||||
# 删除元素
|
||||
# 用法:SREM key member [member ...]
|
||||
SREM tags "缓存"
|
||||
SREM user:1001:skills "Java"
|
||||
|
||||
# 随机删除并返回元素
|
||||
# 用法:SPOP key [count]
|
||||
SPOP tags # 随机删除一个元素
|
||||
SPOP tags 2 # 随机删除两个元素
|
||||
```
|
||||
|
||||
**查询操作**
|
||||
|
||||
```shell
|
||||
# 获取所有元素
|
||||
# 用法:SMEMBERS key
|
||||
SMEMBERS tags
|
||||
|
||||
# 判断元素是否存在
|
||||
# 用法:SISMEMBER key member
|
||||
SISMEMBER tags "Redis" # 返回 1(存在)
|
||||
SISMEMBER tags "MySQL" # 返回 0(不存在)
|
||||
|
||||
# 获取集合大小
|
||||
# 用法:SCARD key
|
||||
SCARD tags # 返回集合元素数量
|
||||
|
||||
# 随机获取元素(不删除)
|
||||
# 用法:SRANDMEMBER key [count]
|
||||
SRANDMEMBER tags # 随机返回一个元素
|
||||
SRANDMEMBER tags 3 # 随机返回三个元素
|
||||
```
|
||||
|
||||
**移动元素**
|
||||
|
||||
```shell
|
||||
# 将元素从一个集合移动到另一个集合
|
||||
# 用法:SMOVE source destination member
|
||||
SMOVE user:1001:skills user:1002:skills "Python"
|
||||
```
|
||||
|
||||
### 集合运算
|
||||
|
||||
Redis 支持集合间的数学运算,这是集合类型的强大特性。
|
||||
|
||||
```shell
|
||||
# 准备测试数据
|
||||
SADD set1 "a" "b" "c" "d"
|
||||
SADD set2 "c" "d" "e" "f"
|
||||
SADD set3 "d" "e" "f" "g"
|
||||
|
||||
# 交集运算
|
||||
# 用法:SINTER key [key ...]
|
||||
SINTER set1 set2 # 返回 {"c", "d"}
|
||||
SINTER set1 set2 set3 # 返回 {"d"}
|
||||
|
||||
# 并集运算
|
||||
# 用法:SUNION key [key ...]
|
||||
SUNION set1 set2 # 返回 {"a", "b", "c", "d", "e", "f"}
|
||||
|
||||
# 差集运算
|
||||
# 用法:SDIFF key [key ...]
|
||||
SDIFF set1 set2 # 返回 {"a", "b"}
|
||||
SDIFF set2 set1 # 返回 {"e", "f"}
|
||||
|
||||
# 将运算结果存储到新集合
|
||||
# 用法:[SINTERSTORE | SUNIONSTORE | SDIFFSTORE] destination key [key ...]
|
||||
|
||||
SINTERSTORE result set1 set2
|
||||
SMEMBERS result # 查看交集结果
|
||||
```
|
||||
|
||||
### 随机操作
|
||||
|
||||
集合的随机操作常用于抽奖、推荐等场景。
|
||||
|
||||
```shell
|
||||
# 抽奖系统示例
|
||||
SADD lottery_pool "用户1" "用户2" "用户3" "用户4" "用户5"
|
||||
|
||||
# 随机抽取一个中奖者(不删除)
|
||||
SRANDMEMBER lottery_pool
|
||||
|
||||
# 随机抽取并移除中奖者
|
||||
SPOP lottery_pool
|
||||
|
||||
# 抽取多个中奖者
|
||||
SPOP lottery_pool 3
|
||||
```
|
||||
|
||||
## 有序集合 (Sorted Set)
|
||||
|
||||
### 有序集合的特点和应用
|
||||
|
||||
有序集合是 Redis 中最复杂的数据类型,它结合了集合和列表的特点。
|
||||
|
||||
**主要特点:**
|
||||
- **唯一性**:成员唯一,不允许重复
|
||||
- **有序性**:按分数(score)排序
|
||||
- **双重索引**:可以按成员或分数查找
|
||||
- **范围查询**:支持按分数或排名范围查询
|
||||
|
||||
**常见应用场景:**
|
||||
- 排行榜系统
|
||||
- 优先级队列
|
||||
- 时间线排序
|
||||
- 范围查询
|
||||
- 权重计算
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**添加和删除元素**
|
||||
|
||||
```shell
|
||||
# 添加元素(分数 成员)
|
||||
# 用法:ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
|
||||
ZADD leaderboard 1500 "玩家A"
|
||||
ZADD leaderboard 2300 "玩家B" 1800 "玩家C" 2100 "玩家D"
|
||||
# 批量添加
|
||||
ZADD leaderboard 1200 "玩家E" 1900 "玩家F" 2500 "玩家G"
|
||||
|
||||
# 删除元素
|
||||
# 用法:ZREM key member [member ...]
|
||||
ZREM leaderboard "玩家E"
|
||||
|
||||
# 按分数范围删除
|
||||
# 用法:ZREMRANGEBYSCORE key min max
|
||||
ZREMRANGEBYSCORE leaderboard 0 1000
|
||||
|
||||
# 按排名范围删除
|
||||
# 用法:ZREMRANGEBYRANK key start stop
|
||||
ZREMRANGEBYRANK leaderboard 0 2 # 删除前3名
|
||||
```
|
||||
|
||||
**查询操作**
|
||||
|
||||
```shell
|
||||
# 获取元素数量
|
||||
# 用法:ZCARD key
|
||||
ZCARD leaderboard
|
||||
|
||||
# 获取元素分数
|
||||
# 用法:ZSCORE key member
|
||||
ZSCORE leaderboard "玩家B"
|
||||
|
||||
# 获取元素排名
|
||||
# 用法:ZRANK key member # 正序排名(从0开始)
|
||||
# 用法:ZREVRANK key member # 逆序排名(从0开始)
|
||||
ZRANK leaderboard "玩家B"
|
||||
ZREVRANK leaderboard "玩家B"
|
||||
|
||||
# 统计分数范围内的元素数量
|
||||
# 用法:ZCOUNT key min max
|
||||
ZCOUNT leaderboard 1500 2000
|
||||
|
||||
# 获取分数范围内的元素数量(字典序)
|
||||
# 用法:ZLEXCOUNT key min max
|
||||
ZLEXCOUNT leaderboard a c
|
||||
```
|
||||
|
||||
### 范围操作
|
||||
|
||||
有序集合的范围操作是其最强大的功能之一。
|
||||
|
||||
**按排名范围查询**
|
||||
|
||||
```shell
|
||||
# 按排名获取元素(正序)
|
||||
# 用法:ZRANGE key start stop [WITHSCORES]
|
||||
ZRANGE leaderboard 0 2 # 获取前3名
|
||||
ZRANGE leaderboard 0 2 WITHSCORES # 获取前3名及分数
|
||||
ZRANGE leaderboard 0 -1 # 获取所有元素
|
||||
|
||||
# 按排名获取元素(逆序)
|
||||
# 用法:ZREVRANGE key start stop [WITHSCORES]
|
||||
ZREVRANGE leaderboard 0 2 WITHSCORES # 获取前3名(高分到低分)
|
||||
```
|
||||
|
||||
**按分数范围查询**
|
||||
|
||||
```shell
|
||||
# 按分数范围获取元素(正序)
|
||||
# 用法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
|
||||
ZRANGEBYSCORE leaderboard 1500 2000
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 WITHSCORES
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 LIMIT 0 5
|
||||
|
||||
# 按分数范围获取元素(逆序)
|
||||
# 用法:ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
|
||||
ZREVRANGEBYSCORE leaderboard 2000 1500 WITHSCORES
|
||||
|
||||
# 使用开区间
|
||||
ZRANGEBYSCORE leaderboard 1500 2000 # 不包含1500和2000
|
||||
ZRANGEBYSCORE leaderboard -inf +inf # 所有元素
|
||||
```
|
||||
|
||||
**按字典序范围查询**
|
||||
|
||||
```shell
|
||||
# 按字典序范围获取元素
|
||||
# 用法:ZRANGEBYLEX key min max [LIMIT offset count]
|
||||
ZRANGEBYLEX leaderboard a c
|
||||
# 用法:ZREVRANGEBYLEX key max min [LIMIT offset count]
|
||||
ZREVRANGEBYLEX leaderboard c a
|
||||
|
||||
# 示例:相同分数的元素按字典序排列
|
||||
ZADD words 0 "apple" 0 "banana" 0 "cherry" 0 "date"
|
||||
ZRANGEBYLEX words a c # 获取a到c之间的单词
|
||||
ZRANGEBYLEX words banana date # 获取banana到date之间的单词
|
||||
```
|
||||
|
||||
### 排行榜应用
|
||||
|
||||
有序集合最典型的应用就是排行榜系统。
|
||||
|
||||
```shell
|
||||
# 游戏排行榜示例
|
||||
# 添加玩家分数
|
||||
ZADD game:leaderboard 15000 "张三"
|
||||
ZADD game:leaderboard 23000 "李四"
|
||||
ZADD game:leaderboard 18000 "王五"
|
||||
ZADD game:leaderboard 21000 "赵六"
|
||||
ZADD game:leaderboard 19000 "钱七"
|
||||
|
||||
# 获取排行榜前10名
|
||||
ZREVRANGE game:leaderboard 0 9 WITHSCORES
|
||||
|
||||
# 获取某玩家的排名
|
||||
ZREVRANK game:leaderboard "张三" # 返回排名(从0开始)
|
||||
|
||||
# 获取某玩家的分数
|
||||
ZSCORE game:leaderboard "张三"
|
||||
|
||||
# 增加玩家分数
|
||||
ZINCRBY game:leaderboard 1000 "张三"
|
||||
|
||||
# 获取分数范围内的玩家
|
||||
ZRANGEBYSCORE game:leaderboard 20000 25000 WITHSCORES
|
||||
|
||||
# 获取排名范围内的玩家
|
||||
ZREVRANGE game:leaderboard 10 19 WITHSCORES # 第11-20名
|
||||
```
|
||||
|
||||
## 哈希 (Hash)
|
||||
|
||||
### 哈希的特点和应用
|
||||
|
||||
Redis 哈希是一个键值对集合,特别适合存储对象。
|
||||
|
||||
**主要特点:**
|
||||
- **结构化存储**:键值对形式存储
|
||||
- **内存优化**:小哈希使用压缩编码
|
||||
- **部分更新**:可以只更新某个字段
|
||||
- **原子操作**:字段级别的原子操作
|
||||
|
||||
**常见应用场景:**
|
||||
- 用户信息存储
|
||||
- 配置信息管理
|
||||
- 购物车实现
|
||||
- 对象缓存
|
||||
- 计数器组合
|
||||
|
||||
### 基本操作命令
|
||||
|
||||
**设置和获取字段**
|
||||
|
||||
```shell
|
||||
# 用法:HSET key field value
|
||||
HSET user:1001 name "张三"
|
||||
HSET user:1001 age 28
|
||||
HSET user:1001 city "北京"
|
||||
HSET user:1001 email "zhangsan@example.com"
|
||||
|
||||
# 用法:HMSET key field value [field value ...]
|
||||
HMSET user:1002 name "李四" age 25 city "上海" email "lisi@example.com"
|
||||
|
||||
# 仅当字段不存在时设置
|
||||
# 用法:HSETNX key field value
|
||||
HSETNX user:1001 phone "13800138000"
|
||||
|
||||
# 获取单个字段
|
||||
# 用法:HGET key field
|
||||
HGET user:1001 name
|
||||
HGET user:1001 age
|
||||
|
||||
# 获取多个字段
|
||||
# 用法:HMGET key field [field ...]
|
||||
HMGET user:1001 name age city
|
||||
|
||||
# 获取所有字段和值
|
||||
# 用法:HGETALL key
|
||||
HGETALL user:1001
|
||||
|
||||
# 获取所有字段名
|
||||
# 用法:HKEYS key
|
||||
HKEYS user:1001
|
||||
|
||||
# 获取所有值
|
||||
# 用法:HVALS key
|
||||
HVALS user:1001
|
||||
```
|
||||
|
||||
**删除和检查字段**
|
||||
|
||||
```shell
|
||||
# 删除字段
|
||||
# 用法:HDEL key field [field ...]
|
||||
HDEL user:1001 email
|
||||
HDEL user:1001 phone city
|
||||
|
||||
# 检查字段是否存在
|
||||
# 用法:HEXISTS key field
|
||||
HEXISTS user:1001 name # 返回 1(存在)
|
||||
HEXISTS user:1001 phone # 返回 0(不存在)
|
||||
|
||||
# 获取字段数量
|
||||
# 用法:HLEN key
|
||||
HLEN user:1001
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
哈希支持高效的批量操作,减少网络往返次数。
|
||||
|
||||
```shell
|
||||
# 批量设置用户信息
|
||||
HMSET user:1003 \
|
||||
name "王五" \
|
||||
age 30 \
|
||||
city "广州" \
|
||||
email "wangwu@example.com" \
|
||||
phone "13900139000" \
|
||||
department "技术部" \
|
||||
position "高级工程师"
|
||||
|
||||
# 批量获取用户信息
|
||||
HMGET user:1003 name age city department position
|
||||
|
||||
# 获取完整用户信息
|
||||
HGETALL user:1003
|
||||
```
|
||||
|
||||
### 数值操作
|
||||
|
||||
哈希字段支持数值操作,常用于计数器场景。
|
||||
|
||||
```shell
|
||||
# 增加字段的数值
|
||||
# 用法:HINCRBY key field increment
|
||||
HSET user:1001 login_count 0
|
||||
HINCRBY user:1001 login_count 1 # login_count = 1
|
||||
HINCRBY user:1001 login_count 5 # login_count = 6
|
||||
|
||||
# 增加字段的浮点数值
|
||||
HINCRBYFLOAT key field increment
|
||||
HSET user:1001 balance 100.50
|
||||
HINCRBYFLOAT user:1001 balance 25.30 # balance = 125.80
|
||||
HINCRBYFLOAT user:1001 balance -10.00 # balance = 115.80
|
||||
```
|
||||
|
||||
### 对象存储应用
|
||||
|
||||
哈希非常适合存储对象数据,提供了比字符串更好的结构化存储。
|
||||
|
||||
**用户信息管理**
|
||||
|
||||
```shell
|
||||
# 创建用户信息
|
||||
HMSET user:1001 \
|
||||
name "张三" \
|
||||
age 28 \
|
||||
email "zhangsan@example.com" \
|
||||
city "北京" \
|
||||
department "研发部" \
|
||||
position "软件工程师" \
|
||||
salary 15000 \
|
||||
join_date "2023-01-15" \
|
||||
status "active"
|
||||
|
||||
# 更新用户信息
|
||||
HSET user:1001 age 29
|
||||
HSET user:1001 salary 16000
|
||||
HSET user:1001 position "高级软件工程师"
|
||||
|
||||
# 获取用户基本信息
|
||||
HMGET user:1001 name age email city
|
||||
|
||||
# 获取用户工作信息
|
||||
HMGET user:1001 department position salary
|
||||
|
||||
# 检查用户状态
|
||||
HGET user:1001 status
|
||||
```
|
||||
|
||||
**购物车实现**
|
||||
|
||||
```shell
|
||||
# 用户购物车:cart:user_id,字段为商品ID,值为数量
|
||||
HSET cart:1001 product:123 2
|
||||
HSET cart:1001 product:456 1
|
||||
HSET cart:1001 product:789 3
|
||||
|
||||
# 增加商品数量
|
||||
HINCRBY cart:1001 product:123 1 # 数量变为3
|
||||
|
||||
# 减少商品数量
|
||||
HINCRBY cart:1001 product:456 -1 # 数量变为0
|
||||
|
||||
# 删除商品(数量为0时)
|
||||
HDEL cart:1001 product:456
|
||||
|
||||
# 获取购物车所有商品
|
||||
HGETALL cart:1001
|
||||
|
||||
# 获取购物车商品数量
|
||||
HLEN cart:1001
|
||||
|
||||
# 清空购物车
|
||||
DEL cart:1001
|
||||
```
|
||||
|
||||
**配置信息管理**
|
||||
|
||||
```shell
|
||||
# 应用配置
|
||||
HMSET app:config \
|
||||
database_host "localhost" \
|
||||
database_port 3306 \
|
||||
database_name "myapp" \
|
||||
redis_host "localhost" \
|
||||
redis_port 6379 \
|
||||
cache_ttl 3600 \
|
||||
max_connections 100 \
|
||||
debug_mode "false"
|
||||
|
||||
# 获取数据库配置
|
||||
HMGET app:config database_host database_port database_name
|
||||
|
||||
# 获取缓存配置
|
||||
HMGET app:config redis_host redis_port cache_ttl
|
||||
|
||||
# 更新配置
|
||||
HSET app:config debug_mode "true"
|
||||
HSET app:config max_connections 200
|
||||
|
||||
# 获取所有配置
|
||||
HGETALL app:config
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
**需求描述:**
|
||||
使用 Redis 的不同数据类型构建一个完整的用户信息存储系统,包括用户基本信息、用户标签、用户行为记录等。
|
||||
|
||||
**实践细节和结果验证:**
|
||||
|
||||
```shell
|
||||
|
||||
# 1. 用户基本信息(使用哈希)
|
||||
redis-cli << 'EOF'
|
||||
# 创建用户基本信息
|
||||
HMSET user:1001 \
|
||||
name "张三" \
|
||||
age 28 \
|
||||
email "zhangsan@example.com" \
|
||||
phone "13800138000" \
|
||||
city "北京" \
|
||||
department "技术部" \
|
||||
position "高级工程师" \
|
||||
join_date "2023-01-15" \
|
||||
status "active"
|
||||
|
||||
HMSET user:1002 \
|
||||
name "李四" \
|
||||
age 25 \
|
||||
email "lisi@example.com" \
|
||||
phone "13900139000" \
|
||||
city "上海" \
|
||||
department "产品部" \
|
||||
position "产品经理" \
|
||||
join_date "2023-03-20" \
|
||||
status "active"
|
||||
|
||||
# 获取用户信息
|
||||
HGETALL user:1001
|
||||
HMGET user:1002 name age department position
|
||||
EOF
|
||||
|
||||
# 2. 用户标签系统(使用集合)
|
||||
redis-cli << 'EOF'
|
||||
# 为用户添加标签
|
||||
SADD user:1001:tags "技术专家" "Redis" "Python" "团队领导" "创新者"
|
||||
SADD user:1002:tags "产品专家" "用户体验" "数据分析" "沟通能力" "创新者"
|
||||
SADD user:1003:tags "设计师" "UI/UX" "创意" "用户体验" "创新者"
|
||||
|
||||
# 查看用户标签
|
||||
SMEMBERS user:1001:tags
|
||||
SMEMBERS user:1002:tags
|
||||
|
||||
# 查找共同标签
|
||||
SINTER user:1001:tags user:1002:tags
|
||||
SINTER user:1002:tags user:1003:tags
|
||||
|
||||
# 查找所有标签
|
||||
SUNION user:1001:tags user:1002:tags user:1003:tags
|
||||
EOF
|
||||
|
||||
# 3. 用户行为记录(使用列表)
|
||||
redis-cli << 'EOF'
|
||||
# 记录用户行为(最新的在前面)
|
||||
LPUSH user:1001:actions "登录:2024-01-15 09:00:00"
|
||||
LPUSH user:1001:actions "查看文档:Redis教程"
|
||||
LPUSH user:1001:actions "编辑代码:user_service.py"
|
||||
LPUSH user:1001:actions "提交代码:修复缓存bug"
|
||||
LPUSH user:1001:actions "参加会议:技术分享"
|
||||
|
||||
LPUSH user:1002:actions "登录:2024-01-15 09:30:00"
|
||||
LPUSH user:1002:actions "查看报表:用户增长数据"
|
||||
LPUSH user:1002:actions "创建需求:新功能设计"
|
||||
LPUSH user:1002:actions "审核设计:UI界面"
|
||||
|
||||
# 获取用户最近行为
|
||||
LRANGE user:1001:actions 0 4
|
||||
LRANGE user:1002:actions 0 4
|
||||
|
||||
# 保持行为记录在合理范围内(最多100条)
|
||||
LTRIM user:1001:actions 0 99
|
||||
LTRIM user:1002:actions 0 99
|
||||
EOF
|
||||
|
||||
# 4. 用户积分排行(使用有序集合)
|
||||
redis-cli << 'EOF'
|
||||
# 设置用户积分
|
||||
ZADD user:points 1500 "user:1001"
|
||||
ZADD user:points 2300 "user:1002"
|
||||
ZADD user:points 1800 "user:1003"
|
||||
ZADD user:points 2100 "user:1004"
|
||||
ZADD user:points 1200 "user:1005"
|
||||
|
||||
# 增加用户积分
|
||||
ZINCRBY user:points 200 "user:1001"
|
||||
ZINCRBY user:points 150 "user:1003"
|
||||
|
||||
# 查看积分排行榜
|
||||
ZREVRANGE user:points 0 -1 WITHSCORES
|
||||
|
||||
# 查看用户排名
|
||||
ZREVRANK user:points "user:1001"
|
||||
ZREVRANK user:points "user:1002"
|
||||
|
||||
# 查看用户积分
|
||||
ZSCORE user:points "user:1001"
|
||||
EOF
|
||||
|
||||
# 5. 用户会话管理(使用字符串)
|
||||
redis-cli << 'EOF'
|
||||
# 创建用户会话
|
||||
SET session:abc123 "user:1001" EX 3600
|
||||
SET session:def456 "user:1002" EX 3600
|
||||
|
||||
# 检查会话
|
||||
GET session:abc123
|
||||
TTL session:abc123
|
||||
|
||||
# 延长会话
|
||||
EXPIRE session:abc123 7200
|
||||
TTL session:abc123
|
||||
EOF
|
||||
|
||||
# 6. 用户统计信息(使用哈希)
|
||||
redis-cli << 'EOF'
|
||||
# 用户统计数据
|
||||
HMSET user:1001:stats \
|
||||
login_count 45 \
|
||||
last_login "2024-01-15 09:00:00" \
|
||||
total_actions 156 \
|
||||
articles_read 23 \
|
||||
code_commits 89
|
||||
|
||||
HMSET user:1002:stats \
|
||||
login_count 38 \
|
||||
last_login "2024-01-15 09:30:00" \
|
||||
total_actions 142 \
|
||||
reports_created 15 \
|
||||
meetings_attended 67
|
||||
|
||||
# 更新统计数据
|
||||
HINCRBY user:1001:stats login_count 1
|
||||
HSET user:1001:stats last_login "2024-01-15 14:30:00"
|
||||
HINCRBY user:1001:stats total_actions 1
|
||||
|
||||
# 查看统计数据
|
||||
HGETALL user:1001:stats
|
||||
HMGET user:1002:stats login_count last_login total_actions
|
||||
EOF
|
||||
|
||||
# 查找活跃用户(积分>1500)
|
||||
redis-cli ZRANGEBYSCORE user:points 1500 +inf WITHSCORES
|
||||
|
||||
# 查找技术相关用户
|
||||
redis-cli SISMEMBER user:1001:tags "技术专家"
|
||||
redis-cli SISMEMBER user:1002:tags "技术专家"
|
||||
|
||||
# 获取用户完整信息
|
||||
redis-cli HMGET user:1001 name age department position
|
||||
redis-cli SMEMBERS user:1001:tags
|
||||
redis-cli LRANGE user:1001:actions 0 2
|
||||
redis-cli ZSCORE user:points "user:1001"
|
||||
redis-cli ZREVRANK user:points "user:1001"
|
||||
redis-cli HMGET user:1001:stats login_count total_actions
|
||||
|
||||
# 清理测试数据
|
||||
redis-cli << 'EOF'
|
||||
DEL user:1001 user:1002 user:1003
|
||||
DEL user:1001:tags user:1002:tags user:1003:tags
|
||||
DEL user:1001:actions user:1002:actions
|
||||
DEL user:points
|
||||
DEL session:abc123 session:def456
|
||||
DEL user:1001:stats user:1002:stats
|
||||
EOF
|
||||
```
|
||||
590
数据库/Redis_2025/04_Redis基本命令.md
Normal file
@@ -0,0 +1,590 @@
|
||||
# Redis 基本命令
|
||||
|
||||
Redis 提供了丰富的命令来管理键、数据库、事务和脚本。掌握这些基本命令是高效使用 Redis 的基础。
|
||||
|
||||
## 键操作命令
|
||||
|
||||
### 键的命名规范
|
||||
|
||||
良好的键命名规范是 Redis 应用的重要基础,有助于提高可维护性和性能。
|
||||
|
||||
**推荐的命名规范:**
|
||||
|
||||
1. **使用冒号分隔层级**
|
||||
```shell
|
||||
user:1001:profile
|
||||
product:category:electronics
|
||||
cache:query:user_list
|
||||
session:web:abc123
|
||||
```
|
||||
|
||||
2. **使用有意义的前缀**
|
||||
```shell
|
||||
# 按功能分类
|
||||
cache:* # 缓存数据
|
||||
session:* # 会话数据
|
||||
config:* # 配置信息
|
||||
temp:* # 临时数据
|
||||
|
||||
# 按业务分类
|
||||
user:* # 用户相关
|
||||
order:* # 订单相关
|
||||
product:* # 商品相关
|
||||
```
|
||||
|
||||
3. **避免特殊字符**
|
||||
```shell
|
||||
# 推荐
|
||||
user:1001:name
|
||||
article:2023:01:15
|
||||
|
||||
# 不推荐
|
||||
user 1001 name
|
||||
article@2023#01#15
|
||||
```
|
||||
|
||||
4. **保持一致性**
|
||||
```shell
|
||||
# 统一使用小写
|
||||
user:profile:name
|
||||
user:profile:email
|
||||
|
||||
# 统一日期格式
|
||||
log:2024:01:15
|
||||
report:2024:01:15
|
||||
```
|
||||
|
||||
### 键的查询和遍历
|
||||
|
||||
**基本查询命令**
|
||||
|
||||
```shell
|
||||
# 检查键是否存在
|
||||
# 用法:EXISTS key [key ...]
|
||||
EXISTS user:1001
|
||||
EXISTS user:1001 user:1002 user:1003 # 返回存在的键数量
|
||||
|
||||
# 获取键的类型
|
||||
# 用法:TYPE key
|
||||
TYPE user:1001 # 返回 hash
|
||||
TYPE user:tags # 返回 set
|
||||
TYPE message_queue # 返回 list
|
||||
|
||||
# 获取键的编码方式
|
||||
# 用法:OBJECT ENCODING key
|
||||
OBJECT ENCODING user:1001
|
||||
OBJECT ENCODING counter
|
||||
|
||||
# 获取键的空闲时间(秒)
|
||||
# 用法:OBJECT IDLETIME key
|
||||
# 注意:返回值为键的空闲时间,单位为秒。
|
||||
OBJECT IDLETIME user:1001
|
||||
|
||||
# 获取键的引用计数
|
||||
# 用法:OBJECT REFCOUNT key
|
||||
# 注意:返回值为键的引用计数,单位为秒。
|
||||
OBJECT REFCOUNT user:1001
|
||||
```
|
||||
|
||||
**模式匹配查询**
|
||||
|
||||
```shell
|
||||
# 查找匹配模式的键(谨慎使用)
|
||||
# 用法:KEYS pattern
|
||||
KEYS user:* # 查找所有用户相关的键
|
||||
KEYS cache:* # 查找所有缓存键
|
||||
KEYS *:1001:* # 查找包含1001的键
|
||||
KEYS user:100? # ?匹配单个字符
|
||||
KEYS user:[12]* # []匹配字符集合
|
||||
|
||||
# 更安全的遍历方式(推荐)
|
||||
# 用法:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
|
||||
SCAN 0 MATCH user:* COUNT 10
|
||||
SCAN 0 MATCH cache:* COUNT 20
|
||||
SCAN 0 TYPE string COUNT 10
|
||||
|
||||
# 示例:安全遍历所有用户键
|
||||
redis-cli --scan --pattern "user:*"
|
||||
```
|
||||
|
||||
**随机获取键**
|
||||
|
||||
```shell
|
||||
# 随机返回一个键
|
||||
# 用法:RANDOMKEY
|
||||
|
||||
# 示例:随机获取键进行采样
|
||||
RANDOMKEY
|
||||
RANDOMKEY
|
||||
RANDOMKEY
|
||||
```
|
||||
|
||||
### 键的过期设置
|
||||
|
||||
Redis 支持为键设置过期时间,这是内存管理和缓存实现的重要功能。
|
||||
|
||||
**设置过期时间**
|
||||
|
||||
```shell
|
||||
# 设置键的过期时间(秒)
|
||||
# 用法:EXPIRE key seconds
|
||||
EXPIRE user:1001 3600 # 1小时后过期
|
||||
EXPIRE temp_data 300 # 5分钟后过期
|
||||
|
||||
# 设置键的过期时间(毫秒)
|
||||
# 用法:PEXPIRE key milliseconds
|
||||
PEXPIRE temp_data 30000 # 30秒后过期
|
||||
|
||||
# 设置键的过期时间戳(秒)
|
||||
# 用法:EXPIREAT key timestamp
|
||||
EXPIREAT temp_data 1705123200 # 指定时间戳过期
|
||||
|
||||
# 设置键的过期时间戳(毫秒)
|
||||
# 用法:PEXPIREAT key milliseconds-timestamp
|
||||
PEXPIREAT temp_data 1705123200000
|
||||
|
||||
# 创建键时同时设置过期时间
|
||||
# 用法:SETEX key seconds value
|
||||
SETEX key seconds value
|
||||
SETEX session:abc123 3600 "user_data"
|
||||
|
||||
# 创建键时同时设置过期时间(毫秒)
|
||||
# 用法:PSETEX key milliseconds value
|
||||
PSETEX session:def456 3600000 "user_data"
|
||||
```
|
||||
|
||||
**查询过期时间**
|
||||
|
||||
```shell
|
||||
# 获取键的剩余生存时间(秒)
|
||||
# 用法:TTL key
|
||||
TTL session:abc123 # 返回剩余秒数,-1表示永不过期,-2表示不存在
|
||||
|
||||
# 获取键的剩余生存时间(毫秒)
|
||||
# 用法:PTTL key
|
||||
PTTL session:abc123 # 返回剩余毫秒数
|
||||
|
||||
# 示例:检查多个键的过期时间
|
||||
SETEX key1 100 "value1"
|
||||
SETEX key2 200 "value2"
|
||||
SET key3 "value3" # 永不过期
|
||||
TTL key1
|
||||
TTL key2
|
||||
TTL key3
|
||||
```
|
||||
|
||||
**移除过期时间**
|
||||
|
||||
```shell
|
||||
# 移除键的过期时间,使其永久存在
|
||||
# 用法:PERSIST key
|
||||
PERSIST session:abc123
|
||||
SETEX temp_key 300 "临时数据"
|
||||
TTL temp_key # 查看过期时间
|
||||
PERSIST temp_key # 移除过期时间
|
||||
TTL temp_key # 返回-1,表示永不过期
|
||||
```
|
||||
|
||||
### 键的删除和重命名
|
||||
|
||||
**删除键**
|
||||
|
||||
```shell
|
||||
# 删除一个或多个键
|
||||
# 用法:DEL key [key ...]
|
||||
DEL user:1001
|
||||
DEL cache:query1 cache:query2 cache:query3
|
||||
|
||||
# 异步删除键(适用于大键)
|
||||
# 用法:UNLINK key [key ...]
|
||||
UNLINK large_list large_set large_hash
|
||||
|
||||
# 删除所有键(危险操作)
|
||||
FLUSHDB # 删除当前数据库所有键
|
||||
FLUSHALL # 删除所有数据库所有键
|
||||
```
|
||||
|
||||
**重命名键**
|
||||
|
||||
```shell
|
||||
# 重命名键
|
||||
# 用法:RENAME key newkey
|
||||
RENAME user:1001 user:1002
|
||||
SET old_name "value"
|
||||
RENAME old_name new_name
|
||||
GET new_name
|
||||
|
||||
# 仅当新键不存在时重命名
|
||||
# 用法:RENAMENX key newkey
|
||||
RENAMENX source_key target_key # 成功返回1
|
||||
RENAMENX source_key target_key # 失败返回0(target_key已存在)
|
||||
```
|
||||
|
||||
## 数据库操作
|
||||
|
||||
### 多数据库概念
|
||||
|
||||
Redis 支持多个数据库,默认有16个数据库(编号0-15),可以通过配置文件修改数量。
|
||||
|
||||
**数据库特点:**
|
||||
- 每个数据库都是独立的键空间
|
||||
- 不同数据库间的键名可以相同
|
||||
- 默认连接到数据库0
|
||||
- 数据库间不支持关联查询
|
||||
|
||||
**使用场景:**
|
||||
- 区分不同环境(开发、测试、生产)
|
||||
- 分离不同类型的数据
|
||||
- 临时数据隔离
|
||||
|
||||
### 数据库切换
|
||||
|
||||
```shell
|
||||
# 切换到指定数据库
|
||||
# 用法:SELECT index
|
||||
SELECT 0 # 切换到数据库0
|
||||
SELECT 1 # 切换到数据库1
|
||||
SELECT 15 # 切换到数据库15
|
||||
|
||||
# 示例:在不同数据库中存储数据
|
||||
SELECT 0
|
||||
SET app:env "production"
|
||||
SET app:version "1.0.0"
|
||||
|
||||
SELECT 1
|
||||
SET app:env "development"
|
||||
SET app:version "1.1.0-dev"
|
||||
|
||||
SELECT 2
|
||||
SET temp:data "临时测试数据"
|
||||
|
||||
# 验证数据隔离
|
||||
SELECT 0
|
||||
GET app:env # 返回 "production"
|
||||
|
||||
SELECT 1
|
||||
GET app:env # 返回 "development"
|
||||
```
|
||||
|
||||
### 数据库清空
|
||||
|
||||
```shell
|
||||
# 清空当前数据库
|
||||
FLUSHDB
|
||||
|
||||
# 异步清空当前数据库
|
||||
FLUSHDB ASYNC
|
||||
|
||||
# 清空所有数据库
|
||||
FLUSHALL
|
||||
|
||||
# 异步清空所有数据库
|
||||
FLUSHALL ASYNC
|
||||
|
||||
# 示例:安全的数据库清理
|
||||
SELECT 15 # 切换到测试数据库
|
||||
SET test:key "test_value"
|
||||
DBSIZE # 查看键数量
|
||||
FLUSHDB # 清空测试数据库
|
||||
DBSIZE # 确认清空结果
|
||||
```
|
||||
|
||||
### 数据库信息查看
|
||||
|
||||
```shell
|
||||
# 获取当前数据库键的数量
|
||||
DBSIZE
|
||||
|
||||
# 获取服务器信息
|
||||
INFO [section]
|
||||
INFO # 获取所有信息
|
||||
INFO server # 服务器信息
|
||||
INFO clients # 客户端信息
|
||||
INFO memory # 内存信息
|
||||
INFO keyspace # 键空间信息
|
||||
|
||||
# 获取配置信息
|
||||
CONFIG GET parameter
|
||||
CONFIG GET databases # 查看数据库数量配置
|
||||
CONFIG GET maxmemory # 查看最大内存配置
|
||||
|
||||
# 示例:查看各数据库使用情况
|
||||
INFO keyspace
|
||||
# 输出示例:
|
||||
# db0:keys=100,expires=10,avg_ttl=3600000
|
||||
# db1:keys=50,expires=5,avg_ttl=1800000
|
||||
```
|
||||
|
||||
## 事务操作
|
||||
|
||||
### 事务的概念
|
||||
|
||||
Redis 事务是一组命令的集合,这些命令会被顺序执行,具有以下特点:
|
||||
|
||||
**事务特性:**
|
||||
- **原子性**:事务中的命令要么全部执行,要么全部不执行
|
||||
- **一致性**:事务执行前后,数据保持一致状态
|
||||
- **隔离性**:事务执行过程中不会被其他命令干扰
|
||||
- **持久性**:事务执行结果会被持久化(如果启用了持久化)
|
||||
|
||||
**Redis 事务限制:**
|
||||
- 不支持回滚
|
||||
- 命令错误不会影响其他命令执行
|
||||
- 不支持嵌套事务
|
||||
|
||||
### MULTI/EXEC 命令
|
||||
|
||||
**基本事务操作**
|
||||
|
||||
```shell
|
||||
# 开始事务
|
||||
MULTI
|
||||
|
||||
# 添加命令到事务队列
|
||||
SET account:1001:balance 1000
|
||||
SET account:1002:balance 500
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
|
||||
# 执行事务
|
||||
EXEC
|
||||
|
||||
# 取消事务
|
||||
# DISCARD
|
||||
```
|
||||
|
||||
**完整事务示例**
|
||||
|
||||
```shell
|
||||
# 银行转账事务示例
|
||||
MULTI
|
||||
GET account:1001:balance # 检查余额
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
SET transfer:log "1001->1002:100"
|
||||
EXEC
|
||||
|
||||
# 批量用户创建事务
|
||||
MULTI
|
||||
HMSET user:2001 name "用户A" email "usera@example.com" status "active"
|
||||
HMSET user:2002 name "用户B" email "userb@example.com" status "active"
|
||||
SADD active_users "user:2001" "user:2002"
|
||||
INCR total_users
|
||||
EXEC
|
||||
```
|
||||
|
||||
**事务错误处理**
|
||||
|
||||
```shell
|
||||
# 语法错误示例(事务不会执行)
|
||||
MULTI
|
||||
SET key1 value1
|
||||
INVALID_COMMAND # 语法错误
|
||||
SET key2 value2
|
||||
EXEC # 返回错误,事务不执行
|
||||
|
||||
# 运行时错误示例(其他命令仍会执行)
|
||||
MULTI
|
||||
SET string_key "hello"
|
||||
LPUSH string_key "world" # 类型错误,但不影响其他命令
|
||||
SET another_key "value"
|
||||
EXEC # 部分命令执行成功
|
||||
```
|
||||
|
||||
### WATCH 命令
|
||||
|
||||
WATCH 命令用于实现乐观锁,监视键的变化。
|
||||
|
||||
**基本用法**
|
||||
|
||||
```shell
|
||||
# 监视键
|
||||
WATCH key [key ...]
|
||||
WATCH account:1001:balance account:1002:balance
|
||||
|
||||
# 开始事务
|
||||
MULTI
|
||||
DECRBY account:1001:balance 100
|
||||
INCRBY account:1002:balance 100
|
||||
EXEC # 如果被监视的键发生变化,事务不会执行
|
||||
|
||||
# 取消监视
|
||||
UNWATCH
|
||||
```
|
||||
|
||||
**乐观锁实现**
|
||||
|
||||
```shell
|
||||
# 实现安全的计数器增加
|
||||
WATCH counter
|
||||
val=$(redis-cli GET counter)
|
||||
if [ "$val" -lt 100 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli INCR counter
|
||||
redis-cli EXEC
|
||||
else
|
||||
redis-cli UNWATCH
|
||||
echo "计数器已达到上限"
|
||||
fi
|
||||
```
|
||||
|
||||
**复杂事务示例**
|
||||
|
||||
```shell
|
||||
# 库存扣减事务(防止超卖)
|
||||
WATCH product:1001:stock
|
||||
stock=$(redis-cli GET product:1001:stock)
|
||||
if [ "$stock" -ge 1 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli DECR product:1001:stock
|
||||
redis-cli LPUSH order:queue "order:$(date +%s)"
|
||||
redis-cli INCR sales:total
|
||||
result=$(redis-cli EXEC)
|
||||
if [ "$result" != "" ]; then
|
||||
echo "购买成功"
|
||||
else
|
||||
echo "购买失败,请重试"
|
||||
fi
|
||||
else
|
||||
redis-cli UNWATCH
|
||||
echo "库存不足"
|
||||
fi
|
||||
```
|
||||
|
||||
### 事务的特性和限制
|
||||
|
||||
**事务特性验证**
|
||||
|
||||
```shell
|
||||
# 原子性验证
|
||||
SET test:atomic "initial"
|
||||
MULTI
|
||||
SET test:atomic "step1"
|
||||
SET test:atomic "step2"
|
||||
SET test:atomic "final"
|
||||
EXEC
|
||||
GET test:atomic # 返回 "final"
|
||||
|
||||
# 隔离性验证(在事务执行期间,其他客户端的命令不会干扰)
|
||||
# 客户端1:
|
||||
MULTI
|
||||
SET test:isolation "value1"
|
||||
# 暂停,不执行EXEC
|
||||
|
||||
# 客户端2:
|
||||
SET test:isolation "value2" # 这个命令会立即执行
|
||||
|
||||
# 客户端1继续:
|
||||
EXEC
|
||||
GET test:isolation # 返回 "value1"(事务中的值覆盖了客户端2的修改)
|
||||
```
|
||||
|
||||
**事务限制示例**
|
||||
|
||||
```shell
|
||||
# 无回滚特性
|
||||
SET balance 1000
|
||||
MULTI
|
||||
DECRBY balance 100 # 成功
|
||||
LPUSH balance "error" # 类型错误,但不影响前面的命令
|
||||
DECRBY balance 50 # 成功
|
||||
EXEC
|
||||
GET balance # 返回 "850"(前面和后面的命令都执行了)
|
||||
|
||||
# 条件执行限制(Redis事务不支持if-else逻辑)
|
||||
# 需要在应用层实现条件判断
|
||||
balance=$(redis-cli GET account:balance)
|
||||
if [ "$balance" -gt 100 ]; then
|
||||
redis-cli MULTI
|
||||
redis-cli DECRBY account:balance 100
|
||||
redis-cli SET last:transaction "withdraw:100"
|
||||
redis-cli EXEC
|
||||
else
|
||||
echo "余额不足"
|
||||
fi
|
||||
```
|
||||
|
||||
## 脚本操作
|
||||
|
||||
### Lua 脚本简介
|
||||
|
||||
Redis 内嵌了 Lua 解释器,支持执行 Lua 脚本。Lua 脚本的优势:
|
||||
|
||||
**主要优势:**
|
||||
- **原子性**:脚本执行过程中不会被其他命令干扰
|
||||
- **减少网络开销**:多个命令在服务器端执行
|
||||
- **复杂逻辑**:支持条件判断、循环等复杂逻辑
|
||||
- **性能优化**:避免多次网络往返
|
||||
|
||||
**使用场景:**
|
||||
- 复杂的原子操作
|
||||
- 条件判断和循环
|
||||
- 批量数据处理
|
||||
- 限流算法实现
|
||||
- 分布式锁
|
||||
|
||||
### EVAL 和 EVALSHA 命令
|
||||
|
||||
**EVAL 命令基本用法**
|
||||
|
||||
```shell
|
||||
# EVAL script numkeys key [key ...] arg [arg ...]
|
||||
# script: Lua脚本
|
||||
# numkeys: 键的数量
|
||||
# key: 键名
|
||||
# arg: 参数
|
||||
|
||||
# 简单示例:设置键值并返回
|
||||
EVAL "return redis.call('SET', KEYS[1], ARGV[1])" 1 mykey myvalue
|
||||
|
||||
# 获取并增加计数器
|
||||
EVAL "local val = redis.call('GET', KEYS[1]) or 0; return redis.call('INCR', KEYS[1])" 1 counter
|
||||
|
||||
# 条件设置(仅当键不存在时设置)
|
||||
EVAL "if redis.call('EXISTS', KEYS[1]) == 0 then return redis.call('SET', KEYS[1], ARGV[1]) else return nil end" 1 newkey newvalue
|
||||
```
|
||||
|
||||
|
||||
**EVALSHA 命令**
|
||||
|
||||
```shell
|
||||
# 加载脚本并获取SHA1值
|
||||
SCRIPT LOAD "return redis.call('GET', KEYS[1])"
|
||||
# 返回:"6b1bf486c81ceb7edf3c093f4c48582e38c0e791"
|
||||
|
||||
# 使用SHA1值执行脚本
|
||||
EVALSHA 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 1 mykey
|
||||
|
||||
# 检查脚本是否存在
|
||||
SCRIPT EXISTS sha1 [sha1 ...]
|
||||
SCRIPT EXISTS 6b1bf486c81ceb7edf3c093f4c48582e38c0e791
|
||||
|
||||
# 清除脚本缓存
|
||||
SCRIPT FLUSH
|
||||
|
||||
# 杀死正在执行的脚本
|
||||
SCRIPT KILL
|
||||
```
|
||||
|
||||
### 脚本缓存
|
||||
|
||||
Redis 会缓存已执行的脚本,提高重复执行的性能。
|
||||
|
||||
```shell
|
||||
# 预加载常用脚本
|
||||
incr_script_sha=$(redis-cli SCRIPT LOAD "return redis.call('INCR', KEYS[1])")
|
||||
echo "增加计数器脚本SHA: $incr_script_sha"
|
||||
|
||||
# 使用缓存的脚本
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
redis-cli EVALSHA $incr_script_sha 1 my_counter
|
||||
|
||||
# 检查脚本缓存状态
|
||||
redis-cli SCRIPT EXISTS $incr_script_sha
|
||||
|
||||
# 获取脚本缓存统计
|
||||
redis-cli INFO memory | grep script
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
502
数据库/Redis_2025/05_Redis持久化.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Redis 持久化
|
||||
|
||||
Redis 是一个内存数据库,为了保证数据的持久性和可靠性,Redis 提供了多种持久化机制。理解和正确配置持久化是 Redis 生产环境部署的关键。
|
||||
|
||||
## RDB 持久化
|
||||
|
||||
### RDB 概述
|
||||
|
||||
RDB(Redis Database)是 Redis 的一种持久化方式,它将某个时间点的数据集快照保存到磁盘上。
|
||||
|
||||
**RDB 特点:**
|
||||
- **快照方式**:保存某个时间点的完整数据集
|
||||
- **紧凑格式**:二进制格式,文件体积小
|
||||
- **恢复快速**:启动时直接加载,恢复速度快
|
||||
- **性能影响小**:通过 fork 子进程执行,对主进程影响小
|
||||
- **数据丢失风险**:两次快照间的数据可能丢失
|
||||
|
||||
**适用场景:**
|
||||
- 对数据完整性要求不高的场景
|
||||
- 需要定期备份的场景
|
||||
- 主从复制的从节点
|
||||
- 数据恢复速度要求高的场景
|
||||
|
||||
### RDB 配置
|
||||
|
||||
**自动触发配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 配置文件中的 RDB 相关配置
|
||||
|
||||
# 自动保存条件(save <seconds> <changes>)
|
||||
# 在指定时间内,如果至少有指定数量的键发生变化,则执行 BGSAVE
|
||||
save 900 1 # 900秒内至少1个键发生变化
|
||||
save 300 10 # 300秒内至少10个键发生变化
|
||||
save 60 10000 # 60秒内至少10000个键发生变化
|
||||
|
||||
# 禁用自动保存(注释掉所有 save 行或使用空字符串)
|
||||
# save ""
|
||||
|
||||
# RDB 文件名:dump.rdb
|
||||
|
||||
# RDB 文件保存目录:/var/lib/redis
|
||||
|
||||
# 当 RDB 持久化出现错误时,是否停止接受写命令
|
||||
stop-writes-on-bgsave-error yes
|
||||
|
||||
# 是否压缩 RDB 文件
|
||||
rdbcompression yes
|
||||
|
||||
# 是否对 RDB 文件进行校验和检查
|
||||
rdbchecksum yes
|
||||
```
|
||||
|
||||
**手动触发配置**
|
||||
|
||||
```shell
|
||||
# 查看当前 RDB 配置
|
||||
redis-cli CONFIG GET save
|
||||
redis-cli CONFIG GET dbfilename
|
||||
redis-cli CONFIG GET dir
|
||||
|
||||
# 动态修改 RDB 配置
|
||||
redis-cli CONFIG SET save "900 1 300 10 60 10000"
|
||||
redis-cli CONFIG SET dbfilename "backup.rdb"
|
||||
redis-cli CONFIG SET rdbcompression yes
|
||||
|
||||
# 保存配置到文件
|
||||
redis-cli CONFIG REWRITE
|
||||
```
|
||||
|
||||
### RDB 操作命令
|
||||
|
||||
**手动生成 RDB 文件**
|
||||
|
||||
```shell
|
||||
# SAVE 命令(阻塞方式)
|
||||
# 在主进程中执行,会阻塞所有客户端请求
|
||||
redis-cli SAVE
|
||||
|
||||
# BGSAVE 命令(非阻塞方式,推荐)
|
||||
# 在后台子进程中执行,不阻塞主进程
|
||||
redis-cli BGSAVE
|
||||
|
||||
# 检查 BGSAVE 是否正在执行
|
||||
redis-cli LASTSAVE # 返回最后一次成功执行 SAVE/BGSAVE 的时间戳
|
||||
|
||||
# 获取 RDB 相关信息
|
||||
redis-cli INFO persistence
|
||||
```
|
||||
|
||||
**RDB 文件管理**
|
||||
|
||||
```shell
|
||||
# 查看 RDB 文件信息
|
||||
ls -la /var/lib/redis/dump.rdb
|
||||
|
||||
# 备份 RDB 文件
|
||||
cp /var/lib/redis/dump.rdb /backup/redis/dump_$(date +%Y%m%d_%H%M%S).rdb
|
||||
|
||||
# 验证 RDB 文件完整性
|
||||
redis-check-rdb /var/lib/redis/dump.rdb
|
||||
|
||||
# 从 RDB 文件恢复数据
|
||||
# 1. 停止 Redis 服务
|
||||
systemctl stop redis
|
||||
|
||||
# 2. 替换 RDB 文件
|
||||
cp /backup/redis/dump_xxx.rdb /var/lib/redis/dump.rdb
|
||||
chown redis:redis /var/lib/redis/dump.rdb
|
||||
|
||||
# 3. 启动 Redis 服务
|
||||
systemctl start redis
|
||||
|
||||
# 4. 验证数据恢复
|
||||
redis-cli DBSIZE
|
||||
```
|
||||
|
||||
### RDB 文件格式
|
||||
|
||||
**RDB 文件结构**
|
||||
|
||||
```shell
|
||||
RDB 文件结构:
|
||||
+-------+-------------+-----------+-----------------+-----+-----------+
|
||||
| REDIS | RDB-VERSION | SELECT-DB | KEY-VALUE-PAIRS | EOF | CHECK-SUM |
|
||||
+-------+-------------+-----------+-----------------+-----+-----------+
|
||||
|
||||
详细说明:
|
||||
- REDIS: 文件标识符(5字节)
|
||||
- RDB-VERSION: RDB 版本号(4字节)
|
||||
- SELECT-DB: 数据库选择器
|
||||
- KEY-VALUE-PAIRS: 键值对数据
|
||||
- EOF: 文件结束标识(1字节)
|
||||
- CHECK-SUM: 校验和(8字节)
|
||||
```
|
||||
|
||||
**分析 RDB 文件**
|
||||
|
||||
```bash
|
||||
# 使用 redis-rdb-tools 分析 RDB 文件
|
||||
pip install rdbtools python-lzf
|
||||
|
||||
# 将 RDB 转换为 JSON 格式
|
||||
rdb --command json /var/lib/redis/dump.rdb > dump.json
|
||||
|
||||
# 生成内存使用报告
|
||||
rdb --command memory /var/lib/redis/dump.rdb > memory_report.csv
|
||||
|
||||
# 按键类型统计
|
||||
rdb --command memory /var/lib/redis/dump.rdb --bytes 128 --largest 10
|
||||
|
||||
# 查看特定键的信息
|
||||
rdb --command memory /var/lib/redis/dump.rdb | grep "user:"
|
||||
```
|
||||
|
||||
## AOF 持久化
|
||||
|
||||
### AOF 概述
|
||||
|
||||
AOF(Append Only File)是 Redis 的另一种持久化方式,它记录服务器接收到的每个写操作命令。
|
||||
|
||||
**AOF 特点:**
|
||||
- **命令记录**:记录每个写操作命令
|
||||
- **数据安全性高**:可配置每秒或每个命令同步
|
||||
- **文件可读**:文本格式,可以直接查看和编辑
|
||||
- **自动重写**:定期压缩 AOF 文件
|
||||
- **恢复较慢**:需要重放所有命令
|
||||
|
||||
**适用场景:**
|
||||
- 对数据完整性要求高的场景
|
||||
- 需要最小数据丢失的场景
|
||||
- 主节点持久化
|
||||
- 需要审计写操作的场景
|
||||
|
||||
### AOF 配置
|
||||
|
||||
**基本配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 配置文件中的 AOF 相关配置
|
||||
|
||||
# 启用 AOF 持久化
|
||||
appendonly yes
|
||||
|
||||
# AOF 文件名
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
# always: 每个写命令都同步到磁盘(最安全,性能最低)
|
||||
# everysec: 每秒同步一次(推荐,平衡安全性和性能)
|
||||
# no: 由操作系统决定何时同步(性能最高,安全性最低)
|
||||
appendfsync everysec
|
||||
|
||||
# 在 AOF 重写期间是否同步
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# AOF 自动重写配置
|
||||
# 当 AOF 文件大小超过上次重写后大小的指定百分比时触发重写
|
||||
auto-aof-rewrite-percentage 100
|
||||
# AOF 文件最小重写大小
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# AOF 加载时是否忽略最后一个不完整的命令
|
||||
aof-load-truncated yes
|
||||
|
||||
# 是否使用 RDB-AOF 混合持久化
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
**动态配置**
|
||||
|
||||
```shell
|
||||
# 查看当前 AOF 配置
|
||||
redis-cli CONFIG GET appendonly
|
||||
redis-cli CONFIG GET appendfsync
|
||||
redis-cli CONFIG GET auto-aof-rewrite-percentage
|
||||
|
||||
# 动态启用 AOF
|
||||
redis-cli CONFIG SET appendonly yes
|
||||
|
||||
# 修改同步策略
|
||||
redis-cli CONFIG SET appendfsync everysec
|
||||
|
||||
# 修改自动重写配置
|
||||
redis-cli CONFIG SET auto-aof-rewrite-percentage 100
|
||||
redis-cli CONFIG SET auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 保存配置
|
||||
redis-cli CONFIG REWRITE
|
||||
```
|
||||
|
||||
### AOF 操作命令
|
||||
|
||||
**手动重写 AOF**
|
||||
|
||||
```shell
|
||||
# 手动触发 AOF 重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 检查 AOF 重写状态
|
||||
redis-cli INFO persistence | grep aof
|
||||
|
||||
# 查看 AOF 相关统计信息
|
||||
redis-cli INFO persistence
|
||||
```
|
||||
|
||||
**AOF 文件管理**
|
||||
|
||||
```shell
|
||||
# 查看 AOF 文件
|
||||
ls -la /var/lib/redis/appendonly.aof
|
||||
tail -f /var/lib/redis/appendonly.aof
|
||||
|
||||
# 检查 AOF 文件完整性
|
||||
redis-check-aof /var/lib/redis/appendonly.aof
|
||||
|
||||
# 修复损坏的 AOF 文件
|
||||
redis-check-aof --fix /var/lib/redis/appendonly.aof
|
||||
|
||||
# 备份 AOF 文件
|
||||
cp /var/lib/redis/appendonly.aof /backup/redis/appendonly_$(date +%Y%m%d_%H%M%S).aof
|
||||
```
|
||||
|
||||
### AOF 文件格式
|
||||
|
||||
**AOF 命令格式**
|
||||
|
||||
AOF 文件使用 RESP(Redis Serialization Protocol)格式记录命令:
|
||||
|
||||
```shell
|
||||
# AOF 文件内容示例
|
||||
*3 # 数组长度(3个元素)
|
||||
$3 # 字符串长度(3字节)
|
||||
SET # 命令
|
||||
$4 # 字符串长度(4字节)
|
||||
name # 键名
|
||||
$5 # 字符串长度(5字节)
|
||||
Alice # 键值
|
||||
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$3
|
||||
age
|
||||
$2
|
||||
25
|
||||
|
||||
*2
|
||||
$4
|
||||
INCR
|
||||
$7
|
||||
counter
|
||||
```
|
||||
|
||||
**分析 AOF 文件**
|
||||
|
||||
```shell
|
||||
# 查看 AOF 文件内容
|
||||
cat /var/lib/redis/appendonly.aof
|
||||
|
||||
# 统计 AOF 文件中的命令
|
||||
grep -c "^\*" /var/lib/redis/appendonly.aof
|
||||
|
||||
# 查看最近的命令
|
||||
tail -20 /var/lib/redis/appendonly.aof
|
||||
|
||||
# 提取特定命令
|
||||
grep -A 10 "SET" /var/lib/redis/appendonly.aof
|
||||
|
||||
# 使用工具分析 AOF 文件:python 脚本工具
|
||||
```
|
||||
|
||||
### AOF 重写机制
|
||||
|
||||
**重写原理**
|
||||
|
||||
AOF 重写是为了解决 AOF 文件不断增长的问题:
|
||||
|
||||
```shell
|
||||
# 重写前的 AOF 文件可能包含:
|
||||
SET counter 1
|
||||
INCR counter
|
||||
INCR counter
|
||||
INCR counter
|
||||
DEL temp_key
|
||||
SET temp_key value
|
||||
DEL temp_key
|
||||
|
||||
# 重写后的 AOF 文件只包含:
|
||||
SET counter 4
|
||||
# temp_key 相关的命令被完全移除,因为最终结果是键不存在
|
||||
```
|
||||
|
||||
**重写配置和监控**
|
||||
|
||||
```shell
|
||||
# 查看重写相关配置
|
||||
redis-cli CONFIG GET auto-aof-rewrite-*
|
||||
|
||||
# 查看重写统计信息
|
||||
redis-cli INFO persistence | grep -E "aof_rewrite|aof_current_size|aof_base_size"
|
||||
|
||||
# 手动触发重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 监控重写进度
|
||||
watch -n 1 'redis-cli INFO persistence | grep aof_rewrite_in_progress'
|
||||
|
||||
```
|
||||
|
||||
**重写性能优化**
|
||||
|
||||
```shell
|
||||
# 优化重写性能的配置
|
||||
# redis.conf
|
||||
|
||||
# 重写期间不进行 fsync,提高性能
|
||||
no-appendfsync-on-rewrite yes
|
||||
|
||||
# 调整重写触发条件
|
||||
auto-aof-rewrite-percentage 100 # 文件大小翻倍时重写
|
||||
auto-aof-rewrite-min-size 64mb # 最小64MB才考虑重写
|
||||
|
||||
# 使用混合持久化减少重写后的文件大小
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
## 混合持久化
|
||||
|
||||
### 混合持久化概述
|
||||
|
||||
Redis 4.0 引入了混合持久化,结合了 RDB 和 AOF 的优点。
|
||||
|
||||
**混合持久化特点:**
|
||||
- **快速恢复**:RDB 部分快速加载
|
||||
- **数据安全**:AOF 部分保证最新数据
|
||||
- **文件较小**:比纯 AOF 文件小
|
||||
- **兼容性好**:向后兼容
|
||||
|
||||
**文件结构:**
|
||||
```
|
||||
混合 AOF 文件结构:
|
||||
+---------------------+------------------------+
|
||||
| RDB 格式的数据快照 | AOF 格式的增量命令 |
|
||||
+---------------------+------------------------+
|
||||
```
|
||||
|
||||
### 混合持久化配置
|
||||
|
||||
```shell
|
||||
# 启用混合持久化
|
||||
# redis.conf
|
||||
appendonly yes
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# 动态启用
|
||||
redis-cli CONFIG SET aof-use-rdb-preamble yes
|
||||
redis-cli CONFIG REWRITE
|
||||
|
||||
# 验证配置
|
||||
redis-cli CONFIG GET aof-use-rdb-preamble
|
||||
```
|
||||
|
||||
### 混合持久化操作
|
||||
|
||||
```shell
|
||||
# 触发混合持久化重写
|
||||
redis-cli BGREWRITEAOF
|
||||
|
||||
# 检查文件格式
|
||||
file /var/lib/redis/appendonly.aof
|
||||
head -c 20 /var/lib/redis/appendonly.aof | xxd
|
||||
|
||||
# 如果是混合格式,开头应该是 "REDIS" 而不是 "*"
|
||||
```
|
||||
|
||||
## 持久化策略选择
|
||||
|
||||
### 不同场景的持久化策略
|
||||
|
||||
**高性能场景**
|
||||
|
||||
```shell
|
||||
# 配置:仅使用 RDB,较长的保存间隔
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly no
|
||||
|
||||
# 适用场景:
|
||||
# - 缓存系统
|
||||
# - 数据丢失容忍度高
|
||||
# - 性能要求极高
|
||||
```
|
||||
|
||||
**高可靠性场景**
|
||||
|
||||
```shell
|
||||
# 配置:AOF + 每秒同步
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# 可选:同时启用 RDB 作为备份
|
||||
save 900 1
|
||||
|
||||
# 适用场景:
|
||||
# - 金融系统
|
||||
# - 重要业务数据
|
||||
# - 数据丢失容忍度低
|
||||
```
|
||||
|
||||
**平衡场景**
|
||||
|
||||
```shell
|
||||
# 配置:混合持久化
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
save 900 1
|
||||
save 300 10
|
||||
|
||||
# 适用场景:
|
||||
# - 大多数生产环境
|
||||
# - 平衡性能和可靠性
|
||||
# - 中等数据量
|
||||
```
|
||||
|
||||
### 持久化最佳实践
|
||||
|
||||
**生产环境配置建议**
|
||||
|
||||
```shell
|
||||
# 推荐的生产环境配置
|
||||
# redis.conf
|
||||
|
||||
# 启用混合持久化
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
aof-use-rdb-preamble yes
|
||||
|
||||
# RDB 配置(作为备份)
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
|
||||
# AOF 重写配置
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
no-appendfsync-on-rewrite no
|
||||
aof-load-truncated yes
|
||||
|
||||
# 文件路径配置
|
||||
dir /var/lib/redis
|
||||
dbfilename dump.rdb
|
||||
appendfilename "appendonly.aof"
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
546
数据库/Redis_2025/06_Redis主从复制.md
Normal file
@@ -0,0 +1,546 @@
|
||||
# Redis 主从复制
|
||||
|
||||
Redis 主从复制是 Redis 高可用性架构的基础,通过数据复制实现读写分离、故障转移和数据备份。理解主从复制的原理和配置是构建可靠 Redis 集群的关键。
|
||||
|
||||
## 主从复制概述
|
||||
|
||||
### 主从复制的概念
|
||||
|
||||
主从复制(Master-Slave Replication)是 Redis 提供的数据同步机制,其中一个 Redis 实例作为主节点(Master),其他实例作为从节点(Slave)。
|
||||
|
||||
**基本概念:**
|
||||
- **主节点(Master)**:接受写操作,负责数据的修改
|
||||
- **从节点(Slave)**:从主节点复制数据,通常只处理读操作
|
||||
- **复制流(Replication Stream)**:主节点向从节点发送的数据同步流
|
||||
- **复制偏移量(Replication Offset)**:用于标识复制进度的位置
|
||||
|
||||
### 主从复制的优势
|
||||
|
||||
**数据安全性:**
|
||||
- 数据冗余备份
|
||||
- 防止单点故障
|
||||
- 支持数据恢复
|
||||
|
||||
**性能提升:**
|
||||
- 读写分离
|
||||
- 负载均衡
|
||||
- 减轻主节点压力
|
||||
|
||||
**高可用性:**
|
||||
- 故障转移基础
|
||||
- 服务连续性
|
||||
- 自动故障检测
|
||||
|
||||
**扩展性:**
|
||||
- 水平扩展读能力
|
||||
- 支持多级复制
|
||||
- 灵活的架构设计
|
||||
|
||||
### 主从复制的应用场景
|
||||
|
||||
|
||||
典型应用场景:
|
||||
|
||||
1. 读写分离架构
|
||||
应用 → 写操作 → Master
|
||||
应用 → 读操作 → Slave1, Slave2, Slave3
|
||||
|
||||
2. 数据备份
|
||||
Master → 实时数据
|
||||
Slave → 备份数据(可设置不同的持久化策略)
|
||||
|
||||
3. 故障转移
|
||||
Master 故障 → Slave 提升为新 Master
|
||||
|
||||
4. 数据分析
|
||||
Master → 生产数据
|
||||
Slave → 数据分析和报表生成
|
||||
|
||||
5. 跨地域部署
|
||||
Master → 主数据中心
|
||||
Slave → 异地数据中心
|
||||
|
||||
|
||||
## 主从复制原理
|
||||
|
||||
### 复制过程
|
||||
|
||||
**全量复制(Full Resynchronization)**
|
||||
|
||||
全量复制发生在从节点首次连接主节点或复制中断后无法进行增量复制时。
|
||||
|
||||
全量复制流程:
|
||||
|
||||
1. 从节点发送 PSYNC 命令
|
||||
Slave → Master: PSYNC ? -1
|
||||
|
||||
2. 主节点响应 FULLRESYNC
|
||||
Master → Slave: FULLRESYNC <runid> <offset>
|
||||
|
||||
3. 主节点执行 BGSAVE
|
||||
Master: 生成 RDB 快照文件
|
||||
|
||||
4. 主节点发送 RDB 文件
|
||||
Master → Slave: RDB 文件数据
|
||||
|
||||
5. 主节点发送缓冲区命令
|
||||
Master → Slave: 复制期间的写命令
|
||||
|
||||
6. 从节点加载数据
|
||||
Slave: 清空数据库 → 加载 RDB → 执行缓冲命令
|
||||
|
||||
|
||||
**增量复制(Partial Resynchronization)**
|
||||
|
||||
Redis 2.8+ 支持增量复制,用于处理短暂的网络中断。
|
||||
|
||||
增量复制流程:
|
||||
|
||||
1. 从节点重连并发送 PSYNC
|
||||
Slave → Master: PSYNC <runid> <offset>
|
||||
|
||||
2. 主节点检查复制积压缓冲区
|
||||
Master: 检查 offset 是否在缓冲区范围内
|
||||
|
||||
3. 主节点响应 CONTINUE
|
||||
Master → Slave: CONTINUE
|
||||
|
||||
4. 主节点发送缺失的命令
|
||||
Master → Slave: 缓冲区中 offset 之后的命令
|
||||
|
||||
5. 从节点执行命令
|
||||
Slave: 执行接收到的命令,恢复同步
|
||||
|
||||
### 复制相关的数据结构
|
||||
|
||||
**复制积压缓冲区(Replication Backlog)**
|
||||
|
||||
```shell
|
||||
# 复制积压缓冲区配置
|
||||
# redis.conf
|
||||
|
||||
# 缓冲区大小(默认 1MB)
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 缓冲区超时时间(默认 3600 秒)
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 查看缓冲区状态
|
||||
redis-cli INFO replication | grep backlog
|
||||
```
|
||||
|
||||
**运行 ID(Run ID)**
|
||||
|
||||
```shell
|
||||
# 查看运行 ID
|
||||
redis-cli INFO server | grep run_id
|
||||
|
||||
# 运行 ID 的作用:
|
||||
# 1. 标识 Redis 实例的唯一性
|
||||
# 2. 用于增量复制的验证
|
||||
# 3. 重启后会生成新的 Run ID
|
||||
```
|
||||
|
||||
**复制偏移量(Replication Offset)**
|
||||
|
||||
```shell
|
||||
# 查看复制偏移量
|
||||
redis-cli INFO replication | grep offset
|
||||
|
||||
# 主节点偏移量:master_repl_offset
|
||||
# 从节点偏移量:slave_repl_offset
|
||||
# 偏移量差异表示复制延迟
|
||||
```
|
||||
|
||||
## 主从复制配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**主节点配置**
|
||||
|
||||
```shell
|
||||
# 主节点 redis.conf 配置
|
||||
|
||||
# 绑定地址(允许从节点连接)
|
||||
bind 0.0.0.0
|
||||
|
||||
# 端口
|
||||
port 6379
|
||||
|
||||
# 设置密码(可选)
|
||||
requirepass master_password
|
||||
|
||||
# 主从复制相关配置
|
||||
# 复制积压缓冲区大小
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 复制超时时间
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用 TCP_NODELAY(可选,提高网络效率)
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制优先级(用于故障转移)
|
||||
slave-priority 100
|
||||
|
||||
# 最小从节点数量(可选)
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
```
|
||||
|
||||
**从节点配置**
|
||||
|
||||
```shell
|
||||
# 从节点 redis.conf 配置
|
||||
|
||||
# 绑定地址
|
||||
bind 0.0.0.0
|
||||
|
||||
# 端口(通常使用不同端口)
|
||||
port 6380
|
||||
|
||||
# 指定主节点
|
||||
slaveof 192.168.1.100 6379
|
||||
# 或者使用新的配置项
|
||||
replicaof 192.168.1.100 6379
|
||||
|
||||
# 主节点密码
|
||||
masterauth master_password
|
||||
|
||||
# 从节点密码(可选)
|
||||
requirepass slave_password
|
||||
|
||||
# 从节点只读(推荐)
|
||||
slave-read-only yes
|
||||
|
||||
# 复制相关配置
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 从节点持久化配置(可选)
|
||||
# 通常从节点可以禁用持久化以提高性能
|
||||
save ""
|
||||
appendonly no
|
||||
```
|
||||
|
||||
### 动态配置
|
||||
|
||||
**运行时配置主从关系**
|
||||
|
||||
```shell
|
||||
# 将当前实例设置为从节点
|
||||
redis-cli SLAVEOF 192.168.1.100 6379
|
||||
# 或使用新命令
|
||||
redis-cli REPLICAOF 192.168.1.100 6379
|
||||
|
||||
# 取消主从关系(将从节点提升为主节点)
|
||||
redis-cli SLAVEOF NO ONE
|
||||
# 或
|
||||
redis-cli REPLICAOF NO ONE
|
||||
|
||||
# 设置主节点密码
|
||||
redis-cli CONFIG SET masterauth master_password
|
||||
|
||||
# 查看复制状态
|
||||
redis-cli INFO replication
|
||||
```
|
||||
|
||||
**配置验证**
|
||||
|
||||
```shell
|
||||
# 在主节点上查看从节点信息
|
||||
redis-cli -h 192.168.1.100 -p 6379 INFO replication
|
||||
|
||||
# 在从节点上查看复制状态
|
||||
redis-cli -h 192.168.1.101 -p 6380 INFO replication
|
||||
|
||||
# 测试数据同步
|
||||
# 在主节点写入数据
|
||||
redis-cli -h 192.168.1.100 -p 6379 SET test_key "test_value"
|
||||
|
||||
# 在从节点读取数据
|
||||
redis-cli -h 192.168.1.101 -p 6380 GET test_key
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
**复制安全配置**
|
||||
|
||||
```shell
|
||||
# redis.conf 安全配置
|
||||
|
||||
# 保护模式
|
||||
protected-mode yes
|
||||
|
||||
# 绑定特定网络接口
|
||||
bind 192.168.1.100 127.0.0.1
|
||||
|
||||
# 设置强密码
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_$(openssl rand -hex 8)"
|
||||
|
||||
# 限制客户端连接数
|
||||
maxclients 1000
|
||||
|
||||
# 设置内存限制
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
**网络优化配置**
|
||||
|
||||
```shell
|
||||
# 网络相关优化配置
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 复制超时
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用 Nagle 算法(降低延迟)
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制积压缓冲区大小(根据网络情况调整)
|
||||
repl-backlog-size 10mb
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
```
|
||||
|
||||
## 主从复制管理
|
||||
|
||||
### 监控主从状态
|
||||
|
||||
**复制状态监控**
|
||||
|
||||
```shell
|
||||
# 主节点重点指标
|
||||
INFO server:
|
||||
redis_version:6.2.19
|
||||
uptime_in_seconds:14339
|
||||
INFO replication:
|
||||
role:master
|
||||
connected_slaves:1
|
||||
master_repl_offset:1000000
|
||||
repl_backlog_size:10485760
|
||||
repl_backlog_ttl:3600
|
||||
INFO stats:
|
||||
total_commands_processed:1000000
|
||||
instantaneous_ops_per_sec:1000
|
||||
INFO memory:
|
||||
used_memory_human:100.00M
|
||||
|
||||
# 从节点重点指标
|
||||
INFO server:
|
||||
redis_version:6.2.19
|
||||
uptime_in_seconds:14339
|
||||
INFO replication:
|
||||
role:slave
|
||||
master_host:192.168.1.100
|
||||
master_port:6379
|
||||
master_link_status:up
|
||||
master_last_io_seconds_ago:0
|
||||
master_sync_in_progress:0
|
||||
# lag = master_repl_offset - slave_repl_offset
|
||||
slave_repl_offset:1000000
|
||||
slave_priority:100
|
||||
```
|
||||
|
||||
### 性能优化
|
||||
|
||||
**复制性能调优**
|
||||
|
||||
```shell
|
||||
# 复制性能优化配置
|
||||
# 禁用 TCP_NODELAY 以提高网络效率
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 增大复制积压缓冲区
|
||||
repl-backlog-size 100mb
|
||||
|
||||
# 增大客户端输出缓冲区
|
||||
client-output-buffer-limit slave 512mb 128mb 60
|
||||
|
||||
# 复制超时
|
||||
repl-timeout 60
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 7200
|
||||
|
||||
# 主节点:启用 AOF,禁用 RDB 自动保存
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
save ""
|
||||
|
||||
# 从节点:禁用持久化以提高性能
|
||||
# save ""
|
||||
# appendonly no
|
||||
|
||||
# 设置合适的内存策略
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 增加最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
```
|
||||
|
||||
**读写分离优化**
|
||||
|
||||
```shell
|
||||
# 读写分离连接池配置示例(Python)- redis_pool_example.py
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
**需求描述:**
|
||||
在本地环境搭建一主两从的 Redis 复制架构,学习配置方法和管理技巧。
|
||||
|
||||
**实践细节和结果验证:**
|
||||
|
||||
```shell
|
||||
# 创建工作目录
|
||||
mkdir -p /data/redis_cluster/{master,slave1,slave2}
|
||||
|
||||
# 生成配置文件
|
||||
cat > /data/redis_cluster/master/redis.conf << EOF
|
||||
# 主节点配置
|
||||
port 6679
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/master
|
||||
logfile "redis-master.log"
|
||||
pidfile "redis-master.pid"
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-master.aof"
|
||||
|
||||
# 复制配置
|
||||
repl-backlog-size 10mb
|
||||
repl-backlog-ttl 3600
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
EOF
|
||||
|
||||
# 从节点1配置
|
||||
cat > /data/redis_cluster/slave1/redis.conf << EOF
|
||||
# 从节点1配置
|
||||
port 6680
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/slave1
|
||||
logfile "redis-slave1.log"
|
||||
pidfile "redis-slave1.pid"
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6679
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 持久化配置(从节点可以禁用以提高性能)
|
||||
save ""
|
||||
appendonly no
|
||||
EOF
|
||||
|
||||
# 从节点2配置
|
||||
cat > /data/redis_cluster/slave2/redis.conf << EOF
|
||||
# 从节点2配置
|
||||
port 6681
|
||||
bind 0.0.0.0
|
||||
dir /data/redis_cluster/slave2
|
||||
logfile "redis-slave2.log"
|
||||
pidfile "redis-slave2.pid"
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6679
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 90
|
||||
|
||||
# 持久化配置
|
||||
save ""
|
||||
appendonly no
|
||||
EOF
|
||||
|
||||
# 启动主节点
|
||||
redis-server /data/redis_cluster/master/redis.conf --daemonize yes
|
||||
# 验证主节点启动
|
||||
redis-cli -p 6679 ping
|
||||
|
||||
# 启动从节点
|
||||
redis-server /data/redis_cluster/slave1/redis.conf --daemonize yes
|
||||
redis-server /data/redis_cluster/slave2/redis.conf --daemonize yes
|
||||
# 验证从节点启动
|
||||
redis-cli -p 6680 ping
|
||||
redis-cli -p 6681 ping
|
||||
|
||||
# 验证主从复制
|
||||
# 查看主节点状态
|
||||
redis-cli -p 6679 INFO replication
|
||||
|
||||
# 查看从节点1状态
|
||||
redis-cli -p 6680 INFO replication | grep -E "role|master_host|master_port|master_link_status"
|
||||
# 查看从节点2状态
|
||||
redis-cli -p 6681 INFO replication | grep -E "role|master_host|master_port|master_link_status"
|
||||
|
||||
# 测试数据同步
|
||||
# 在主节点写入数据
|
||||
redis-cli -p 6679 << EOF
|
||||
SET test:string "Hello Redis Replication"
|
||||
LPUSH test:list "item1" "item2" "item3"
|
||||
SADD test:set "member1" "member2" "member3"
|
||||
HMSET test:hash field1 "value1" field2 "value2"
|
||||
ZADD test:zset 1 "first" 2 "second" 3 "third"
|
||||
SETEX test:expire 3600 "will expire in 1 hour"
|
||||
EOF
|
||||
|
||||
# 在从节点验证数据
|
||||
redis-cli -p 6680 << EOF
|
||||
GET test:string
|
||||
LRANGE test:list 0 -1
|
||||
SMEMBERS test:set
|
||||
HGETALL test:hash
|
||||
ZRANGE test:zset 0 -1 WITHSCORES
|
||||
TTL test:expire
|
||||
EOF
|
||||
|
||||
# 测试读写分离
|
||||
# 测试从节点只读
|
||||
redis-cli -p 6680 SET readonly_test "should_fail"
|
||||
|
||||
# 监控复制延迟
|
||||
# 获取复制偏移量
|
||||
master_offset=$(redis-cli -p 6679 INFO replication | grep master_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
slave1_offset=$(redis-cli -p 6680 INFO replication | grep slave_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
slave2_offset=$(redis-cli -p 6681 INFO replication | grep slave_repl_offset | cut -d: -f2 | tr -d '\r')
|
||||
echo "主节点偏移量: $master_offset"
|
||||
echo "从节点1偏移量: $slave1_offset (延迟: $((master_offset - slave1_offset)) 字节)"
|
||||
echo "从节点2偏移量: $slave2_offset (延迟: $((master_offset - slave2_offset)) 字节)"
|
||||
|
||||
# 故障模拟
|
||||
# 模拟从节点故障
|
||||
redis-cli -p 6680 SHUTDOWN NOSAVE
|
||||
# 检查主节点状态
|
||||
redis-cli -p 6679 INFO replication | grep connected_slaves
|
||||
|
||||
# 恢复从节点1
|
||||
redis-server /data/redis_cluster/slave1/redis.conf --daemonize yes
|
||||
|
||||
# 验证恢复
|
||||
redis-cli -p 6680 ping
|
||||
redis-cli -p 6679 INFO replication | grep connected_slaves
|
||||
|
||||
```
|
||||
671
数据库/Redis_2025/07_Redis哨兵架构.md
Normal file
@@ -0,0 +1,671 @@
|
||||
# Redis 哨兵架构
|
||||
|
||||
Redis 哨兵(Sentinel)是 Redis 官方提供的高可用性解决方案,通过监控、通知、自动故障转移和配置提供者等功能,确保 Redis 服务的持续可用性。
|
||||
|
||||
## 哨兵模式概述
|
||||
|
||||
### 哨兵模式的概念
|
||||
|
||||
Redis 哨兵是一个分布式系统,用于管理多个 Redis 实例,提供以下核心功能:
|
||||
|
||||
**核心组件:**
|
||||
- **哨兵节点(Sentinel)**:监控和管理 Redis 实例的独立进程
|
||||
- **主节点(Master)**:处理写操作的 Redis 实例
|
||||
- **从节点(Slave/Replica)**:从主节点复制数据的 Redis 实例
|
||||
- **客户端(Client)**:连接到哨兵系统的应用程序
|
||||
|
||||
**核心功能:**
|
||||
1. **监控(Monitoring)**:持续监控主从节点的健康状态
|
||||
2. **通知(Notification)**:当实例出现问题时发送通知
|
||||
3. **自动故障转移(Automatic Failover)**:主节点故障时自动选举新主节点
|
||||
4. **配置提供者(Configuration Provider)**:为客户端提供当前主节点信息
|
||||
|
||||
### 哨兵模式的优势
|
||||
|
||||
**高可用性:**
|
||||
- 自动故障检测和转移
|
||||
- 无需人工干预
|
||||
- 最小化服务中断时间
|
||||
- 支持多数据中心部署
|
||||
|
||||
**可靠性:**
|
||||
- 分布式决策机制
|
||||
- 避免脑裂问题
|
||||
- 多哨兵节点冗余
|
||||
- 客观下线判断
|
||||
|
||||
**易用性:**
|
||||
- 客户端自动发现主节点
|
||||
- 透明的故障转移
|
||||
- 简化的运维管理
|
||||
- 丰富的监控信息
|
||||
|
||||
**扩展性:**
|
||||
- 支持动态添加哨兵节点
|
||||
- 支持多主从架构
|
||||
- 灵活的配置管理
|
||||
- 可编程的通知机制
|
||||
|
||||
### 哨兵模式的应用场景
|
||||
|
||||
```
|
||||
典型应用场景:
|
||||
|
||||
1. 生产环境高可用
|
||||
应用 → 哨兵集群 → Redis 主从集群
|
||||
自动故障转移,保证服务连续性
|
||||
|
||||
2. 多数据中心部署
|
||||
数据中心A: 主节点 + 哨兵
|
||||
数据中心B: 从节点 + 哨兵
|
||||
数据中心C: 从节点 + 哨兵
|
||||
|
||||
3. 读写分离架构
|
||||
写操作 → 哨兵发现的主节点
|
||||
读操作 → 哨兵管理的从节点
|
||||
|
||||
4. 缓存层高可用
|
||||
Web应用 → 哨兵 → Redis缓存集群
|
||||
缓存故障时自动切换
|
||||
|
||||
5. 会话存储
|
||||
负载均衡器 → 应用服务器 → 哨兵 → Redis会话存储
|
||||
保证会话数据的高可用性
|
||||
```
|
||||
|
||||
## 哨兵模式原理
|
||||
|
||||
### 哨兵工作机制
|
||||
|
||||
**监控机制**
|
||||
|
||||
哨兵通过定期发送命令来监控 Redis 实例的状态:
|
||||
|
||||
监控流程:
|
||||
|
||||
1. 发送 PING 命令
|
||||
哨兵 → Redis实例: PING
|
||||
Redis实例 → 哨兵: PONG
|
||||
|
||||
2. 获取实例信息
|
||||
哨兵 → 主节点: INFO replication
|
||||
主节点 → 哨兵: 从节点列表和状态
|
||||
|
||||
3. 发现新实例
|
||||
哨兵根据主节点信息自动发现从节点
|
||||
哨兵之间通过发布/订阅发现彼此
|
||||
|
||||
4. 状态判断
|
||||
主观下线(SDOWN):单个哨兵认为实例不可用
|
||||
客观下线(ODOWN):多数哨兵认为实例不可用
|
||||
|
||||
**故障检测**
|
||||
|
||||
```shell
|
||||
# 故障检测参数
|
||||
# sentinel.conf
|
||||
|
||||
# 主观下线时间(毫秒)
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# 客观下线需要的哨兵数量
|
||||
sentinel quorum mymaster 2
|
||||
|
||||
# 故障转移超时时间
|
||||
sentinel failover-timeout mymaster 180000
|
||||
|
||||
# 并行同步的从节点数量
|
||||
sentinel parallel-syncs mymaster 1
|
||||
```
|
||||
|
||||
**故障检测流程:**
|
||||
|
||||
1. **主观下线(Subjectively Down, SDOWN)**
|
||||
- 单个哨兵在指定时间内无法与实例通信
|
||||
- 哨兵将实例标记为主观下线
|
||||
- 开始询问其他哨兵的意见
|
||||
|
||||
2. **客观下线(Objectively Down, ODOWN)**
|
||||
- 足够数量的哨兵认为实例主观下线
|
||||
- 达到 quorum 配置的数量要求
|
||||
- 实例被标记为客观下线
|
||||
|
||||
3. **故障转移触发**
|
||||
- 只有主节点的客观下线会触发故障转移
|
||||
- 从节点的客观下线只会影响监控状态
|
||||
|
||||
**故障转移过程**
|
||||
|
||||
故障转移详细流程:
|
||||
|
||||
1. 选举领导者哨兵
|
||||
- 检测到主节点客观下线
|
||||
- 哨兵之间进行领导者选举
|
||||
- 使用 Raft 算法确保只有一个领导者
|
||||
|
||||
2. 选择新主节点
|
||||
领导者哨兵根据以下优先级选择:
|
||||
a. 排除主观下线的从节点
|
||||
b. 排除断线时间超过阈值的从节点
|
||||
c. 选择 slave-priority 最小的从节点
|
||||
d. 选择复制偏移量最大的从节点
|
||||
e. 选择 run_id 最小的从节点
|
||||
|
||||
3. 提升新主节点
|
||||
- 向选中的从节点发送 SLAVEOF NO ONE
|
||||
- 等待从节点变为主节点
|
||||
- 验证新主节点状态
|
||||
|
||||
4. 更新其他从节点
|
||||
- 向其他从节点发送 SLAVEOF 新主节点
|
||||
- 控制并行同步数量(parallel-syncs)
|
||||
- 监控同步进度
|
||||
|
||||
5. 更新配置
|
||||
- 更新哨兵配置文件
|
||||
- 通知客户端新主节点信息
|
||||
- 发布配置变更事件
|
||||
|
||||
### 哨兵通信机制
|
||||
|
||||
**发布/订阅通信**
|
||||
|
||||
哨兵使用 Redis 的发布/订阅功能进行通信:
|
||||
|
||||
```shell
|
||||
# 哨兵通信频道
|
||||
__sentinel__:hello # 哨兵发现和信息交换
|
||||
+switch-master # 主节点切换通知
|
||||
+slave # 从节点发现通知
|
||||
+sentinel # 哨兵发现通知
|
||||
+sdown # 主观下线通知
|
||||
+odown # 客观下线通知
|
||||
+failover-triggered # 故障转移触发通知
|
||||
+failover-state-* # 故障转移状态变更
|
||||
```
|
||||
|
||||
**哨兵发现机制**
|
||||
|
||||
哨兵发现流程:
|
||||
|
||||
1. 主节点发现
|
||||
- 通过配置文件指定初始主节点
|
||||
- 哨兵连接并监控主节点
|
||||
|
||||
2. 从节点发现
|
||||
- 通过 INFO replication 命令获取从节点列表
|
||||
- 自动连接和监控发现的从节点
|
||||
|
||||
3. 哨兵发现
|
||||
- 通过 __sentinel__:hello 频道发布自己的信息
|
||||
- 订阅该频道发现其他哨兵
|
||||
- 建立哨兵之间的连接
|
||||
|
||||
4. 信息同步
|
||||
- 定期交换监控信息
|
||||
- 同步实例状态和配置
|
||||
- 协调故障检测和转移
|
||||
|
||||
|
||||
## 哨兵模式配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**哨兵配置文件**
|
||||
|
||||
```shell
|
||||
# 创建哨兵配置文件 sentinel.conf
|
||||
cat > /tmp/sentinel.conf << EOF
|
||||
# Redis 哨兵配置文件
|
||||
|
||||
# 哨兵端口
|
||||
port 26379
|
||||
|
||||
# 哨兵工作目录
|
||||
dir /tmp
|
||||
|
||||
# 监控的主节点配置
|
||||
# sentinel monitor <master-name> <ip> <port> <quorum>
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
# 主节点认证密码
|
||||
sentinel auth-pass mymaster your_password
|
||||
|
||||
# 主观下线时间(毫秒)
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
|
||||
# 故障转移超时时间(毫秒)
|
||||
sentinel failover-timeout mymaster 180000
|
||||
|
||||
# 并行同步的从节点数量
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 哨兵认证(可选)
|
||||
requirepass sentinel_password
|
||||
|
||||
# 日志配置
|
||||
logfile "/var/log/redis/sentinel.log"
|
||||
loglevel notice
|
||||
|
||||
# 通知脚本(可选)
|
||||
# sentinel notification-script mymaster /path/to/notify.sh
|
||||
|
||||
# 客户端重配置脚本(可选)
|
||||
# sentinel client-reconfig-script mymaster /path/to/reconfig.sh
|
||||
|
||||
# 拒绝危险命令
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
```
|
||||
|
||||
**多哨兵配置**
|
||||
|
||||
```shell
|
||||
# 哨兵1配置
|
||||
cat > /tmp/sentinel-1.conf << EOF
|
||||
port 26379
|
||||
dir /tmp/sentinel-1
|
||||
logfile "sentinel-1.log"
|
||||
pidfile "sentinel-1.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
|
||||
# 哨兵2配置
|
||||
cat > /tmp/sentinel-2.conf << EOF
|
||||
port 26380
|
||||
dir /tmp/sentinel-2
|
||||
logfile "sentinel-2.log"
|
||||
pidfile "sentinel-2.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
|
||||
# 哨兵3配置
|
||||
cat > /tmp/sentinel-3.conf << EOF
|
||||
port 26381
|
||||
dir /tmp/sentinel-3
|
||||
logfile "sentinel-3.log"
|
||||
pidfile "sentinel-3.pid"
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
sentinel auth-pass mymaster master_password
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
EOF
|
||||
```
|
||||
|
||||
**安全配置**
|
||||
|
||||
```shell
|
||||
# 安全增强的哨兵配置
|
||||
cat > /tmp/sentinel_secure.conf << 'EOF'
|
||||
# 安全增强的哨兵配置
|
||||
|
||||
# 基本配置
|
||||
port 26379
|
||||
dir /var/lib/redis/sentinel
|
||||
logfile "/var/log/redis/sentinel.log"
|
||||
pidfile "/var/run/redis/sentinel.pid"
|
||||
|
||||
# 绑定特定接口
|
||||
bind 192.168.1.100 127.0.0.1
|
||||
|
||||
# 保护模式
|
||||
protected-mode yes
|
||||
|
||||
# 哨兵认证
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
|
||||
# 监控配置
|
||||
sentinel monitor mymaster 192.168.1.100 6379 2
|
||||
sentinel auth-pass mymaster "$(openssl rand -base64 32)"
|
||||
|
||||
# 超时配置
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 拒绝脚本重配置
|
||||
sentinel deny-scripts-reconfig yes
|
||||
|
||||
# 通知脚本(使用绝对路径)
|
||||
sentinel notification-script mymaster /usr/local/bin/sentinel_notify.sh
|
||||
sentinel client-reconfig-script mymaster /usr/local/bin/client_reconfig.sh
|
||||
|
||||
# 日志级别
|
||||
loglevel notice
|
||||
|
||||
# 限制连接数
|
||||
# maxclients 100
|
||||
EOF
|
||||
```
|
||||
|
||||
### 动态配置管理
|
||||
|
||||
**运行时配置修改**
|
||||
|
||||
```shell
|
||||
# 连接到哨兵
|
||||
redis-cli -p 26379
|
||||
|
||||
# 查看监控的主节点
|
||||
SENTINEL masters
|
||||
|
||||
# 查看特定主节点的从节点
|
||||
SENTINEL slaves mymaster
|
||||
|
||||
# 查看哨兵节点
|
||||
SENTINEL sentinels mymaster
|
||||
|
||||
# 获取主节点地址
|
||||
SENTINEL get-master-addr-by-name mymaster
|
||||
|
||||
# 动态修改配置
|
||||
SENTINEL set mymaster down-after-milliseconds 60000
|
||||
SENTINEL set mymaster failover-timeout 300000
|
||||
SENTINEL set mymaster parallel-syncs 2
|
||||
|
||||
# 重置主节点(清除故障状态)
|
||||
SENTINEL reset mymaster
|
||||
|
||||
# 强制故障转移
|
||||
SENTINEL failover mymaster
|
||||
|
||||
# 移除主节点监控
|
||||
SENTINEL remove mymaster
|
||||
|
||||
# 添加新的主节点监控
|
||||
SENTINEL monitor newmaster 192.168.1.200 6379 2
|
||||
```
|
||||
|
||||
**配置持久化**
|
||||
|
||||
```shell
|
||||
# 哨兵配置自动更新机制
|
||||
echo "哨兵配置文件会自动更新以下内容:"
|
||||
echo "1. 发现的从节点信息"
|
||||
echo "2. 发现的其他哨兵信息"
|
||||
echo "3. 故障转移后的新主节点信息"
|
||||
echo "4. 实例状态变更记录"
|
||||
|
||||
# 查看自动更新的配置
|
||||
cat /tmp/sentinel.conf | grep -E "^# Generated by CONFIG REWRITE|^sentinel known-"
|
||||
|
||||
# 手动保存配置
|
||||
redis-cli -p 26379 CONFIG REWRITE
|
||||
```
|
||||
|
||||
## 哨兵模式管理
|
||||
|
||||
### 启动和停止
|
||||
|
||||
**启动哨兵**
|
||||
|
||||
```shell
|
||||
# 方法1:使用 redis-sentinel 命令
|
||||
redis-sentinel /path/to/sentinel.conf
|
||||
|
||||
# 方法2:使用 redis-server 命令
|
||||
redis-server /path/to/sentinel.conf --sentinel
|
||||
|
||||
# 后台启动
|
||||
redis-sentinel /path/to/sentinel.conf --daemonize yes
|
||||
|
||||
# 使用 systemd 管理
|
||||
sudo systemctl start redis-sentinel
|
||||
sudo systemctl enable redis-sentinel
|
||||
|
||||
# 检查启动状态
|
||||
ps aux | grep sentinel
|
||||
netstat -tlnp | grep 26379
|
||||
```
|
||||
|
||||
**停止哨兵**
|
||||
|
||||
```shell
|
||||
# 优雅停止
|
||||
redis-cli -p 26379 SHUTDOWN
|
||||
|
||||
# 使用 systemd 停止
|
||||
sudo systemctl stop redis-sentinel
|
||||
|
||||
# 强制停止
|
||||
kill -TERM $(cat /var/run/redis/sentinel.pid)
|
||||
|
||||
# 检查停止状态
|
||||
ps aux | grep sentinel
|
||||
```
|
||||
|
||||
### 监控和诊断
|
||||
|
||||
**状态监控**
|
||||
|
||||
```shell
|
||||
# 检查哨兵连接
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT ping
|
||||
|
||||
# 获取主节点信息
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT SENTINEL get-master-addr-by-name $MASTER_NAME
|
||||
|
||||
# 获取从节点信息
|
||||
redis-cli -h $SENTINEL_HOST -p $SENTINEL_PORT SENTINEL slaves $MASTER_NAME
|
||||
|
||||
```
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
搭建一个完整的 Redis 哨兵集群,包括1个主节点、2个从节点和3个哨兵节点,学习哨兵的配置、管理和故障转移机制。
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 环境准备
|
||||
# 创建工作目录
|
||||
mkdir -p /tmp/redis_sentinel_cluster/{redis-master,redis-slave1,redis-slave2,sentinel1,sentinel2,sentinel3}
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
|
||||
# 2. 配置 Redis 实例
|
||||
# 主节点配置
|
||||
cat > redis-master/redis.conf << EOF
|
||||
port 6779
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-master
|
||||
logfile "redis-master.log"
|
||||
pidfile "redis-master.pid"
|
||||
daemonize yes
|
||||
|
||||
# 持久化
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-master.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 复制配置
|
||||
repl-backlog-size 10mb
|
||||
repl-backlog-ttl 3600
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 从节点1配置
|
||||
cat > redis-slave1/redis.conf << EOF
|
||||
port 6780
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-slave1
|
||||
logfile "redis-slave1.log"
|
||||
pidfile "redis-slave1.pid"
|
||||
daemonize yes
|
||||
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6779
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 100
|
||||
|
||||
# 禁用持久化以提高性能
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 从节点2配置
|
||||
cat > redis-slave2/redis.conf << EOF
|
||||
port 6781
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/redis-slave2
|
||||
logfile "redis-slave2.log"
|
||||
pidfile "redis-slave2.pid"
|
||||
daemonize yes
|
||||
|
||||
# 主从配置
|
||||
replicaof 127.0.0.1 6779
|
||||
slave-read-only yes
|
||||
slave-serve-stale-data yes
|
||||
slave-priority 90
|
||||
|
||||
# 禁用持久化
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
# 性能优化
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
EOF
|
||||
|
||||
# 3. 配置哨兵节点
|
||||
# 哨兵1配置
|
||||
cat > sentinel1/sentinel.conf << EOF
|
||||
port 26779
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel1
|
||||
logfile "sentinel1.log"
|
||||
pidfile "sentinel1.pid"
|
||||
daemonize yes
|
||||
|
||||
# 监控主节点
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
|
||||
# 故障检测配置
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
# 通知脚本
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
|
||||
# 安全配置
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 哨兵2配置
|
||||
cat > sentinel2/sentinel.conf << EOF
|
||||
port 26780
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel2
|
||||
logfile "sentinel2.log"
|
||||
pidfile "sentinel2.pid"
|
||||
daemonize yes
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 哨兵3配置
|
||||
cat > sentinel3/sentinel.conf << EOF
|
||||
port 26781
|
||||
bind 127.0.0.1
|
||||
dir /tmp/redis_sentinel_cluster/sentinel3
|
||||
logfile "sentinel3.log"
|
||||
pidfile "sentinel3.pid"
|
||||
daemonize yes
|
||||
|
||||
sentinel monitor mymaster 127.0.0.1 6779 2
|
||||
sentinel down-after-milliseconds mymaster 30000
|
||||
sentinel failover-timeout mymaster 180000
|
||||
sentinel parallel-syncs mymaster 1
|
||||
|
||||
sentinel notification-script mymaster /tmp/redis_sentinel_cluster/notify.sh
|
||||
sentinel client-reconfig-script mymaster /tmp/redis_sentinel_cluster/reconfig.sh
|
||||
sentinel deny-scripts-reconfig yes
|
||||
EOF
|
||||
|
||||
# 4. 创建通知脚本
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
cat > notify.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "$(date): 哨兵事件 - $*" >> /tmp/redis_sentinel_cluster/sentinel_events.log
|
||||
EOF
|
||||
cat > reconfig.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
echo "$(date): 客户端重配置 - $*" >> /tmp/redis_sentinel_cluster/reconfig_events.log
|
||||
EOF
|
||||
chmod +x notify.sh reconfig.sh
|
||||
|
||||
# 5. 启动 Redis 实例
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
redis-server redis-master/redis.conf
|
||||
redis-server redis-slave1/redis.conf
|
||||
redis-server redis-slave2/redis.conf
|
||||
|
||||
|
||||
# 6. 启动哨兵节点
|
||||
cd /tmp/redis_sentinel_cluster
|
||||
redis-sentinel sentinel1/sentinel.conf
|
||||
redis-sentinel sentinel2/sentinel.conf
|
||||
redis-sentinel sentinel3/sentinel.conf
|
||||
|
||||
# 7. 验证集群状态
|
||||
# 检查主从复制
|
||||
redis-cli -p 6779 INFO replication | grep -E "role|connected_slaves"
|
||||
redis-cli -p 6780 INFO replication | grep -E "role|master_host|master_link_status"
|
||||
redis-cli -p 6781 INFO replication | grep -E "role|master_host|master_link_status"
|
||||
# 检查哨兵状态
|
||||
for port in 26779 26780 26781; do redis-cli -p $port SENTINEL masters | grep -E "name|ip|port|num-slaves|num-other-sentinels" | head -5;done
|
||||
|
||||
# 8. [可选]测试数据同步
|
||||
|
||||
# 9. 测试故障转移
|
||||
# 停止主节点
|
||||
redis-cli -p 6779 SHUTDOWN NOSAVE
|
||||
# 获取新主节点
|
||||
redis-cli -p 26779 SENTINEL get-master-addr-by-name mymaster
|
||||
# 验证新主节点
|
||||
redis-cli -p 6781 INFO replication | grep -E "role|connected_slaves"
|
||||
# 测试写入新主节点
|
||||
redis-cli -p 6781 SET failover:test "success"
|
||||
# 查看哨兵事件日志
|
||||
tail -10 sentinel_events.log
|
||||
|
||||
```
|
||||
684
数据库/Redis_2025/08_Redis集群架构.md
Normal file
@@ -0,0 +1,684 @@
|
||||
# Redis 集群架构
|
||||
|
||||
Redis 集群(Redis Cluster)是 Redis 官方提供的分布式解决方案,通过数据分片和自动故障转移实现水平扩展和高可用性。它是构建大规模 Redis 应用的核心技术。
|
||||
|
||||
## Redis 集群概述
|
||||
|
||||
### 集群模式的概念
|
||||
|
||||
Redis 集群是一个分布式、去中心化的 Redis 实现,具有以下特点:
|
||||
|
||||
**核心特性:**
|
||||
- **数据分片(Sharding)**:自动将数据分布到多个节点
|
||||
- **高可用性(High Availability)**:支持主从复制和自动故障转移
|
||||
- **水平扩展(Horizontal Scaling)**:支持动态添加和删除节点
|
||||
- **去中心化(Decentralized)**:没有单点故障,所有节点地位平等
|
||||
|
||||
**架构组件:**
|
||||
- **主节点(Master)**:处理读写请求,负责数据分片
|
||||
- **从节点(Slave)**:复制主节点数据,提供读服务和故障转移
|
||||
- **哈希槽(Hash Slot)**:数据分片的基本单位,共16384个槽
|
||||
- **集群总线(Cluster Bus)**:节点间通信的专用通道
|
||||
|
||||
### 集群模式的优势
|
||||
|
||||
**性能优势:**
|
||||
- 数据分片提高并发处理能力
|
||||
- 多节点并行处理请求
|
||||
- 减少单节点内存压力
|
||||
- 支持大数据量存储
|
||||
|
||||
**可用性优势:**
|
||||
- 自动故障检测和转移
|
||||
- 主从复制保证数据安全
|
||||
- 部分节点故障不影响整体服务
|
||||
- 支持在线扩容和缩容
|
||||
|
||||
**扩展性优势:**
|
||||
- 线性扩展存储容量
|
||||
- 动态调整集群规模
|
||||
- 支持跨数据中心部署
|
||||
- 灵活的数据迁移机制
|
||||
|
||||
**管理优势:**
|
||||
- 自动数据分布和负载均衡
|
||||
- 简化的集群管理工具
|
||||
- 丰富的监控和诊断功能
|
||||
- 标准化的客户端支持
|
||||
|
||||
### 集群模式的应用场景
|
||||
|
||||
典型应用场景:
|
||||
|
||||
1. 大规模缓存系统
|
||||
应用层 → 负载均衡 → Redis集群
|
||||
支持TB级别的缓存数据
|
||||
|
||||
2. 分布式会话存储
|
||||
Web应用 → 会话管理 → Redis集群
|
||||
支持大量并发用户会话
|
||||
|
||||
3. 实时数据分析
|
||||
数据采集 → 实时计算 → Redis集群
|
||||
支持高频数据写入和查询
|
||||
|
||||
4. 消息队列系统
|
||||
生产者 → Redis集群 → 消费者
|
||||
支持大规模消息处理
|
||||
|
||||
5. 游戏排行榜
|
||||
游戏服务器 → Redis集群 → 排行榜系统
|
||||
支持全球用户排行榜
|
||||
|
||||
6. 电商购物车
|
||||
电商平台 → Redis集群 → 购物车服务
|
||||
支持海量用户购物车数据
|
||||
|
||||
|
||||
## Redis 集群原理
|
||||
|
||||
### 数据分片机制
|
||||
|
||||
**哈希槽(Hash Slot)**
|
||||
|
||||
Redis 集群使用哈希槽来实现数据分片:
|
||||
|
||||
哈希槽机制:
|
||||
|
||||
1. 槽位总数:16384 个(0-16383)
|
||||
2. 槽位分配:平均分配给各个主节点
|
||||
3. 数据映射:key → CRC16(key) % 16384 → 槽位 → 节点
|
||||
4. 槽位迁移:支持在线重新分配槽位
|
||||
|
||||
示例分配(3个主节点):
|
||||
节点A:槽位 0-5460 (5461个槽)
|
||||
节点B:槽位 5461-10922 (5462个槽)
|
||||
节点C:槽位 10923-16383(5461个槽)
|
||||
|
||||
**数据路由**
|
||||
|
||||
```shell
|
||||
# 数据路由过程
|
||||
|
||||
# 1. 客户端计算槽位
|
||||
key = "user:1001"
|
||||
slot = CRC16(key) % 16384
|
||||
# 假设 slot = 8000
|
||||
|
||||
# 2. 查找负责的节点
|
||||
# 槽位 8000 属于节点B(5461-10922)
|
||||
|
||||
# 3. 重定向机制
|
||||
# 如果客户端连接到错误的节点:
|
||||
redis-cli -c -p 7001 GET user:1001
|
||||
# 节点A响应:(error) MOVED 8000 192.168.1.102:7002
|
||||
# 客户端自动重定向到节点B
|
||||
|
||||
# 4. ASK重定向(槽位迁移中)
|
||||
# 如果槽位正在迁移:
|
||||
# 源节点响应:(error) ASK 8000 192.168.1.103:7003
|
||||
# 客户端发送 ASKING 命令后重试
|
||||
```
|
||||
|
||||
### 集群通信机制
|
||||
|
||||
**集群总线(Cluster Bus)**
|
||||
|
||||
集群总线特性:
|
||||
|
||||
1. 端口:Redis端口 + 10000
|
||||
Redis端口:7001 → 集群总线端口:17001
|
||||
|
||||
2. 协议:二进制协议,效率更高
|
||||
|
||||
3. 通信内容:
|
||||
- 节点状态信息
|
||||
- 槽位分配信息
|
||||
- 故障检测信息
|
||||
- 配置更新信息
|
||||
|
||||
4. 通信频率:
|
||||
- 心跳:每秒1次
|
||||
- Gossip:随机选择节点交换信息
|
||||
- 故障检测:实时
|
||||
|
||||
|
||||
### Gossip 协议
|
||||
|
||||
Gossip 协议(流言协议)是一种分布式系统中的信息传播协议,类似于现实生活中流言的传播方式。在Redis集群中,Gossip协议用于维护集群状态的一致性。
|
||||
|
||||
**Gossip 协议原理:**
|
||||
|
||||
1. **去中心化设计**
|
||||
- 没有中央协调节点
|
||||
- 每个节点都是平等的
|
||||
- 信息通过节点间的随机通信传播
|
||||
|
||||
2. **最终一致性**
|
||||
- 不保证强一致性
|
||||
- 通过多轮传播达到最终一致
|
||||
- 容忍网络分区和节点故障
|
||||
|
||||
3. **概率性传播**
|
||||
- 随机选择通信节点
|
||||
- 降低网络负载
|
||||
- 提高系统可扩展性
|
||||
|
||||
**Gossip 协议特点:**
|
||||
|
||||
1. **高可用性**
|
||||
- 单点故障不影响整体运行
|
||||
- 网络分区时仍能部分工作
|
||||
- 自动故障恢复能力
|
||||
|
||||
2. **可扩展性**
|
||||
- 通信复杂度为 O(log N)
|
||||
- 支持大规模集群
|
||||
- 动态添加/删除节点
|
||||
|
||||
3. **容错性**
|
||||
- 容忍节点故障
|
||||
- 容忍消息丢失
|
||||
- 容忍网络延迟
|
||||
|
||||
**Redis 中的 Gossip 实现:**
|
||||
|
||||
**Gossip 协议工作流程**:
|
||||
|
||||
1. **节点选择**:每次随机选择几个节点进行通信
|
||||
2. **信息交换**:发送自己已知的集群状态信息
|
||||
3. **信息合并**:接收并更新集群状态信息
|
||||
4. **信息传播**:将新信息传播给其他节点
|
||||
|
||||
**消息类型:**
|
||||
|
||||
- **PING**:心跳消息,包含发送者状态和已知的其他节点信息
|
||||
- **PONG**:心跳响应,包含接收者状态和集群视图
|
||||
- **MEET**:新节点加入集群时的握手消息
|
||||
- **FAIL**:节点故障通知,标记节点为失效状态
|
||||
- **PUBLISH**:发布/订阅消息在集群间的传播
|
||||
|
||||
**Gossip 消息结构:**
|
||||
|
||||
Gossip 消息头:
|
||||
- 消息类型 (PING/PONG/MEET/FAIL)
|
||||
- 发送者节点ID
|
||||
- 消息序列号
|
||||
- 集群配置版本
|
||||
|
||||
Gossip 消息体:
|
||||
- 节点状态信息
|
||||
- 槽位分配信息
|
||||
- 其他节点的状态摘要
|
||||
- 故障检测信息
|
||||
|
||||
|
||||
**传播机制:**
|
||||
|
||||
1. **主动传播**
|
||||
- 每个节点定期发送PING消息
|
||||
- 频率:每秒选择随机节点发送
|
||||
- 目标:维持集群连通性
|
||||
|
||||
2. **被动传播**
|
||||
- 接收到消息后回复PONG
|
||||
- 携带本地集群状态信息
|
||||
- 实现双向信息交换
|
||||
|
||||
3. **故障传播**
|
||||
- 检测到节点故障时发送FAIL消息
|
||||
- 快速传播故障信息
|
||||
- 触发故障转移流程
|
||||
|
||||
**Gossip 协议优势:**
|
||||
|
||||
1. **网络效率**
|
||||
- 避免广播风暴
|
||||
- 减少网络带宽消耗
|
||||
- 适合大规模集群
|
||||
|
||||
2. **故障隔离**
|
||||
- 局部故障不影响全局
|
||||
- 自动绕过故障节点
|
||||
- 提高系统稳定性
|
||||
|
||||
3. **动态适应**
|
||||
- 自动发现新节点
|
||||
- 自动移除故障节点
|
||||
- 支持集群拓扑变化
|
||||
|
||||
|
||||
### 故障检测和转移
|
||||
|
||||
**故障检测机制**
|
||||
|
||||
```shell
|
||||
# 故障检测配置
|
||||
# redis.conf
|
||||
|
||||
# 集群节点超时时间(毫秒)
|
||||
cluster-node-timeout 15000
|
||||
|
||||
# 故障转移投票有效时间
|
||||
cluster-slave-validity-factor 10
|
||||
|
||||
# 从节点迁移屏障
|
||||
cluster-migration-barrier 1
|
||||
|
||||
# 集群要求槽位完整覆盖
|
||||
cluster-require-full-coverage yes
|
||||
```
|
||||
|
||||
**故障检测流程:**
|
||||
|
||||
1. **主观下线(PFAIL)**
|
||||
- 节点在超时时间内无响应
|
||||
- 标记为主观下线状态
|
||||
- 开始收集其他节点意见
|
||||
|
||||
2. **客观下线(FAIL)**
|
||||
- 超过半数主节点认为故障
|
||||
- 标记为客观下线状态
|
||||
- 触发故障转移流程
|
||||
|
||||
3. **故障转移**
|
||||
- 从节点发起选举
|
||||
- 获得多数票的从节点成为新主节点
|
||||
- 更新槽位分配信息
|
||||
|
||||
**故障转移过程**
|
||||
|
||||
故障转移详细流程:
|
||||
|
||||
1. 故障检测
|
||||
- 主节点A无响应超过cluster-node-timeout
|
||||
- 其他节点标记A为PFAIL
|
||||
- 收集到足够PFAIL报告后标记为FAIL
|
||||
|
||||
2. 从节点选举
|
||||
- A的从节点们开始选举
|
||||
- 计算选举延迟:rank * 1000 + random(0,1000)
|
||||
- 延迟最小的从节点首先发起选举
|
||||
|
||||
3. 投票过程
|
||||
- 候选从节点向所有主节点请求投票
|
||||
- 主节点在一个配置纪元内只能投一票
|
||||
- 获得多数票(N/2+1)的从节点胜出
|
||||
|
||||
4. 角色切换
|
||||
- 胜出的从节点执行CLUSTER FAILOVER
|
||||
- 接管原主节点的槽位
|
||||
- 更新集群配置并广播
|
||||
|
||||
5. 配置传播
|
||||
- 新主节点广播配置更新
|
||||
- 其他节点更新路由表
|
||||
- 客户端更新连接信息
|
||||
|
||||
|
||||
## Redis 集群配置
|
||||
|
||||
### 基本配置
|
||||
|
||||
**节点配置文件**
|
||||
|
||||
```shell
|
||||
# Redis 集群节点配置模板
|
||||
|
||||
# 基本配置
|
||||
port 7001
|
||||
bind 127.0.0.1
|
||||
dir ./
|
||||
logfile "redis-7001.log"
|
||||
pidfile "redis-7001.pid"
|
||||
daemonize yes
|
||||
|
||||
# 集群配置
|
||||
cluster-enabled yes
|
||||
cluster-config-file nodes-7001.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7001.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 内存配置
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 网络配置
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
|
||||
# 性能优化
|
||||
tcp-backlog 511
|
||||
databases 1 # 集群模式只支持数据库0
|
||||
```
|
||||
|
||||
**多节点配置生成**
|
||||
|
||||
1. 复制节点配置模板
|
||||
2. 修改端口号和配置文件名
|
||||
3. 生成节点配置文件
|
||||
|
||||
### 集群创建
|
||||
|
||||
**使用 redis-cli 创建集群**
|
||||
|
||||
1. 启动所有节点
|
||||
2. 连接其中一个节点(如 7001)
|
||||
3. 执行集群创建命令
|
||||
```shell
|
||||
redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
**集群安全配置**
|
||||
|
||||
```shell
|
||||
# 安全配置
|
||||
requirepass "$(openssl rand -base64 32)"
|
||||
masterauth "$(openssl rand -base64 32)"
|
||||
|
||||
# 重命名危险命令
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_$(openssl rand -hex 8)"
|
||||
rename-command EVAL "EVAL_$(openssl rand -hex 8)"
|
||||
|
||||
# 连接限制
|
||||
maxclients 10000
|
||||
tcp-backlog 511
|
||||
|
||||
# 客户端输出缓冲区限制
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
```
|
||||
|
||||
**性能优化配置**
|
||||
|
||||
```shell
|
||||
# 内存优化
|
||||
maxmemory 8gb
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory-samples 10
|
||||
|
||||
# 持久化优化
|
||||
# 禁用 RDB 以提高性能
|
||||
save ""
|
||||
stop-writes-on-bgsave-error no
|
||||
|
||||
# AOF 优化
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7001.aof"
|
||||
appendfsync no # 由操作系统决定何时同步
|
||||
no-appendfsync-on-rewrite yes
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 1gb
|
||||
|
||||
# 网络优化
|
||||
tcp-keepalive 60
|
||||
tcp-backlog 2048
|
||||
timeout 0
|
||||
|
||||
# 连接优化
|
||||
maxclients 50000
|
||||
|
||||
# 客户端输出缓冲区优化
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit slave 1gb 256mb 60
|
||||
client-output-buffer-limit pubsub 128mb 32mb 60
|
||||
|
||||
# 性能调优
|
||||
databases 1
|
||||
lua-time-limit 5000
|
||||
slowlog-log-slower-than 1000
|
||||
slowlog-max-len 1000
|
||||
|
||||
# 哈希表优化
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
list-max-ziplist-size -2
|
||||
list-compress-depth 0
|
||||
set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# HyperLogLog 优化
|
||||
hll-sparse-max-bytes 3000
|
||||
|
||||
# 流优化
|
||||
stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
```
|
||||
|
||||
## Redis 集群管理
|
||||
|
||||
### 集群操作命令
|
||||
|
||||
**基本管理命令**
|
||||
|
||||
```shell
|
||||
# 集群管理命令大全
|
||||
# 基本连接
|
||||
redis-cli -c -h 127.0.0.1 -p 7001
|
||||
|
||||
# 查看集群基本信息
|
||||
127.0.0.1:7001> CLUSTER INFO
|
||||
# 查看集群节点信息
|
||||
127.0.0.1:7001> CLUSTER NODES
|
||||
# 查看槽位分配
|
||||
127.0.0.1:7001> CLUSTER SLOTS
|
||||
|
||||
# 槽位管理
|
||||
# 查看键所在的槽位
|
||||
127.0.0.1:7001> CLUSTER KEYSLOT key_name
|
||||
# 查看槽位中的键数量
|
||||
127.0.0.1:7001> CLUSTER COUNTKEYSINSLOT 1000
|
||||
# 获取槽位中的键,最多返回10个
|
||||
127.0.0.1:7001> CLUSTER GETKEYSINSLOT 1000 10
|
||||
# 删除槽位分配
|
||||
127.0.0.1:7001> CLUSTER DELSLOTS 7000 7001 7002
|
||||
# 手动分配槽位
|
||||
127.0.0.1:7001> CLUSTER ADDSLOTS 7000 7001 7002
|
||||
|
||||
# 节点管理
|
||||
# 添加节点
|
||||
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7007
|
||||
# 忘记节点
|
||||
127.0.0.1:7001> CLUSTER FORGET node_id
|
||||
# 设置从节点
|
||||
127.0.0.1:7001> CLUSTER REPLICATE 127.0.0.1 7007
|
||||
# 故障转移
|
||||
127.0.0.1:7001> CLUSTER FAILOVER
|
||||
127.0.0.1:7001> CLUSTER FAILOVER FORCE
|
||||
127.0.0.1:7001> CLUSTER FAILOVER TAKEOVER
|
||||
|
||||
# 槽位迁移
|
||||
# 设置槽位为迁移状态
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 MIGRATING target_node_id
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 IMPORTING source_node_id
|
||||
# 迁移键
|
||||
127.0.0.1:7001> MIGRATE 127.0.0.1 7002 key_name 0 5000
|
||||
# 完成槽位迁移
|
||||
127.0.0.1:7001> CLUSTER SETSLOT 1000 NODE target_node_id
|
||||
# 检查槽位迁移状态
|
||||
127.0.0.1:7001> CLUSTER SLOTS
|
||||
# 检查键是否迁移完成
|
||||
127.0.0.1:7001> EXISTS key_name
|
||||
|
||||
# 调试命令
|
||||
# 保存集群配置
|
||||
127.0.0.1:7001> CLUSTER SAVECONFIG
|
||||
# 设置配置纪元
|
||||
127.0.0.1:7001> CLUSTER SET-CONFIG-EPOCH 1
|
||||
# 获取节点ID
|
||||
127.0.0.1:7001> CLUSTER MYID
|
||||
|
||||
```
|
||||
|
||||
### 集群扩容和缩容
|
||||
|
||||
**添加节点流程**
|
||||
1. 复制节点配置模板
|
||||
2. 修改端口号和配置文件名
|
||||
3. 生成节点配置文件
|
||||
4. 启动新节点
|
||||
5. 连接其中一个节点(如 7001)
|
||||
6. 执行集群添加节点命令
|
||||
```shell
|
||||
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
|
||||
```
|
||||
7. 重新分配槽位
|
||||
```shell
|
||||
# NEW_MASTER_ID
|
||||
redis-cli -h 127.0.0.1 -p 7007 CLUSTER MYID
|
||||
# SLOTS_TO_MIGRATE
|
||||
16384 / 4 = 4096
|
||||
|
||||
redis-cli --cluster reshard 127.0.0.1:7001 \
|
||||
--cluster-from all \
|
||||
--cluster-to <NEW_MASTER_ID> \
|
||||
--cluster-slots <SLOTS_TO_MIGRATE> \
|
||||
--cluster-yes
|
||||
```
|
||||
8. 添加新从节点
|
||||
```shell
|
||||
redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7001 --cluster-slave --cluster-master-id <NEW_MASTER_ID>
|
||||
```
|
||||
9. 验证扩容结果
|
||||
10. 测试新节点 & 验证数据读写
|
||||
|
||||
|
||||
**删除节点流程**
|
||||
1. 确认要删除的主节点
|
||||
2. 迁移该主节点上的所有槽位:注意整个集群槽位分配是否平均
|
||||
```shell
|
||||
# REMOVE_MASTER_ID:删除节点
|
||||
# TARGET_ID:选择一个节点作为目标节点
|
||||
# RANGE_COUNT:槽位迁移数量
|
||||
redis-cli --cluster reshard 127.0.0.1:7001 \
|
||||
--cluster-from <REMOVE_MASTER_ID> \
|
||||
--cluster-to <TARGET_ID> \
|
||||
--cluster-slots <RANGE_COUNT> \
|
||||
--cluster-yes > /dev/null 2>&1
|
||||
```
|
||||
3. 删除从节点 & 主节点
|
||||
```shell
|
||||
redis-cli --cluster del-node 127.0.0.1:7001 <REMOVE_SLAVE_ID>
|
||||
redis-cli --cluster del-node 127.0.0.1:7001 <REMOVE_MASTER_ID>
|
||||
```
|
||||
4. 停掉相关进程
|
||||
|
||||
### 故障处理
|
||||
|
||||
**节点故障恢复流程**
|
||||
1. 检测故障节点:ping 不通
|
||||
2. 分析故障类型:通过 `cluster info` 查看集群状态 `cluster_state` 字段 && `cluster nodes` 查看节点状态
|
||||
- `cluster_state: ok`:可能是从节点故障
|
||||
- `cluster_state: fail`:可能是主节点故障
|
||||
- `cluster_state: unknown`:未知故障
|
||||
3. 执行故障恢复:
|
||||
- 尝试重启相关 Redis 进程:如果需要可以重置节点集群状态 `CLUSTER RESET SOFT` 后重新加入集群 `CLUSTER MEET $failed_ip $failed_port`
|
||||
- 手动故障转移:如果节点故障持续存在,可能需要手动触发故障转移,从节点上执行 `CLUSTER FAILOVER FORCE`
|
||||
4. 检查恢复结果:通过 `cluster nodes` 查看节点状态,确认故障节点已恢复
|
||||
|
||||
**数据一致性检查**
|
||||
|
||||
数据一致性检查是确保Redis集群数据完整性和可靠性的重要环节。
|
||||
|
||||
**1. 槽位分配一致性检查**
|
||||
```shell
|
||||
# 检查所有槽位是否完整分配
|
||||
redis-cli --cluster check 127.0.0.1:7001
|
||||
|
||||
# 查看槽位分配详情
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER SLOTS
|
||||
|
||||
# 检查特定槽位的分配情况
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER KEYSLOT mykey
|
||||
redis-cli -h 127.0.0.1 -p 7001 CLUSTER NODES | grep "0-5460"
|
||||
```
|
||||
|
||||
**2. 主从复制一致性检查**
|
||||
```shell
|
||||
# 检查主从节点数据同步状态
|
||||
redis-cli -h 127.0.0.1 -p 7001 INFO replication
|
||||
redis-cli -h 127.0.0.1 -p 7004 INFO replication
|
||||
|
||||
# 比较主从节点的数据量
|
||||
redis-cli -h 127.0.0.1 -p 7001 DBSIZE
|
||||
redis-cli -h 127.0.0.1 -p 7004 DBSIZE
|
||||
|
||||
# 检查复制延迟
|
||||
redis-cli -h 127.0.0.1 -p 7004 LASTSAVE
|
||||
```
|
||||
|
||||
**3. 数据完整性验证**
|
||||
- 写入足够分散的测试数据
|
||||
- 随机从集群中的节点读取数据进行验证
|
||||
|
||||
|
||||
## 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
完成一个完整的 Redis Cluster 集群搭建、测试和管理过程:
|
||||
1. 搭建3主3从节点的Redis Cluster 集群
|
||||
2. 尝试扩容集群节点,添加一个主节点和一个从节点
|
||||
3. 尝试缩容集群节点,删除一个主节点和一个从节点
|
||||
4. 整个过程中,集群状态符合预期
|
||||
|
||||
### 实践细节和结果验证
|
||||
|
||||
```shell
|
||||
# 1. 搭建3主3从节点的Redis Cluster 集群
|
||||
# 准备目录
|
||||
[root@localhost ~]# mkdir -pv /data/redis_cluster_cluster/{7101,7102,7103,7104,7105,7106}
|
||||
# 为每个节点生成配置:参考 genrate_cluster_configs.sh 脚本
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
# 修改 BASE_PORT 为 7101
|
||||
# 查看生成的目录文件结构
|
||||
[root@localhost ~]# tree /data/redis_cluster_cluster/
|
||||
/data/redis_cluster_cluster/
|
||||
├── node-7101
|
||||
│ └── redis.conf
|
||||
├── node-7102
|
||||
│ └── redis.conf
|
||||
├── node-7103
|
||||
│ └── redis.conf
|
||||
├── node-7104
|
||||
│ └── redis.conf
|
||||
├── node-7105
|
||||
│ └── redis.conf
|
||||
└── node-7106
|
||||
└── redis.conf
|
||||
# 启动所有节点并创建集群: 参考 create_redis_cluster.sh 脚本
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
# 修改 BASE_PORT 为 7101
|
||||
# 手动执行集群管理命令:集群状态符合预期
|
||||
|
||||
# 2. 尝试扩容集群节点,添加一个主节点和一个从节点
|
||||
# 参考 cluster_scale_out.sh 脚本
|
||||
# 修改 EXISTING_NODE 为 127.0.0.1:7101
|
||||
# 修改 NEW_MASTER 为 127.0.0.1:7107
|
||||
# 修改 NEW_SLAVE 为 127.0.0.1:7108
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
|
||||
# 3. 尝试缩容集群节点,删除一个主节点和一个从节点
|
||||
# 参考 cluster_scale_in.sh 脚本
|
||||
# 修改 EXISTING_NODE 为 127.0.0.1:7101
|
||||
# 修改 REMOVE_MASTER_ID 为 7107
|
||||
# 修改 REMOVE_SLAVE_ID 为 7108
|
||||
# 修改 BASE_DIR 为 /data/redis_cluster_cluster/
|
||||
|
||||
```
|
||||
456
数据库/Redis_2025/09_Redis性能优化.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# Redis 性能优化
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 性能指标分析
|
||||
|
||||
Redis 性能监控需要关注以下关键指标:
|
||||
|
||||
**内存指标**:
|
||||
- `used_memory`:Redis 使用的内存总量
|
||||
- `used_memory_rss`:Redis 进程占用的物理内存
|
||||
- `used_memory_peak`:Redis 使用内存的峰值
|
||||
- `mem_fragmentation_ratio`:内存碎片率
|
||||
|
||||
**性能指标**:
|
||||
- `instantaneous_ops_per_sec`:每秒操作数
|
||||
- `keyspace_hits`:键空间命中次数
|
||||
- `keyspace_misses`:键空间未命中次数
|
||||
- `hit_rate`:缓存命中率
|
||||
|
||||
**连接指标**:
|
||||
- `connected_clients`:当前连接的客户端数量
|
||||
- `blocked_clients`:被阻塞的客户端数量
|
||||
- `rejected_connections`:被拒绝的连接数
|
||||
|
||||
### 监控工具使用
|
||||
|
||||
**Redis 内置监控命令**:
|
||||
|
||||
```shell
|
||||
# 查看服务器信息
|
||||
redis-cli info
|
||||
|
||||
# 查看特定分类信息
|
||||
redis-cli info memory
|
||||
redis-cli info stats
|
||||
redis-cli info clients
|
||||
|
||||
# 实时监控命令执行
|
||||
redis-cli monitor
|
||||
|
||||
# 查看慢查询日志
|
||||
redis-cli slowlog get 10
|
||||
```
|
||||
|
||||
**性能测试工具**:
|
||||
|
||||
```shell
|
||||
# Redis 基准测试
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
|
||||
|
||||
# 测试特定命令性能
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -t set,get -n 10000 -q
|
||||
|
||||
# 测试管道性能:Pepeline模式
|
||||
redis-benchmark -h 127.0.0.1 -p 6379 -n 10000 -P 16
|
||||
|
||||
```
|
||||
|
||||
### 慢查询日志
|
||||
|
||||
**配置慢查询**:
|
||||
|
||||
```shell
|
||||
# 设置慢查询阈值(微秒)
|
||||
CONFIG SET slowlog-log-slower-than 10000
|
||||
|
||||
# 设置慢查询日志长度
|
||||
CONFIG SET slowlog-max-len 128
|
||||
|
||||
# 查看慢查询配置
|
||||
CONFIG GET slowlog*
|
||||
```
|
||||
|
||||
**分析慢查询**:
|
||||
|
||||
```shell
|
||||
# 获取慢查询日志
|
||||
SLOWLOG GET 10
|
||||
|
||||
# 获取慢查询日志长度
|
||||
SLOWLOG LEN
|
||||
|
||||
# 清空慢查询日志
|
||||
SLOWLOG RESET
|
||||
```
|
||||
|
||||
### 内存使用分析
|
||||
|
||||
**内存分析命令**:
|
||||
|
||||
```shell
|
||||
# 分析内存使用情况
|
||||
MEMORY USAGE key_name
|
||||
|
||||
# 获取内存统计信息
|
||||
MEMORY STATS
|
||||
|
||||
# 分析键空间
|
||||
MEMORY DOCTOR
|
||||
|
||||
# 查看大键
|
||||
redis-cli --bigkeys
|
||||
```
|
||||
|
||||
## 内存优化
|
||||
|
||||
### 内存使用策略
|
||||
|
||||
**过期策略配置**:
|
||||
|
||||
```shell
|
||||
# 设置最大内存限制
|
||||
maxmemory 2gb
|
||||
|
||||
# 设置内存淘汰策略
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 可选的淘汰策略:
|
||||
# noeviction:不淘汰,返回错误
|
||||
# allkeys-lru:所有键中淘汰最近最少使用
|
||||
# allkeys-lfu:所有键中淘汰最少使用频率
|
||||
# volatile-lru:过期键中淘汰最近最少使用
|
||||
# volatile-lfu:过期键中淘汰最少使用频率
|
||||
# allkeys-random:所有键中随机淘汰
|
||||
# volatile-random:过期键中随机淘汰
|
||||
# volatile-ttl:过期键中淘汰即将过期的
|
||||
```
|
||||
|
||||
### 数据结构优化
|
||||
|
||||
**字符串优化**:
|
||||
|
||||
```shell
|
||||
# 使用整数编码
|
||||
SET counter 100 # 使用 int 编码
|
||||
SET counter "100" # 使用 raw 编码
|
||||
|
||||
# 小字符串使用 embstr 编码(<=44字节)
|
||||
SET small_string "hello world"
|
||||
|
||||
# 大字符串使用 raw 编码(>44字节)
|
||||
SET large_string "very long string content..."
|
||||
```
|
||||
|
||||
**哈希优化**:
|
||||
|
||||
```shell
|
||||
# 配置哈希压缩列表阈值
|
||||
hash-max-ziplist-entries 512
|
||||
hash-max-ziplist-value 64
|
||||
|
||||
# 小哈希使用压缩列表
|
||||
HSET user:1 name "john" age 25
|
||||
|
||||
# 大哈希使用哈希表
|
||||
for i in {1..1000}; do
|
||||
redis-cli HSET large_hash field$i value$i
|
||||
done
|
||||
```
|
||||
|
||||
**列表优化**:
|
||||
|
||||
```shell
|
||||
# 配置列表压缩参数
|
||||
list-max-ziplist-size -2
|
||||
list-compress-depth 0
|
||||
|
||||
# 使用压缩列表的小列表
|
||||
LPUSH small_list item1 item2 item3
|
||||
|
||||
# 使用快速列表的大列表
|
||||
for i in {1..10000}; do
|
||||
redis-cli LPUSH large_list item$i
|
||||
done
|
||||
```
|
||||
|
||||
### 过期策略配置
|
||||
|
||||
**过期策略参数**:
|
||||
|
||||
```shell
|
||||
# 设置过期扫描频率
|
||||
hz 10
|
||||
|
||||
# 设置过期删除的CPU时间比例
|
||||
maxmemory-samples 5
|
||||
|
||||
# 配置惰性删除
|
||||
lazyfree-lazy-eviction yes
|
||||
lazyfree-lazy-expire yes
|
||||
lazyfree-lazy-server-del yes
|
||||
```
|
||||
|
||||
### 内存碎片处理
|
||||
|
||||
**内存碎片分析**:
|
||||
|
||||
```shell
|
||||
# 查看内存碎片率
|
||||
INFO memory | grep mem_fragmentation_ratio
|
||||
|
||||
# 内存碎片率计算
|
||||
# mem_fragmentation_ratio = used_memory_rss / used_memory
|
||||
# 正常范围:1.0 - 1.5
|
||||
# > 1.5:内存碎片较多
|
||||
# < 1.0:可能发生了内存交换
|
||||
```
|
||||
|
||||
**内存整理**:
|
||||
|
||||
```shell
|
||||
# 主动内存整理(Redis 4.0+)
|
||||
MEMORY PURGE
|
||||
|
||||
# 配置自动内存整理
|
||||
activedefrag yes
|
||||
active-defrag-ignore-bytes 100mb
|
||||
active-defrag-threshold-lower 10
|
||||
active-defrag-threshold-upper 100
|
||||
```
|
||||
|
||||
## 网络优化
|
||||
|
||||
### 连接池配置
|
||||
|
||||
**连接池参数优化**:
|
||||
|
||||
```shell
|
||||
# 设置最大客户端连接数
|
||||
maxclients 10000
|
||||
|
||||
# 设置客户端超时时间
|
||||
timeout 300
|
||||
|
||||
# 设置TCP keepalive
|
||||
tcp-keepalive 300
|
||||
|
||||
# 设置TCP backlog
|
||||
tcp-backlog 511
|
||||
```
|
||||
|
||||
### 管道技术
|
||||
|
||||
**技术原理**
|
||||
1. 客户端批量发送命令 → 服务器批量处理 → 批量返回结果
|
||||
2. 管道技术可以减少网络往返次数,提高批量操作的效率
|
||||
|
||||
|
||||
**管道批量操作**:
|
||||
|
||||
```shell
|
||||
# 传统模式 - 逐个执行命令(每个命令都需要等待响应)
|
||||
redis-cli SET key1 value1
|
||||
redis-cli SET key2 value2
|
||||
redis-cli SET key3 value3
|
||||
redis-cli GET key1
|
||||
redis-cli GET key2
|
||||
redis-cli GET key3
|
||||
|
||||
# Pipeline模式 - 批量发送命令(减少网络往返)
|
||||
# 方法1:使用管道符
|
||||
echo -e "SET key1 value1\nSET key2 value2\nSET key3 value3\nGET key1\nGET key2\nGET key3" | redis-cli --pipe
|
||||
|
||||
# 方法2:使用文件批量执行
|
||||
cat > commands.txt << EOF
|
||||
SET key1 value1
|
||||
SET key2 value2
|
||||
SET key3 value3
|
||||
GET key1
|
||||
GET key2
|
||||
GET key3
|
||||
EOF
|
||||
redis-cli --pipe < commands.txt
|
||||
|
||||
|
||||
# 性能对比测试
|
||||
# 传统模式:100个SET命令
|
||||
time for i in {1..100}; do redis-cli SET test_key_$i value_$i > /dev/null; done
|
||||
|
||||
# Pipeline模式:100个SET命令
|
||||
time (for i in {1..100}; do echo "SET test_key_$i value_$i"; done | redis-cli --pipe > /dev/null)
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
**批量命令优化**:
|
||||
|
||||
```shell
|
||||
# 传统方式 - 多个单独命令
|
||||
SET key1 value1
|
||||
SET key2 value2
|
||||
SET key3 value3
|
||||
GET key1
|
||||
GET key2
|
||||
GET key3
|
||||
|
||||
# 优化方式 - 使用批量命令
|
||||
# 使用 MSET 代替多个 SET
|
||||
MSET key1 value1 key2 value2 key3 value3
|
||||
|
||||
# 使用 MGET 代替多个 GET
|
||||
MGET key1 key2 key3
|
||||
|
||||
# 使用 HMSET 批量设置哈希字段
|
||||
HMSET user:1 name john age 25 email john@example.com
|
||||
|
||||
# 使用 HMGET 批量获取哈希字段
|
||||
HMGET user:1 name age email
|
||||
|
||||
# 使用 redis-benchmark 对比批量操作和单个操作的性能
|
||||
# 对比 SET vs MSET 性能
|
||||
redis-benchmark -t set -n 100000 -q
|
||||
# 结果示例: SET: 28352.71 requests per second, p50=0.871 msec
|
||||
redis-benchmark -t mset -n 100000 -q
|
||||
# 结果示例: MSET (10 keys): 26860.06 requests per second, p50=0.927 msec
|
||||
|
||||
# 性能分析:
|
||||
# - SET 单个操作: 28,352 ops/sec,平均延迟 0.871ms
|
||||
# - MSET 批量操作: 26,860 ops/sec,平均延迟 0.927ms
|
||||
# - 注意:MSET 测试的是每次设置10个键值对,实际吞吐量为 26,860 * 10 = 268,600 键/秒
|
||||
# - 批量操作的真实性能提升约为: 268,600 / 28,352 ≈ 9.5倍
|
||||
|
||||
```
|
||||
|
||||
### 网络延迟优化
|
||||
|
||||
**网络参数调优**:
|
||||
|
||||
```shell
|
||||
# 禁用 Nagle 算法
|
||||
tcp-nodelay yes
|
||||
|
||||
# 设置发送缓冲区大小
|
||||
client-output-buffer-limit normal 0 0 0
|
||||
client-output-buffer-limit replica 256mb 64mb 60
|
||||
client-output-buffer-limit pubsub 32mb 8mb 60
|
||||
```
|
||||
|
||||
## 配置优化
|
||||
|
||||
### 持久化优化
|
||||
|
||||
**RDB 优化配置**:
|
||||
|
||||
```shell
|
||||
# RDB 保存策略
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# RDB 文件压缩
|
||||
rdbcompression yes
|
||||
|
||||
# RDB 文件校验
|
||||
rdbchecksum yes
|
||||
|
||||
# RDB 文件名
|
||||
dbfilename dump.rdb
|
||||
|
||||
# 后台保存出错时停止写入
|
||||
stop-writes-on-bgsave-error yes
|
||||
```
|
||||
|
||||
**AOF 优化配置**:
|
||||
|
||||
```shell
|
||||
# 启用 AOF
|
||||
appendonly yes
|
||||
|
||||
# AOF 文件名
|
||||
appendfilename "appendonly.aof"
|
||||
|
||||
# AOF 同步策略
|
||||
appendfsync everysec
|
||||
|
||||
# AOF 重写优化
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# AOF 重写时不同步
|
||||
no-appendfsync-on-rewrite no
|
||||
|
||||
# AOF 加载时忽略错误
|
||||
aof-load-truncated yes
|
||||
|
||||
# 混合持久化
|
||||
aof-use-rdb-preamble yes
|
||||
```
|
||||
|
||||
### 复制优化
|
||||
|
||||
**主从复制优化**:
|
||||
|
||||
```shell
|
||||
# 复制积压缓冲区大小
|
||||
repl-backlog-size 1mb
|
||||
|
||||
# 复制积压缓冲区超时
|
||||
repl-backlog-ttl 3600
|
||||
|
||||
# 复制超时时间
|
||||
repl-timeout 60
|
||||
|
||||
# 禁用TCP_NODELAY
|
||||
repl-disable-tcp-nodelay no
|
||||
|
||||
# 复制ping周期
|
||||
repl-ping-replica-period 10
|
||||
```
|
||||
|
||||
### 系统级优化
|
||||
|
||||
**操作系统参数调优**:
|
||||
|
||||
```shell
|
||||
# 内存过量分配
|
||||
echo 1 > /proc/sys/vm/overcommit_memory
|
||||
|
||||
# 禁用透明大页
|
||||
echo never > /sys/kernel/mm/transparent_hugepage/enabled
|
||||
|
||||
# 设置文件描述符限制
|
||||
ulimit -n 65535
|
||||
|
||||
# 设置内存映射限制
|
||||
echo 262144 > /proc/sys/vm/max_map_count
|
||||
|
||||
# TCP 参数优化
|
||||
echo 'net.core.somaxconn = 65535' >> /etc/sysctl.conf
|
||||
echo 'net.ipv4.tcp_max_syn_backlog = 65535' >> /etc/sysctl.conf
|
||||
sysctl -p
|
||||
```
|
||||
|
||||
**文件系统优化**:
|
||||
|
||||
```shell
|
||||
# 使用高性能文件系统
|
||||
# 推荐:ext4, xfs
|
||||
|
||||
# 挂载选项优化
|
||||
# /etc/fstab
|
||||
/dev/sdb1 /var/lib/redis ext4 defaults,noatime,nodiratime 0 2
|
||||
|
||||
# SSD 优化
|
||||
echo deadline > /sys/block/sdb/queue/scheduler
|
||||
echo 1 > /sys/block/sdb/queue/iosched/fifo_batch
|
||||
```
|
||||
|
||||
## [扩展] 实践操作
|
||||
|
||||
### 需求描述
|
||||
|
||||
通过实际操作来监控 Redis 性能、优化内存使用,并测试性能提升效果。
|
||||
|
||||
### 实践细节和结果验证
|
||||
784
数据库/Redis_2025/10_Redis安全管理.md
Normal file
@@ -0,0 +1,784 @@
|
||||
# Redis 安全管理
|
||||
|
||||
## 访问控制
|
||||
|
||||
### 密码认证
|
||||
|
||||
Redis 提供了基本的密码认证机制来保护数据安全。
|
||||
|
||||
**配置密码认证**:
|
||||
|
||||
```shell
|
||||
# 在配置文件中设置密码
|
||||
# /etc/redis/redis.conf
|
||||
requirepass your_strong_password
|
||||
|
||||
# 动态设置密码
|
||||
redis-cli CONFIG SET requirepass "your_strong_password"
|
||||
|
||||
# 使用密码连接
|
||||
redis-cli -a your_strong_password
|
||||
|
||||
# 或者连接后认证
|
||||
redis-cli
|
||||
127.0.0.1:6379> AUTH your_strong_password
|
||||
OK
|
||||
```
|
||||
|
||||
**密码安全最佳实践**:
|
||||
|
||||
```shell
|
||||
# 生成强密码
|
||||
openssl rand -base64 32
|
||||
|
||||
# 密码复杂度要求:
|
||||
# - 长度至少16位
|
||||
# - 包含大小写字母、数字、特殊字符
|
||||
# - 避免使用字典词汇
|
||||
# - 定期更换密码
|
||||
|
||||
# 示例强密码
|
||||
requirepass "Rd!s@2024#Str0ng&P@ssw0rd"
|
||||
```
|
||||
|
||||
### 用户管理 (ACL)
|
||||
|
||||
Redis 6.0+ 引入了 ACL(Access Control List)功能,提供更细粒度的权限控制。
|
||||
|
||||
**ACL 基本概念**:
|
||||
|
||||
```shell
|
||||
# 查看当前用户
|
||||
ACL WHOAMI
|
||||
|
||||
# 列出所有用户
|
||||
ACL LIST
|
||||
|
||||
# 查看用户详细信息
|
||||
ACL GETUSER username
|
||||
|
||||
# 查看当前用户权限
|
||||
ACL GETUSER default
|
||||
```
|
||||
|
||||
**创建和管理用户**:
|
||||
|
||||
```shell
|
||||
# 创建只读用户
|
||||
ACL SETUSER readonly on >readonly_password ~* &* -@all +@read
|
||||
|
||||
# 创建读写用户(限制特定键模式)
|
||||
ACL SETUSER readwrite on >readwrite_password ~app:* &* -@all +@read +@write
|
||||
|
||||
# 创建管理员用户
|
||||
ACL SETUSER admin on >admin_password ~* &* +@all
|
||||
|
||||
# 创建应用用户(限制命令)
|
||||
ACL SETUSER appuser on >app_password ~app:* &* -@all +get +set +del +exists +expire
|
||||
|
||||
# 删除用户
|
||||
ACL DELUSER username
|
||||
```
|
||||
|
||||
**ACL 规则详解**:
|
||||
|
||||
```shell
|
||||
# ACL 规则语法:
|
||||
# on/off:启用/禁用用户
|
||||
# >password:设置密码
|
||||
# ~pattern:允许访问的键模式
|
||||
# &pattern:允许访问的发布订阅频道模式
|
||||
# +command:允许的命令
|
||||
# -command:禁止的命令
|
||||
# +@category:允许的命令分类
|
||||
# -@category:禁止的命令分类
|
||||
|
||||
# 常用命令分类:
|
||||
# @read:读命令
|
||||
# @write:写命令
|
||||
# @admin:管理命令
|
||||
# @dangerous:危险命令
|
||||
# @keyspace:键空间命令
|
||||
# @string:字符串命令
|
||||
# @list:列表命令
|
||||
# @set:集合命令
|
||||
# @hash:哈希命令
|
||||
# @sortedset:有序集合命令
|
||||
```
|
||||
|
||||
### 权限控制
|
||||
|
||||
**细粒度权限配置**:
|
||||
|
||||
```shell
|
||||
# 数据库管理员
|
||||
ACL SETUSER dba on >dba_password ~* &* +@all
|
||||
|
||||
# 应用开发者
|
||||
ACL SETUSER developer on >dev_password ~dev:* &dev:* -@all +@read +@write -flushdb -flushall -shutdown
|
||||
|
||||
# 监控用户
|
||||
ACL SETUSER monitor on >monitor_password ~* &* -@all +info +ping +client +config|get
|
||||
|
||||
# 备份用户
|
||||
ACL SETUSER backup on >backup_password ~* &* -@all +@read +bgsave +lastsave
|
||||
|
||||
# 只读分析用户
|
||||
ACL SETUSER analyst on >analyst_password ~analytics:* &* -@all +@read +scan +keys
|
||||
```
|
||||
|
||||
**权限验证测试**:
|
||||
|
||||
```shell
|
||||
# 测试用户权限
|
||||
redis-cli --user readonly --pass readonly_password
|
||||
127.0.0.1:6379> GET some_key # 应该成功
|
||||
127.0.0.1:6379> SET some_key value # 应该失败
|
||||
|
||||
# 测试键模式限制
|
||||
redis-cli --user developer --pass dev_password
|
||||
127.0.0.1:6379> GET dev:config # 应该成功
|
||||
127.0.0.1:6379> GET prod:config # 应该失败
|
||||
```
|
||||
|
||||
### IP 白名单
|
||||
|
||||
**网络访问控制**:
|
||||
|
||||
```shell
|
||||
# 绑定特定IP地址
|
||||
# /etc/redis/redis.conf
|
||||
bind 127.0.0.1 192.168.1.100 10.0.0.50
|
||||
|
||||
# 禁用保护模式(仅在安全网络环境中)
|
||||
protected-mode no
|
||||
|
||||
# 使用防火墙限制访问
|
||||
sudo firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='192.168.1.0/24' port protocol='tcp' port='6379' accept"
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
## 网络安全
|
||||
|
||||
### 端口安全
|
||||
|
||||
**端口配置和保护**:
|
||||
|
||||
```shell
|
||||
# 更改默认端口
|
||||
# /etc/redis/redis.conf
|
||||
port 16379 # 使用非标准端口
|
||||
|
||||
# 禁用端口(仅使用Unix套接字)
|
||||
port 0
|
||||
unixsocket /var/run/redis/redis.sock
|
||||
unixsocketperm 700
|
||||
|
||||
# 连接Unix套接字
|
||||
redis-cli -s /var/run/redis/redis.sock
|
||||
```
|
||||
|
||||
**网络接口绑定**:
|
||||
|
||||
```shell
|
||||
# 仅绑定内网接口
|
||||
bind 127.0.0.1 192.168.1.100
|
||||
|
||||
# 绑定多个接口
|
||||
bind 127.0.0.1 10.0.0.100 172.16.0.100
|
||||
|
||||
# 监听所有接口(不推荐)
|
||||
# bind 0.0.0.0
|
||||
```
|
||||
|
||||
### SSL/TLS 加密
|
||||
|
||||
Redis 6.0+ 支持 SSL/TLS 加密传输。
|
||||
|
||||
**生成SSL证书**:
|
||||
|
||||
```shell
|
||||
# 创建证书目录
|
||||
sudo mkdir -p /etc/redis/ssl
|
||||
cd /etc/redis/ssl
|
||||
|
||||
# 生成私钥
|
||||
sudo openssl genrsa -out redis.key 2048
|
||||
|
||||
# 生成证书签名请求
|
||||
sudo openssl req -new -key redis.key -out redis.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Company/CN=redis.example.com"
|
||||
|
||||
# 生成自签名证书
|
||||
sudo openssl x509 -req -days 365 -in redis.csr -signkey redis.key -out redis.crt
|
||||
|
||||
# 生成DH参数文件
|
||||
sudo openssl dhparam -out redis.dh 2048
|
||||
|
||||
# 设置权限
|
||||
sudo chown redis:redis /etc/redis/ssl/*
|
||||
sudo chmod 600 /etc/redis/ssl/redis.key
|
||||
sudo chmod 644 /etc/redis/ssl/redis.crt
|
||||
sudo chmod 644 /etc/redis/ssl/redis.dh
|
||||
```
|
||||
|
||||
**配置SSL/TLS**:
|
||||
|
||||
```shell
|
||||
# Redis 配置文件
|
||||
# /etc/redis/redis.conf
|
||||
|
||||
# 启用TLS端口
|
||||
port 0
|
||||
tls-port 6380
|
||||
|
||||
# 证书文件路径
|
||||
tls-cert-file /etc/redis/ssl/redis.crt
|
||||
tls-key-file /etc/redis/ssl/redis.key
|
||||
tls-dh-params-file /etc/redis/ssl/redis.dh
|
||||
|
||||
# CA证书(如果使用)
|
||||
# tls-ca-cert-file /etc/redis/ssl/ca.crt
|
||||
|
||||
# 客户端证书验证
|
||||
tls-auth-clients yes
|
||||
|
||||
# TLS协议版本
|
||||
tls-protocols "TLSv1.2 TLSv1.3"
|
||||
|
||||
# 密码套件
|
||||
tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
|
||||
tls-ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
|
||||
|
||||
# 会话缓存
|
||||
tls-session-caching no
|
||||
tls-session-cache-size 5000
|
||||
tls-session-cache-timeout 60
|
||||
```
|
||||
|
||||
**SSL客户端连接**:
|
||||
|
||||
```shell
|
||||
# 使用SSL连接
|
||||
redis-cli --tls --cert /etc/redis/ssl/client.crt --key /etc/redis/ssl/client.key --cacert /etc/redis/ssl/ca.crt -p 6380
|
||||
|
||||
# 跳过证书验证(仅测试环境)
|
||||
redis-cli --tls --insecure -p 6380
|
||||
```
|
||||
|
||||
### 防火墙配置
|
||||
|
||||
**iptables 配置**:
|
||||
|
||||
```shell
|
||||
# 允许特定IP访问Redis
|
||||
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 6379 -j ACCEPT
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -j DROP
|
||||
|
||||
# 限制连接频率
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m connlimit --connlimit-above 10 -j DROP
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --set --name redis
|
||||
sudo iptables -A INPUT -p tcp --dport 6379 -m recent --update --seconds 60 --hitcount 20 --name redis -j DROP
|
||||
|
||||
# 保存规则
|
||||
sudo iptables-save > /etc/iptables/rules.v4
|
||||
```
|
||||
|
||||
**UFW 配置**:
|
||||
|
||||
```shell
|
||||
# 启用UFW
|
||||
sudo ufw enable
|
||||
|
||||
# 允许SSH
|
||||
sudo ufw allow ssh
|
||||
|
||||
# 允许特定网络访问Redis
|
||||
sudo ufw allow from 192.168.1.0/24 to any port 6379
|
||||
|
||||
# 拒绝其他Redis连接
|
||||
sudo ufw deny 6379
|
||||
|
||||
# 查看规则
|
||||
sudo ufw status numbered
|
||||
```
|
||||
|
||||
### VPN 访问
|
||||
|
||||
**OpenVPN 配置示例**:
|
||||
|
||||
```shell
|
||||
# 安装OpenVPN
|
||||
sudo apt update
|
||||
sudo apt install openvpn easy-rsa
|
||||
|
||||
# 配置VPN服务器
|
||||
sudo make-cadir /etc/openvpn/easy-rsa
|
||||
cd /etc/openvpn/easy-rsa
|
||||
|
||||
# 初始化PKI
|
||||
./easyrsa init-pki
|
||||
./easyrsa build-ca
|
||||
./easyrsa gen-req server nopass
|
||||
./easyrsa sign-req server server
|
||||
./easyrsa gen-dh
|
||||
|
||||
# 生成客户端证书
|
||||
./easyrsa gen-req client1 nopass
|
||||
./easyrsa sign-req client client1
|
||||
```
|
||||
|
||||
## 数据安全
|
||||
|
||||
### 数据加密
|
||||
|
||||
**应用层加密**:
|
||||
|
||||
```python
|
||||
# Python 数据加密示例
|
||||
import redis
|
||||
import json
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
class EncryptedRedis:
|
||||
def __init__(self, host='localhost', port=6379, password=None, key=None):
|
||||
self.redis = redis.Redis(host=host, port=port, password=password)
|
||||
self.cipher = Fernet(key or Fernet.generate_key())
|
||||
|
||||
def set(self, name, value, ex=None):
|
||||
"""加密存储数据"""
|
||||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
encrypted_value = self.cipher.encrypt(value.encode())
|
||||
return self.redis.set(name, encrypted_value, ex=ex)
|
||||
|
||||
def get(self, name):
|
||||
"""解密获取数据"""
|
||||
encrypted_value = self.redis.get(name)
|
||||
if encrypted_value:
|
||||
decrypted_value = self.cipher.decrypt(encrypted_value)
|
||||
return decrypted_value.decode()
|
||||
return None
|
||||
|
||||
def hset(self, name, key, value):
|
||||
"""加密存储哈希字段"""
|
||||
if isinstance(value, dict):
|
||||
value = json.dumps(value)
|
||||
encrypted_value = self.cipher.encrypt(value.encode())
|
||||
return self.redis.hset(name, key, encrypted_value)
|
||||
|
||||
def hget(self, name, key):
|
||||
"""解密获取哈希字段"""
|
||||
encrypted_value = self.redis.hget(name, key)
|
||||
if encrypted_value:
|
||||
decrypted_value = self.cipher.decrypt(encrypted_value)
|
||||
return decrypted_value.decode()
|
||||
return None
|
||||
|
||||
# 使用示例
|
||||
key = Fernet.generate_key()
|
||||
encrypted_redis = EncryptedRedis(password='your_password', key=key)
|
||||
|
||||
# 存储加密数据
|
||||
user_data = {'name': 'John', 'email': 'john@example.com', 'phone': '123-456-7890'}
|
||||
encrypted_redis.set('user:1', json.dumps(user_data))
|
||||
|
||||
# 获取解密数据
|
||||
data = encrypted_redis.get('user:1')
|
||||
user_info = json.loads(data)
|
||||
print(user_info)
|
||||
```
|
||||
|
||||
### 敏感数据处理
|
||||
|
||||
**敏感数据脱敏**:
|
||||
|
||||
```python
|
||||
# 数据脱敏工具
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
class DataMasking:
|
||||
@staticmethod
|
||||
def mask_phone(phone):
|
||||
"""手机号脱敏"""
|
||||
if len(phone) == 11:
|
||||
return phone[:3] + '****' + phone[7:]
|
||||
return phone
|
||||
|
||||
@staticmethod
|
||||
def mask_email(email):
|
||||
"""邮箱脱敏"""
|
||||
if '@' in email:
|
||||
local, domain = email.split('@')
|
||||
if len(local) > 2:
|
||||
masked_local = local[0] + '*' * (len(local) - 2) + local[-1]
|
||||
else:
|
||||
masked_local = '*' * len(local)
|
||||
return f"{masked_local}@{domain}"
|
||||
return email
|
||||
|
||||
@staticmethod
|
||||
def mask_id_card(id_card):
|
||||
"""身份证脱敏"""
|
||||
if len(id_card) == 18:
|
||||
return id_card[:6] + '********' + id_card[14:]
|
||||
return id_card
|
||||
|
||||
@staticmethod
|
||||
def hash_sensitive_data(data, salt=''):
|
||||
"""敏感数据哈希"""
|
||||
return hashlib.sha256((str(data) + salt).encode()).hexdigest()
|
||||
|
||||
# 使用示例
|
||||
masker = DataMasking()
|
||||
|
||||
# 存储脱敏数据
|
||||
user_data = {
|
||||
'name': 'John Doe',
|
||||
'phone': masker.mask_phone('13812345678'),
|
||||
'email': masker.mask_email('john.doe@example.com'),
|
||||
'id_card': masker.mask_id_card('110101199001011234')
|
||||
}
|
||||
|
||||
redis_client = redis.Redis(password='your_password')
|
||||
redis_client.hset('user:masked:1', mapping=user_data)
|
||||
```
|
||||
|
||||
### 备份安全
|
||||
|
||||
**安全备份策略**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# 安全备份脚本
|
||||
|
||||
BACKUP_DIR="/secure/backup/redis"
|
||||
ENCRYPTION_KEY="/secure/keys/backup.key"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="redis_backup_$DATE.rdb"
|
||||
ENCRYPTED_FILE="$BACKUP_FILE.enc"
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# 生成备份
|
||||
redis-cli --rdb $BACKUP_DIR/$BACKUP_FILE
|
||||
|
||||
# 加密备份文件
|
||||
openssl enc -aes-256-cbc -salt -in $BACKUP_DIR/$BACKUP_FILE -out $BACKUP_DIR/$ENCRYPTED_FILE -pass file:$ENCRYPTION_KEY
|
||||
|
||||
# 删除未加密文件
|
||||
rm $BACKUP_DIR/$BACKUP_FILE
|
||||
|
||||
# 计算校验和
|
||||
sha256sum $BACKUP_DIR/$ENCRYPTED_FILE > $BACKUP_DIR/$ENCRYPTED_FILE.sha256
|
||||
|
||||
# 设置权限
|
||||
chmod 600 $BACKUP_DIR/$ENCRYPTED_FILE
|
||||
chown backup:backup $BACKUP_DIR/$ENCRYPTED_FILE
|
||||
|
||||
echo "备份完成:$BACKUP_DIR/$ENCRYPTED_FILE"
|
||||
|
||||
# 清理旧备份(保留30天)
|
||||
find $BACKUP_DIR -name "*.enc" -mtime +30 -delete
|
||||
find $BACKUP_DIR -name "*.sha256" -mtime +30 -delete
|
||||
```
|
||||
|
||||
**备份恢复脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# 安全恢复脚本
|
||||
|
||||
BACKUP_FILE="$1"
|
||||
ENCRYPTION_KEY="/secure/keys/backup.key"
|
||||
TEMP_DIR="/tmp/redis_restore"
|
||||
|
||||
if [ -z "$BACKUP_FILE" ]; then
|
||||
echo "用法: $0 <encrypted_backup_file>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证文件存在
|
||||
if [ ! -f "$BACKUP_FILE" ]; then
|
||||
echo "备份文件不存在: $BACKUP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 验证校验和
|
||||
if [ -f "$BACKUP_FILE.sha256" ]; then
|
||||
echo "验证文件完整性..."
|
||||
sha256sum -c "$BACKUP_FILE.sha256"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "文件完整性验证失败"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 创建临时目录
|
||||
mkdir -p $TEMP_DIR
|
||||
|
||||
# 解密备份文件
|
||||
echo "解密备份文件..."
|
||||
DECRYPTED_FILE="$TEMP_DIR/$(basename $BACKUP_FILE .enc)"
|
||||
openssl enc -aes-256-cbc -d -in "$BACKUP_FILE" -out "$DECRYPTED_FILE" -pass file:$ENCRYPTION_KEY
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "备份文件已解密到: $DECRYPTED_FILE"
|
||||
echo "请手动将文件复制到Redis数据目录并重启服务"
|
||||
else
|
||||
echo "解密失败"
|
||||
rm -rf $TEMP_DIR
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### 审计日志
|
||||
|
||||
**审计日志配置**:
|
||||
|
||||
```shell
|
||||
# Redis 配置文件
|
||||
# /etc/redis/redis.conf
|
||||
|
||||
# 启用命令日志
|
||||
logfile /var/log/redis/redis-server.log
|
||||
loglevel notice
|
||||
|
||||
# 启用慢查询日志
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# 客户端连接日志
|
||||
# 通过监控脚本实现
|
||||
```
|
||||
|
||||
**审计日志脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 审计日志脚本
|
||||
|
||||
LOG_FILE="/var/log/redis/audit.log"
|
||||
REDIS_LOG="/var/log/redis/redis-server.log"
|
||||
|
||||
# 监控Redis连接
|
||||
tail -f $REDIS_LOG | while read line; do
|
||||
if echo "$line" | grep -q "Accepted\|Client closed connection"; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $line" >> $LOG_FILE
|
||||
fi
|
||||
done &
|
||||
|
||||
# 监控命令执行(需要启用monitor)
|
||||
redis-cli monitor | while read line; do
|
||||
# 过滤敏感命令
|
||||
if echo "$line" | grep -qE "AUTH|CONFIG|EVAL|FLUSHDB|FLUSHALL|SHUTDOWN"; then
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') SENSITIVE: $line" >> $LOG_FILE
|
||||
fi
|
||||
done &
|
||||
|
||||
echo "审计日志监控已启动"
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 安全配置检查
|
||||
|
||||
**安全配置检查清单**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全配置检查脚本
|
||||
|
||||
echo "=== Redis 安全配置检查 ==="
|
||||
|
||||
# 检查密码配置
|
||||
echo "1. 检查密码配置"
|
||||
if redis-cli CONFIG GET requirepass | grep -q "requirepass"; then
|
||||
echo "✓ 已配置密码认证"
|
||||
else
|
||||
echo "✗ 未配置密码认证"
|
||||
fi
|
||||
|
||||
# 检查绑定地址
|
||||
echo "2. 检查绑定地址"
|
||||
BIND_ADDR=$(redis-cli CONFIG GET bind | tail -1)
|
||||
if [ "$BIND_ADDR" != "" ] && [ "$BIND_ADDR" != "0.0.0.0" ]; then
|
||||
echo "✓ 绑定地址配置安全: $BIND_ADDR"
|
||||
else
|
||||
echo "✗ 绑定地址不安全: $BIND_ADDR"
|
||||
fi
|
||||
|
||||
# 检查保护模式
|
||||
echo "3. 检查保护模式"
|
||||
PROTECTED_MODE=$(redis-cli CONFIG GET protected-mode | tail -1)
|
||||
if [ "$PROTECTED_MODE" = "yes" ]; then
|
||||
echo "✓ 保护模式已启用"
|
||||
else
|
||||
echo "✗ 保护模式未启用"
|
||||
fi
|
||||
|
||||
# 检查危险命令
|
||||
echo "4. 检查危险命令"
|
||||
DANGEROUS_COMMANDS=("FLUSHDB" "FLUSHALL" "CONFIG" "EVAL" "SHUTDOWN" "DEBUG")
|
||||
for cmd in "${DANGEROUS_COMMANDS[@]}"; do
|
||||
if redis-cli CONFIG GET "rename-command" | grep -q "$cmd"; then
|
||||
echo "✓ 危险命令 $cmd 已重命名或禁用"
|
||||
else
|
||||
echo "✗ 危险命令 $cmd 未处理"
|
||||
fi
|
||||
done
|
||||
|
||||
# 检查文件权限
|
||||
echo "5. 检查文件权限"
|
||||
REDIS_CONF="/etc/redis/redis.conf"
|
||||
if [ -f "$REDIS_CONF" ]; then
|
||||
PERM=$(stat -c "%a" "$REDIS_CONF")
|
||||
if [ "$PERM" = "640" ] || [ "$PERM" = "600" ]; then
|
||||
echo "✓ 配置文件权限安全: $PERM"
|
||||
else
|
||||
echo "✗ 配置文件权限不安全: $PERM"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 检查日志配置
|
||||
echo "6. 检查日志配置"
|
||||
LOGFILE=$(redis-cli CONFIG GET logfile | tail -1)
|
||||
if [ "$LOGFILE" != "" ]; then
|
||||
echo "✓ 已配置日志文件: $LOGFILE"
|
||||
else
|
||||
echo "✗ 未配置日志文件"
|
||||
fi
|
||||
|
||||
echo "=== 检查完成 ==="
|
||||
```
|
||||
|
||||
### 漏洞防护
|
||||
|
||||
**常见漏洞防护措施**:
|
||||
|
||||
```shell
|
||||
# 1. 禁用或重命名危险命令
|
||||
# /etc/redis/redis.conf
|
||||
rename-command FLUSHDB ""
|
||||
rename-command FLUSHALL ""
|
||||
rename-command CONFIG "CONFIG_b840fc02d524045429941cc15f59e41cb7be6c52"
|
||||
rename-command EVAL ""
|
||||
rename-command DEBUG ""
|
||||
rename-command SHUTDOWN "SHUTDOWN_b840fc02d524045429941cc15f59e41cb7be6c52"
|
||||
|
||||
# 2. 限制客户端连接
|
||||
maxclients 1000
|
||||
timeout 300
|
||||
tcp-keepalive 300
|
||||
|
||||
# 3. 禁用Lua脚本调试
|
||||
lua-replicate-commands yes
|
||||
|
||||
# 4. 设置内存限制
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
```
|
||||
|
||||
### 安全更新
|
||||
|
||||
**安全更新策略**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全更新脚本
|
||||
|
||||
echo "检查Redis版本和安全更新"
|
||||
|
||||
# 获取当前版本
|
||||
CURRENT_VERSION=$(redis-server --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
|
||||
echo "当前Redis版本: $CURRENT_VERSION"
|
||||
|
||||
# 检查是否有安全更新
|
||||
echo "检查安全公告..."
|
||||
echo "请访问以下链接查看最新安全公告:"
|
||||
echo "- https://redis.io/topics/security"
|
||||
echo "- https://github.com/redis/redis/security/advisories"
|
||||
|
||||
# 备份当前配置
|
||||
echo "备份当前配置..."
|
||||
cp /etc/redis/redis.conf /etc/redis/redis.conf.backup.$(date +%Y%m%d)
|
||||
|
||||
# 更新前检查
|
||||
echo "更新前安全检查:"
|
||||
redis-cli CONFIG GET '*' > /tmp/redis_config_before_update.txt
|
||||
|
||||
echo "请手动执行以下步骤:"
|
||||
echo "1. 下载最新稳定版本"
|
||||
echo "2. 测试环境验证"
|
||||
echo "3. 制定回滚计划"
|
||||
echo "4. 执行更新"
|
||||
echo "5. 验证功能和安全配置"
|
||||
```
|
||||
|
||||
### 应急响应
|
||||
|
||||
**安全事件应急响应**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# Redis 安全事件应急响应脚本
|
||||
|
||||
INCIDENT_LOG="/var/log/redis/security_incident.log"
|
||||
DATE=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
echo "=== Redis 安全事件应急响应 ==="
|
||||
echo "事件时间: $DATE" | tee -a $INCIDENT_LOG
|
||||
|
||||
# 1. 立即隔离
|
||||
echo "1. 立即隔离Redis服务"
|
||||
echo "停止Redis服务? (y/n)"
|
||||
read -r response
|
||||
if [ "$response" = "y" ]; then
|
||||
sudo systemctl stop redis
|
||||
echo "$DATE: Redis服务已停止" | tee -a $INCIDENT_LOG
|
||||
fi
|
||||
|
||||
# 2. 收集证据
|
||||
echo "2. 收集安全证据"
|
||||
EVIDENCE_DIR="/tmp/redis_incident_$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p $EVIDENCE_DIR
|
||||
|
||||
# 收集日志
|
||||
cp /var/log/redis/* $EVIDENCE_DIR/ 2>/dev/null
|
||||
|
||||
# 收集配置
|
||||
cp /etc/redis/redis.conf $EVIDENCE_DIR/
|
||||
|
||||
# 收集进程信息
|
||||
ps aux | grep redis > $EVIDENCE_DIR/processes.txt
|
||||
|
||||
# 收集网络连接
|
||||
netstat -tulpn | grep :6379 > $EVIDENCE_DIR/connections.txt
|
||||
|
||||
# 收集系统信息
|
||||
uname -a > $EVIDENCE_DIR/system_info.txt
|
||||
whoami > $EVIDENCE_DIR/current_user.txt
|
||||
|
||||
echo "证据已收集到: $EVIDENCE_DIR"
|
||||
echo "$DATE: 证据收集完成 - $EVIDENCE_DIR" | tee -a $INCIDENT_LOG
|
||||
|
||||
# 3. 分析威胁
|
||||
echo "3. 分析潜在威胁"
|
||||
echo "检查可疑连接..."
|
||||
redis-cli CLIENT LIST > $EVIDENCE_DIR/client_list.txt 2>/dev/null
|
||||
|
||||
echo "检查慢查询日志..."
|
||||
redis-cli SLOWLOG GET 100 > $EVIDENCE_DIR/slowlog.txt 2>/dev/null
|
||||
|
||||
# 4. 修复建议
|
||||
echo "4. 安全修复建议:"
|
||||
echo "- 更改Redis密码"
|
||||
echo "- 检查ACL配置"
|
||||
echo "- 更新防火墙规则"
|
||||
echo "- 检查系统用户账户"
|
||||
echo "- 扫描恶意软件"
|
||||
echo "- 更新Redis到最新版本"
|
||||
|
||||
echo "应急响应完成,详细日志: $INCIDENT_LOG"
|
||||
```
|
||||
2838
数据库/Redis_2025/11_Redis应用实战.md
Normal file
625
数据库/Redis_2025/12_Redis运维管理.md
Normal file
@@ -0,0 +1,625 @@
|
||||
# 第十二章:Redis 运维管理
|
||||
|
||||
## 概述
|
||||
|
||||
Redis运维管理是确保Redis服务稳定运行的关键环节。本章将深入介绍Redis的日常运维管理,包括监控告警、备份恢复、故障处理、容量规划等核心内容,帮助运维人员建立完善的Redis运维体系。
|
||||
|
||||
### 学习目标
|
||||
|
||||
- 掌握Redis监控指标和告警策略
|
||||
- 学会Redis备份恢复的最佳实践
|
||||
- 了解Redis故障诊断和处理方法
|
||||
- 掌握Redis容量规划和扩容策略
|
||||
- 学会Redis运维自动化工具的使用
|
||||
|
||||
## 监控告警
|
||||
|
||||
### 核心监控指标
|
||||
|
||||
**性能指标**:
|
||||
|
||||
```shell
|
||||
# Redis性能监控指标
|
||||
# 1. 连接数
|
||||
redis-cli info clients | grep connected_clients
|
||||
|
||||
# 2. 内存使用
|
||||
redis-cli info memory | grep used_memory_human
|
||||
|
||||
# 3. 命令执行统计
|
||||
redis-cli info commandstats
|
||||
|
||||
# 4. 键空间统计
|
||||
redis-cli info keyspace
|
||||
|
||||
# 5. 复制延迟
|
||||
redis-cli info replication | grep master_repl_offset
|
||||
|
||||
# 6. 慢查询
|
||||
redis-cli slowlog get 10
|
||||
```
|
||||
|
||||
**系统指标**:
|
||||
|
||||
```shell
|
||||
# 系统资源监控
|
||||
# CPU使用率
|
||||
top -p $(pgrep redis-server)
|
||||
|
||||
# 内存使用
|
||||
ps aux | grep redis-server
|
||||
|
||||
# 网络连接
|
||||
netstat -an | grep :6379
|
||||
|
||||
# 磁盘I/O
|
||||
iostat -x 1
|
||||
|
||||
# 文件描述符
|
||||
lsof -p $(pgrep redis-server) | wc -l
|
||||
```
|
||||
|
||||
### 监控脚本实现
|
||||
|
||||
**Redis监控脚本**:`参考 redis_monitor.py 工具`
|
||||
|
||||
### 告警配置
|
||||
|
||||
**告警规则配置**: `参考 redis_alerts.yml 工具`
|
||||
|
||||
**告警处理脚本**: `参考 redis_alert_handler.py 工具`
|
||||
|
||||
## 备份恢复
|
||||
|
||||
### 备份策略
|
||||
|
||||
**自动备份脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# redis_backup.sh - Redis自动备份脚本
|
||||
|
||||
# 配置参数
|
||||
REDIS_HOST="localhost"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_PASSWORD=""
|
||||
BACKUP_DIR="/data/redis_backup"
|
||||
RETENTION_DAYS=7
|
||||
LOG_FILE="/var/log/redis_backup.log"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
|
||||
}
|
||||
|
||||
# 创建备份目录
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# 备份函数
|
||||
backup_redis() {
|
||||
local backup_type=$1
|
||||
local timestamp=$(date '+%Y%m%d_%H%M%S')
|
||||
local backup_name="redis_${backup_type}_${timestamp}"
|
||||
local backup_path="$BACKUP_DIR/$backup_name"
|
||||
|
||||
log "开始 $backup_type 备份: $backup_name"
|
||||
|
||||
case $backup_type in
|
||||
"rdb")
|
||||
# RDB备份
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD BGSAVE
|
||||
else
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT BGSAVE
|
||||
fi
|
||||
|
||||
# 等待备份完成
|
||||
while true; do
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD LASTSAVE)
|
||||
else
|
||||
result=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT LASTSAVE)
|
||||
fi
|
||||
|
||||
if [ "$result" != "$last_save" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 复制RDB文件
|
||||
redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
|
||||
rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename | tail -1)
|
||||
|
||||
if [ -f "$redis_data_dir/$rdb_filename" ]; then
|
||||
cp "$redis_data_dir/$rdb_filename" "$backup_path.rdb"
|
||||
log "RDB备份完成: $backup_path.rdb"
|
||||
else
|
||||
log "错误: RDB文件不存在"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
|
||||
"aof")
|
||||
# AOF备份
|
||||
redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir | tail -1)
|
||||
aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename | tail -1)
|
||||
|
||||
if [ -f "$redis_data_dir/$aof_filename" ]; then
|
||||
cp "$redis_data_dir/$aof_filename" "$backup_path.aof"
|
||||
log "AOF备份完成: $backup_path.aof"
|
||||
else
|
||||
log "警告: AOF文件不存在"
|
||||
fi
|
||||
;;
|
||||
|
||||
"full")
|
||||
# 全量备份(包含配置文件)
|
||||
mkdir -p "$backup_path"
|
||||
|
||||
# 备份RDB
|
||||
backup_redis "rdb"
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$BACKUP_DIR/redis_rdb_"*.rdb "$backup_path/"
|
||||
fi
|
||||
|
||||
# 备份AOF
|
||||
backup_redis "aof"
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$BACKUP_DIR/redis_aof_"*.aof "$backup_path/"
|
||||
fi
|
||||
|
||||
# 备份配置文件
|
||||
redis_config=$(ps aux | grep redis-server | grep -v grep | awk '{for(i=1;i<=NF;i++) if($i ~ /\.conf$/) print $i}')
|
||||
if [ -n "$redis_config" ] && [ -f "$redis_config" ]; then
|
||||
cp "$redis_config" "$backup_path/redis.conf"
|
||||
log "配置文件备份完成: $backup_path/redis.conf"
|
||||
fi
|
||||
|
||||
# 创建备份信息文件
|
||||
cat > "$backup_path/backup_info.txt" << EOF
|
||||
备份时间: $(date)
|
||||
备份类型: 全量备份
|
||||
Redis版本: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO server | grep redis_version | cut -d: -f2 | tr -d '\r')
|
||||
数据库大小: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
|
||||
内存使用: $(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
|
||||
EOF
|
||||
|
||||
# 压缩备份
|
||||
cd $BACKUP_DIR
|
||||
tar -czf "${backup_name}.tar.gz" "$backup_name"
|
||||
rm -rf "$backup_name"
|
||||
|
||||
log "全量备份完成: ${backup_path}.tar.gz"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_old_backups() {
|
||||
log "清理 $RETENTION_DAYS 天前的备份文件"
|
||||
find $BACKUP_DIR -name "redis_*" -type f -mtime +$RETENTION_DAYS -delete
|
||||
log "清理完成"
|
||||
}
|
||||
|
||||
# 验证备份
|
||||
verify_backup() {
|
||||
local backup_file=$1
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
log "错误: 备份文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_size=$(stat -c%s "$backup_file")
|
||||
if [ $file_size -eq 0 ]; then
|
||||
log "错误: 备份文件为空: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "备份验证通过: $backup_file (大小: $file_size 字节)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local backup_type=${1:-"full"}
|
||||
|
||||
log "=== Redis备份开始 ==="
|
||||
log "备份类型: $backup_type"
|
||||
|
||||
# 检查Redis连接
|
||||
if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "错误: 无法连接到Redis服务器"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行备份
|
||||
backup_redis $backup_type
|
||||
|
||||
# 验证备份
|
||||
latest_backup=$(ls -t $BACKUP_DIR/redis_${backup_type}_* 2>/dev/null | head -1)
|
||||
if [ -n "$latest_backup" ]; then
|
||||
verify_backup "$latest_backup"
|
||||
fi
|
||||
|
||||
# 清理过期备份
|
||||
cleanup_old_backups
|
||||
|
||||
log "=== Redis备份完成 ==="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main $@
|
||||
```
|
||||
|
||||
### 恢复策略
|
||||
|
||||
**数据恢复脚本**:
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
# redis_restore.sh - Redis数据恢复脚本
|
||||
|
||||
# 配置参数
|
||||
REDIS_HOST="localhost"
|
||||
REDIS_PORT="6379"
|
||||
REDIS_PASSWORD=""
|
||||
BACKUP_DIR="/data/redis_backup"
|
||||
LOG_FILE="/var/log/redis_restore.log"
|
||||
|
||||
# 日志函数
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
|
||||
}
|
||||
|
||||
# 停止Redis服务
|
||||
stop_redis() {
|
||||
log "停止Redis服务"
|
||||
|
||||
# 尝试优雅关闭
|
||||
if [ -n "$REDIS_PASSWORD" ]; then
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT -a $REDIS_PASSWORD SHUTDOWN SAVE
|
||||
else
|
||||
redis-cli -h $REDIS_HOST -p $REDIS_PORT SHUTDOWN SAVE
|
||||
fi
|
||||
|
||||
# 等待进程结束
|
||||
sleep 5
|
||||
|
||||
# 强制杀死进程(如果仍在运行)
|
||||
pkill -f redis-server
|
||||
|
||||
log "Redis服务已停止"
|
||||
}
|
||||
|
||||
# 启动Redis服务
|
||||
start_redis() {
|
||||
log "启动Redis服务"
|
||||
|
||||
# 查找Redis配置文件
|
||||
local config_file="/etc/redis/redis.conf"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
config_file="/usr/local/etc/redis.conf"
|
||||
fi
|
||||
|
||||
if [ -f "$config_file" ]; then
|
||||
redis-server "$config_file" &
|
||||
else
|
||||
redis-server &
|
||||
fi
|
||||
|
||||
# 等待服务启动
|
||||
local retry_count=0
|
||||
while [ $retry_count -lt 30 ]; do
|
||||
if redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "Redis服务启动成功"
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
retry_count=$((retry_count + 1))
|
||||
done
|
||||
|
||||
log "错误: Redis服务启动失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 恢复RDB文件
|
||||
restore_rdb() {
|
||||
local rdb_file=$1
|
||||
|
||||
if [ ! -f "$rdb_file" ]; then
|
||||
log "错误: RDB文件不存在: $rdb_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复RDB文件: $rdb_file"
|
||||
|
||||
# 获取Redis数据目录
|
||||
local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
|
||||
if [ -z "$redis_data_dir" ]; then
|
||||
redis_data_dir="/var/lib/redis"
|
||||
fi
|
||||
|
||||
local rdb_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dbfilename 2>/dev/null | tail -1)
|
||||
if [ -z "$rdb_filename" ]; then
|
||||
rdb_filename="dump.rdb"
|
||||
fi
|
||||
|
||||
# 停止Redis
|
||||
stop_redis
|
||||
|
||||
# 备份现有RDB文件
|
||||
if [ -f "$redis_data_dir/$rdb_filename" ]; then
|
||||
mv "$redis_data_dir/$rdb_filename" "$redis_data_dir/${rdb_filename}.backup.$(date +%s)"
|
||||
log "现有RDB文件已备份"
|
||||
fi
|
||||
|
||||
# 复制新的RDB文件
|
||||
cp "$rdb_file" "$redis_data_dir/$rdb_filename"
|
||||
chown redis:redis "$redis_data_dir/$rdb_filename" 2>/dev/null
|
||||
|
||||
# 启动Redis
|
||||
start_redis
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "RDB恢复完成"
|
||||
return 0
|
||||
else
|
||||
log "错误: RDB恢复失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 恢复AOF文件
|
||||
restore_aof() {
|
||||
local aof_file=$1
|
||||
|
||||
if [ ! -f "$aof_file" ]; then
|
||||
log "错误: AOF文件不存在: $aof_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复AOF文件: $aof_file"
|
||||
|
||||
# 验证AOF文件
|
||||
redis-check-aof --fix "$aof_file"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "警告: AOF文件可能有问题,已尝试修复"
|
||||
fi
|
||||
|
||||
# 获取Redis数据目录
|
||||
local redis_data_dir=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET dir 2>/dev/null | tail -1)
|
||||
if [ -z "$redis_data_dir" ]; then
|
||||
redis_data_dir="/var/lib/redis"
|
||||
fi
|
||||
|
||||
local aof_filename=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT CONFIG GET appendfilename 2>/dev/null | tail -1)
|
||||
if [ -z "$aof_filename" ]; then
|
||||
aof_filename="appendonly.aof"
|
||||
fi
|
||||
|
||||
# 停止Redis
|
||||
stop_redis
|
||||
|
||||
# 备份现有AOF文件
|
||||
if [ -f "$redis_data_dir/$aof_filename" ]; then
|
||||
mv "$redis_data_dir/$aof_filename" "$redis_data_dir/${aof_filename}.backup.$(date +%s)"
|
||||
log "现有AOF文件已备份"
|
||||
fi
|
||||
|
||||
# 复制新的AOF文件
|
||||
cp "$aof_file" "$redis_data_dir/$aof_filename"
|
||||
chown redis:redis "$redis_data_dir/$aof_filename" 2>/dev/null
|
||||
|
||||
# 启动Redis
|
||||
start_redis
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log "AOF恢复完成"
|
||||
return 0
|
||||
else
|
||||
log "错误: AOF恢复失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 恢复全量备份
|
||||
restore_full() {
|
||||
local backup_file=$1
|
||||
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
log "错误: 备份文件不存在: $backup_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "恢复全量备份: $backup_file"
|
||||
|
||||
# 创建临时目录
|
||||
local temp_dir="/tmp/redis_restore_$(date +%s)"
|
||||
mkdir -p "$temp_dir"
|
||||
|
||||
# 解压备份文件
|
||||
if [[ "$backup_file" == *.tar.gz ]]; then
|
||||
tar -xzf "$backup_file" -C "$temp_dir"
|
||||
elif [[ "$backup_file" == *.zip ]]; then
|
||||
unzip "$backup_file" -d "$temp_dir"
|
||||
else
|
||||
log "错误: 不支持的备份文件格式"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 查找解压后的目录
|
||||
local extract_dir=$(find "$temp_dir" -maxdepth 1 -type d | grep -v "^$temp_dir$" | head -1)
|
||||
if [ -z "$extract_dir" ]; then
|
||||
extract_dir="$temp_dir"
|
||||
fi
|
||||
|
||||
# 恢复配置文件
|
||||
if [ -f "$extract_dir/redis.conf" ]; then
|
||||
log "发现配置文件,请手动检查是否需要恢复"
|
||||
log "配置文件位置: $extract_dir/redis.conf"
|
||||
fi
|
||||
|
||||
# 恢复数据文件
|
||||
local rdb_file=$(find "$extract_dir" -name "*.rdb" | head -1)
|
||||
local aof_file=$(find "$extract_dir" -name "*.aof" | head -1)
|
||||
|
||||
if [ -n "$rdb_file" ]; then
|
||||
restore_rdb "$rdb_file"
|
||||
elif [ -n "$aof_file" ]; then
|
||||
restore_aof "$aof_file"
|
||||
else
|
||||
log "错误: 未找到数据文件"
|
||||
rm -rf "$temp_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 清理临时文件
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
log "全量恢复完成"
|
||||
}
|
||||
|
||||
# 列出可用备份
|
||||
list_backups() {
|
||||
log "可用的备份文件:"
|
||||
|
||||
if [ ! -d "$BACKUP_DIR" ]; then
|
||||
log "备份目录不存在: $BACKUP_DIR"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backup_files=$(find "$BACKUP_DIR" -name "redis_*" -type f | sort -r)
|
||||
|
||||
if [ -z "$backup_files" ]; then
|
||||
log "未找到备份文件"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local index=1
|
||||
echo "$backup_files" | while read file; do
|
||||
local size=$(stat -c%s "$file" 2>/dev/null || echo "unknown")
|
||||
local date=$(stat -c%y "$file" 2>/dev/null | cut -d' ' -f1,2 | cut -d'.' -f1)
|
||||
printf "%2d. %s (大小: %s, 日期: %s)\n" $index "$(basename "$file")" "$size" "$date"
|
||||
index=$((index + 1))
|
||||
done
|
||||
}
|
||||
|
||||
# 验证恢复结果
|
||||
verify_restore() {
|
||||
log "验证恢复结果"
|
||||
|
||||
# 检查Redis连接
|
||||
if ! redis-cli -h $REDIS_HOST -p $REDIS_PORT ping > /dev/null 2>&1; then
|
||||
log "错误: Redis服务未正常运行"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 获取基本信息
|
||||
local db_size=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT DBSIZE)
|
||||
local memory_usage=$(redis-cli -h $REDIS_HOST -p $REDIS_PORT INFO memory | grep used_memory_human | cut -d: -f2 | tr -d '\r')
|
||||
|
||||
log "恢复验证结果:"
|
||||
log "- 数据库大小: $db_size 个键"
|
||||
log "- 内存使用: $memory_usage"
|
||||
log "- Redis状态: 正常"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
local action=$1
|
||||
local backup_file=$2
|
||||
|
||||
log "=== Redis恢复开始 ==="
|
||||
|
||||
case $action in
|
||||
"list")
|
||||
list_backups
|
||||
;;
|
||||
"rdb")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定RDB备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_rdb "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
"aof")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定AOF备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_aof "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
"full")
|
||||
if [ -z "$backup_file" ]; then
|
||||
log "错误: 请指定全量备份文件"
|
||||
exit 1
|
||||
fi
|
||||
restore_full "$backup_file"
|
||||
verify_restore
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {list|rdb|aof|full} [backup_file]"
|
||||
echo " list - 列出可用备份"
|
||||
echo " rdb - 恢复RDB备份"
|
||||
echo " aof - 恢复AOF备份"
|
||||
echo " full - 恢复全量备份"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log "=== Redis恢复完成 ==="
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main $@
|
||||
```
|
||||
|
||||
## 故障处理
|
||||
|
||||
### 常见故障诊断
|
||||
|
||||
**故障诊断脚本**: `待补充`
|
||||
|
||||
### 故障处理手册
|
||||
|
||||
**常见故障及解决方案**:
|
||||
|
||||
```shell
|
||||
# 网络问题:
|
||||
- 客户端无法连接Redis
|
||||
- 连接超时
|
||||
- 连接被拒绝
|
||||
|
||||
# 内存问题:
|
||||
- 内存使用率过高
|
||||
- OOM错误
|
||||
- 性能下降
|
||||
|
||||
# 性能问题
|
||||
- 响应时间慢
|
||||
- QPS下降
|
||||
- 慢查询增多
|
||||
|
||||
# 持久化问题
|
||||
- RDB保存失败
|
||||
- AOF文件损坏
|
||||
- 数据丢失
|
||||
|
||||
# 主从复制问题
|
||||
- 主从同步失败
|
||||
- 复制延迟过大
|
||||
- 从节点数据不一致
|
||||
|
||||
```
|
||||
|
||||
## 容量规划
|
||||
|
||||
### 容量评估脚本
|
||||
|
||||
**Redis容量分析工具**:`参考 redis_capacity_planner.py 工具`
|
||||
3
数据库/Redis_2025/Redis.md
Normal file
@@ -0,0 +1,3 @@
|
||||
[Redis官网地址](https://redis.io/)
|
||||
|
||||
[Redis项目地址](https://github.com/redis/redis)
|
||||
262
数据库/Redis_2025/cluster_delete.sh
Normal file
@@ -0,0 +1,262 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Redis集群删除脚本
|
||||
# 功能:删除指定Redis集群,包括停止服务、销毁集群、清理配置文件
|
||||
# 作者:EaglesLab
|
||||
# 日期:2025
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色定义
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 显示使用说明
|
||||
show_usage() {
|
||||
echo "使用方法: $0 <集群配置目录>"
|
||||
echo "示例: $0 /path/to/redis-cluster"
|
||||
echo ""
|
||||
echo "说明:"
|
||||
echo " 集群配置目录应包含Redis节点的配置文件和数据文件"
|
||||
echo " 脚本会自动检测目录中的Redis实例并进行删除操作"
|
||||
}
|
||||
|
||||
# 检查参数
|
||||
if [ $# -ne 1 ]; then
|
||||
print_error "参数错误!"
|
||||
show_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CLUSTER_DIR="$1"
|
||||
|
||||
# 检查目录是否存在
|
||||
if [ ! -d "$CLUSTER_DIR" ]; then
|
||||
print_error "目录不存在: $CLUSTER_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 转换为绝对路径
|
||||
CLUSTER_DIR=$(cd "$CLUSTER_DIR" && pwd)
|
||||
|
||||
print_info "准备删除Redis集群: $CLUSTER_DIR"
|
||||
|
||||
# 检查目录中是否有Redis配置文件
|
||||
redis_configs=$(find "$CLUSTER_DIR" -name "redis.conf" -o -name "redis_*.conf" 2>/dev/null || true)
|
||||
if [ -z "$redis_configs" ]; then
|
||||
print_warning "在目录 $CLUSTER_DIR 中未找到Redis配置文件"
|
||||
print_info "继续执行将删除整个目录及其内容"
|
||||
else
|
||||
print_info "发现以下Redis配置文件:"
|
||||
echo "$redis_configs" | while read -r config; do
|
||||
echo " - $config"
|
||||
done
|
||||
fi
|
||||
|
||||
# 获取运行中的Redis进程信息
|
||||
get_redis_processes() {
|
||||
local cluster_dir="$1"
|
||||
# 查找与集群目录相关的Redis进程
|
||||
ps aux | grep redis-server | grep -v grep | grep "$cluster_dir" || true
|
||||
}
|
||||
|
||||
# 显示将要删除的内容
|
||||
print_warning "=== 删除操作概览 ==="
|
||||
print_warning "将要删除的目录: $CLUSTER_DIR"
|
||||
print_warning "目录大小: $(du -sh "$CLUSTER_DIR" 2>/dev/null | cut -f1 || echo '未知')"
|
||||
|
||||
# 检查运行中的Redis进程
|
||||
running_processes=$(get_redis_processes "$CLUSTER_DIR")
|
||||
if [ -n "$running_processes" ]; then
|
||||
print_warning "发现运行中的Redis进程:"
|
||||
echo "$running_processes" | while read -r process; do
|
||||
echo " $process"
|
||||
done
|
||||
else
|
||||
print_info "未发现运行中的Redis进程"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_warning "警告: 此操作将永久删除集群数据,无法恢复!"
|
||||
echo ""
|
||||
|
||||
# 第一次确认
|
||||
read -p "$(echo -e "${YELLOW}确认要删除Redis集群吗?请输入 'yes' 继续: ${NC}")" first_confirm
|
||||
if [ "$first_confirm" != "yes" ]; then
|
||||
print_info "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_error "最后确认: 即将删除 $CLUSTER_DIR 及其所有内容!"
|
||||
read -p "$(echo -e "${RED}请再次输入 'DELETE' 确认删除操作: ${NC}")" second_confirm
|
||||
if [ "$second_confirm" != "DELETE" ]; then
|
||||
print_info "操作已取消"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "开始删除Redis集群..."
|
||||
|
||||
# 停止Redis服务
|
||||
stop_redis_services() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在停止Redis服务..."
|
||||
|
||||
# 方法1: 通过配置文件查找并停止Redis进程
|
||||
find "$cluster_dir" -name "*.conf" | while read -r config_file; do
|
||||
if grep -q "^port" "$config_file" 2>/dev/null; then
|
||||
port=$(grep "^port" "$config_file" | awk '{print $2}')
|
||||
if [ -n "$port" ]; then
|
||||
print_info "尝试停止端口 $port 上的Redis服务..."
|
||||
redis-cli -p "$port" shutdown nosave 2>/dev/null || true
|
||||
sleep 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 方法2: 强制终止与集群目录相关的Redis进程
|
||||
local pids=$(ps aux | grep redis-server | grep "$cluster_dir" | grep -v grep | awk '{print $2}' || true)
|
||||
if [ -n "$pids" ]; then
|
||||
print_info "强制终止剩余的Redis进程..."
|
||||
echo "$pids" | while read -r pid; do
|
||||
if [ -n "$pid" ]; then
|
||||
print_info "终止进程 PID: $pid"
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
sleep 2
|
||||
# 如果进程仍然存在,使用KILL信号
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
print_warning "强制杀死进程 PID: $pid"
|
||||
kill -KILL "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 等待进程完全停止
|
||||
sleep 3
|
||||
|
||||
# 验证是否还有运行中的进程
|
||||
remaining_processes=$(get_redis_processes "$cluster_dir")
|
||||
if [ -n "$remaining_processes" ]; then
|
||||
print_warning "仍有Redis进程在运行:"
|
||||
echo "$remaining_processes"
|
||||
print_warning "请手动停止这些进程后重新运行脚本"
|
||||
return 1
|
||||
else
|
||||
print_success "所有Redis服务已停止"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# 销毁集群配置
|
||||
destroy_cluster() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在销毁集群配置..."
|
||||
|
||||
# 尝试通过任意一个节点重置集群
|
||||
find "$cluster_dir" -name "*.conf" | head -1 | while read -r config_file; do
|
||||
if [ -n "$config_file" ] && grep -q "^port" "$config_file" 2>/dev/null; then
|
||||
port=$(grep "^port" "$config_file" | awk '{print $2}')
|
||||
if [ -n "$port" ]; then
|
||||
print_info "尝试通过端口 $port 重置集群..."
|
||||
timeout 10 redis-cli -p "$port" cluster reset hard 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
print_success "集群配置销毁完成"
|
||||
}
|
||||
|
||||
# 清理配置文件和数据
|
||||
cleanup_files() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在清理配置文件和数据..."
|
||||
|
||||
# 删除集群相关文件
|
||||
find "$cluster_dir" -name "nodes-*.conf" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "dump.rdb" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "appendonly.aof" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "*.log" -delete 2>/dev/null || true
|
||||
find "$cluster_dir" -name "*.pid" -delete 2>/dev/null || true
|
||||
|
||||
print_success "临时文件清理完成"
|
||||
}
|
||||
|
||||
# 删除整个目录
|
||||
delete_directory() {
|
||||
local cluster_dir="$1"
|
||||
|
||||
print_info "正在删除集群目录: $cluster_dir"
|
||||
|
||||
if rm -rf "$cluster_dir"; then
|
||||
print_success "集群目录删除成功"
|
||||
else
|
||||
print_error "删除集群目录失败"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行删除操作
|
||||
main() {
|
||||
local cluster_dir="$1"
|
||||
local success=true
|
||||
|
||||
# 停止Redis服务
|
||||
if ! stop_redis_services "$cluster_dir"; then
|
||||
print_error "停止Redis服务失败"
|
||||
success=false
|
||||
fi
|
||||
|
||||
# 销毁集群(即使停止服务失败也尝试销毁)
|
||||
destroy_cluster "$cluster_dir"
|
||||
|
||||
# 清理文件
|
||||
cleanup_files "$cluster_dir"
|
||||
|
||||
# 删除目录
|
||||
if ! delete_directory "$cluster_dir"; then
|
||||
success=false
|
||||
fi
|
||||
|
||||
if [ "$success" = true ]; then
|
||||
echo ""
|
||||
print_success "=== Redis集群删除完成 ==="
|
||||
print_success "集群目录 $cluster_dir 已被完全删除"
|
||||
print_info "所有Redis进程已停止,配置文件和数据已清理"
|
||||
else
|
||||
echo ""
|
||||
print_error "=== 删除过程中出现错误 ==="
|
||||
print_warning "请检查上述错误信息并手动处理剩余问题"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$CLUSTER_DIR"
|
||||
|
||||
echo ""
|
||||
print_info "脚本执行完成"
|
||||
173
数据库/Redis_2025/cluster_scale_in.sh
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== Redis 集群缩容操作 ==="
|
||||
|
||||
# 配置参数
|
||||
EXISTING_NODE="127.0.0.1:7001"
|
||||
REMOVE_MASTER="127.0.0.1:7007"
|
||||
REMOVE_SLAVE="127.0.0.1:7008"
|
||||
|
||||
# 1. 检查要删除的节点
|
||||
echo "1. 检查要删除的节点..."
|
||||
|
||||
# 获取要删除的主节点ID
|
||||
REMOVE_MASTER_ID=$(redis-cli -h ${REMOVE_MASTER%:*} -p ${REMOVE_MASTER#*:} CLUSTER MYID 2>/dev/null)
|
||||
REMOVE_SLAVE_ID=$(redis-cli -h ${REMOVE_SLAVE%:*} -p ${REMOVE_SLAVE#*:} CLUSTER MYID 2>/dev/null)
|
||||
|
||||
echo "要删除的主节点: $REMOVE_MASTER (ID: $REMOVE_MASTER_ID)"
|
||||
echo "要删除的从节点: $REMOVE_SLAVE (ID: $REMOVE_SLAVE_ID)"
|
||||
|
||||
# 检查节点状态
|
||||
if [ -z "$REMOVE_MASTER_ID" ]; then
|
||||
echo "❌ 无法连接到要删除的主节点"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. 迁移槽位
|
||||
echo "2. 迁移槽位..."
|
||||
|
||||
# 获取要删除节点的槽位
|
||||
slots_info=$(redis-cli -h ${REMOVE_MASTER%:*} -p ${REMOVE_MASTER#*:} CLUSTER NODES | grep $REMOVE_MASTER_ID)
|
||||
slots_range=$(echo "$slots_info" | awk '{for(i=9;i<=NF;i++) printf "%s%s", $i, (i==NF?"":" ")}')
|
||||
|
||||
echo "要迁移的槽位: $slots_range"
|
||||
|
||||
if [ -n "$slots_range" ] && [ "$slots_range" != "-" ]; then
|
||||
# 解析多个槽位范围(用空格分隔)
|
||||
# 例如: 179-1364 5461-6826 10923-12287
|
||||
slot_ranges_array=()
|
||||
total_slot_count=0
|
||||
|
||||
# 将槽位范围字符串分割成数组
|
||||
IFS=' ' read -ra SLOT_RANGES <<< "$slots_range"
|
||||
|
||||
echo "解析到的槽位范围:"
|
||||
for range in "${SLOT_RANGES[@]}"; do
|
||||
if [[ $range =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||
start_slot=${BASH_REMATCH[1]}
|
||||
end_slot=${BASH_REMATCH[2]}
|
||||
range_count=$((end_slot - start_slot + 1))
|
||||
slot_ranges_array+=("$start_slot:$end_slot:$range_count")
|
||||
total_slot_count=$((total_slot_count + range_count))
|
||||
echo " 范围 $start_slot-$end_slot: $range_count 个槽位"
|
||||
elif [[ $range =~ ^[0-9]+$ ]]; then
|
||||
# 单个槽位
|
||||
slot_ranges_array+=("$range:$range:1")
|
||||
total_slot_count=$((total_slot_count + 1))
|
||||
echo " 单个槽位 $range: 1 个槽位"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "总共需要迁移 $total_slot_count 个槽位"
|
||||
|
||||
# 获取目标节点(选择第一个其他主节点)
|
||||
target_nodes=$(redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | \
|
||||
grep "master" | grep -v $REMOVE_MASTER_ID | head -3)
|
||||
|
||||
echo "目标节点:"
|
||||
echo "$target_nodes"
|
||||
|
||||
# 将目标节点信息存储到数组中
|
||||
target_nodes_array=()
|
||||
while IFS= read -r target_line; do
|
||||
if [ -n "$target_line" ]; then
|
||||
target_nodes_array+=("$target_line")
|
||||
fi
|
||||
done <<< "$target_nodes"
|
||||
|
||||
target_count=${#target_nodes_array[@]}
|
||||
echo "找到 $target_count 个目标节点"
|
||||
|
||||
# 执行槽位迁移 - 处理每个槽位范围
|
||||
echo "开始槽位迁移..."
|
||||
|
||||
range_index=0
|
||||
target_index=0
|
||||
|
||||
# 遍历每个槽位范围
|
||||
for slot_range_info in "${slot_ranges_array[@]}"; do
|
||||
# 解析槽位范围信息 (格式: start:end:count)
|
||||
IFS=':' read -ra RANGE_INFO <<< "$slot_range_info"
|
||||
range_start=${RANGE_INFO[0]}
|
||||
range_end=${RANGE_INFO[1]}
|
||||
range_count=${RANGE_INFO[2]}
|
||||
|
||||
# 选择目标节点(轮询分配)
|
||||
target_line="${target_nodes_array[$target_index]}"
|
||||
target_id=$(echo $target_line | awk '{print $1}')
|
||||
target_addr=$(echo $target_line | awk '{print $2}')
|
||||
|
||||
echo "迁移槽位范围 $range_start-$range_end ($range_count 个槽位) 到 $target_addr"
|
||||
|
||||
# 使用 redis-cli 迁移整个槽位范围
|
||||
redis-cli --cluster reshard ${EXISTING_NODE} \
|
||||
--cluster-from $REMOVE_MASTER_ID \
|
||||
--cluster-to $target_id \
|
||||
--cluster-slots $range_count \
|
||||
--cluster-yes > /dev/null 2>&1
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 槽位范围 $range_start-$range_end 迁移成功"
|
||||
else
|
||||
echo "❌ 槽位范围 $range_start-$range_end 迁移失败"
|
||||
fi
|
||||
|
||||
# 验证槽位迁移结果
|
||||
echo "验证槽位范围 $range_start-$range_end 的迁移状态..."
|
||||
sleep 5
|
||||
|
||||
# 检查槽位是否已成功迁移
|
||||
migrated_slots=$(redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | \
|
||||
grep $target_id | awk '{print $9}')
|
||||
echo "目标节点 $target_addr 当前槽位: $migrated_slots"
|
||||
|
||||
# 轮询到下一个目标节点
|
||||
target_index=$(((target_index + 1) % target_count))
|
||||
range_index=$((range_index + 1))
|
||||
done
|
||||
echo "所有槽位范围迁移操作完成"
|
||||
|
||||
else
|
||||
echo "该节点没有分配槽位,跳过迁移"
|
||||
fi
|
||||
|
||||
# 3. 删除从节点
|
||||
echo "3. 删除从节点..."
|
||||
|
||||
echo "删除从节点 $REMOVE_SLAVE..."
|
||||
redis-cli --cluster del-node ${EXISTING_NODE} $REMOVE_SLAVE_ID
|
||||
|
||||
# 4. 删除主节点
|
||||
echo "4. 删除主节点..."
|
||||
|
||||
echo "删除主节点 $REMOVE_MASTER..."
|
||||
redis-cli --cluster del-node ${EXISTING_NODE} $REMOVE_MASTER_ID
|
||||
|
||||
|
||||
# 5. 停止已删除的节点
|
||||
echo "5. 停止已删除的节点..."
|
||||
|
||||
for port in ${REMOVE_MASTER#*:} ${REMOVE_SLAVE#*:}; do
|
||||
echo "停止节点 $port..."
|
||||
redis-cli -p $port SHUTDOWN NOSAVE 2>/dev/null || echo "节点 $port 已停止或不可用"
|
||||
done
|
||||
|
||||
# 6. 验证缩容结果
|
||||
echo "6. 验证缩容结果..."
|
||||
|
||||
echo "剩余集群节点:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER INFO
|
||||
|
||||
echo
|
||||
echo "槽位分配验证:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER SLOTS
|
||||
|
||||
echo
|
||||
echo "=== 集群缩容完成 ==="
|
||||
echo "已删除主节点: $REMOVE_MASTER"
|
||||
echo "已删除从节点: $REMOVE_SLAVE"
|
||||
echo "集群现有节点数: 6 (3主3从)"
|
||||
153
数据库/Redis_2025/cluster_scale_out.sh
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== Redis 集群扩容操作 ==="
|
||||
|
||||
# 配置参数
|
||||
EXISTING_NODE="127.0.0.1:7001"
|
||||
NEW_MASTER="127.0.0.1:7007"
|
||||
NEW_SLAVE="127.0.0.1:7008"
|
||||
BASE_DIR="/tmp/redis_cluster"
|
||||
|
||||
# 1. 准备新节点
|
||||
echo "1. 准备新节点配置..."
|
||||
|
||||
# 创建新节点目录
|
||||
mkdir -p ${BASE_DIR}/node-${NEW_MASTER#*:}
|
||||
mkdir -p ${BASE_DIR}/node-${NEW_SLAVE#*:}
|
||||
|
||||
# 生成新主节点配置
|
||||
cat > ${BASE_DIR}/node-${NEW_MASTER#*:}/redis.conf << EOF
|
||||
port ${NEW_MASTER#*:}
|
||||
bind 127.0.0.1
|
||||
dir ${BASE_DIR}/node-${NEW_MASTER#*:}
|
||||
logfile ${BASE_DIR}/node-${NEW_MASTER#*:}/redis-${NEW_MASTER#*:}.log
|
||||
pidfile ${BASE_DIR}/node-${NEW_MASTER#*:}/redis-${NEW_MASTER#*:}.pid
|
||||
daemonize yes
|
||||
|
||||
cluster-enabled yes
|
||||
cluster-config-file ${BASE_DIR}/node-${NEW_MASTER#*:}/nodes-${NEW_MASTER#*:}.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-7007.aof"
|
||||
appendfsync everysec
|
||||
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
# 生成新从节点配置
|
||||
cat > ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis.conf << EOF
|
||||
port ${NEW_SLAVE#*:}
|
||||
bind 127.0.0.1
|
||||
dir ${BASE_DIR}/node-${NEW_SLAVE#*:}
|
||||
logfile ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis-${NEW_SLAVE#*:}.log
|
||||
pidfile ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis-${NEW_SLAVE#*:}.pid
|
||||
daemonize yes
|
||||
|
||||
cluster-enabled yes
|
||||
cluster-config-file ${BASE_DIR}/node-${NEW_SLAVE#*:}/nodes-${NEW_SLAVE#*:}.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
save ""
|
||||
appendonly no
|
||||
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
tcp-keepalive 300
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
# 2. 启动新节点
|
||||
echo "2. 启动新节点..."
|
||||
|
||||
redis-server ${BASE_DIR}/node-${NEW_MASTER#*:}/redis.conf
|
||||
redis-server ${BASE_DIR}/node-${NEW_SLAVE#*:}/redis.conf
|
||||
|
||||
sleep 3
|
||||
|
||||
# 验证新节点启动
|
||||
for port in ${NEW_MASTER#*:} ${NEW_SLAVE#*:}; do
|
||||
if redis-cli -p $port ping > /dev/null 2>&1; then
|
||||
echo "✅ 新节点 $port 启动成功"
|
||||
else
|
||||
echo "❌ 新节点 $port 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 添加新主节点到集群
|
||||
echo "3. 添加新主节点到集群..."
|
||||
|
||||
echo "使用 redis-cli 添加新主节点:"
|
||||
redis-cli --cluster add-node ${NEW_MASTER} ${EXISTING_NODE}
|
||||
|
||||
# 等待节点加入
|
||||
sleep 5
|
||||
|
||||
# 验证节点加入
|
||||
echo "验证新主节点加入:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES | grep ${NEW_MASTER%:*}:${NEW_MASTER#*:}
|
||||
|
||||
# 4. 重新分配槽位
|
||||
echo "4. 重新分配槽位..."
|
||||
|
||||
# 获取新主节点ID
|
||||
NEW_MASTER_ID=$(redis-cli -h ${NEW_MASTER%:*} -p ${NEW_MASTER#*:} CLUSTER MYID)
|
||||
echo "新主节点ID: $NEW_MASTER_ID"
|
||||
|
||||
# 计算要迁移的槽位数量(假设平均分配)
|
||||
CURRENT_MASTERS=3
|
||||
NEW_MASTERS=4
|
||||
SLOTS_PER_MASTER=$((16384 / NEW_MASTERS))
|
||||
SLOTS_TO_MIGRATE=$((16384 / NEW_MASTERS))
|
||||
|
||||
echo "每个主节点应分配槽位数: $SLOTS_PER_MASTER"
|
||||
echo "需要迁移的槽位数: $SLOTS_TO_MIGRATE"
|
||||
|
||||
# 使用 redis-cli 重新分片
|
||||
echo "执行重新分片..."
|
||||
redis-cli --cluster reshard ${EXISTING_NODE} \
|
||||
--cluster-from all \
|
||||
--cluster-to $NEW_MASTER_ID \
|
||||
--cluster-slots $SLOTS_TO_MIGRATE \
|
||||
--cluster-yes
|
||||
sleep 5
|
||||
|
||||
# 5. 添加新从节点
|
||||
echo "5. 添加新从节点..."
|
||||
redis-cli --cluster add-node ${NEW_SLAVE} ${EXISTING_NODE} --cluster-slave --cluster-master-id ${NEW_MASTER_ID}
|
||||
|
||||
# 等待从节点加入
|
||||
sleep 5
|
||||
|
||||
# 6. 验证扩容结果
|
||||
echo "集群节点信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER NODES
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER INFO
|
||||
|
||||
echo
|
||||
echo "槽位分配验证:"
|
||||
redis-cli -h ${EXISTING_NODE%:*} -p ${EXISTING_NODE#*:} CLUSTER SLOTS | grep -A 2 -B 2 ${NEW_MASTER%:*}
|
||||
|
||||
# 7. 测试新节点 - 写入数据至新主节点并验证
|
||||
|
||||
echo
|
||||
echo "=== 集群扩容完成 ==="
|
||||
echo "新主节点: $NEW_MASTER (ID: $NEW_MASTER_ID)"
|
||||
echo "新从节点: $NEW_SLAVE"
|
||||
echo "集群现有节点数: 8 (4主4从)"
|
||||
70
数据库/Redis_2025/create_redis_cluster.sh
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 配置参数
|
||||
BASE_PORT=7001
|
||||
NODE_COUNT=6
|
||||
REPLICAS=1
|
||||
BASE_DIR=/tmp/redis_cluster_cluster/
|
||||
|
||||
# 1. 生成配置文件
|
||||
echo "1. 生成配置文件... generate_cluster_configs.sh"
|
||||
|
||||
# 2. 启动所有节点
|
||||
echo "2. 启动所有节点..."
|
||||
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_DIR="node-$PORT"
|
||||
|
||||
echo "启动节点 $PORT..."
|
||||
redis-server $BASE_DIR/$NODE_DIR/redis.conf
|
||||
|
||||
# 等待节点启动
|
||||
sleep 1
|
||||
|
||||
# 验证节点启动
|
||||
if redis-cli -p $PORT ping > /dev/null 2>&1; then
|
||||
echo "✅ 节点 $PORT 启动成功"
|
||||
else
|
||||
echo "❌ 节点 $PORT 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
echo "3. 创建集群..."
|
||||
|
||||
# 构建节点列表
|
||||
NODE_LIST=""
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_LIST="$NODE_LIST 127.0.0.1:$PORT"
|
||||
done
|
||||
|
||||
echo "节点列表: $NODE_LIST"
|
||||
echo "副本数量: $REPLICAS"
|
||||
echo
|
||||
|
||||
# 创建集群
|
||||
echo "执行集群创建命令..."
|
||||
redis-cli --cluster create $NODE_LIST --cluster-replicas $REPLICAS --cluster-yes
|
||||
|
||||
# 4. 验证集群状态
|
||||
echo "4. 验证集群状态..."
|
||||
sleep 3
|
||||
|
||||
echo "集群节点信息:"
|
||||
redis-cli -p $BASE_PORT cluster nodes
|
||||
|
||||
echo
|
||||
echo "集群状态信息:"
|
||||
redis-cli -p $BASE_PORT cluster info
|
||||
|
||||
echo
|
||||
echo "槽位分配信息:"
|
||||
redis-cli -p $BASE_PORT cluster slots
|
||||
|
||||
echo
|
||||
echo "=== 集群创建完成 ==="
|
||||
echo "主节点端口: $BASE_PORT, $((BASE_PORT+1)), $((BASE_PORT+2))"
|
||||
echo "从节点端口: $((BASE_PORT+3)), $((BASE_PORT+4)), $((BASE_PORT+5))"
|
||||
62
数据库/Redis_2025/generate_cluster_configs.sh
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 集群配置参数
|
||||
BASE_PORT=7001
|
||||
NODE_COUNT=6
|
||||
BASE_DIR=/tmp/redis_cluster_cluster/
|
||||
|
||||
# 创建工作目录
|
||||
mkdir -pv $BASE_DIR
|
||||
|
||||
# 为每个节点生成配置
|
||||
for i in $(seq 0 $((NODE_COUNT-1))); do
|
||||
PORT=$((BASE_PORT + i))
|
||||
NODE_DIR="node-$PORT"
|
||||
|
||||
echo "生成节点 $PORT 配置..."
|
||||
mkdir -p $BASE_DIR/$NODE_DIR
|
||||
|
||||
cat > $BASE_DIR/$NODE_DIR/redis.conf << EOF
|
||||
# Redis 集群节点 $PORT 配置
|
||||
|
||||
# 基本配置
|
||||
port $PORT
|
||||
bind 127.0.0.1
|
||||
dir $BASE_DIR/$NODE_DIR
|
||||
logfile "$BASE_DIR/$NODE_DIR/redis-$PORT.log"
|
||||
pidfile "$BASE_DIR/$NODE_DIR/redis-$PORT.pid"
|
||||
daemonize yes
|
||||
|
||||
# 集群配置
|
||||
cluster-enabled yes
|
||||
cluster-config-file nodes-$PORT.conf
|
||||
cluster-node-timeout 15000
|
||||
cluster-slave-validity-factor 10
|
||||
cluster-migration-barrier 1
|
||||
cluster-require-full-coverage yes
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfilename "appendonly-$PORT.aof"
|
||||
appendfsync everysec
|
||||
|
||||
# 内存配置
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 网络配置
|
||||
tcp-keepalive 300
|
||||
timeout 0
|
||||
|
||||
# 性能优化
|
||||
tcp-backlog 511
|
||||
databases 1
|
||||
EOF
|
||||
|
||||
done
|
||||
|
||||
echo "配置文件生成完成!"
|
||||
echo "节点目录: $BASE_DIR/node-*"
|
||||
98
数据库/Redis_2025/redis_alert_handler.py
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_alert_handler.py - Redis告警处理
|
||||
|
||||
import smtplib
|
||||
import requests
|
||||
import yaml
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from datetime import datetime
|
||||
|
||||
class AlertHandler:
|
||||
def __init__(self, config_file='redis_alerts.yml'):
|
||||
with open(config_file, 'r') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
|
||||
def send_email(self, subject, message, recipients=None):
|
||||
"""发送邮件告警"""
|
||||
email_config = self.config['notifications']['email']
|
||||
if not email_config.get('enabled'):
|
||||
return
|
||||
|
||||
recipients = recipients or email_config['recipients']
|
||||
|
||||
try:
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = email_config['username']
|
||||
msg['To'] = ', '.join(recipients)
|
||||
msg['Subject'] = subject
|
||||
|
||||
msg.attach(MIMEText(message, 'plain'))
|
||||
|
||||
server = smtplib.SMTP(email_config['smtp_server'], email_config['smtp_port'])
|
||||
server.starttls()
|
||||
server.login(email_config['username'], email_config['password'])
|
||||
|
||||
text = msg.as_string()
|
||||
server.sendmail(email_config['username'], recipients, text)
|
||||
server.quit()
|
||||
|
||||
print(f"邮件告警已发送: {subject}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送邮件失败: {e}")
|
||||
|
||||
def send_webhook(self, message):
|
||||
"""发送Webhook告警"""
|
||||
webhook_config = self.config['notifications']['webhook']
|
||||
if not webhook_config.get('enabled'):
|
||||
return
|
||||
|
||||
try:
|
||||
payload = {
|
||||
'text': message,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
response = requests.post(webhook_config['url'], json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
print(f"Webhook告警已发送: {message}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送Webhook失败: {e}")
|
||||
|
||||
def process_alert(self, alert):
|
||||
"""处理告警"""
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
subject = f"Redis告警 - {alert['type']}"
|
||||
message = f"""
|
||||
时间: {timestamp}
|
||||
级别: {alert['level'].upper()}
|
||||
类型: {alert['type']}
|
||||
消息: {alert['message']}
|
||||
|
||||
请及时处理!
|
||||
"""
|
||||
|
||||
# 发送邮件
|
||||
self.send_email(subject, message)
|
||||
|
||||
# 发送Webhook
|
||||
self.send_webhook(f"[{alert['level'].upper()}] {alert['message']}")
|
||||
|
||||
# 记录到文件
|
||||
with open('processed_alerts.log', 'a') as f:
|
||||
f.write(f"{timestamp} - {alert['level']} - {alert['message']}\n")
|
||||
|
||||
if __name__ == '__main__':
|
||||
handler = AlertHandler()
|
||||
|
||||
# 测试告警
|
||||
test_alert = {
|
||||
'level': 'warning',
|
||||
'type': 'memory_fragmentation',
|
||||
'message': '内存碎片率过高: 1.8'
|
||||
}
|
||||
|
||||
handler.process_alert(test_alert)
|
||||
62
数据库/Redis_2025/redis_alerts.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# redis_alerts.yml - Redis告警配置
|
||||
|
||||
alerts:
|
||||
memory:
|
||||
fragmentation_ratio:
|
||||
threshold: 1.5
|
||||
level: warning
|
||||
message: "内存碎片率过高"
|
||||
|
||||
used_memory_ratio:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "内存使用率过高"
|
||||
|
||||
performance:
|
||||
hit_rate:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "缓存命中率过低"
|
||||
|
||||
slow_queries:
|
||||
threshold: 10
|
||||
level: warning
|
||||
message: "慢查询过多"
|
||||
|
||||
connections:
|
||||
connected_clients_ratio:
|
||||
threshold: 0.8
|
||||
level: warning
|
||||
message: "连接数过高"
|
||||
|
||||
persistence:
|
||||
rdb_changes:
|
||||
threshold: 10000
|
||||
level: warning
|
||||
message: "RDB未保存变更过多"
|
||||
|
||||
aof_rewrite:
|
||||
threshold: 3600 # 1小时
|
||||
level: warning
|
||||
message: "AOF重写时间过长"
|
||||
|
||||
notifications:
|
||||
email:
|
||||
enabled: true
|
||||
smtp_server: "smtp.example.com"
|
||||
smtp_port: 587
|
||||
username: "alert@example.com"
|
||||
password: "password"
|
||||
recipients:
|
||||
- "admin@example.com"
|
||||
- "ops@example.com"
|
||||
|
||||
webhook:
|
||||
enabled: true
|
||||
url: "https://hooks.slack.com/services/xxx"
|
||||
|
||||
sms:
|
||||
enabled: false
|
||||
api_key: "your_sms_api_key"
|
||||
recipients:
|
||||
- "+86138xxxxxxxx"
|
||||
310
数据库/Redis_2025/redis_capacity_planner.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_capacity_planner.py - Redis容量规划工具
|
||||
|
||||
import redis
|
||||
import json
|
||||
import time
|
||||
import statistics
|
||||
from datetime import datetime, timedelta
|
||||
from collections import defaultdict
|
||||
|
||||
class RedisCapacityPlanner:
|
||||
def __init__(self, host='localhost', port=6379, password=None):
|
||||
self.redis_client = redis.Redis(
|
||||
host=host, port=port, password=password, decode_responses=True
|
||||
)
|
||||
self.metrics_history = []
|
||||
|
||||
def collect_metrics(self):
|
||||
"""收集当前指标"""
|
||||
try:
|
||||
info = self.redis_client.info()
|
||||
|
||||
metrics = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'memory': {
|
||||
'used_memory': info.get('used_memory', 0),
|
||||
'used_memory_peak': info.get('used_memory_peak', 0),
|
||||
'used_memory_rss': info.get('used_memory_rss', 0),
|
||||
'mem_fragmentation_ratio': info.get('mem_fragmentation_ratio', 1.0)
|
||||
},
|
||||
'clients': {
|
||||
'connected_clients': info.get('connected_clients', 0),
|
||||
'blocked_clients': info.get('blocked_clients', 0)
|
||||
},
|
||||
'stats': {
|
||||
'total_commands_processed': info.get('total_commands_processed', 0),
|
||||
'instantaneous_ops_per_sec': info.get('instantaneous_ops_per_sec', 0),
|
||||
'keyspace_hits': info.get('keyspace_hits', 0),
|
||||
'keyspace_misses': info.get('keyspace_misses', 0)
|
||||
},
|
||||
'keyspace': self.get_keyspace_info(),
|
||||
'persistence': {
|
||||
'rdb_changes_since_last_save': info.get('rdb_changes_since_last_save', 0),
|
||||
'aof_current_size': info.get('aof_current_size', 0)
|
||||
}
|
||||
}
|
||||
|
||||
return metrics
|
||||
except Exception as e:
|
||||
print(f"收集指标失败: {e}")
|
||||
return None
|
||||
|
||||
def get_keyspace_info(self):
|
||||
"""获取键空间信息"""
|
||||
try:
|
||||
keyspace_info = {}
|
||||
info = self.redis_client.info('keyspace')
|
||||
|
||||
for db_key, db_info in info.items():
|
||||
if db_key.startswith('db'):
|
||||
# 解析db信息: keys=xxx,expires=xxx,avg_ttl=xxx
|
||||
db_stats = {}
|
||||
for item in db_info.split(','):
|
||||
key, value = item.split('=')
|
||||
db_stats[key] = int(value)
|
||||
keyspace_info[db_key] = db_stats
|
||||
|
||||
return keyspace_info
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def analyze_key_patterns(self, sample_size=1000):
|
||||
"""分析键模式"""
|
||||
try:
|
||||
print("分析键模式...")
|
||||
|
||||
# 使用SCAN获取键样本
|
||||
keys_sample = []
|
||||
cursor = 0
|
||||
|
||||
while len(keys_sample) < sample_size:
|
||||
cursor, keys = self.redis_client.scan(cursor, count=100)
|
||||
keys_sample.extend(keys)
|
||||
|
||||
if cursor == 0: # 扫描完成
|
||||
break
|
||||
|
||||
# 分析键模式
|
||||
pattern_stats = defaultdict(lambda: {'count': 0, 'total_size': 0, 'avg_ttl': 0})
|
||||
|
||||
for key in keys_sample[:sample_size]:
|
||||
try:
|
||||
# 提取键模式(前缀)
|
||||
pattern = key.split(':')[0] if ':' in key else 'no_pattern'
|
||||
|
||||
# 获取键信息
|
||||
key_type = self.redis_client.type(key)
|
||||
memory_usage = self.redis_client.memory_usage(key) or 0
|
||||
ttl = self.redis_client.ttl(key)
|
||||
|
||||
pattern_stats[pattern]['count'] += 1
|
||||
pattern_stats[pattern]['total_size'] += memory_usage
|
||||
|
||||
if ttl > 0:
|
||||
pattern_stats[pattern]['avg_ttl'] = (
|
||||
pattern_stats[pattern]['avg_ttl'] + ttl
|
||||
) / 2
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# 计算平均大小
|
||||
for pattern, stats in pattern_stats.items():
|
||||
if stats['count'] > 0:
|
||||
stats['avg_size'] = stats['total_size'] / stats['count']
|
||||
|
||||
return dict(pattern_stats)
|
||||
|
||||
except Exception as e:
|
||||
print(f"键模式分析失败: {e}")
|
||||
return {}
|
||||
|
||||
def predict_growth(self, days=30):
|
||||
"""预测增长趋势"""
|
||||
if len(self.metrics_history) < 2:
|
||||
return None
|
||||
|
||||
# 计算内存增长率
|
||||
memory_values = [m['memory']['used_memory'] for m in self.metrics_history]
|
||||
time_points = [datetime.fromisoformat(m['timestamp']) for m in self.metrics_history]
|
||||
|
||||
if len(memory_values) < 2:
|
||||
return None
|
||||
|
||||
# 简单线性增长预测
|
||||
time_diffs = [(t - time_points[0]).total_seconds() / 3600 for t in time_points] # 小时
|
||||
|
||||
# 计算增长率(每小时)
|
||||
growth_rates = []
|
||||
for i in range(1, len(memory_values)):
|
||||
if time_diffs[i] > 0:
|
||||
rate = (memory_values[i] - memory_values[i-1]) / time_diffs[i]
|
||||
growth_rates.append(rate)
|
||||
|
||||
if not growth_rates:
|
||||
return None
|
||||
|
||||
avg_growth_rate = statistics.mean(growth_rates) # 字节/小时
|
||||
current_memory = memory_values[-1]
|
||||
|
||||
# 预测未来内存使用
|
||||
future_memory = current_memory + (avg_growth_rate * 24 * days)
|
||||
|
||||
return {
|
||||
'current_memory': current_memory,
|
||||
'predicted_memory': future_memory,
|
||||
'growth_rate_per_hour': avg_growth_rate,
|
||||
'growth_rate_per_day': avg_growth_rate * 24,
|
||||
'prediction_days': days
|
||||
}
|
||||
|
||||
def generate_capacity_report(self):
|
||||
"""生成容量规划报告"""
|
||||
print("\n=== Redis容量规划报告 ===")
|
||||
print(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
|
||||
# 当前状态
|
||||
current_metrics = self.collect_metrics()
|
||||
if not current_metrics:
|
||||
print("无法获取当前指标")
|
||||
return
|
||||
|
||||
print("1. 当前状态")
|
||||
print("-" * 40)
|
||||
memory_mb = current_metrics['memory']['used_memory'] / 1024 / 1024
|
||||
print(f"内存使用: {memory_mb:.2f} MB")
|
||||
print(f"连接数: {current_metrics['clients']['connected_clients']}")
|
||||
print(f"QPS: {current_metrics['stats']['instantaneous_ops_per_sec']}")
|
||||
|
||||
# 键空间分析
|
||||
total_keys = sum(
|
||||
db_info.get('keys', 0)
|
||||
for db_info in current_metrics['keyspace'].values()
|
||||
)
|
||||
print(f"总键数: {total_keys}")
|
||||
|
||||
# 键模式分析
|
||||
print("\n2. 键模式分析")
|
||||
print("-" * 40)
|
||||
key_patterns = self.analyze_key_patterns()
|
||||
|
||||
if key_patterns:
|
||||
# 按内存使用排序
|
||||
sorted_patterns = sorted(
|
||||
key_patterns.items(),
|
||||
key=lambda x: x[1]['total_size'],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
print(f"{'模式':<20} {'数量':<10} {'总大小(KB)':<15} {'平均大小(B)':<15}")
|
||||
print("-" * 65)
|
||||
|
||||
for pattern, stats in sorted_patterns[:10]:
|
||||
avg_size = stats.get('avg_size', 0)
|
||||
total_kb = stats['total_size'] / 1024
|
||||
print(f"{pattern:<20} {stats['count']:<10} {total_kb:<15.2f} {avg_size:<15.0f}")
|
||||
|
||||
# 增长预测
|
||||
print("\n3. 增长预测")
|
||||
print("-" * 40)
|
||||
|
||||
if len(self.metrics_history) >= 2:
|
||||
prediction = self.predict_growth(30)
|
||||
if prediction:
|
||||
current_gb = prediction['current_memory'] / 1024 / 1024 / 1024
|
||||
predicted_gb = prediction['predicted_memory'] / 1024 / 1024 / 1024
|
||||
growth_mb_day = prediction['growth_rate_per_day'] / 1024 / 1024
|
||||
|
||||
print(f"当前内存: {current_gb:.2f} GB")
|
||||
print(f"30天后预测: {predicted_gb:.2f} GB")
|
||||
print(f"日增长率: {growth_mb_day:.2f} MB/天")
|
||||
|
||||
# 容量建议
|
||||
print("\n4. 容量建议")
|
||||
print("-" * 40)
|
||||
|
||||
if predicted_gb > 8:
|
||||
print("⚠ 建议: 考虑增加内存或优化数据结构")
|
||||
elif predicted_gb > 4:
|
||||
print("⚠ 建议: 监控内存使用,准备扩容计划")
|
||||
else:
|
||||
print("✓ 当前容量充足")
|
||||
|
||||
# 扩容时间点预测
|
||||
if growth_mb_day > 0:
|
||||
# 假设8GB为容量上限
|
||||
max_capacity = 8 * 1024 * 1024 * 1024
|
||||
remaining_capacity = max_capacity - prediction['current_memory']
|
||||
days_to_full = remaining_capacity / prediction['growth_rate_per_day']
|
||||
|
||||
if days_to_full > 0:
|
||||
full_date = datetime.now() + timedelta(days=days_to_full)
|
||||
print(f"预计容量耗尽时间: {full_date.strftime('%Y-%m-%d')} ({days_to_full:.0f}天后)")
|
||||
else:
|
||||
print("数据不足,无法进行增长预测")
|
||||
else:
|
||||
print("需要更多历史数据进行预测")
|
||||
|
||||
# 性能建议
|
||||
print("\n5. 性能优化建议")
|
||||
print("-" * 40)
|
||||
|
||||
fragmentation = current_metrics['memory']['mem_fragmentation_ratio']
|
||||
if fragmentation > 1.5:
|
||||
print("⚠ 内存碎片率过高,建议重启Redis或使用MEMORY PURGE")
|
||||
|
||||
hit_rate = 0
|
||||
hits = current_metrics['stats']['keyspace_hits']
|
||||
misses = current_metrics['stats']['keyspace_misses']
|
||||
if hits + misses > 0:
|
||||
hit_rate = hits / (hits + misses)
|
||||
if hit_rate < 0.8:
|
||||
print(f"⚠ 缓存命中率较低({hit_rate:.2%}),建议优化缓存策略")
|
||||
|
||||
if current_metrics['clients']['connected_clients'] > 1000:
|
||||
print("⚠ 连接数较多,建议使用连接池")
|
||||
|
||||
# 保存报告
|
||||
report_data = {
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'current_metrics': current_metrics,
|
||||
'key_patterns': key_patterns,
|
||||
'prediction': prediction if len(self.metrics_history) >= 2 else None,
|
||||
'recommendations': []
|
||||
}
|
||||
|
||||
report_file = f"redis_capacity_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||
with open(report_file, 'w') as f:
|
||||
json.dump(report_data, f, indent=2, default=str)
|
||||
|
||||
print(f"\n详细报告已保存到: {report_file}")
|
||||
|
||||
def monitor_and_collect(self, duration_hours=24, interval_minutes=10):
|
||||
"""监控并收集数据"""
|
||||
print(f"开始收集数据,持续{duration_hours}小时,间隔{interval_minutes}分钟")
|
||||
|
||||
end_time = datetime.now() + timedelta(hours=duration_hours)
|
||||
|
||||
while datetime.now() < end_time:
|
||||
metrics = self.collect_metrics()
|
||||
if metrics:
|
||||
self.metrics_history.append(metrics)
|
||||
print(f"已收集数据点: {len(self.metrics_history)}")
|
||||
|
||||
time.sleep(interval_minutes * 60)
|
||||
|
||||
print(f"数据收集完成,共收集{len(self.metrics_history)}个数据点")
|
||||
|
||||
if __name__ == '__main__':
|
||||
planner = RedisCapacityPlanner()
|
||||
|
||||
# 收集一些历史数据(演示用)
|
||||
for i in range(5):
|
||||
metrics = planner.collect_metrics()
|
||||
if metrics:
|
||||
planner.metrics_history.append(metrics)
|
||||
time.sleep(1)
|
||||
|
||||
# 生成报告
|
||||
planner.generate_capacity_report()
|
||||
65
数据库/Redis_2025/redis_doc.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Redis 课程文档规范
|
||||
|
||||
本文档旨在定义 Redis 课程相关文件的用途、格式风格和编写原则,以确保课程内容的一致性、专业性和易读性。
|
||||
|
||||
---
|
||||
|
||||
## 1. 文件定义
|
||||
|
||||
### 1.1. 课程大纲 (`redis_tpl.md`)
|
||||
|
||||
- **用途**:作为 Redis 课程的整体教学计划和结构指南。它定义了课程的章节、知识点、学时分配和实践环节。
|
||||
- **目标读者**:课程开发者、上课教师。
|
||||
- **核心要求**:结构清晰,逻辑性强,全面覆盖 Redis 的核心知识体系。
|
||||
|
||||
### 1.2. 课程内容
|
||||
|
||||
- **用途**:根据课程大纲,详细阐述每个知识点的具体内容,包括理论讲解、代码示例、实践操作和练习题。
|
||||
- **目标读者**:学生。
|
||||
- **核心要求**:内容详实,通俗易懂,理论与实践相结合。
|
||||
|
||||
---
|
||||
|
||||
## 2. 编写原则
|
||||
|
||||
### 2.1. 内容组织:从浅入深
|
||||
|
||||
课程内容应遵循认知规律,从基础概念开始,逐步深入到高级主题和实战应用。
|
||||
|
||||
- **基础先行**:首先介绍 NoSQL 和 Redis 的基本概念、架构和安装部署。
|
||||
- **核心操作**:然后讲解数据类型、基本命令和持久化等核心技能。
|
||||
- **高级进阶**:接着深入主从复制、哨兵模式、集群部署和性能优化等高级主题。
|
||||
- **实战应用**:最后通过项目实战,巩固所学知识,培养解决实际问题的能力。
|
||||
|
||||
### 2.2. 语言风格:通俗易懂
|
||||
|
||||
- **简化复杂概念**:使用简单的语言和比喻来解释复杂的技术概念。
|
||||
- **图文并茂**:适当使用图表、流程图和示意图,帮助理解抽象的知识。
|
||||
- **代码注释**:所有代码示例都应有清晰的注释,解释代码的功能和逻辑。
|
||||
|
||||
### 2.3. 理论与实践融合
|
||||
|
||||
- **理论指导实践**:每个理论知识点都应配有相应的实践案例或代码示例。
|
||||
- **实践巩固理论**:通过实验、练习和项目,加深对理论知识的理解和应用。
|
||||
- **场景驱动**:结合真实的应用场景(如缓存、会话管理、消息队列、排行榜等)进行讲解,激发学习兴趣。
|
||||
|
||||
### 2.4. 格式风格:统一规范
|
||||
|
||||
- **Markdown 语法**:统一使用 Markdown 进行文档编写。
|
||||
- **标题层级**:遵循清晰的标题层级结构(`#`、`##`、`###`),与课程大纲保持一致,从 Redis 基础概念开始作为 `#` 标题,其他平级标题也用 `#` 格式,不需要 x.x.x 格式。
|
||||
- **代码块**:使用代码块来格式化代码示例,并注明语言类型。(bash/shell 统一用 shell)
|
||||
- **术语规范**:专业术语首次出现时应予以解释,并保持中英文术语的统一性。
|
||||
- **实践操作**:每一章内容最后有一个实践环节,标题为 **实践操作**,有需求描述、实践细节和结果验证。并将实践细节和结果验证放到同一个代码块中。
|
||||
|
||||
---
|
||||
|
||||
## 3. 协作流程
|
||||
|
||||
1. **大纲评审**:首先共同评审和确定 `redis_tpl.md` 中的课程大纲。
|
||||
2. **内容开发**:根据大纲,分工协作编写具体内容。
|
||||
3. **内容评审**:定期进行内容评审(Peer Review),确保内容质量和风格统一。
|
||||
4. **持续迭代**:根据教学反馈和技术发展,持续更新和完善课程内容。
|
||||
|
||||
## 4. 软件版本
|
||||
|
||||
1. **Redis 版本**:Redis 6.2.19
|
||||
274
数据库/Redis_2025/redis_monitor.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/env python3
|
||||
# redis_monitor.py - Redis监控脚本
|
||||
|
||||
import redis
|
||||
import time
|
||||
import json
|
||||
import psutil
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any
|
||||
|
||||
class RedisMonitor:
|
||||
def __init__(self, host='localhost', port=6379, password=None):
|
||||
self.redis_client = redis.Redis(
|
||||
host=host, port=port, password=password, decode_responses=True
|
||||
)
|
||||
self.setup_logging()
|
||||
|
||||
def setup_logging(self):
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('redis_monitor.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def get_redis_info(self) -> Dict[str, Any]:
|
||||
"""获取Redis信息"""
|
||||
try:
|
||||
info = self.redis_client.info()
|
||||
return {
|
||||
'server': {
|
||||
'version': info.get('redis_version'),
|
||||
'uptime': info.get('uptime_in_seconds'),
|
||||
'role': info.get('role')
|
||||
},
|
||||
'clients': {
|
||||
'connected': info.get('connected_clients'),
|
||||
'blocked': info.get('blocked_clients'),
|
||||
'max_clients': info.get('maxclients')
|
||||
},
|
||||
'memory': {
|
||||
'used': info.get('used_memory'),
|
||||
'used_human': info.get('used_memory_human'),
|
||||
'peak': info.get('used_memory_peak'),
|
||||
'peak_human': info.get('used_memory_peak_human'),
|
||||
'fragmentation_ratio': info.get('mem_fragmentation_ratio')
|
||||
},
|
||||
'stats': {
|
||||
'total_commands': info.get('total_commands_processed'),
|
||||
'ops_per_sec': info.get('instantaneous_ops_per_sec'),
|
||||
'keyspace_hits': info.get('keyspace_hits'),
|
||||
'keyspace_misses': info.get('keyspace_misses'),
|
||||
'expired_keys': info.get('expired_keys'),
|
||||
'evicted_keys': info.get('evicted_keys')
|
||||
},
|
||||
'persistence': {
|
||||
'rdb_last_save': info.get('rdb_last_save_time'),
|
||||
'rdb_changes_since_save': info.get('rdb_changes_since_last_save'),
|
||||
'aof_enabled': info.get('aof_enabled'),
|
||||
'aof_rewrite_in_progress': info.get('aof_rewrite_in_progress')
|
||||
},
|
||||
'replication': {
|
||||
'role': info.get('role'),
|
||||
'connected_slaves': info.get('connected_slaves'),
|
||||
'master_repl_offset': info.get('master_repl_offset'),
|
||||
'repl_backlog_size': info.get('repl_backlog_size')
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取Redis信息失败: {e}")
|
||||
return {}
|
||||
|
||||
def get_system_info(self) -> Dict[str, Any]:
|
||||
"""获取系统信息"""
|
||||
try:
|
||||
# 查找Redis进程
|
||||
redis_processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
if 'redis-server' in proc.info['name']:
|
||||
redis_processes.append(proc)
|
||||
|
||||
if not redis_processes:
|
||||
return {}
|
||||
|
||||
redis_proc = redis_processes[0]
|
||||
|
||||
return {
|
||||
'cpu': {
|
||||
'percent': redis_proc.cpu_percent(),
|
||||
'times': redis_proc.cpu_times()._asdict()
|
||||
},
|
||||
'memory': {
|
||||
'rss': redis_proc.memory_info().rss,
|
||||
'vms': redis_proc.memory_info().vms,
|
||||
'percent': redis_proc.memory_percent()
|
||||
},
|
||||
'io': {
|
||||
'read_count': redis_proc.io_counters().read_count,
|
||||
'write_count': redis_proc.io_counters().write_count,
|
||||
'read_bytes': redis_proc.io_counters().read_bytes,
|
||||
'write_bytes': redis_proc.io_counters().write_bytes
|
||||
},
|
||||
'net_connections': len(redis_proc.net_connections()),
|
||||
'open_files': len(redis_proc.open_files()),
|
||||
'threads': redis_proc.num_threads()
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取系统信息失败: {e}")
|
||||
return {}
|
||||
|
||||
def get_slow_queries(self, count=10) -> list:
|
||||
"""获取慢查询"""
|
||||
try:
|
||||
slow_log = self.redis_client.slowlog_get(count)
|
||||
return [
|
||||
{
|
||||
'id': entry['id'],
|
||||
'start_time': entry['start_time'],
|
||||
'duration': entry['duration'],
|
||||
'command': ' '.join(str(cmd) for cmd in entry['command'])
|
||||
}
|
||||
for entry in slow_log
|
||||
]
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取慢查询失败: {e}")
|
||||
return []
|
||||
|
||||
def check_alerts(self, metrics: Dict[str, Any]) -> list:
|
||||
"""检查告警条件"""
|
||||
alerts = []
|
||||
|
||||
# 内存使用率告警
|
||||
if metrics.get('memory', {}).get('fragmentation_ratio', 0) > 1.5:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'memory_fragmentation',
|
||||
'message': f"内存碎片率过高: {metrics['memory']['fragmentation_ratio']}"
|
||||
})
|
||||
|
||||
# 连接数告警
|
||||
connected_clients = metrics.get('clients', {}).get('connected', 0)
|
||||
max_clients = metrics.get('clients', {}).get('max_clients', 10000)
|
||||
if connected_clients > max_clients * 0.8:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'high_connections',
|
||||
'message': f"连接数过高: {connected_clients}/{max_clients}"
|
||||
})
|
||||
|
||||
# 命中率告警
|
||||
hits = metrics.get('stats', {}).get('keyspace_hits', 0)
|
||||
misses = metrics.get('stats', {}).get('keyspace_misses', 0)
|
||||
if hits + misses > 0:
|
||||
hit_rate = hits / (hits + misses)
|
||||
if hit_rate < 0.8:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'low_hit_rate',
|
||||
'message': f"缓存命中率过低: {hit_rate:.2%}"
|
||||
})
|
||||
|
||||
# 持久化告警
|
||||
rdb_changes = metrics.get('persistence', {}).get('rdb_changes_since_save', 0)
|
||||
if rdb_changes > 10000:
|
||||
alerts.append({
|
||||
'level': 'warning',
|
||||
'type': 'rdb_not_saved',
|
||||
'message': f"RDB未保存变更过多: {rdb_changes}"
|
||||
})
|
||||
|
||||
return alerts
|
||||
|
||||
def collect_metrics(self) -> Dict[str, Any]:
|
||||
"""收集所有监控指标"""
|
||||
timestamp = datetime.now().isoformat()
|
||||
|
||||
metrics = {
|
||||
'timestamp': timestamp,
|
||||
'redis': self.get_redis_info(),
|
||||
'system': self.get_system_info(),
|
||||
'slow_queries': self.get_slow_queries()
|
||||
}
|
||||
|
||||
# 检查告警
|
||||
alerts = self.check_alerts(metrics['redis'])
|
||||
metrics['alerts'] = alerts
|
||||
|
||||
return metrics
|
||||
|
||||
def save_metrics(self, metrics: Dict[str, Any]):
|
||||
"""保存监控数据"""
|
||||
try:
|
||||
filename = f"redis_metrics_{datetime.now().strftime('%Y%m%d')}.json"
|
||||
|
||||
# 读取现有数据
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
data = []
|
||||
|
||||
# 添加新数据
|
||||
data.append(metrics)
|
||||
|
||||
# 保持最近1000条记录
|
||||
if len(data) > 1000:
|
||||
data = data[-1000:]
|
||||
|
||||
# 保存数据
|
||||
with open(filename, 'w') as f:
|
||||
json.dump(data, f, indent=2, default=str)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"保存监控数据失败: {e}")
|
||||
|
||||
def send_alerts(self, alerts: list):
|
||||
"""发送告警"""
|
||||
for alert in alerts:
|
||||
message = f"[{alert['level'].upper()}] {alert['type']}: {alert['message']}"
|
||||
self.logger.warning(message)
|
||||
|
||||
# 这里可以集成邮件、短信、钉钉等告警方式
|
||||
# 示例:发送到日志文件
|
||||
with open('redis_alerts.log', 'a') as f:
|
||||
f.write(f"{datetime.now().isoformat()} - {message}\n")
|
||||
|
||||
def run_monitor(self, interval=60):
|
||||
"""运行监控"""
|
||||
self.logger.info("开始Redis监控")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 收集指标
|
||||
metrics = self.collect_metrics()
|
||||
|
||||
# 保存数据
|
||||
self.save_metrics(metrics)
|
||||
|
||||
# 处理告警
|
||||
if metrics.get('alerts'):
|
||||
self.send_alerts(metrics['alerts'])
|
||||
|
||||
# 输出关键指标
|
||||
redis_info = metrics.get('redis', {})
|
||||
self.logger.info(
|
||||
f"连接数: {redis_info.get('clients', {}).get('connected', 0)}, "
|
||||
f"内存: {redis_info.get('memory', {}).get('used_human', 'N/A')}, "
|
||||
f"QPS: {redis_info.get('stats', {}).get('ops_per_sec', 0)}"
|
||||
)
|
||||
|
||||
time.sleep(interval)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("监控已停止")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"监控异常: {e}")
|
||||
time.sleep(interval)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
host = sys.argv[1] if len(sys.argv) > 1 else 'localhost'
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 6379
|
||||
password = sys.argv[3] if len(sys.argv) > 3 else None
|
||||
interval = int(sys.argv[4]) if len(sys.argv) > 4 else 60
|
||||
|
||||
monitor = RedisMonitor(host, port, password)
|
||||
monitor.run_monitor(interval)
|
||||
70
数据库/Redis_2025/redis_pool_example.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import redis
|
||||
import random
|
||||
from redis.sentinel import Sentinel
|
||||
|
||||
class RedisCluster:
|
||||
def __init__(self, master_host, master_port, slave_hosts, password=None):
|
||||
# 主节点连接池(写操作)
|
||||
self.master_pool = redis.ConnectionPool(
|
||||
host=master_host,
|
||||
port=master_port,
|
||||
password=password,
|
||||
max_connections=20,
|
||||
retry_on_timeout=True
|
||||
)
|
||||
|
||||
# 从节点连接池(读操作)
|
||||
self.slave_pools = []
|
||||
for host, port in slave_hosts:
|
||||
pool = redis.ConnectionPool(
|
||||
host=host,
|
||||
port=port,
|
||||
password=password,
|
||||
max_connections=10,
|
||||
retry_on_timeout=True
|
||||
)
|
||||
self.slave_pools.append(pool)
|
||||
|
||||
def get_master_client(self):
|
||||
"""获取主节点客户端(写操作)"""
|
||||
return redis.Redis(connection_pool=self.master_pool)
|
||||
|
||||
def get_slave_client(self):
|
||||
"""获取从节点客户端(读操作)"""
|
||||
if not self.slave_pools:
|
||||
return self.get_master_client()
|
||||
|
||||
# 随机选择一个从节点
|
||||
pool = random.choice(self.slave_pools)
|
||||
return redis.Redis(connection_pool=pool)
|
||||
|
||||
def set(self, key, value, **kwargs):
|
||||
"""写操作"""
|
||||
client = self.get_master_client()
|
||||
return client.set(key, value, **kwargs)
|
||||
|
||||
def get(self, key):
|
||||
"""读操作"""
|
||||
client = self.get_slave_client()
|
||||
return client.get(key)
|
||||
|
||||
def delete(self, *keys):
|
||||
"""删除操作"""
|
||||
client = self.get_master_client()
|
||||
return client.delete(*keys)
|
||||
|
||||
# 使用示例
|
||||
if __name__ == "__main__":
|
||||
cluster = RedisCluster(
|
||||
master_host="192.168.1.100",
|
||||
master_port=6379,
|
||||
slave_hosts=[("192.168.1.101", 6380), ("192.168.1.102", 6381)],
|
||||
password="your_password"
|
||||
)
|
||||
|
||||
# 写操作(发送到主节点)
|
||||
cluster.set("test_key", "test_value")
|
||||
|
||||
# 读操作(发送到从节点)
|
||||
value = cluster.get("test_key")
|
||||
print(f"读取到的值: {value}")
|
||||
410
数据库/Redis_2025/redis_tpl.md
Normal file
@@ -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
|
||||
- 第三方监控方案
|
||||
|
||||
## 实践操作
|
||||
- 使用管理工具
|
||||
- 集成开发框架
|
||||
- 配置监控方案
|
||||
BIN
数据库/redis/AOF工作原理01.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
数据库/redis/AOF工作原理02.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
数据库/redis/CDN缓存.png
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
数据库/redis/CDN请求流程01.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
数据库/redis/CDN请求流程02.png
Normal file
|
After Width: | Height: | Size: 334 KiB |
BIN
数据库/redis/CPU缓存01.png
Normal file
|
After Width: | Height: | Size: 219 KiB |
BIN
数据库/redis/CPU缓存02.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
数据库/redis/RDB.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
数据库/redis/RDB工作原理01.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
数据库/redis/RDB工作原理02.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
数据库/redis/Redis_rank.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
数据库/redis/redis业务更新操作.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |