Redis 基础学习(全)

317 阅读52分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

1. 介绍

1.1 特点

  • 速度快

  • 多种数据结构

  • 功能丰富

  • 简单稳定

  • 支持客户端语言多

  • 支持持久化机制

  • 自带高可用架构

1.2 应用场景

  • 键过期:缓存,session 会话保存,优惠券过期

  • 列表:排行榜

  • 天然计数器:帖子浏览数,视频播放数,评论留言数

  • 集合:兴趣标签,广告投放

  • 消息队列:ELK

2. 安装部署

2.1 目录规划

  • redis 下载目录:/data/soft/

  • redis 安装目录:/opt/redis_cluster/redis_{PORT}/{conf,logs,pid}

  • redis 数据目录:/data/redis_cluster/redis_{PORT}/redis_{PORT}.rdb

  • redis 运行脚本:/root/scripts/redis_shell.sh

2.2 redis 安装


mkdir -p /data/soft

mkdir -p /data/redis_cluster/redis_6379

mkdir -p /opt/redis_cluster/redis_6379/{conf,pid,logs}

cd /data/soft/

wget http://download.redis.io/releases/redis-3.2.9.tar.gz

tar zxf redis-3.2.9.tar.gz -C /opt/redis_cluster/

ln -s /opt/redis_cluster/redis-3.2.9/ /opt/redis_cluster/redis

cd /opt/redis_cluster/redis

make && make install

make install:将命令复制到 /usr/local/bin 目录下。执行完 make install 命令后,输出如下信息:


INSTALL install

INSTALL install

INSTALL install

INSTALL install

INSTALL install

2.3 配置文件修改

假设我们想要写一个 redis 配置文件,那么我们应该参考什么呢?肯定是参考官方的配置文件了,那么我么怎么看呢?


cd /opt/redis_cluster/redis-3.2.9/utils

./install_server.sh

脚本输出信息如下:


Welcome to the redis service installer

This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379]

Selecting default: 6379

Please select the redis config file name [/etc/redis/6379.conf]

Selected default - /etc/redis/6379.conf

Please select the redis log file name [/var/log/redis_6379.log]

Selected default - /var/log/redis_6379.log

Please select the data directory for this instance [/var/lib/redis/6379]

Selected default - /var/lib/redis/6379

Please select the redis executable path [/usr/local/bin/redis-server]

Selected config:

Port : 6379

Config file : /etc/redis/6379.conf

Log file : /var/log/redis_6379.log

Data dir : /var/lib/redis/6379

Executable : /usr/local/bin/redis-server

Cli Executable : /usr/local/bin/redis-cli

Is this ok? Then press ENTER to go on or Ctrl-C to abort.

Copied /tmp/6379.conf => /etc/init.d/redis_6379

Installing service...

Successfully added to chkconfig!

Successfully added to runlevels 345!

Starting Redis server...

Installation successful!

我们可以看到配置文件在 /etc/redis/6379.conf,用这个文件参考。

接下来,我们来配置我们自己的配置文件:


cd /opt/redis_cluster/redis_6379/conf

vim redis_6379.conf


# 以守护进程模式启动

daemonize yes

# 绑定的主机地址(一般设置内网地址,不要设置外网地址,外网地址大家都可以访问了)

bind 172.21.0.3

# 监听端口

port 6379

# pid 文件和 log 文件的保存地址

pidfile /opt/redis_cluster/redis_6379/pid/redis_6379.pid

logfile /opt/redis_cluster/redis_6379/logs/redis_6379.log

# 设置数据库的数量,默认数据库为 0

databases 16

# 指定本地持久化文件的文件名,默认是 dump.rdb

dbfilename redis_6379.rdb

# 本地数据库的目录

dir /data/redis_cluster/redis_6379

2.4 服务端启动


redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

查看是否启动成功:


[root@VM-0-3-centos conf]# ps -ef | grep redis

root 6763 1 0 10:53 ? 00:00:00 /usr/local/bin/redis-server 127.0.0.1:6379

root 10626 1 0 11:12 ? 00:00:00 redis-server 172.21.0.3:6379

root 11440 27666 0 11:14 pts/0 00:00:00 grep --color=auto redis

2.5 服务端关闭


[root@VM-0-3-centos conf]# redis-cli

127.0.0.1:6379> SHUTDOWN

3. Redis 基本操作命令

建立连接:


redis-cli -h 172.21.0.3

3.1 全局命令

  1. 查看所有键(非常危险!!!禁止线上使用)

KEYS *

  1. 查看键的总数(dbsize 命令在计算键总数时不会遍历所有键,而是直接获取 redis 内置的键总数数量)

DBSIZE

  1. 检查键是否存在(如果存在则返回 1,不存在则返回 0)

EXISTS key

  1. 删除 key

DEL key

  1. 键过期

  2. redis 支持对键添加过期时间,当超过过期时间后,会自动删除键。

  3. 通过 ttl 命令观察键的剩余时间

  4. >=0:键剩余过期时间

  5. -1:键没设置过期时间

  6. -2:键不存在

  7. 注意:key 设置过期时间后,再 set key,key 就重新设置了,变成了永不过期。比如优惠券功能,执行此操作后,优惠券永不过期。


EXPIRE key seconds

TTL key

  1. 去除过期时间

PERSIST key

  1. 键的数据类型

TYPE key

3.2 字符串

  1. 设置 key

172.21.0.3:6379> SET key1 value

OK

  1. 获取 key

172.21.0.3:6379> GET key1

"value"

  1. 批量设置 key

172.21.0.3:6379> MSET key2 value2 key3 value3

OK

  1. 批量获取 key

172.21.0.3:6379> MGET key1 key2 key3

1) "value"

2) "value2"

3) "value3"

  1. 值自增、自减(INCR 命令将字符串解析)

172.21.0.3:6379> INCR k1

(integer) 101

172.21.0.3:6379> INCRBY k1 10

(integer) 111

172.21.0.3:6379> DECR k1

(integer) 110

172.21.0.3:6379> DECRBY k1 10

(integer) 100

3.3 列表

  1. 在 list 的左边(头部)添加一个新元素 lpush

172.21.0.3:6379> LPUSH list1 A

(integer) 1

172.21.0.3:6379> LPUSH list1 B

(integer) 2

172.21.0.3:6379> TYPE list1

list

  1. 在 list 的右边(尾部)添加一个新元素 rpush

172.21.0.3:6379> RPUSH list1 C

(integer) 3

172.21.0.3:6379> RPUSH list1 D

(integer) 4

  1. 查看列表长度

172.21.0.3:6379> LLEN list1

(integer) 4

  1. 查看数据

172.21.0.3:6379> LRANGE list1 0 -1

1) "B"

2) "A"

3) "C"

4) "D"


172.21.0.3:6379> LRANGE list1 0 1

1) "B"

2) "A"

  1. 从右侧删除数据

172.21.0.3:6379> RPOP list1

"D"

172.21.0.3:6379> LRANGE list1 0 -1

1) "B"

2) "A"

3) "C"

  1. 从左侧删除数据

172.21.0.3:6379> LPOP list1

"B"

172.21.0.3:6379> LRANGE list1 0 -1

1) "A"

2) "C"

3.4 哈希

  1. 设置 hash 类型值

172.21.0.3:6379> HMSET user1 name wys age 20

OK

  1. 获取哈希值中某个字段

172.21.0.3:6379> HGET user1 name

"wys"

  1. 获取哈希值所有字段

172.21.0.3:6379> hgetall user1

1) "name"

2) "wys"

3) "age"

4) "20"

3.5 集合

  1. 集合添加元素(和 list 不同的是,集合不允许出现 重复数据)

172.21.0.3:6379> SADD set1 3 4 5 5

(integer) 3

  1. 获取集合元素

172.21.0.3:6379> SMEMBERS set1

1) "3"

2) "4"

3) "5"

  1. 集合中删除指定值

172.21.0.3:6379> SREM set1 5

(integer) 1

172.21.0.3:6379> SMEMBERS set1

1) "3"

2) "4"

  1. 计算集合差异成员(第二个集合没有的)

172.21.0.3:6379> SADD set2 3 5 6

(integer) 3

172.21.0.3:6379> SADD set3 5 7

(integer) 2

172.21.0.3:6379> SDIFF set2 set3

1) "3"

2) "6"

  1. 计算集合交集(两个集合都有的)

172.21.0.3:6379> SINTER set2 set3

1) "5"

  1. 计算集合并集(多个集合所有的)

172.21.0.3:6379> SUNION set2 set3

1) "3"

2) "5"

3) "6"

4) "7"

4. redis 持久化

4.1 RDB

RDB 持久化是把当前进程数据生成快照保存到磁盘的过程,触发 RDB 持久化过程分为手动触发和自动触发。

