poetry

旧江山浑是新愁。
欲买桂花同载酒,终不似,少年游。

Redis简介

Redis特点

  • K-V 数据库
  • 支持丰富的数据类型:String、Hash、List、Set、Zset(5大基础类型)
  • 和Memcache对比:数据都是存储在内存中的,但是Redis可以做持久化存储(数据可以存储在磁盘上)
  • 工作模型:单线程 | IO多路复用 | 内存
  • 集群方案
  • 速度快

Redis为什么快?

  • 数据库存储在内存中
  • 一般采用单线程IO多路复用
    • 单线程可以避免CPU竞争带来的开销(Redis属于CPU密集型,单独部署,避免和其他业务产生CPU竞争问题)
    • 单线程数据结构等开销较小

常用场景

1、缓存

image-20200406090310175.webp

2、计数,许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。例如笔者所在团队 的视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每 播放一次视频,相应的视频播放数就会自增1:

1
2
3
long incrVideoCounter(long id) { 
key = "video:playCount:" + id; return redis.incr(key);
}

3、共享Session

image-20200406090504714.webp

4、限速

很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率

1
2
3
4
5
6
7
8
phoneNum = "138xxxxxxxx"; key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
// 通过
}else{
// 限速
}

初入Redis

1
[root@localhost ~]# yum -y install redis
1
2
3
4
5
6
7
8
9
[root@server yum.repos.d]# rpm -ql redis
/etc/logrotate.d/redis # 日志配置
/etc/redis-sentinel.conf # 哨兵配置
/etc/redis.conf # 主配置文件
/usr/bin/redis-cli # 客户端工具
/usr/bin/redis-sentinel # 哨兵配置工具
/usr/bin/redis-server # redis服务工具
/usr/bin/redis-check-aof # aof文件修复工具
/usr/bin/redis-check-rdb # rdb文件修复工具
1
2
3
4
5
# Redis服务的启动
redis-server

# 连接Redis服务
redis-cli

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[root@server ~]# cat /etc/redis.conf | grep -Ev '^$|^#'
bind 127.0.0.1 # 监听服务地址
protected-mode yes
port 6379 # 端口
tcp-backlog 511 # tcp全连接数量
timeout 0 # 超时时间
tcp-keepalive 300 # tcp长连接数量
daemonize no # 是否在后台运行
supervised no # supervised管理是否打开
pidfile /var/run/redis_6379.pid # redis服务的pid文件位置
loglevel notice # 日志等级记录
logfile /var/log/redis/redis.log # 日志存放位置
databases 16 # redis数据库实例的设置数量
save 900 1 # 出发rdb持久化存储的策略900s -> 1条写入 ;
save 300 10 # 同上
save 60 10000
stop-writes-on-bgsave-error yes # bgsave手动触发rdb持久化存储,命令失败停止写入
rdbcompression yes # rdb文件压缩
rdbchecksum yes # rdb文件校验
dbfilename dump.rdb # rdb文件名称
dir /var/lib/redis # redis服务目录
slave-serve-stale-data yes
slave-read-only yes # 当此结点作为slaver的时候,开启只读模式
# master-slaver之间的复制策略
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100 # slave优先级
appendonly no # 持久化aof存储
appendfilename "appendonly.aof" # aof文件名称
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000 # 慢日志设定时间,单位微秒
slowlog-max-len 128 # 慢日志长度,当慢查询日志处于其最大长度时,将最早插入的命令移出
latency-monitor-threshold 0
notify-keyspace-events ""
# 数据类型底层编码的策略:相同的数据类型,底层的数据结构编码方式可能不同
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
hll-sparse-max-bytes 3000
activerehashing yes
# 客户端输出缓冲区显示策略
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10 # 内存回收策略,定期删除过期任务每s运行次数
aof-rewrite-incremental-fsync yes

慢查询