4.1.1 手动触发

  1. 数据插入:

172.21.0.3:6379> set k1 v1

OK

  1. 手动保存 RDB 文件

172.21.0.3:6379> BGSAVE

Background saving started

  1. 我们可以在我们的数据目录看到持久化文件:

cd /data/redis_cluster/redis_6379

ll

总用量 4

-rw-r--r-- 1 root root 88 3月 8 15:34 redis_6379.rdb

  1. 关闭 redis

172.21.0.3:6379> SHUTDOWN

not connected> exit

  1. 启动 redis 并连接

redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

redis-cli -h 172.21.0.3

  1. 测试数据

172.21.0.3:6379> keys *

1) "k1"

注:redis 中如果检测数据目录有 .rdb 文件,下次启动时就会使用该文件恢复数据。

4.1.2 自动触发

在配置文件中加入如下内容:


# 每 900 秒(15 分钟)如果有 1 个更新则执行 bgsave

save 900 1

# 每 300 秒(5 分钟)如果有 10 个更新则执行 bgsave

save 300 10

# 每 60 秒(5 分钟)如果有 10000 个更新则执行 bgsave

save 60 10000

4.1.3 优点

  1. 可用于备份恢复。RDB 是一个紧凑压缩的二进制文件,代表 redis 在某个时间点上的数据快照。非常适用于备份、全量恢复等场景,比如每 6 小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器,用于灾难恢复。

  2. 恢复速度快。redis 加载 RDB 恢复数据远远快于 AOF 方式

4.1.4 缺点

  1. 没有办法做到实时持久化/秒级持久化。因为 bgsave 每次运行都要执行 fork 操作创建子进程,数据重量级操作,执行频繁成本过高。

  2. RDB 文件格式问题,在不同版本之间可能不通用。RDB 文件使用特定二进制格式保存,redis 版本演进过程中有多个格式的 RDB 版本,存在老版本 redis 不兼容新版 Redis 格式的问题。

4.1.5 特殊点

  1. 当你执行 SHUTDOWN 命令,实际相当于执行了两条命令,相当于执行了两条命令,bgsave 命令和 关闭命令,这也就是为什么,当你执行 SHUTDOWN 的时候,我们其实没有 bgsave 命令,但是系统帮你执行了,所以数据仍然不会丢。

  2. 执行 kill 操作也是如此。kill 可以理解为正常的退出,执行完当前未做完的才退出。

  3. 执行 kill -9 会丢数据。不管你在做什么都直接 kill 掉。

4.2 AOF

AOF 存储模式会记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。

AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。

4.2.1 配置文件

在配置文件中添加如下内容,注意:两个存储方式可以共存。


# 是否打开 AOF 日志功能

appendonly yes

# 每 1 个命令,都立即同步到 AOF

appendfsync always

# 每秒写 1 次

appendfsync everysec

# 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到 AOF

appendfsync no

# 指定 AOF 文件名称

appendfilename "appendonly.aof"

4.2.2 工作流程

  • 存储所有命令,把所有命令追加到文件后

  • 当文件过大的时候,redis 内部有算法,会将部分命令删除

  1. set k1 v1

  2. set k2 v2

  3. del k1

  4. get k2

内部删除命令会将 1、3、4 条记录都会删除。

4.2.3 优点

  • 可以最大程度保证数据不丢

4.2.4 缺点

  • 使用 AOF 恢复数据速度较慢

  • 日志记录量级较大

4.2.5 特殊点

  • 当 RDB 文件和 AOF 文件同时存在时,会使用 AOF 文件来恢复数据。

具体测试方法是:

  1. 把旧的 RDB 文件复制出来。

  2. 写数据

  3. 执行 SHUTDOWN 命令

  4. 使用旧的 RDB 文件覆盖新的 RDB 文件。

  5. 重启 redis

  6. 查看 redis 中是否有新插入的数据。有则说明是使用 AOF 恢复数据,没有则说明使用 RDB 恢复数据。

4.3 面试题

  1. redis 持久化方式有哪些,有什么区别?

  2. 有两种方式,分别为 RDB 和 AOF。

  3. RDB 恢复数据较快,AOF 恢复数据较慢。

  4. RDB 不实时,AOF 实时,但是仍然可能丢失部分数据,最大程度上保证数据不丢。

  5. AOF 日志量较大,存储的是命令,相当于 mysql 的 binlog。

  6. AOF 格式通用,RDB 文件可能存在不同的版本无法识别问题。

  7. 在 RDB 持久化方式中,执行 SHUTDOWN 后,为什么数据没有丢呢?

  8. 执行 SHUTDOWN 相当于执行了两条命令,bgsave 命令和关闭命令。因此对数据进行了持久化操作。

  9. kill 也是同理。

  10. kill -9 则会丢数据,相当于立即退出。

  11. 在 RDB 和 AOF 两种持久化方式都存在的时候,使用哪种方式恢复数据?

  12. 优先使用 AOF 恢复数据,可以测试。

5. Redis 安全模式

redis 默认开启了保护模式,只允许本地会话地址登录并访问数据库。

禁止 protected-mode


# 保护模式,是否只允许本地访问

protected-mode yes/no

  1. Bind:指定 IP 进行监听

vim /opt/redis_cluster/redis_6379/conf/redis_6379.conf

bind 172.21.0.3 127.0.0.1

  1. 增加 requirepass {password}

vim /opt/redis_cluster/redis_6379/conf/redis_6379.conf

requirepass 123456

  1. 密码测试验证

[root@VM-0-3-centos conf]# redis-cli -h 172.21.0.3

172.21.0.3:6379> set k1 v1

(error) NOAUTH Authentication required.

172.21.0.3:6379> auth 123456

OK

172.21.0.3:6379> set k1 v1

OK

172.21.0.3:6379> get k1

"v1"

6. Redis 主从复制

6.1 主从复制环境搭建

Redis 主从复制可以在两台机器上实验,因为我只有一台机器,所以我在单机上搭建 Redis 主从复制了。


mkdir -p /home/service/

mkdir -p /home/work/

mkdir -p /home/work/redis_cluster/redis_6380

mkdir -p /home/service/redis_cluster/redis_6380/{conf,pid,logs}

cp /opt/redis_cluster/redis_6379/conf/redis_6379.conf /home/service/redis_cluster/redis_6380/conf/redis_6380.conf

/home/service/redis_cluster/redis_6380/conf/redis_6380.conf 配置文件内容如下:


# 以守护进程模式启动

daemonize yes

# 绑定的主机地址(一般设置内网地址,不要设置外网地址,外网地址大家都可以访问了)

bind 172.21.0.3

# 监听端口

port 6380

# pid 文件和 log 文件的保存地址

pidfile /home/service/redis_cluster/redis_6380/pid/redis_6380.pid

logfile /home/service/redis_cluster/redis_6380/logs/redis_6380.log

# 设置数据库的数量,默认数据库为 0

databases 16

# 指定本地持久化文件的文件名,默认是 dump.rdb

dbfilename redis_6380.rdb

save 900 1

save 300 10

save 60 10000

# 本地数据库的目录

dir /home/work/redis_cluster/redis_6380

# 是否打开 AOF 日志功能

#appendonly yes

# 每 1 个命令,都立即同步到 AOF

#appendfsync always

# 每秒写 1 次

#appendfsync everysec

# 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到 AOF

#appendfsync no

# 指定 AOF 文件名称

#appendfilename "appendonly.aof"

6.2 测试主从复制

  1. 登录主库,端口为 6379

redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

redis-cli -h 172.21.0.3 -p 6379

  1. 登录从库,端口为 6380

redis-server /home/service/redis_cluster/redis_6380/conf/redis_6380.conf

redis-cli -h 172.21.0.3 -p 6380

  1. 从库查询数据

172.21.0.3:6380> get k1

(nil)

  1. 实现主从复制(从库执行)

172.21.0.3:6380> SLAVEOF 172.21.0.3 6379

OK

  1. 主库插入数据

172.21.0.3:6379> set k1 v1

OK

  1. 从库读取数据

172.21.0.3:6380> get k1

"v1"

  1. 从库插入数据(无法插入)

172.21.0.3:6380> set k2 v2

(error) READONLY You can't write against a read only slave.

  1. 断开主从复制(从节点断开复制后不会抛弃原有数据,只是无法再获取主节点上的数据变化)

172.21.0.3:6380> slaveof no one

OK

  1. 断开主从复制后,从库插入数据

172.21.0.3:6380> set k3 v3

OK

6.3 主从复制日志查看

6.3.1 主库日志


cd /opt/redis_cluster/redis_6379/logs/

tail redis_6379.log


7649:M 08 Mar 17:45:36.277 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.

7649:M 08 Mar 17:45:36.277 * The server is now ready to accept connections on port 6379