1
2
3
slowlog get # 获取慢查询日志
slowlog len # 获取慢查询日志长度
slowlog reset # 重置慢查询日志

Redis小工具

1
2
3
4
5
6
7
8
9
redis-cli -r 100 -i 1 info | grep used_memory_human
# -r repeat
# -i interval
# -a auth
# -c Cluster
# -x 从标准输入(stdin)读取数据
# --slave 把客户端模拟成当前Redis节点的从节点
# --latency 测试客户端到目标Redis的延迟
# --stat 实时获取Redis统计信息
1
2
redis-server
# --test-memory 1024 检测操作系统能否稳定分配1G内存给Reids
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
redis-benchmark 基准性能测试
# -c clients 客户端并发数,默认50
# -n<requests> 客户端请求总量,默认100000
# -p pipeline 每个请求的pipeline数量,默认为1
# -k<boolean> 是否启用keepalive,1为使用
# -t<get,set> 使用指定命令进行基准测试
# --csv 结果以csv格式输出

[root@hw ~]# redis-benchmark -c 100 -n 2000
.........
24.50% <= 1 milliseconds
60.05% <= 2 milliseconds
66.15% <= 3 milliseconds
76.60% <= 4 milliseconds
87.05% <= 5 milliseconds
93.60% <= 6 milliseconds
95.90% <= 7 milliseconds
97.70% <= 8 milliseconds
98.65% <= 13 milliseconds
99.65% <= 14 milliseconds
100.00% <= 14 milliseconds
29850.75 requests per second

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 监视Redis正在执行的命令,可能会占用大量的内存
127.0.0.1:6379> monitor
OK
1586155547.048775 [0 121.36.70.55:34886] "auth" "abc"
1586155555.897676 [0 121.36.70.55:34886] "keys" "*"

# 客户端统计片段
121.36.70.55:6379> info clients
connected_clients:2
client_longest_output_list:0 # 客户端缓冲区可能会造成内存陡增,尤其是对于主节点;内存陡增还可能是存在大量写入;或者是客户端在执行monitor命令
解决办法:1、kill这个连接 2、禁止monitor命令 3、限制输出缓冲区的大小
client_biggest_input_buf:0
blocked_clients:0

数据类型

http://redisdoc.com/index.html

String

1、设置获取键值

1
SET key value [EX seconds] [PX milliseconds] [NX|XX]
  • EX seconds : 将键的过期时间设置为 seconds 秒。

  • PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒。

  • NX : 只在键不存在时, 才对键进行设置操作。

  • XX : 只在键已经存在时, 才对键进行设置操作。

1
2
3
4
5
6
7
8
9
10
11
12
[root@server ~]# redis-cli
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379>

ttl 获取键值的生存时间
incr 为键值进行加操作
incrby
decr 为键值进行减操作
decrby

不想写了,都在这:

http://redisdoc.com/index.html

list

lpush

rpush

lpop

rpop

lrange

lset

lindex

Hash

Set

sadd

scard

sdiff

sinter

sunion

smembers

spop

scre

zset

zadd

zcard

zcount

zrange

操作 含义
rename key newkey 重命名key
renamenx key newkey 确保只有newKey不存在时候才被覆盖
randomkey 随机返回一个键
expire key seconds 键在seconds秒后过期
ttl key 观察它的过期剩余时间(单位:秒)
pexpire key milliseconds 键在milliseconds毫秒后过期
persist key 将键的过期时间清除
keys pattern 获取所有的键(key *)
scan cursor [match pattern] [count number] 扫描字典键,对其他数据类型,则是hscan、sscan、zscan。渐进式地遍历

键迁移

1
dump key restore key ttl value

image-20200406092031687.webp

数据库管理

命令 含义
select dbIndex 切换数据库
databases 16
flushdb 只清除当前数据库
flushall 清除所有数据库
auth “youpassword” 输入redis密码,即配置文件中requirepass

image-20200406093745003.webp

1
2
3
4
5
127.0.0.1:6379> set hello world OK
127.0.0.1:6379> get hello "world"
127.0.0.1:6379> select 15
OK 127.0.0.1:6379[15]> get hello
(nil)

持久化存储

  • RDB持久化存储
  • AOF持久化存储

RDB

RDB持久化存储:将内存中的数据存储快照写到磁盘中(快照方式)

优点:

  1. 应用场景:做全量备份;快照代表的一个时间的数据集状态
  2. RDB文件:a. 二进制文件 b. 压缩机制;所以对于AOF文件来说,体积更小
  3. 生成RDB文件:redis主进程去fork()一个子进程去负责RDB文件生成工作,fork()这个操作肯定会有阻塞

缺点:

  1. 没办法做到实时持久化
  2. RDB二进制格式随着Redis版本的得带,可能存在兼容性问题
  3. 周期性执行一次性备份操作,如果Redis在此期间down掉,会存在数据丢失
  4. RDB压缩会消耗CPU资源

触发方式:

  1. 自动触发:主配置文件中的save字段
  2. 手动触发:bgsave

config get dir可以查看RDB文件的备份路径,

服务启动时,热更新配置:

config set dir /path

config set dbfilename value

redis服务启动时,会去dir下读取dbfilename文件

停止RDB持久化:注释配置文件中的save关键字

RDB工作流程:

image-20200406164400714.webp

Clip_20200410_151059.webp

  1. bgsave命令发送给主进程
  2. redis父进程接收到bgsave命令之后,fork()创建一个子进程
  3. 当子进程完成之后,继续去响应客户端请求
  4. 子进程负责将内存中的数据打上快照并生成RDB文件存储在本地指定位置上
  5. 子进程完成工作后发信号通知父进程
1
2
3
4
5
6
获取最近一个fork操作的耗 时,单位为微秒
info stats
...
latest_fork_usec:133

执行lastsave命令可以获取最后一次生成RDB的时间

Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启

AOF

Redis的持久化方式之一,RDB是通过保存数据库中的键值对来记录数据库的状态,而另一种持久化方式AOF则是通过保存Redis服务所执行的命令来记录数据库的状态

优点:

  1. AOF持久化的方法提供了多种同步频率,即使使用默认同步频率每秒同步一次,redis最多也就丢失一秒的数据
  2. AOF文件使用Redis命令追加的形式来构造,因此,即使Redis只能向AOF文件写入命令的片段,使用redis-check-aof工具也很容易修正AOF文件
  3. AOF文件可读性较强,这也为使用者提供了更灵活的处理方式。如果我们不小心错用了FLUSHALL命令,在重写还没进行时,我们可以手工将最后FLUSHALL命令去掉,然后再使用AOF文件来恢复数据

缺点:

  1. 对于具有相同数据的Redis,AOF文件通常会比RDB文件体积更大
  2. 虽然AOF提供了多种同步频率,默认情况下,每秒同步一次的频率也具有较高的性能。但是在Redis的负载较高时,RDB比AOF具有更好的性能保证
  3. RDB使用快照的形式来持久化整个Redis数据,而AOF只是将每次执行的命令追加到AOF文件中,因此从理论上来说RDB比AOF方式更健壮,官方文档也指出,AOF的确存在一些BUG。

开启AOF

  • 将redis.conf的appendonly改为yes
  • 客户端内:config set appendonly yes,查看是否开启 config get appendonly
  • 手动备份:bgrewriteaof

AOF工作流程

20200316211130205.webp
  1. 用户操作命令追加写入到AOF Buf
  2. AOF缓冲区会将操作命令根据指定的同步策略写入到AOF文件中
  3. 进行重写操作(新的AOF文件替换就的AOF文件)
  4. 服务启动时,可以去加载AOF文件达到的数据恢复的状态