7649:M 08 Mar 17:46:57.983 * Slave 172.21.0.3:6380 asks for synchronization

7649:M 08 Mar 17:46:57.983 * Full resync requested by slave 172.21.0.3:6380

7649:M 08 Mar 17:46:57.983 * Starting BGSAVE for SYNC with target: disk

7649:M 08 Mar 17:46:57.983 * Background saving started by pid 9619

9619:C 08 Mar 17:46:58.043 * DB saved on disk

9619:C 08 Mar 17:46:58.043 * RDB: 0 MB of memory used by copy-on-write

7649:M 08 Mar 17:46:58.088 * Background saving terminated with success

7649:M 08 Mar 17:46:58.088 * Synchronization with slave 172.21.0.3:6380 succeeded

  • Full resync requested by slave 172.21.0.3:6380:表示主从建立连接成功

  • Starting BGSAVE for SYNC with target: disk:表示开始持久化数据到磁盘

  • Background saving started by pid 9619:表示新开一个进程持久化数据

  • DB saved on disk:表示数据持久化磁盘

  • Background saving terminated with success:表示持久化数据成功

  • Synchronization with slave 172.21.0.3:6380 succeeded:表示同步数据到从库成功

6.3.2 从库日志


cd /home/service/redis_cluster/redis_6380/logs/

tail redis_6380.log


8206:S 08 Mar 17:46:57.982 * Connecting to MASTER 172.21.0.3:6379

8206:S 08 Mar 17:46:57.982 * MASTER <-> SLAVE sync started

8206:S 08 Mar 17:46:57.982 * Non blocking connect for SYNC fired the event.

8206:S 08 Mar 17:46:57.982 * Master replied to PING, replication can continue...

8206:S 08 Mar 17:46:57.982 * Partial resynchronization not possible (no cached master)

8206:S 08 Mar 17:46:57.983 * Full resync from master: a2fc4c71b2f3803e0c7f11f68a124396981e3454:1

8206:S 08 Mar 17:46:58.088 * MASTER <-> SLAVE sync: receiving 76 bytes from master

8206:S 08 Mar 17:46:58.088 * MASTER <-> SLAVE sync: Flushing old data

8206:S 08 Mar 17:46:58.088 * MASTER <-> SLAVE sync: Loading DB in memory

8206:S 08 Mar 17:46:58.088 * MASTER <-> SLAVE sync: Finished with success

  • Connecting to MASTER 172.21.0.3:6379:表示与主库建立连接

  • MASTER <-> SLAVE sync started:表示开启主从同步

  • MASTER <-> SLAVE sync: receiving 76 bytes from master:表示从库接收了 76 字节从主库,76 字节表示 RDB 文件的大小。

  • MASTER <-> SLAVE sync: Flushing old data:表示清空老数据,即从库所有的数据。(以前的数据也会被覆盖,可以测试。从库之前有数据,执行完主从复制后,数据便没有了)

  • MASTER <-> SLAVE sync: Loading DB in memory:表示加载主库发送过来的数据到内存里。

  • MASTER <-> SLAVE sync: Finished with success:表示主从同步成功。

6.4 主从同步流程

  1. 从库发送同步请求

  2. 主库收到请求后执行 bgsave 保存当前内存里的数据到磁盘

  3. 主库将持久化的数据发送给从库的数据目录

  4. 从库收到主库的持久化数据之后,先清空自己当前内存中的所有数据

  5. 从库将主库发送过来的持久化文件加载到自己的内存里。

6.5 结论

  1. 执行主从复制之前,先将数据备份一份

  2. 建议将主从复制写入到配置文件中

  3. 在业务低峰期做主从复制

  4. 拷贝数据时会占用带宽

  5. 不能自动完成主从切换,需要人工介入

7. 哨兵(Sentinel)

7.1 哨兵介绍

在 Redis 的主从模式下,主节点一旦发生故障不能提供服务,需要人工干预,将从节点晋升为主节点,同时还需要修改客户端配置。对于很多应用场景这种方式无法接受。

Sentinel(哨兵)架构解决了 Redis 主从人工干预的问题。

Redis Sentinel 是 Redis 的高可用实现方案,实际生产环境中,对提高整个系统可用性非常有帮助。

7.2 哨兵主要功能

Redis Sentinel 是一个分布式系统,Redis Sentinel 为 Redis 提供高可用性。可以在没有人为干预的情况下进行自动故障转移。

Redis Sentinel 系统用于管理多个 Redis 服务器(instance)。该系统有以下三个功能:

  1. 监控(Monitoring)

Sentinel 会不断的定期检查你的主服务器和从服务器是否运作正常。

  1. 提醒(Notification)

当被监控的某个 Redis 服务器出现问题时,Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

  1. 自动故障转移(Automatic Failover)

当一个主服务器不能正常工作时,Sentinel 会开始一次自动故障转移操作,它会将失效主服务器的齐总一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端视图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址,使得集群可以使用新主服务器代替失效服务器。

Redis Sentinel 架构如下:

3391e2c4861977dfd41f8a5c2ec52516.png

当 Master 挂了之后,我们的切换步骤是选择一个从库退出主从复制,并将另一个从数据库改为复制新的主库。


从库1:slaveof no one

从库2:slaveof 从库1 从库1_port

哨兵是一个特殊的节点,每个节点都可以监测到。

7.3 目录规划

角色IP端口
Master172.21.0.36379
Slave1172.21.0.36380
Slave2172.21.0.36381
Sentinel-01172.21.0.326379
Sentinel-02172.21.0.326380
Sentinel-03172.21.0.326381

7.4 安装配置命令

哨兵模式是基于主从复制的,所以要先部署好主从复制。

因为我们是在一台机器上部署的,所以我们再创建两个类似目录,只是端口号不一致。

7.4.1 创建配置文件


mkdir -p /opt/redis_cluster/redis_6380/

mkdir -p /opt/redis_cluster/redis_6381/