AOF同步策略:调用两个系统接口:write & fsync

  • always:保证文件存储到磁盘中才返回(write -> buf -> fsync -> disk) 可靠性高阻塞长
  • no :只能保证文件存储到缓冲区(write -> buf ;直接返回)缓冲区中的数据同步到磁盘上是由系统调度机制去完成的)阻塞相对较短,但是可靠性不高
  • everysec:调用write操作将内存中的数据写入到缓冲区中,然后后天会有一个专门的fsync线程每s执行一次将缓冲区的内容写入到磁盘中。相对于always和no来说,可靠性和效率都有所保证。

write操作:会触发延迟写机制,因为Linux在内核提供页缓冲区来提供磁盘IO性能,write操作在写入系统缓冲区后直接返回,同步到硬盘依赖于系统的调度机制。

fsync操作:针对单个文件操作,做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据的持久化。

AOF重写工作流程:

20200316211148929.webp
  1. 执行bgrewriteaof操作交由redis父进程
  2. 父进程fork()创建一个子进程(完成AOF生成替换工作)
  3. 父进程继续响应用户请求操作
    1. 写入到AOF_BUF中,追加到old aof文件中
    2. 写入到AOF_rewrite_buf
  4. 子进程根据内存数据集完成AOF文件的工作
  5. 还需要将AOF_rewrite_buf中的数据追加生成到AOF文件中(从而保证子进程创建AOF文件时,父进程继续相应用户请求数据
  6. 新的AOF文件替换旧的AOF文件

为什么要重写?

比如操作:

1
2
set k1 v1 
del k1

这个操作最后没有任何数据产生,是没有意义的,所以AOF不需要写入。

AOF和RDB文件同时存在的时候,优先加载AOF文件。

主从复制

缺点:

  1. 如果master服务器出现问题,则不能提供服务,需要人工修改配置,将slave提升为master
  2. 主从复制,master服务器成为写操作瓶颈
  3. 单机节点存储能力有限

复制原理

从节点复制主结点数据,从节点只能响应读操作。

  • 全量复制:将主结点的所有数据复制到本地
  • 部分复制:主结点持续响应用户请求操作,然后同步到本地执行
Clip_20200410_151307.webp
  1. 从节点执行 slaveof 命令
  2. 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制
  3. 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点
  4. 连接建立成功后,发送 ping 命令,希望得到 pong 命令响应,否则会进行重连
  5. 如果主节点设置了权限,那么就需要进行权限验证;如果验证失败,复制终止。
  6. 权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。
  7. 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。

全量复制

1473308-20190627192702427-1600655856.webp

部分复制

1473308-20190627193858733-1483050713.webp
  • 优点:备份;读写分离
  • 缺点:
    1. 如果主结点down了,我们需要人工切换slave节点的新主节信息(slaveof)
    2. 单机存储容量受限(数据只会存储在master节点上),master节点读操作负载容易变高

实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
mkdir /tmp/redis/{data,conf,log} -pv
# 主服务器
cat << EOF >/tmp/redis/conf/redis_6380.conf
bind 127.0.0.1
port 6380
daemonize yes
pidfile /tmp/redis/redis_6380.pid
loglevel notice
logfile /tmp/redis/log/redis_6380.log
dir /tmp/redis/data/
EOF

cat << EOF >/tmp/redis/conf/redis_6381.conf
bind 127.0.0.1
port 6381
daemonize yes
pidfile /tmp/redis/redis_6381.pid
loglevel notice
logfile /tmp/redis/log/redis_6381.log
dir /tmp/redis/data/
slaveof 127.0.0.1 6380
EOF

cat << EOF >/tmp/redis/conf/redis_6382.conf
bind 127.0.0.1
port 6382
daemonize yes
pidfile /tmp/redis/redis_6382.pid
loglevel notice
logfile /tmp/redis/log/redis_6382.log
dir /tmp/redis/data/
slaveof 127.0.0.1 6380
EOF

redis-server /tmp/redis/conf/redis_6380.conf
redis-server /tmp/redis/conf/redis_6381.conf
redis-server /tmp/redis/conf/redis_6382.conf

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@localhost ~]# redis-cli -p 6380
127.0.0.1:6380> info Replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=99,lag=0
slave1:ip=127.0.0.1,port=6382,state=online,offset=99,lag=0
master_repl_offset:99
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:98

# 写入一定的值
127.0.0.1:6380> set k1 v1
OK
127.0.0.1:6380> get k1
"v1"
127.0.0.1:6380>
[root@node01 ~]# redis-cli -p 6381
127.0.0.1:6381> keys *
1) "k1"

master日志分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
10974:M 05 Apr 15:08:11.474 * Starting BGSAVE for SYNC with target: disk
# bgsave执行过程
10974:M 05 Apr 15:08:11.474 * Background saving started by pid 10982
# master接收到来自slave2的全量复制请求
10974:M 05 Apr 15:08:11.483 * Slave 127.0.0.1:6382 asks for synchronization
10974:M 05 Apr 15:08:11.483 * Full resync requested by slave 127.0.0.1:6382
10974:M 05 Apr 15:08:11.483 * Waiting for end of BGSAVE for SYNC
10982:C 05 Apr 15:08:11.485 * DB saved on disk
10982:C 05 Apr 15:08:11.485 * RDB: 0 MB of memory used by copy-on-write

10974:M 05 Apr 15:08:11.567 * Background saving terminated with success
# 数据同步给slave1和2,增量复制
10974:M 05 Apr 15:08:11.568 * Synchronization with slave 127.0.0.1:6381 succeeded
10974:M 05 Apr 15:08:11.568 * Synchronization with slave 127.0.0.1:6382 succeeded

slaver日志分析

1
2
3
4
5
6
7
8
9
10
11
12
13
# 与master节点建立连接通信
10978:S 05 Apr 15:08:11.473 * Connecting to MASTER 127.0.0.1:6380
# 全量复制同步开始前的准备阶段
10978:S 05 Apr 15:08:11.473 * MASTER <-> SLAVE sync started
10978:S 05 Apr 15:08:11.473 * Non blocking connect for SYNC fired the event.
10978:S 05 Apr 15:08:11.473 * Master replied to PING, replication can continue...
10978:S 05 Apr 15:08:11.474 * Partial resynchronization not possible (no cached master)
# 开始全量复制
10978:S 05 Apr 15:08:11.474 * Full resync from master: 0ae1eafaf5a6804f35d3a3a5a5934a0f62d4f390:1
10978:S 05 Apr 15:08:11.568 * MASTER <-> SLAVE sync: receiving 77 bytes from master
10978:S 05 Apr 15:08:11.568 * MASTER <-> SLAVE sync: Flushing old data
10978:S 05 Apr 15:08:11.568 * MASTER <-> SLAVE sync: Loading DB in memory
10978:S 05 Apr 15:08:11.568 * MASTER <-> SLAVE sync: Finished with success

哨兵架构

Sentinel Mode

  • 优点:备份 ; 读写分离 ; 可以自动切换主节点
  • 缺点:
    1. 单机存储容量受限
    2. 主结点写负载会很高
    3. 扩展性较差

哨兵原理:

  1. 每个哨兵会向其他哨兵、主服务器、从服务器定时发送消息,以确认对方是否活着。
  2. 当主节点挂掉的时候,某个哨兵1通过检测机制发现了这样一个情况,主观的认为这个主节点挂掉了(暂时还不能确认),会将这个信息发送给其他的哨兵,其他哨兵如果都赞同哨兵1的确认信息,则主观认为这个节点挂掉了(主结点真正的被分为挂掉了)。
  3. 发现master挂掉之后,从剩下的slave中重新选择新的master节点;每个哨兵都会监控slaver节点的状态信息,通过slave节点的状态信息进行投票选择,票数高的成为新的主结点,并且哨兵回去向其他节点告知新主节点的信息(IP_PORT) ,其他节点得到哨兵的通知消息后,自动执行slaveof去同步新的主节点信息。