cp -r /opt/redis_cluster/redis_6379/* /opt/redis_cluster/redis_6380/

cp -r /opt/redis_cluster/redis_6379/* /opt/redis_cluster/redis_6381/

rm /opt/redis_cluster/redis_6380/logs/redis_6379.log

rm /opt/redis_cluster/redis_6381/logs/redis_6379.log

rm /opt/redis_cluster/redis_6380/pid/redis_6379.pid

rm /opt/redis_cluster/redis_6381/pid/redis_6379.pid

mv /opt/redis_cluster/redis_6380/conf/redis_6379.conf /opt/redis_cluster/redis_6380/conf/redis_6380.conf

mv /opt/redis_cluster/redis_6381/conf/redis_6379.conf /opt/redis_cluster/redis_6381/conf/redis_6381.conf

vim /opt/redis_cluster/redis_6380/conf/redis_6380.conf

:%s/6379/6380/g

vim /opt/redis_cluster/redis_6381/conf/redis_6381.conf

:%s/6379/6381/g

mkdir -p /data/redis_cluster/redis_6380/

mkdir -p /data/redis_cluster/redis_6381/

哨兵使用的端口号前面加个 2,也就是 26379、26380、26381。我们来创建相应目录。


mkdir -p /data/redis_cluster/redis_26379

mkdir -p /data/redis_cluster/redis_26380

mkdir -p /data/redis_cluster/redis_26381

mkdir -p /opt/redis_cluster/redis_26379/{conf,pid,logs}

mkdir -p /opt/redis_cluster/redis_26380/{conf,pid,logs}

mkdir -p /opt/redis_cluster/redis_26381/{conf,pid,logs}

修改配置文件,哨兵的配置文件和普通的 redis 配置文件类似,只不过增加了几行配置。


vim /opt/redis_cluster/redis_26379/conf/redis_26379.conf

配置文件内容如下:


bind 172.21.0.3

port 26379

daemonize yes

logfile /opt/redis_cluster/redis_26379/logs/redis_26379.log

dir /data/redis_cluster/redis_26379

sentinel monitor mymaster 172.21.0.3 6379 2

sentinel down-after-milliseconds mymaster 3000

sentinel parallel-syncs mymaster 1

sentinel failover-timeout mymaster 18000

我们再 copy 出两份配置文件,并修改。


cp /opt/redis_cluster/redis_26379/conf/redis_26379.conf /opt/redis_cluster/redis_26380/conf/redis_26380.conf

cp /opt/redis_cluster/redis_26379/conf/redis_26379.conf /opt/redis_cluster/redis_26381/conf/redis_26381.conf

vim /opt/redis_cluster/redis_26380/conf/redis_26380.conf

:%s/26379/26380/g

vim /opt/redis_cluster/redis_26381/conf/redis_26381.conf

:%s/26379/26381/g

7.4.2 配置文件解释

  • sentinel monitor mymaster 172.21.0.3 6379 2:mymaster 主节点别名,主节点 IP 和端口,2 表示判断主节点失败,需要两个 Sentinel 节点同意

  • sentinel down-after-milliseconds mymaster 3000:该配置指定了 Sentinel 认为服务器已经断线所需的毫秒数

  • sentinel parallel-syncs mymaster 1:表示向新的主节点发起复制操作的从节点个数,1 表示轮询发起复制。如下图(右)所示

  • sentinel failover-timeout mymaster 18000:表示故障转移超时时间。

image.png

7.4.3 配置主从


vim /opt/redis_cluster/redis_6380/conf/redis_6380.conf

slaveof 172.21.0.3 6379

vim /opt/redis_cluster/redis_6381/conf/redis_6381.conf

7.4.4 启动三个实例


redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf

redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf


[root@VM-0-3-centos conf]# ps -ef | grep redis

root 3010 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6379

root 3014 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6380

root 3019 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6381


172.21.0.3:6379> set test1 hhh

OK

172.21.0.3:6380> get test1

"hhh"

172.21.0.3:6381> get test1

"hhh"

172.21.0.3:6380> set test2 hhh

(error) READONLY You can't write against a read only slave.

172.21.0.3:6381> set test2 hhh

(error) READONLY You can't write against a read only slave.

7.4.5 启动哨兵


redis-sentinel /opt/redis_cluster/redis_26379/conf/redis_26379.conf

redis-sentinel /opt/redis_cluster/redis_26380/conf/redis_26380.conf

redis-sentinel /opt/redis_cluster/redis_26381/conf/redis_26381.conf


[root@VM-0-3-centos conf]# ps -ef | grep redis

root 3010 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6379

root 3014 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6380

root 3019 1 0 11:05 ? 00:00:00 redis-server 172.21.0.3:6381

root 3138 3054 0 11:07 pts/1 00:00:00 redis-cli -h 172.21.0.3 -p 6380

root 3139 3096 0 11:07 pts/2 00:00:00 redis-cli -h 172.21.0.3 -p 6381

root 3190 1 0 11:09 ? 00:00:00 redis-sentinel 172.21.0.3:26379 [sentinel]

root 3200 1 0 11:09 ? 00:00:00 redis-sentinel 172.21.0.3:26380 [sentinel]

root 3204 1 0 11:09 ? 00:00:00 redis-sentinel 172.21.0.3:26381 [sentinel]

root 3218 1383 0 11:10 pts/0 00:00:00 grep --color=auto redis

7.4.6 查看哨兵配置文件变化


bind 172.21.0.3

port 26379

daemonize yes

logfile "/opt/redis_cluster/redis_26379/logs/redis_26379.log"

dir "/data/redis_cluster/redis_26379"

sentinel myid 341cd7783bbaac95262ddb07083c52acf4d3d700

sentinel monitor mymaster 172.21.0.3 6379 2

sentinel down-after-milliseconds mymaster 3000

sentinel failover-timeout mymaster 18000

# Generated by CONFIG REWRITE

sentinel config-epoch mymaster 0

sentinel leader-epoch mymaster 0

sentinel known-slave mymaster 172.21.0.3 6381

sentinel known-slave mymaster 172.21.0.3 6380

sentinel known-sentinel mymaster 172.21.0.3 26381 caf9ec5e1bfc9148377d58fddd962943a6e08da9

sentinel known-sentinel mymaster 172.21.0.3 26380 c9771fe6db4884e2a77c2ffa39805e42621cc9e3

sentinel current-epoch 0


bind 172.21.0.3

port 26380

daemonize yes

logfile "/opt/redis_cluster/redis_26380/logs/redis_26380.log"

dir "/data/redis_cluster/redis_26380"

sentinel myid c9771fe6db4884e2a77c2ffa39805e42621cc9e3

sentinel monitor mymaster 172.21.0.3 6379 2

sentinel down-after-milliseconds mymaster 3000

sentinel failover-timeout mymaster 18000

# Generated by CONFIG REWRITE

sentinel config-epoch mymaster 0

sentinel leader-epoch mymaster 0

sentinel known-slave mymaster 172.21.0.3 6381

sentinel known-slave mymaster 172.21.0.3 6380

sentinel known-sentinel mymaster 172.21.0.3 26381 caf9ec5e1bfc9148377d58fddd962943a6e08da9

sentinel known-sentinel mymaster 172.21.0.3 26379 341cd7783bbaac95262ddb07083c52acf4d3d700

sentinel current-epoch 0


bind 172.21.0.3

port 26381

daemonize yes

logfile "/opt/redis_cluster/redis_26381/logs/redis_26381.log"

dir "/data/redis_cluster/redis_26381"

sentinel myid caf9ec5e1bfc9148377d58fddd962943a6e08da9

sentinel monitor mymaster 172.21.0.3 6379 2

sentinel down-after-milliseconds mymaster 3000

sentinel failover-timeout mymaster 18000

# Generated by CONFIG REWRITE

sentinel config-epoch mymaster 0

sentinel leader-epoch mymaster 0

sentinel known-slave mymaster 172.21.0.3 6381

sentinel known-slave mymaster 172.21.0.3 6380

sentinel known-sentinel mymaster 172.21.0.3 26379 341cd7783bbaac95262ddb07083c52acf4d3d700

sentinel known-sentinel mymaster 172.21.0.3 26380 c9771fe6db4884e2a77c2ffa39805e42621cc9e3

sentinel current-epoch 0

当所有节点启动后,配置文件的内容发生了变化,体现在三个方面:

  1. Sentinel 节点自动发现了从节点,其余 Sentinel 节点。

  2. 去掉了默认配置,例如 parallel-syncs failover-timeout 参数。

  3. 添加了配置纪元相关参数

7.4.6 查看哨兵日志文件


3190:X 09 Mar 11:09:39.815 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

3190:X 09 Mar 11:09:39.826 # Sentinel ID is 341cd7783bbaac95262ddb07083c52acf4d3d700

3190:X 09 Mar 11:09:39.826 # +monitor master mymaster 172.21.0.3 6379 quorum 2

3190:X 09 Mar 11:09:39.827 * +slave slave 172.21.0.3:6380 172.21.0.3 6380 @ mymaster 172.21.0.3 6379

3190:X 09 Mar 11:09:39.834 * +slave slave 172.21.0.3:6381 172.21.0.3 6381 @ mymaster 172.21.0.3 6379

3190:X 09 Mar 11:09:51.400 * +sentinel sentinel c9771fe6db4884e2a77c2ffa39805e42621cc9e3 172.21.0.3 26380 @ mymaster 172.21.0.3 6379

3190:X 09 Mar 11:09:56.620 * +sentinel sentinel caf9ec5e1bfc9148377d58fddd962943a6e08da9 172.21.0.3 26381 @ mymaster 172.21.0.3 6379

7.5 哨兵常用 API

  1. 登录

redis-cli -h 172.21.0.3 -p 26379

  1. 查看哨兵信息

172.21.0.3:26379> INFO SENTINEL

# Sentinel

sentinel_masters:1

sentinel_tilt:0

sentinel_running_scripts:0

sentinel_scripts_queue_length:0

sentinel_simulate_failure_flags:0

master0:name=mymaster,status=ok,address=172.21.0.3:6379,slaves=2,sentinels=3

  1. 查看 master 信息

172.21.0.3:26379> Sentinel masters

1) 1) "name"

2) "mymaster"

3) "ip"

4) "172.21.0.3"

5) "port"

6) "6379"

7) "runid"

8) "134a225eb4509100ab06f9edd6429909c6f2ee22"

9) "flags"

10) "master"

11) "link-pending-commands"

12) "0"

13) "link-refcount"

14) "1"

15) "last-ping-sent"

16) "0"

17) "last-ok-ping-reply"

18) "500"

19) "last-ping-reply"

20) "500"

21) "down-after-milliseconds"

22) "3000"

23) "info-refresh"

24) "2358"

25) "role-reported"

26) "master"

27) "role-reported-time"

28) "785446"

29) "config-epoch"

30) "0"

31) "num-slaves"

32) "2"

33) "num-other-sentinels"

34) "2"

35) "quorum"

36) "2"

37) "failover-timeout"

38) "18000"

39) "parallel-syncs"

40) "1"

  1. 获取单个 master 信息

172.21.0.3:26379> Sentinel master mymaster

  1. 查看 slave 信息

172.21.0.3:26379> Sentinel slaves mymaster

  1. 查看其它哨兵

Sentinel sentinels mymaster

  1. 查看 master IP 地址(通过该条命令,客户端可以知道连接哪个 IP 端口了)

172.21.0.3:26379> Sentinel get-master-addr-by-name mymaster

1) "172.21.0.3"

2) "6379"

  1. 查看 Failover 状态

172.21.0.3:26379> Sentinel failover mymaster

OK

  1. 更新配置

172.21.0.3:26379> Sentinel flushconfig

OK

7.6 模拟故障转移

正常情况下,redis 哨兵模式一主二从应该部署在三台机器上,一一对应。假设出现故障,应该是一台机器都发生故障。

那么,我们将 2679、26379 的进程都 kill 掉。

通过观察 26380 的日志我们可以看到,已经发生了自动故障转移。master 现在为 6381 端口的进程。


3200:X 09 Mar 11:43:27.553 # +sdown slave 172.21.0.3:6379 172.21.0.3 6379 @ mymaster 172.21.0.3 6381

3200:X 09 Mar 11:43:27.554 # +sdown sentinel 341cd7783bbaac95262ddb07083c52acf4d3d700 172.21.0.3 26379 @ mymaster 172.21.0.3 6381

我们再来看看 Sentinel 配置。


bind 172.21.0.3

port 26380

daemonize yes

logfile "/opt/redis_cluster/redis_26380/logs/redis_26380.log"

dir "/data/redis_cluster/redis_26380"

sentinel myid c9771fe6db4884e2a77c2ffa39805e42621cc9e3

sentinel monitor mymaster 172.21.0.3 6381 2

sentinel down-after-milliseconds mymaster 3000

sentinel failover-timeout mymaster 18000

# Generated by CONFIG REWRITE

sentinel config-epoch mymaster 1

sentinel leader-epoch mymaster 0

sentinel known-slave mymaster 172.21.0.3 6379

sentinel known-slave mymaster 172.21.0.3 6380

sentinel known-sentinel mymaster 172.21.0.3 26381 caf9ec5e1bfc9148377d58fddd962943a6e08da9

sentinel known-sentinel mymaster 172.21.0.3 26379 341cd7783bbaac95262ddb07083c52acf4d3d700

sentinel current-epoch 1

可以看到现在 master 节点 PORT 已经换了。

我们再次查询验证一下:


172.21.0.3:26380> Sentinel get-master-addr-by-name mymaster

1) "172.21.0.3"

2) "6381"

同时我们也可以分别登陆 redis 节点查看一下主节点是否已经切换。


172.21.0.3:6381> set ggg hhh

OK

172.21.0.3:6380> set lll hhh

(error) READONLY You can't write against a read only slave.

172.21.0.3:6380> get ggg

"hhh"

可以看到故障转移已经成功。

我们再来启动一下出故障的节点。


redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

redis-sentinel /opt/redis_cluster/redis_26379/conf/redis_26379.conf

可以看到 Sentinel 日志已经发生变化了,新节点已经加进来了,当做一个从节点,并执行了主从复制。


3200:X 09 Mar 11:54:48.331 * +reboot slave 172.21.0.3:6379 172.21.0.3 6379 @ mymaster 172.21.0.3 6381

3200:X 09 Mar 11:54:48.383 # -sdown slave 172.21.0.3:6379 172.21.0.3 6379 @ mymaster 172.21.0.3 6381

3200:X 09 Mar 11:55:08.014 # -sdown sentinel 341cd7783bbaac95262ddb07083c52acf4d3d700 172.21.0.3 26379 @ mymaster 172.21.0.3 6381

7.7 故障转移流程

image.png

会选择 ID 最大的节点,会默认两个节点的数据是一样的。

当我们想要强制选主。我们可以设置权重。

每个节点的权重默认是一样的。

Redis Sentinel 存在多个从节点时,如果想将指定的从节点晋升为主节点,可以将其他从节点的slavepriority 配置为 0,但是需要注意 failover 后,将 slave-priority 调回原值。

有两种方法,一种是将想要强制选主的节点调高。另一种是将其他节点调低。

  1. 查看权重

172.21.0.3:6379> CONFIG GET slave-priority

1) "slave-priority"

2) "100"

  1. 修改权重(我想要 6379 端口当主节点,其他端口权重设置为 0,6379 端口不需要修改)

172.21.0.3:6380> CONFIG SET slave-priority 0

OK

172.21.0.3:6381> CONFIG SET slave-priority 0

OK

  1. 强制发生故障转移(在哨兵节点执行)

172.21.0.3:26379> sentinel failover mymaster

OK

  1. 查看此时的主节点

172.21.0.3:26379> Sentinel get-master-addr-by-name mymaster

1) "172.21.0.3"

2) "6379"

  1. 其他两节点权重恢复回去,设置成 100,保证下次可以正常自动故障转移。

172.21.0.3:6381> CONFIG SET slave-priority 100

OK

172.21.0.3:6380> CONFIG SET slave-priority 100

OK

8. Redis Cluster

8.1 集群介绍

Redis Cluster 是 redis 的分布式解决方案,在 3.0 版本正式推出。

当遇到单机、内存、并发、流量等瓶颈时,可以采用 Cluster 架构方案达到负载均衡目的。

Redis Cluster 之前的分布式方案有两种:

  1. 客户端分区方案:

  2. 优点:分区逻辑可控;

  3. 缺点:需要自己处理数据路由、高可用和故障转移等。

  4. 代理方案:

  5. 有点:简化客户端分布式逻辑,升级维护便利;

  6. 缺点:架构大、性能消耗大

8.2 数据分布

分布式数据库首先要解决把整个数据库集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集,需要关注的是数据分片规则,Redis Cluster 采用哈希分片规则。

8.3 目录规划

  • redis 安装目录:/opt/redis_cluster/redis_{PORT}/{conf,logs,pid}

  • redis 数据目录:/data/redis_cluster/redis_{PORT}/redis_{PORT}.rdb

  • redis 运维脚本:/root/scripts/redis_shell.sh

8.4 手动搭建部署集群

我们还是要在一台机器上搭建 Redis 集群。

实际上应该有三台机器,每台机器上有 1 主 1 从。

我们规划如下:

  • 主(6479) <-----> 从(6480)

  • 主(6481) <-----> 从(6482)

  • 主(6483) <-----> 从(6484)

  1. 创建目录

mkdir -p /opt/redis_cluster/redis_{6479,6480,6481,6482,6483,6484}/{conf,logs,pid}

mkdir –p /data/redis_cluster/redis_{6479,6480,6481,6482,6483,6484}

cat >/opt/redis_cluster/redis_6479/conf/redis_6479.conf<<EOF

bind 172.21.0.3

port 6479

daemonize yes

pidfile "/opt/redis_cluster/redis_6479/pid/redis_6479.pid"

logfile "/opt/redis_cluster/redis_6479/logs/redis_6479.log"

dbfilename "redis_6479.rdb"

dir "/data/redis_cluster/redis_6479/"

cluster-enabled yes

cluster-config-file nodes_6479.conf

cluster-node-timeout 15000

EOF

cd /opt/redis_cluster/

cp redis_6479/conf/redis_6479.conf redis_6480/conf/redis_6480.conf

sed -i 's#6479#6480#g' redis_6480/conf/redis_6480.conf

cd /opt/redis_cluster/

cp redis_6479/conf/redis_6479.conf redis_6481/conf/redis_6481.conf

sed -i 's#6479#6481#g' redis_6481/conf/redis_6481.conf

cd /opt/redis_cluster/

cp redis_6479/conf/redis_6479.conf redis_6482/conf/redis_6482.conf

sed -i 's#6479#6482#g' redis_6482/conf/redis_6482.conf

cd /opt/redis_cluster/

cp redis_6479/conf/redis_6479.conf redis_6483/conf/redis_6483.conf

sed -i 's#6479#6480#g' redis_6483/conf/redis_6483.conf

cd /opt/redis_cluster/

cp redis_6479/conf/redis_6479.conf redis_6484/conf/redis_6484.conf

sed -i 's#6479#6484#g' redis_6484/conf/redis_6484.conf

  1. 启动 redis 服务

redis-server /opt/redis_cluster/redis_6479/conf/redis_6479.conf

redis-server /opt/redis_cluster/redis_6480/conf/redis_6480.conf

redis-server /opt/redis_cluster/redis_6481/conf/redis_6481.conf

redis-server /opt/redis_cluster/redis_6482/conf/redis_6482.conf

redis-server /opt/redis_cluster/redis_6483/conf/redis_6483.conf

redis-server /opt/redis_cluster/redis_6484/conf/redis_6484.conf

8.4.1 配置文件解读


# 打开集群模式

cluster-enabled yes

# 集群的配置文件(在数据目录位置)

cluster-config-file nodes_6479.conf

# 集群超时时间

cluster-node-timeout 15000


cd /data/redis_cluster/redis_6479

ll

cat nodes_6479.conf

514ca73ae546b1a71e055c63b08caa06b53ff35e :0 myself,master - 0 0 0 connected

vars currentEpoch 0 lastVoteEpoch 0

8.4.2 集群命令

  1. 查看集群节点(节点信息同 nodes.conf 文件中内容一样)

172.21.0.3:6479> CLUSTER NODES

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 :6479 myself,master - 0 0 0 connected

  1. 发现集群节点

172.21.0.3:6479> CLUSTER MEET 172.21.0.3 6481

OK

172.21.0.3:6479> CLUSTER NODES

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 master - 0 1615289900344 1 connected

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 myself,master - 0 0 0 connected

  1. 其他节点加入到两个节点组成的集群中

172.21.0.3:6483> CLUSTER NODES

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 :6483 myself,master - 0 0 0 connected

172.21.0.3:6483> CLUSTER MEET 172.21.0.3 6479

OK

172.21.0.3:6483> CLUSTER NODES

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 master - 0 1615289968089 1 connected

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 master - 0 1615289968901 0 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 myself,master - 0 0 2 connected

8.4.3 手动配置节点发现

当我们把所有的节点都启动后,查看进程我们会发现,所有的进程都有 cluster 的字样:


root 15524 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6479 [cluster]

root 15526 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6480 [cluster]

root 15528 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6481 [cluster]

root 15532 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6482 [cluster]

root 15536 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6483 [cluster]

root 15544 1 0 19:37 ? 00:00:00 redis-server 172.21.0.3:6484 [cluster]

root 15556 14740 0 19:37 pts/0 00:00:00 grep --color=auto redis

但是当我们连接到 redis 服务器,并执行 CLUSTER NODES 命令之后,我们就会发现之后每个节点自己的 ID,目前集群内的节点还没有互相发现,所以搭建 redis 集群,我们第一步要做的就是让集群内的节点互相发现。

在执行节点发现命令之前我们先查看一下集群的数据目录会发现有生成集群的配置文件。

查看后发现只有自己的节点内容,等节点全部发现后会把所发现的节点 ID 写入这个文件。

集群模式的 Redis 除了原有的配置文件之外又加了一份集群配置文件。当集群内节点信息发生变化,如添加节点、节点下线、故障转移等。节点会自动保存集群状态到配置文件。需要注意的是,Redis 自动维护集群配置文件,不需要手动修改,防止节点重启时产生错乱。

提示:在集群内任意一台机器执行节点发现命令都可以

我们把每个节点加入到集群中:


172.21.0.3:6479> CLUSTER MEET 172.21.0.3 6481

OK

172.21.0.3:6483> CLUSTER MEET 172.21.0.3 6479

OK

172.21.0.3:6480> CLUSTER MEET 172.21.0.3 6479

OK

172.21.0.3:6482> CLUSTER MEET 172.21.0.3 6479

OK

172.21.0.3:6484> CLUSTER MEET 172.21.0.3 6479

OK

172.21.0.3:6479> CLUSTER NODES

8260b2c30b5e94f3768d5f192cfafd475988214a 172.21.0.3:6482 master - 0 1615290155164 4 connected

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 master - 0 1615290154158 1 connected

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 myself,master - 0 0 0 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 master - 0 1615290156170 2 connected

016a681b4dd4aa45b72130dbe55be37568ee3851 172.21.0.3:6480 master - 0 1615290157174 3 connected

61b33f247ba176806caf498ad7493a63d6e142de 172.21.0.3:6484 master - 0 1615290152146 5 connected

8.4.4 redis 集群启动流程图

8.4.5 Redis Cluster 通讯流程

在分布式存储中需要提供维护节点 元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障等状态。

Redis 集群采用 Gossip(流言)协议,Gossip 协议工作原理就是节点彼此不断交换信息,一段时间后所有的节点都会知道集群完整信息,这种方式类似流言传播。

其通信过程如下:

  1. 集群中的每一个节点都会单独开辟一个 TCP 通道,用于节点之间彼此通信,通信端口在基础端口上加 10000;

  2. 每个节点在固定周期内通过特定规则选择结构节点发送 ping 消息。

  3. 接收到 ping 消息的节点用 pong 消息作为相应。集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终他们会达成一致的状态。当节点出现故障、新节点加入、主从状态变化等,它能够给不断的 ping/pong 消息,从而达到同步目的。

Gossip 协议职责就是信息交换。常见的 Gossip 消息分为:ping、pong、meet、fail 等。

  • meet:用于通知新节点加入,消息发送者通知接收者加入到当前集群,meet 消息通信正常完成后,接收节点会加入到集群中并进行 ping、pong 消息交换。

  • ping:集群内交换最频繁的消息,集群内每个节点每秒向多个其它节点发送 ping 消息,用于检测节点是否在线和交换彼此信息。

  • pong:当接收到 ping、meet 消息时,作为响应消息回复给发送方确认消息正常通信,节点也可以向集群内广播自身的 pong 消息来通知整个集群对自身状态进行更新。

  • fail:当节点判定集群内另一个节点下线时,会向集群内广播一个 fail 消息,其他节点收到 fail 消息之后会把对应节点更新为下线状态。

8.4.6 手动分配槽位

虽然节点之间已经互相发现了,但是此时集群还是不可用的状态,因为并没有给节点分配槽位,而且必须所有的槽位都分配完毕后整个集群才是可用的状态。

反之,也就是说我们只要有一个槽位没有分配,那么整个集群就是不可用的。

  1. 集群中添加 key(发现无法添加)

172.21.0.3:6479> set k1 v1

(error) CLUSTERDOWN Hash slot not served

  1. 查看集群信息(cluster_state:fail 表示集群状态是 fail 的)

172.21.0.3:6479> CLUSTER INFO

cluster_state:fail

cluster_slots_assigned:0

cluster_slots_ok:0

cluster_slots_pfail:0

cluster_slots_fail:0

cluster_known_nodes:6

cluster_size:0

cluster_current_epoch:5

cluster_my_epoch:0

cluster_stats_messages_sent:21311

cluster_stats_messages_received:21311

  1. 选择分配的槽节点。前面我们已经说了,虽然我们有 6 个节点,但是真正负责数据写入的只有 3 个节点,其他 3 个节点只是作为主节点的从节点,也就是说,只需要分配其中三个节点的槽位就可以了。我们选择 6479、6481、6483。

  2. 分配槽位有两种方法(需要在每个主节点上来配置):

  3. 分别登陆到每个主节点的客户端来执行命令

  4. 在其中一台机器上用 redis 客户端远程登录到其他机器的主节点上执行命令。

  5. 节点槽数量规划:

  6. 16384/3=5461.333

  7. 因此每个节点槽数量为:{0..5461}、{5462..10922}、{10923..16383}

  8. 16384 槽需要单独分配

  9. 分配 {0..5461}、{5462..10922}、{10923..16383} 槽

  10. redis-cli -h 172.21.0.3 -p 6479 cluster addslots {0..5461}

  11. redis-cli -h 172.21.0.3 -p 6481 cluster addslots {5462..10922}

  12. redis-cli -h 172.21.0.3 -p 6483 cluster addslots {10923..16383}

  13. 查看集群信息(cluster_state:ok 表示集群状态是成功的)


172.21.0.3:6479> CLUSTER INFO

cluster_state:ok

cluster_slots_assigned:16384

cluster_slots_ok:16384

cluster_slots_pfail:0

cluster_slots_fail:0

cluster_known_nodes:6

cluster_size:3

cluster_current_epoch:5

cluster_my_epoch:0

cluster_stats_messages_sent:23763

cluster_stats_messages_received:23763

8.4.7 手动配置集群高可用

虽然这时候集群是可用的了,但是整个集群只要有一台机器坏掉了,那么整个集群都是不可用的。所以这时候需要用到其他三个节点分别作为现在三个主节点的从节点,以应对集群主节点故障时可以进行自动切换以保证集群持续可用。

注意:

  1. 不要让复制节点复制本机器的主节点,因为如果那样的话机器挂了集群还是不可用状态,所以复制节点要复制其他服务器的主节点。

  2. 使用 redis-trid 工具自动分配的时候会出现复制节点和主节点在同一台机器上的情况,需要注意。

因为我们在一台机器上做 redis 集群,那么我们就让 6480 复制 4379,6482 复制 6481,6484 复制 6483。

操作过程中,需要注意以下几点:

  1. 需要执行命令的是每个服务器的从节点;

  2. 注意主从的 ID 不要搞混了。


172.21.0.3:6480> CLUSTER NODES

8260b2c30b5e94f3768d5f192cfafd475988214a 172.21.0.3:6482 master - 0 1615341504146 4 connected

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 master - 0 1615341501126 1 connected 5462-10922

016a681b4dd4aa45b72130dbe55be37568ee3851 172.21.0.3:6480 myself,master - 0 0 3 connected

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 master - 0 1615341503139 0 connected 0-5461

61b33f247ba176806caf498ad7493a63d6e142de 172.21.0.3:6484 master - 0 1615341498100 5 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 master - 0 1615341502132 2 connected 10923-16383


redis-cli -h 172.21.0.3 -p 6480 CLUSTER REPLICATE a43fc8f84d0f887f2a41292c742ca7cd69c36b18

redis-cli -h 172.21.0.3 -p 6482 CLUSTER REPLICATE 9c7864769ea4461a150d627418f297d17763f622

redis-cli -h 172.21.0.3 -p 6484 CLUSTER REPLICATE 9aa430f83c3585ebf56f3f48cfbde8c77b9461b9

测试一下复制是否成功:


[root@VM-0-3-centos ~]# redis-cli -h 172.21.0.3 -p 6479

172.21.0.3:6479> set key1 value1

(error) MOVED 9189 172.21.0.3:6481

失败了,没关系,我们去 6481 上插入测试一下。


[root@VM-0-3-centos ~]# redis-cli -h 172.21.0.3 -p 6481

172.21.0.3:6481> set key1 value1

OK

我们去 6482 上看一下有没有这个数据。


[root@VM-0-3-centos ~]# redis-cli -h 172.21.0.3 -p 6482

172.21.0.3:6482> get key1

(error) MOVED 9189 172.21.0.3:6481

172.21.0.3:6482> keys *

1) "key1"

证明主从复制是成功的。

8.5 集群测试

我们按照之前向 redis 中写入数据的方式,向 redis 插入数据,看看会发生什么。


[root@VM-0-3-centos ~]# redis-cli -h 172.21.0.3 -p 6479

172.21.0.3:6479> set key1 value1

(error) MOVED 9189 172.21.0.3:6481

结果提示了 error,但是给出了集群的另一个节点的地址。那么这条数据到底有没有写入呢?我们分别在 6479、6481 节点查看一下。


172.21.0.3:6479> get key1

(error) MOVED 9189 172.21.0.3:6481

[root@VM-0-3-centos ~]# redis-cli -h 172.21.0.3 -p 6481

172.21.0.3:6481> get key1

(nil)

发现数据没有写入,这是为什么呢?

这是因为使用集群后由于数据被分片了,所以并不是说在 6479 上写数据,数据就会写在 6479 节点上,集群的数据写入和读取涉及到了集群的另一个概念,ASK 路由。

在集群模式下,Redis 接受任何键相关命令时首先会计算键对应的槽,再根据槽找出所对应的节点。如果节点是自身,则处理键命令;否则报出 MOVED 重定向错误,通知客户端请求正确的节点,这个过程称为 Mover 重定向。

那么这种情况我们该怎么处理呢?我们在连接到 redis 服务器的时候,加一个 -c 参数。-c 参数作用如下:


-c Enable cluster mode (follow -ASK and -MOVED redirections).

我们再次设置 key,发现可以执行了。


[root@VM-0-3-centos ~]# redis-cli -c -h 172.21.0.3 -p 6479

172.21.0.3:6479> set key1 value1

-> Redirected to slot [9189] located at 172.21.0.3:6481

OK

8.6 模拟故障转移

上面我们已经把 redis 集群搭建起来了,但是我们还没有模拟集群故障转移。这里我们模拟一下故障,把 master 节点 6479 直接 kill -9,查看一下集群变化。

模拟前集群状态如下:


172.21.0.3:6481> CLUSTER NODES

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 master - 0 1615346493813 0 connected 0-5461

61b33f247ba176806caf498ad7493a63d6e142de 172.21.0.3:6484 slave 9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 0 1615346492804 5 connected

8260b2c30b5e94f3768d5f192cfafd475988214a 172.21.0.3:6482 slave 9c7864769ea4461a150d627418f297d17763f622 0 1615346491799 4 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 master - 0 1615346490791 2 connected 10923-16383

016a681b4dd4aa45b72130dbe55be37568ee3851 172.21.0.3:6480 slave a43fc8f84d0f887f2a41292c742ca7cd69c36b18 0 1615346494820 3 connected

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 myself,master - 0 0 1 connected 5462-10922

172.21.0.3:6481> CLUSTER INFO

cluster_state:ok

cluster_slots_assigned:16384

cluster_slots_ok:16384

cluster_slots_pfail:0

cluster_slots_fail:0

cluster_known_nodes:6

cluster_size:3

cluster_current_epoch:5

cluster_my_epoch:1

cluster_stats_messages_sent:123805

cluster_stats_messages_received:123805

6480 的日志如下:


15526:S 10 Mar 11:22:52.886 # Error condition on socket for SYNC: Connection refused

15526:S 10 Mar 11:22:53.189 # Starting a failover election for epoch 6.

15526:S 10 Mar 11:22:53.199 # Failover election won: I'm the new master.

15526:S 10 Mar 11:22:53.199 # configEpoch set to 6 after successful failover

15526:M 10 Mar 11:22:53.199 * Discarding previously cached master state.

15526:M 10 Mar 11:22:53.199 # Cluster state changed: ok

从日志中我们可以明显的看到,发生了重新选举,Failover 的过程。

我们再来看一下此时节点的状态。


172.21.0.3:6481> CLUSTER NODES

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 master,fail - 1615346554608 1615346550183 0 disconnected

61b33f247ba176806caf498ad7493a63d6e142de 172.21.0.3:6484 slave 9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 0 1615346597554 5 connected

8260b2c30b5e94f3768d5f192cfafd475988214a 172.21.0.3:6482 slave 9c7864769ea4461a150d627418f297d17763f622 0 1615346598561 4 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 master - 0 1615346596540 2 connected 10923-16383

016a681b4dd4aa45b72130dbe55be37568ee3851 172.21.0.3:6480 master - 0 1615346599568 6 connected 0-5461

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 myself,master - 0 0 1 connected 5462-10922

可以看到 6480 变成了主节点,而之前的 6479 节点显示 fail。

我们再将 6479 节点启动,查看一下集群的状态,看看有什么变化。


redis-server /opt/redis_cluster/redis_6479/conf/redis_6479.conf


172.21.0.3:6481> CLUSTER NODES

a43fc8f84d0f887f2a41292c742ca7cd69c36b18 172.21.0.3:6479 slave 016a681b4dd4aa45b72130dbe55be37568ee3851 0 1615346830399 6 connected

61b33f247ba176806caf498ad7493a63d6e142de 172.21.0.3:6484 slave 9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 0 1615346829394 5 connected

8260b2c30b5e94f3768d5f192cfafd475988214a 172.21.0.3:6482 slave 9c7864769ea4461a150d627418f297d17763f622 0 1615346824361 4 connected

9aa430f83c3585ebf56f3f48cfbde8c77b9461b9 172.21.0.3:6483 master - 0 1615346828386 2 connected 10923-16383

016a681b4dd4aa45b72130dbe55be37568ee3851 172.21.0.3:6480 master - 0 1615346826377 6 connected 0-5461

9c7864769ea4461a150d627418f297d17763f622 172.21.0.3:6481 myself,master - 0 0 1 connected 5462-10922

可以发现,6479 启动后自动变成一个从节点,并复制 6480 节点。

那么我们还想测一下,发生故障转移的时候,会不会丢数据,能不能用?我们怎么测呢,我们可以在 kill 节点之后,一遍读一遍写。

观察到的结果是在集群出故障的一段时间内是写不进去的,一段时间之后可以正常写。原因是我们在 redis 配置文件中设置了超时时间 cluster-node-timeout 15000。只有达到了超时时间之后,集群才会做 Failover。

结论:

  1. 当某一个主节点发生故障时,集群会做自动故障转移,从节点会变成主节点。

  2. 当出故障的主节点重新加入到集群中时,会变成从节点;

  3. 故障转移有时间。这个时间一般设置 5-10 秒,时间太长故障时间就很长,时间太短,比如设置 1 秒,如果网络抖动,主从就会一直切换。而主从切换是需要时间的,需要复制 RDB 文件。如果文件过大,非常耗时,可能比网络抖动时间还长。

8.7 使用工具搭建部署 redis cluster

手动搭建集群便于理解集群创建的流程和细节,不过手动搭建集群需要很多步骤,当集群节点众多时,必然会加大集群的复杂度和运维成本,因此官方提供了 redis-trib.rb 工具方便我们快速搭建集群。

redis-trib.rb 是采用 Ruby 实现的 redis 集群管理工具,内部通过 Cluster 相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用前要安装 ruby 依赖环境。


yum makecache fast

yum install rubygems

gem sources --remove https://rubygems.org/

gem sources -a http://mirrors.aliyun.com/rubygems/

gem update – system

gem install redis -v 3.3.5

我们可以停掉所有的节点,然后清空数据,恢复成一个全新的集群。命令如下:


pkill redis

rm -rf /data/redis_cluster/redis_64{79,80,81,82,83,84}/*

全部清空后启动所有节点:


redis-server /opt/redis_cluster/redis_6479/conf/redis_6479.conf

redis-server /opt/redis_cluster/redis_6480/conf/redis_6480.conf

redis-server /opt/redis_cluster/redis_6481/conf/redis_6481.conf

redis-server /opt/redis_cluster/redis_6482/conf/redis_6482.conf

redis-server /opt/redis_cluster/redis_6483/conf/redis_6483.conf

redis-server /opt/redis_cluster/redis_6484/conf/redis_6484.conf

使用 redis-trib.rb 工具创建集群:


cd /opt/redis_cluster/redis/src/

./redis-trib.rb create --replicas 1 172.21.0.3:6479 172.21.0.3:6481 172.21.0.3:6483 172.21.0.3:6480 172.21.0.3:6482 172.21.0.3:6484

1 表示每个节点有一个复制节点。那么谁是主节点谁是从节点呢?写在前面的就是主节点,写在后面的就是从节点。

检查集群完整性:


./redis-trib.rb check 172.21.0.3:6479

注意该工具有一个问题:就是会有一台机器上的机器复制他自己机器上的主节点。这在生产环境上是无法保证高可用的。如果这台机器挂了,集群就不可用了。由于我们是在一台机器上部署的,因此看不出这个问题,但是要注意这个问题。需要我们手动调整复制集群。

8.8 使用工具扩容节点

我们还可以使用工具进行扩容。槽的数量是固定的,即 16384,扩容首先要分配槽、迁移槽,然后还要把数据迁移过去。迁移步骤如下:

  1. 准备新节点

  2. 加入集群

  3. 迁移槽和数据

接下来我们来演示一下使用工具进行集群扩容:

  1. 创建新节点:

mkdir -p /opt/redis_cluster/redis_64{85,86}/{conf,logs,pid}

mkdir -p /data/redis_cluster/redis_64{85,86}

cp /opt/redis_cluster/redis_6479/conf/redis_6479.conf /opt/redis_cluster/redis_6485/conf/redis_6485.conf

cp /opt/redis_cluster/redis_6479/conf/redis_6479.conf /opt/redis_cluster/redis_6486/conf/redis_6486.conf

sed -i 's#6479#6485#g' /opt/redis_cluster/redis_6485/conf/redis_6485.conf

sed -i 's#6479#6486#g' /opt/redis_cluster/redis_6486/conf/redis_6486.conf

  1. 启动节点

redis-server /opt/redis_cluster/redis_6485/conf/redis_6485.conf

redis-server /opt/redis_cluster/redis_6486/conf/redis_6486.conf

  1. 发现节点

redis-cli -c -h 172.21.0.3 -p 6485 cluster meet 172.21.0.3 6479

redis-cli -c -h 172.21.0.3 -p 6486 cluster meet 172.21.0.3 6479

  1. 使用工具进行扩容

cd /opt/redis_cluster/redis/src/

./redis-trib.rb reshard 172.21.0.3:6479

  1. 接下来会有交互操作了。工具提示我们要分配多少个槽给新节点,我们刚才算了 16384/4=4096。所以我们分配 4096 个槽给新节点

How many slots do you want to move (from 1 to 16384)? 4096

  1. 接下来判断哪个 ID 要来接收槽,我们要用 6485 端口的 ID 来接收槽,因此我们输入 6485 端口 ID。

What is the receiving node ID? 2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

  1. 接下来让我们输入源 ID,我们可以一个一个输入,也可以让所有节点都分配给这台机器槽。我们输入 all 就可以。

Source node #1:all

  1. 迁移命令完成后会自动退出,这个时候我们可以检测一下迁移的状态。

./redis-trib.rb rebalance 172.21.0.3:6479

  1. 我们还要给新节点做复制节点。我们就直接 6496 复制 6485 了。但是线上环境不可以,不能同一台机器复制,可以调整复制节点,比如:

image.png


redis-cli -c -h 172.21.0.3 -p 6486 cluster replicate 2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

8.9 使用工具缩容节点

节点收缩是节点扩容的反向操作。其流程如下:

  1. 首先需要确定下线节点是否有负责的槽,如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽点映射的完整性。

  2. 需要将该槽平均分配给其他节点。比如我们现在有 4 个节点,要缩容一个节点,就要把槽平均分配给其他 3 个节点,4096/3=1365.333。而且要执行三次,因为每次只能从一个地方移到另一个地方。

  3. 当下线节点不在负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭。

我们来操作一下:

  1. 第一次迁移槽:

cd /opt/redis_cluster/redis/src/

How many slots do you want to move (from 1 to 16384)? 1365

What is the receiving node ID? d118021cf062f3ff2dc5d4a7aee62918db22dfc4

Source node #1:2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

Source node #2:done

  1. 第二次迁移槽:

./redis-trib.rb reshard 172.21.0.3:6485

How many slots do you want to move (from 1 to 16384)? 1365

What is the receiving node ID? 9017f1e2bda6515408601b64b7e567f908b54c4e

Source node #1:2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

Source node #2:done

  1. 第三次迁移槽(把剩下的槽都迁移了)

./redis-trib.rb reshard 172.21.0.3:6485

How many slots do you want to move (from 1 to 16384)? 1366

What is the receiving node ID? 347a7e63742b624f1c23789214c4ab4bc2552a4f

Source node #1:2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

Source node #2:done

  1. 忘记节点

./redis-trib.rb del-node 172.21.0.3:6485 2a8b867d98534ef9d7ec9d454dd8a2f8000dbdf5

./redis-trib.rb del-node 172.21.0.3:6486 206fb6c3f6b0abb91c6995c51011d9c051bbbb76

9. Redis 运维工具

9.1 数据导入导出工具

需求背景

刚切换到 redis 集群的时候肯定会面临数据导入的问题,所以这里推荐使用 redis-migrate-tool 工具来导入单节点数据到集群里。

官方地址

redis-migrate-tool

安装工具


cd /opt/redis_cluster/

git clone https://github.com/vipshop/redis-migrate-tool.git

cd redis-migrate-tool/

yum install autoconf automake libtool ncurses-devel

autoreconf -fvi

./configure

make && make install

创建配置文件


cd

vim redis_6379_to_6479.conf


[source]

type: single

servers:

- 172.21.0.3:6379

[target]

type: redis cluster

servers:

- 172.21.0.3:6479

[common]

listen: 0.0.0.0:8888

source_safe: true

生成测试数据


vim input_key.sh


#!/bin/bash

for i in $(seq 1 1000)

do

redis-cli -c -h 172.21.0.3 -p 6379 set wys_${i} v_${i} && echo "set wys_${i} is ok"

done


chmod 777 input_key.sh

redis-server /opt/redis_cluster/redis_6379/conf/redis_6379.conf

bash input_key.sh

导入前数据校验


172.21.0.3:6379> get wys_999

"v_999"

172.21.0.3:6479> get wys_999

(nil)

数据导入


redis-migrate-tool -c redis_6379_to_6479.conf

数据校验

  • 手动查询数据:

172.21.0.3:6479> get wys_999

"v_999"

  • 使用工具校验:

redis-migrate-tool -c redis_6379_to_6479.conf -C redis_check

9.2 分析键值大小

需求背景

redis 的内存使用太大键值太多,不知道哪些键值占用的容量比较大,而且在线分析会影响性能。

安装工具


yum install python-pip gcc python-devel

cd /opt/

git clone https://github.com/sripathikrishnan/redis-rdb-tools

cd redis-rdb-tools

python setup.py install

使用方法


cd /data/redis_cluster/redis_6479/

rdb -c memory redis_6479.rdb -f redis_6479.rdb.csv

分析 rdb 并导出


awk -F ',' '{print $4,$2,$3,$1}' redis_6479.rdb.csv |sort > 6479.txt

其中最上面的就是最大的 key。

9.3 监控过期 key

需求背景

因为开发重复提交,导致电商网站优惠卷过期时间失效

问题分析

如果一个键已经设置了过期时间,这时候在 set 这个键,过期时间就会取消

解决思路

如何在不影响机器性能的前提下批量获取需要监控键过期时间

  1. Keys *:查出来匹配的键名。然后循环读取 ttl 时间

  2. scan *:范围查询键名。然后循环读取 ttl 时间

  • Keys 重操作,会影响服务器性能,除非是不提供服务的从节点

  • Scan 负担小,但是需要去多次才能取完,需要写脚本

脚本内容


cat 01get_key.sh

#!/bin/bash

key_num=0

> key_name.log

for line in $(cat key_list.txt)

do

while true

do

scan_num=$(redis-cli -h 192.168.47.75 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR==1{print $0}')

key_name=$(redis-cli -h 192.168.47.75 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR>1{print $0}')

echo ${key_name}|xargs -n 1 >> key_name.log

((key_num=scan_num))

if [ ${key_num} == 0 ]

then

break

fi

done

done

参考文档