哨兵实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/bash
#配置哨兵1,监听26378端口
cat << EOF > /tmp/redis/conf/redis-sentinel-26378.conf
port 26378
daemonize yes
dir '/tmp'
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 6000
sentinel failover-timeout mymaster 18000
logfile "/tmp/redis/log/sentine1.log"
EOF

#配置哨兵2,监听26379端口
cat << EOF > /tmp/redis/conf/redis-sentinel-26379.conf
port 26379
daemonize yes
dir '/tmp'
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 6000
sentinel failover-timeout mymaster 18000
logfile "/tmp/redis/log/sentine2.log"
EOF

#配置哨兵3,监听26380端口
cat << EOF > /tmp/redis/conf/redis-sentinel-26380.conf
port 26380
daemonize yes
dir '/tmp'
sentinel monitor mymaster 127.0.0.1 6380 1
sentinel down-after-milliseconds mymaster 6000
sentinel failover-timeout mymaster 18000
logfile "/tmp/redis/log/sentine3.log"
EOF

# 启动哨兵
redis-sentinel /tmp/redis/conf/redis-sentinel-26379.conf
redis-sentinel /tmp/redis/conf/redis-sentinel-26378.conf
redis-sentinel /tmp/redis/conf/redis-sentinel-26380.conf

测试:

1
2
3
4
5
6
7
8
9
[root@node01 ~]# redis-cli -p 26379
127.0.0.1:26379> sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6380"
....
1
2
3
4
5
6
7
8
9
sentinel master <master-name> : 显示所有被监控主节点状态及统计信息
sentinel slaves <master-name> : 显示指定<master-name>的从节点状态及统计信息
sentinel sentinels <master-name> : 显示指定<master-name>的sentinel节点集合
sentinel failover <master-name> : 强制故障转移
sentinel ckquorum <master-name> : 检查当前可达Sentinel节点总数是否到<quoro>个数
sentinel flushconfig :将节点的配置强制刷到磁盘上
sentinel remove <master-name> : 取消当前Sentinel对指定<master>节点的监控
sentinel set <master-name>:动态修改Sentinel节点配置
sentinel is-master-down-by-addr :Sentinel节点之间用来交换对主节点是否下线的判断

模拟master挂掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@node01 ~]# ps -ef | grep redis
root 10974 1 0 15:08 ? 00:00:08 redis-server 127.0.0.1:6380
root 10978 1 0 15:08 ? 00:00:07 redis-server 127.0.0.1:6381
root 10983 1 0 15:08 ? 00:00:08 redis-server 127.0.0.1:6382
root 11060 1 0 17:10 ? 00:00:01 redis-sentinel *:26379 [sentinel]
root 11064 1 0 17:10 ? 00:00:01 redis-sentinel *:26378 [sentinel]
root 11068 1 0 17:10 ? 00:00:01 redis-sentinel *:26380 [sentinel]
root 11123 10907 0 17:19 pts/0 00:00:00 grep --color=auto redis
[root@node01 ~]# kill -9 10974

# 分析哨兵日志
[root@node01 ~]# tailf /tmp/redis/log/sentine1.log
11068:X 05 Apr 17:10:26.924 # Sentinel ID is 58bdbb66f27e1e87dea74caed765090bdcef84f2
11068:X 05 Apr 17:10:26.924 # +monitor master mymaster 127.0.0.1 6380 quorum 1
11068:X 05 Apr 17:10:26.925 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6380
11068:X 05 Apr 17:10:26.927 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6380
11068:X 05 Apr 17:10:28.906 * +sentinel sentinel 3756a9cd418f17b20e29b51a71a100c221a37379 127.0.0.1 26379 @ mymaster 127.0.0.1 6380
11068:X 05 Apr 17:10:28.932 * +sentinel sentinel 125c85846d42c32b15f364db43afab85c4858888 127.0.0.1 26378 @ mymaster 127.0.0.1 6380
11068:X 05 Apr 17:20:17.292 # +new-epoch 1
11068:X 05 Apr 17:20:17.294 # +vote-for-leader 125c85846d42c32b15f364db43afab85c4858888 1

# 主观下线
11068:X 05 Apr 17:20:17.371 # +sdown master mymaster 127.0.0.1 6380

# 客观下线
11068:X 05 Apr 17:20:17.371 # +odown master mymaster 127.0.0.1 6380 #quorum 1/1
11068:X 05 Apr 17:20:17.371 # Next failover delay: I will not start a failover before Sun Apr 5 17:20:54 2020
11068:X 05 Apr 17:20:18.415 # +config-update-from sentinel 125c85846d42c32b15f364db43afab85c4858888 127.0.0.1 26378 @ mymaster 127.0.0.1 6380

# master切换
11068:X 05 Apr 17:20:18.415 # +switch-master mymaster 127.0.0.1 6380 127.0.0.1 6381
11068:X 05 Apr 17:20:18.416 * +slave slave 127.0.0.1:6382 127.0.0.1 6382 @ mymaster 127.0.0.1 6381
11068:X 05 Apr 17:20:18.416 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381
11068:X 05 Apr 17:20:24.461 # +sdown slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381

重新启动6380后,6380将变成6381的slave节点

1
2
3
4
5
6
[root@node01 ~]# redis-server /tmp/redis/conf/redis_6380.conf
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381

集群架构

是一种分布式解决方案

  • 优点 :

    1. 每个节点都可以存储数据,每个节点都可以响应读写操作
    2. 更容易扩容
  • 缺点:

    1. 部署复杂
    2. redis部分cli命令受限,比如批量操作等

    每个节点如何存储数据?(虚拟槽)

  1. 对象保存到Redis之前先经过CRC16哈希到一个Node上
  2. 每个Node被平均分配到一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储
  3. Node之间相互监听,一旦有Node退出或者加入,会按照Slot为单位做数据迁移。例如Node1掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会再自己身上不会被重新分配,所以执行过程中不会影响到5641-16384Slot段的使用。
1
计算CRC的过程,就是用一个特殊的“除法”,来得到余数,这个余数就是CRC。 它不是真正的算术上的除法!过程和算术除法过程一样,只是加减运算变成了XOR(异或)运算!

集群实验

Redis5.0之后部署会简单一点

1
2
3
4
5
6
7
8
wget http://download.redis.io/releases/redis-5.0.8.tar.gz
yum install -y wget gcc make tcl
tar -xzf redis-5.0.8.tar.gz
cd redis-5.0.8/
make # 这里需要注意一下, 如果之前没有装gcc直接make,然后装了gcc再次make,会提示有个文件没有
# PATH=$PATH:/usr/local/redis/bin,如果安装的时候prefix指定了路径,可以修改环境变量
mv redis-5.0.8/ redis
mkdir -p /tmp/redis-cluster
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
#=====================================================================
#搭建一个Redis集群
#=====================================================================
mkdir /tmp/redis-cluster/redis{7000..7005} -pv
touch /tmp/redis-cluster/redis{7000..7005}/redis.conf
for i in {7000..7005};
do
cat << EOF > /tmp/redis-cluster/redis$i/redis.conf
daemonize yes
port $i
cluster-enabled yes
cluster-config-file /tmp/redis-cluster/redis$i/nodes-$i.conf
cluster-node-timeout 5000
appendonly yes
EOF
/root/redis/src/redis-server /tmp/redis-cluster/redis$i/redis.conf
done

集群中有6个节点,设置3个master3个slaver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[root@node01 ~]# /root/redis/src/redis-cli --cluster create 127.0.0.1:7000 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 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
# 分配Slot槽
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7004 to 127.0.0.1:7000
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7003 to 127.0.0.1:7002
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 2518beb0563857adb5c1497fffb4c5eb3d682103 127.0.0.1:7000
slots:[0-5460] (5461 slots) master
M: 1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b 127.0.0.1:7001
slots:[5461-10922] (5462 slots) master
M: 141382f53c59164a232329efcc9530daffc43a25 127.0.0.1:7002
slots:[10923-16383] (5461 slots) master
S: 9269115a3ef21c7f378dcc68855a72059e943605 127.0.0.1:7003
replicates 2518beb0563857adb5c1497fffb4c5eb3d682103
S: 6c33423da686f301f756dd624174bfd9f0487a67 127.0.0.1:7004
replicates 1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b
S: f8c74822b558a2e11ae36cf144b8a9388783bf95 127.0.0.1:7005
replicates 141382f53c59164a232329efcc9530daffc43a25
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 2518beb0563857adb5c1497fffb4c5eb3d682103 127.0.0.1:7000
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 9269115a3ef21c7f378dcc68855a72059e943605 127.0.0.1:7003
slots: (0 slots) slave
replicates 2518beb0563857adb5c1497fffb4c5eb3d682103
M: 1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b 127.0.0.1:7001
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: f8c74822b558a2e11ae36cf144b8a9388783bf95 127.0.0.1:7005
slots: (0 slots) slave
replicates 141382f53c59164a232329efcc9530daffc43a25
S: 6c33423da686f301f756dd624174bfd9f0487a67 127.0.0.1:7004
slots: (0 slots) slave
replicates 1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b
M: 141382f53c59164a232329efcc9530daffc43a25 127.0.0.1:7002
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

验证1:

1
2
3
4
5
6
7
8
[root@node01 ~]# ps -ef | grep redis
root 12733 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7000 [cluster]
root 12736 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7001 [cluster]
root 12742 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7002 [cluster]
root 12751 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7003 [cluster]
root 12757 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7004 [cluster]
root 12764 1 0 21:02 ? 00:00:00 /root/redis/src/redis-server *:7005 [cluster]

验证2:登录集群查看信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@node01 ~]# redis/src/redis-cli -c -p 7000
127.0.0.1:7000> info cluster
# Cluster
cluster_enabled:1
127.0.0.1:7000> 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:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:259
cluster_stats_messages_pong_sent:267
cluster_stats_messages_sent:526
cluster_stats_messages_ping_received:262
cluster_stats_messages_pong_received:259
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:526

# slave节点后缀ID和Master的ID号对应
2518beb0563857adb5c1497fffb4c5eb3d682103 127.0.0.1:7000@17000 myself,master - 0 1586092155000 1 connected 0-5460
9269115a3ef21c7f378dcc68855a72059e943605 127.0.0.1:7003@17003 slave 2518beb0563857adb5c1497fffb4c5eb3d682103 0 1586092157502 4 connected
1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b 127.0.0.1:7001@17001 master - 0 1586092156896 2 connected 5461-10922
f8c74822b558a2e11ae36cf144b8a9388783bf95 127.0.0.1:7005@17005 slave 141382f53c59164a232329efcc9530daffc43a25 0 1586092157502 6 connected
6c33423da686f301f756dd624174bfd9f0487a67 127.0.0.1:7004@17004 slave 1d39c766c5275ec08ae47f90b69d5e9c6c88eb8b 0 1586092156391 5 connected
141382f53c59164a232329efcc9530daffc43a25 127.0.0.1:7002@17002 master - 0 1586092157401 3 connected 10923-16383

# 写入值,重定向k1 v1 CRC16计算,--> slot_12706,给7002号master
127.0.0.1:7000> set k1 v1
-> Redirected to slot [12706] located at 127.0.0.1:7002
OK