Redis运维


引言、单机Redis存在的问题

image-20210725144240631

零、redis 基本命令

1、redis-cli

redis-cli 命令在 redis/src 目录下

连接本机 redis

./redis-cli -p 6379 -a 密码

连接远程 redis

./redis-cli -h 远程ip -p 6379 -a 密码

2、切换数据库

SELECT 1

以下命令返回当前数据库的 key 的数量

DBSIZE

3、查询

(1)keys

查找所有符合给定模式 pattern 的 key 。

  • KEYS * 匹配数据库中所有 key 。

  • KEYS h?llo 匹配 hello , hallo 和 hxllo 等。

  • KEYS h*llo 匹配 hllo 和 heeeeello 等。

  • KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。

特殊符号用 \ 隔开

通配符 说明
* 匹配任意数量的任意字符
? 匹配单个字符
[] 匹配指定的字符

(2)GET(字符串类型)

get key

返回 key 所关联的字符串值。

如果 key 不存在那么返回特殊值 nil 。

假如 key 储存的值不是字符串类型,返回一个错误,因为 GET 只能用于处理字符串值。

时间复杂度O(1),比 keys 的 O(n) 快

(3)exists

exists key

检查给定 key 是否存在。若 key 存在,返回 1 ,否则返回 0 。

4、过期时间

(1)查询过期时间

ttl key

以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。

  • 当 key 不存在时,返回 -2 。

  • 当 key 存在但没有设置剩余生存时间时,返回 -1 。

  • 否则,以秒为单位,返回 key 的剩余生存时间。

注:在 Redis 2.8 以前,当 key 不存在,或者 key 没有设置剩余生存时间时,命令

都返回 -1 。

(2)设置过期时间

expire key seconds

为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。

设置成功返回 1 。

当 key 不存在或者不能为 key 设置生存时间时,返回 0 。

(3)移除过期时间

persist key

移除给定 key 的生存时间,将这个 key 从『可挥发』的(带生存时间 key )转换成『持久化』的(一个不带生存时间、永不过期的 key )。

当生存时间移除成功时,返回 1 .

如果 key 不存在或 key 没有设置生存时间,返回 0 。

5、删除

DEL key
DEL [key ...]

删除给定的一个或多个 key 。

不存在的 key 会被忽略。

返回值: 被删除 key 的数量。

6、新增(字符串)

(1)set

set key value

如果 key 已经持有其他值, SET 就覆写旧值,无视类型

(2)setnx

setnx key value

若给定的 key 已经存在,则 SETNX 不做任何动作。

(3)setex

setex key seconds value

将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。

如果 key 已经存在, SETEX 命令将覆写旧值。

这个命令类似于以下两个命令:

  • SET key value

  • EXPIRE key seconds # 设置生存时间

不同之处是, SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会

在同一时间内完成,该命令在 Redis 用作缓存时,非常实用。

一、Redis持久化

Redis有两种持久化方案:

  • RDB持久化
  • AOF持久化

1、RDB 持久化

RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。快照文件称为RDB文件,默认是保存在当前运行目录。

执行时机

RDB持久化在四种情况下会执行:

  • 执行save命令
  • 执行bgsave命令
  • Redis停机时
  • 触发RDB条件时

(1)save 命令

执行下面的命令,可以立即执行一次RDB(需先用 redis-cli 连接 Redsi):

save

save命令会导致主进程执行RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。

(2)bgsave命令

下面的命令可以异步执行RDB:

bgsave

这个命令执行后会开启独立进程完成RDB,主进程可以持续处理用户请求,不受影响。

(3)停机时

Redis停机时会执行一次save命令,实现RDB持久化。

(4)触发RDB条件

Redis内部有触发RDB的机制,可以在redis.conf文件中找到,格式如下:(一般用默认即可)

# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是save "" 则表示禁用RDB
save 900 1  
save 300 10  
save 60 10000 

save "" 为禁用 RDB

RDB的其它配置也可以在redis.conf文件中设置:

# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes

# RDB文件名称
dbfilename dump.rdb  

# 文件保存的路径目录
dir ./ 

(5)RDB原理

bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB 文件。

fork采用的是copy-on-write技术:

  • 当主进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。

image-20210725151319695

RDB方式bgsave的基本流程?

  • fork主进程得到一个子进程,共享内存空间
  • 子进程读取内存数据并写入新的RDB文件
  • 用新RDB文件替换旧的RDB文件

RDB会在什么时候执行?save 60 1000代表什么含义?

  • 默认是服务停止时
  • 代表60秒内至少执行1000次修改则触发RDB

RDB的缺点?

  • RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
  • fork子进程、压缩、写出RDB文件都比较耗时

2、AOF 持久化

(1)原理

AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。

(2)配置

AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:

# 是否开启AOF功能,默认是no
appendonly yes
# AOF文件的名称
appendfilename "appendonly.aof"

AOF的命令记录的频率也可以通过redis.conf文件来配:(三种策略)

# 表示每执行一次写命令,立即记录到AOF文件
appendfsync always 
# 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案
appendfsync everysec 
# 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
appendfsync no

三种策略对比:

image-20210725151654046

(3)AOF文件重写

因为是记录命令,AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行 bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

set num 123
set name jack
set num 666

案例,AOF原本有3个命令,但是set num 123 和 set num 666都是对num的操作,第二次会覆盖第一次的值,因此第一个命令记录下来没有意义。

所以重写命令后,AOF文件内容就是:mset name jack num 666

Redis也会在触发阈值时自动去重写AOF文件。阈值也可以在redis.conf中配置(基本默认就行):

# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写 
auto-aof-rewrite-min-size 64mb 

3、RDB和AOF对比

RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

image-20210725151940515

4、阿里云Redis采用哪种?

阿里云默认采用 AOF,且每天进行一次 RDB

RDB持久化

RDB持久化是指周期性地为引擎中保存的数据创建快照,生成RDB文件,保存到磁盘中,实现数据的持久化。RDB文件占用空间小,便于移动,非常适合用于备份或迁移指定时间点的数据。

开源Redis在生成RDB文件时会可能会带来操作阻塞,阻塞时间取决于实例的数据总量。而云数据库 Redis 版对此优化并实现了“无阻塞备份”,使实例的备份不影响客户端请求。

Redis的RDB持久化策略默认每天备份一次,您可以根据业务需求修改自动备份策略,也可以手动发起临时的备份。

AOF持久化

AOF持久化是指以日志的形式记录所有的写入类操作(例如SET)。当服务重启时,通过重新执行AOF文件中的操作来恢复数据。

由于AOF文件记录了所有写入类操作,随着AOF文件中的记录越来越多,当AOF文件过大时,Redis会自动执行AOF Rewrite,重组AOF文件,降低其占用的存储空间,提高数据恢复效率,但AOF Rewrite对写入性能会有一定损耗。

云数据库 Redis 版默认启用AOF持久化,策略为AOF_FSYNC_EVERYSEC且不支持修改,即Redis每秒执行一次fsync,将AOF缓冲区中的写入类命令刷新到磁盘,这个过程又称为AOF落盘。AOF_FSYNC_EVERYSEC策略对Redis服务性能的影响较小,同时也能极大地降低意外情况下的数据损失风险。

二、主从架构

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

image-20210725152037611

1、主从搭建

(1)修改为 RDB 模式

修改 redis.conf文件,将其中的持久化模式改为默认的RDB模式,AOF保持关闭状态。(每台都一样)

# 开启RDB
# save ""
save 3600 1
save 300 100
save 60 10000

# 关闭AOF
appendonly no

(2)开启主从关系

从节点设置

现在三个实例还没有任何关系,要配置主从可以使用replicaof 或者slaveof(5.0以前)命令。

有临时和永久两种模式:

  • 修改配置文件(永久生效)

    • 在redis.conf中添加一行配置:slaveof <masterip> <masterport>
  • 使用redis-cli客户端连接到redis服务,执行slaveof命令(重启后失效):

slaveof  
slaveof 127.0.0.1 6379

注意:在5.0以后新增命令replicaof,与salveof效果一致。

至此集群搭建完毕(主节点无需修改配置)

搭建完成后,主节点可以读写,从节点只能读,写的话报错

查看集群状态

在 主节点的 redis-cli -p 6379

info replication

2、主从同步原理

(1)全量同步(首次)

主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程:

image-20210725152222497

这里有一个问题,master如何得知salve是第一次来连接呢??

有几个概念,可以作为判断依据:

  • Replication Id(用于判断是否首次同步):简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
  • offset(用于非首次同步确认同步位置):偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。

因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。

master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。

master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。

因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致

完整流程描述:

  • slave节点请求增量同步,携带 replid和offset
  • master节点判断replid,发现不一致,拒绝增量同步
  • master将完整内存数据生成RDB,发送RDB到slave
  • slave清空本地数据,加载master的RDB
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步

(2)增量同步

全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候(如从库重启后同步数据时)slave与master都是做增量同步

什么是增量同步?就是只更新slave与master存在差异的部分数据。如图:

image-20210725153201086

(3)repl_backlog原理

master怎么知道slave与自己的数据差异在哪里呢?

这就要说到全量同步时的 repl_baklog 文件了。

这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。

repl_baklog 中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:

image-20210725153359022

slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。

随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset,直到数组被填满

此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。

但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:

如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:

image-20210725154155984

棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。

注意

repl_backlog 大小有上限,写满之后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于 log 做增量同步,只能再次全量同步

(4)主从同步优化

主从同步可以保证主从数据的一致性,非常重要。

可以从以下几个方面来优化Redis主从就集群:

  • 在master中配置 repl-diskless-sync yes 启用无磁盘复制(不存储,直接通过网络同步),避免全量同步时的磁盘IO。(磁盘慢,网络快的情况下可使用)
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 适当提高 repl_baklog 的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

主从从架构图:

image-20210725154405899

(5)总结

简述全量同步和增量同步区别?

  • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
  • 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时

三、哨兵模式

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

1、工作原理

(1)集群结构

哨兵的结构如图:

image-20210725154528072

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的master和slave是否按预期工作
  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

(2)监控原理

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

  • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线

  • 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

image-20210725154632354

(3)故障恢复原理

一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

  • 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后是判断slave节点的运行id大小,越小优先级越高。

当选出一个新的master后,该如何实现切换呢?

流程如下:

  • sentinel给备选的slave1节点发送 slaveof no one 命令,让该节点成为master
  • sentinel给所有其它slave发送 slaveof 新主节点ip 端口 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
  • 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
image-20210725154816841

(4)总结

Sentinel的三个作用是什么?

  • 监控
  • 故障转移
  • 通知

Sentinel如何判断一个redis实例是否健康?

  • 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
  • 如果大多数sentinel都认为实例主观下线,则判定服务下线

故障转移步骤有哪些?

  • 首先选定一个slave作为新的master,执行slaveof no one
  • 然后让所有节点都执行slaveof 新master
  • 修改故障节点配置,添加slaveof 新master

2、搭建哨兵集群

(1)搭建

三台 Rdis 主从,三台 Rdis 哨兵。。。六台服务器,玩不起。。。

创建配置文件

创建配置文件 /temp/s1/sentinel.conf ,随便创建一个位置即可

port 27001
sentinel announce-ip 192.168.150.101
sentinel monitor mymaster 192.168.150.101 7001 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
dir "/tmp/s1"

解读:

  • port 27001:是当前sentinel实例的端口
  • sentinel monitor mymaster 192.168.150.101 7001 2:指定主节点信息
    • mymaster:主节点名称,自定义,任意写
    • 192.168.150.101 7001:主节点的ip和端口
    • 2:选举master时的quorum值

启动哨兵

redis-sentinel /temp/s1/sentinel.conf

单台哨兵完成,其他服务器同理

(2)日志查看

redis sentinel启动默认是非守护进程的,如何改成守护进程启动,并打印日志呢?

./redis-sentinel ../sentinel.conf 2>&1 >> /usr/redis/Sentinel1/sentinel.log &

Sentinel服务启动后会打印一些相关日志信息,相关日志特殊字符说明:

+reset-master  :主服务器已被重置。

+slave  :一个新的从服务器已经被 Sentinel 识别并关联。

+failover-state-reconf-slaves  :故障转移状态切换到了reconf-slaves 状态。

+failover-detected :另一个 Sentinel 开始了一次故障转移操作,或者一个从服务器转换成了主服务器。

+slave-reconf-sent :领头(leader)的 Sentinel 向实例发送了 SLAVEOF 命令,为实例设置新的主服务器。

+slave-reconf-inprog  :实例正在将自己设置为指定主服务器的从服务器,但相应的同步过程仍未完成。

+slave-reconf-done :从服务器已经成功完成对新主服务器的同步。

-dup-sentinel  :对给定主服务器进行监视的一个或多个 Sentinel 已经因为重复出现而被移除 —— 当 Sentinel 实例重启的时候,就会出现这种情况。

+sentinel  :一个监视给定主服务器的新 Sentinel 已经被识别并添加。

+sdown  :给定的实例现在处于主观下线状态。

-sdown  :给定的实例已经不再处于主观下线状态。

+odown  :给定的实例现在处于客观下线状态。

-odown  :给定的实例已经不再处于客观下线状态。

+new-epoch  :当前的纪元(epoch)已经被更新。

+try-failover  :一个新的故障迁移操作正在执行中,等待被大多数 Sentinel 选中(waiting to be elected by themajority)。

+elected-leader  :赢得指定纪元的选举,可以进行故障迁移操作了。

+failover-state-select-slave  :故障转移操作现在处于select-slave 状态 —— Sentinel 正在寻找可以升级为主服务器的从服务器。

no-good-slave  :Sentinel 操作未能找到适合进行升级的从服务器。Sentinel 会在一段时间之后再次尝试寻找合适的从服务器来进行升级,又或者直接放弃执行故障转移操作。

selected-slave  :Sentinel 顺利找到适合进行升级的从服务器。

failover-state-send-slaveof-noone :Sentinel 正在将指定的从服务器升级为主服务器,等待升级功能完成。

failover-end-for-timeout  :故障转移因为超时而中止,不过最终所有从服务器都会开始复制新的主服务器(slaves will eventually be configured to replicate with the newmaster anyway)。

failover-end  :故障转移操作顺利完成。所有从服务器都开始复制新的主服务器了。

+switch-master     :配置变更,主服务器的 IP 和地址已经改变。 这是绝大多数外部用户都关心的信息。

+tilt :进入 tilt 模式。

-tilt :退出 tilt 模式。

3、RedisTemplate的哨兵模式

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。

(1)依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)配置Redis地址

然后在配置文件application.yml中指定redis的sentinel相关信息:

只需配置哨兵的地址,不需要配置真实的 redis 集群地址

spring:
  redis:
    sentinel:
      master: mymaster    # master 名称
      nodes:    # sentinel 集群地址
        - 192.168.150.101:27001
        - 192.168.150.101:27002
        - 192.168.150.101:27003

master 名称:见 3-2搭建哨兵集群 的配置文件中

(3)配置主从分离

在项目的启动类中,添加一个新的bean:

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

这个bean中配置的就是读写策略,包括四种:

  • MASTER:从主节点读取
  • MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica
  • REPLICA:从slave(replica)节点读取
  • REPLICA _PREFERRED(推荐):优先从slave(replica)节点读取,所有的slave都不可用才读取master

四、分片集群

1、搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题

  • 高并发写的问题

使用分片集群可以解决上述问题,如图:

image-20210725155747294

分片集群特征:

  • 集群中有多个master,每个master保存不同数据

  • 每个master都可以有多个slave节点

  • master之间通过ping监测彼此健康状态(类似哨兵)

  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

(1)配置文件

创建配置文件 /temp/redis.conf

port 6379
# 开启集群功能
cluster-enabled yes
# 集群的配置文件名称,不需要我们创建,由redis自己维护
cluster-config-file /tmp/6379/nodes.conf
# 节点心跳失败的超时时间
cluster-node-timeout 5000
# 持久化文件存放目录
dir /tmp/6379
# 绑定地址
bind 0.0.0.0
# 让redis后台运行
daemonize yes
# 注册的实例ip
replica-announce-ip 192.168.150.101
# 保护模式,即连接无需密码
protected-mode no
# 数据库数量
databases 1
# 日志
logfile /tmp/6379/run.log

在redis默认配置中,如果发现任意一个插槽不可用,则整个集群都会停止对外服务,所以这里建议将下面参数改为false,从而保证集群完整性

cluster-require-full-coverage false

(2)启动 redis

redis-server /temp/redis.conf

查看服务状态

ps -ef | grep redis

关闭进程

ps -ef | grep redis | awk '{print $2}' | xargs kill

或(推荐)

redis-cli -p 6379 shutdown

重复(1)(2)启动所有集群服务,此时集群中各服务之间还没有联系

(3)创建集群

虽然服务启动了,但是目前每个服务之间都是独立的,没有任何关联。

我们需要执行命令来创建集群,在Redis5.0之前创建集群比较麻烦,5.0之后集群管理命令都集成到了redis-cli中。

Redis 5.0 之前

Redis5.0之前集群命令都是用redis安装包下的src/redis-trib.rb来实现的。因为redis-trib.rb是有ruby语言编写的所以需要安装ruby环境。

 # 安装依赖
 yum -y install zlib ruby rubygems
 gem install redis

然后通过命令来管理集群:

# 进入redis的src目录
cd /tmp/redis-6.2.4/src
# 创建集群
./redis-trib.rb create --replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

Redis5.0以后

我们使用的是Redis6.2.4版本,集群管理以及集成到了redis-cli中,格式如下:

redis-cli --cluster create --cluster-replicas 1 192.168.150.101:7001 192.168.150.101:7002 192.168.150.101:7003 192.168.150.101:8001 192.168.150.101:8002 192.168.150.101:8003

命令说明:

  • redis-cli --cluster或者./redis-trib.rb:代表集群操作命令
  • create:代表是创建集群
  • --replicas 1或者--cluster-replicas 1 :指定集群中每个master的副本个数为1,此时节点总数 ÷ (replicas + 1) 得到的就是master的数量。因此节点列表中的前n个就是master,其它节点都是slave节点,随机分配到不同master

运行后查看状态

运行后的样子:

image-20210702181101969

这里输入yes,则集群开始创建:

image-20210702181215705

通过命令可以查看集群状态:

redis-cli -p 7001 cluster nodes

image-20210702181922809

(4)测试

集群操作时,需要给redis-cli加上-c参数才可以:

redis-cli -c -p 7001

操作

# 存储数据
set num 123
# 读取数据
get num
# 再次存储
set a 1

2、散列插槽

Redis会把每一个master节点映射到0~16383共 16384(2的14次方)个插槽(hash slot)上,查看集群信息时就能看到:

image-20210725155820320

数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:

  • key中包含”{}”,且“{}”中至少包含1个字符,“{}”中的部分是有效部分
  • key中不包含“{}”,整个key都是有效部分

例如:key是num,那么就根据num计算,如果是{itcast}num,则根据itcast计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。

散列插槽2

如图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到7003节点。

到了7003后,执行get num时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点

总结

Redis如何判断某个key应该在哪个实例?

  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384取余
  • 余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个Redis实例?

  • 这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

3、集群伸缩

节点操作命令(其他命令可通过 redis-cli --cluster help 查阅)

(1)新增节点

add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id 

参数解析:

  • new_host:new_port:要添加的节点的 ip 端口
  • existing_host:existing_port:集群中任意已存在的节点的 ip 端口
  • --cluster-slave:指定该节点为从节点
  • --cluster-master-id <arg> :指定该从节点的主节点

新增节点默认是 master ,--cluster-slave--cluster-master-id <arg> 一般一起使用

新增主节点示例

redis-cli --cluster add-node  192.168.150.101:7004 192.168.150.101:7001

通过命令查看集群状态:

redis-cli -p 7001 cluster nodes

7004加入了集群,并且默认是一个master节点,但是,可以看到7004节点的插槽数量为0,因此没有任何数据可以存储到7004上

删除节点

del-node       host:port node_id

(2)转移插槽

我们可以将0~3000的插槽从7001转移到7004,命令格式如下:

redis-cli --cluster reshard host:port
                 --cluster-from 
                 --cluster-to 
                 --cluster-slots 
                 --cluster-yes
                 --cluster-timeout 
                 --cluster-pipeline 
                 --cluster-replace

具体命令如下:

建立连接:

image-20210725161506241

得到下面的反馈:

image-20210725161540841

询问要移动多少个插槽,我们计划是3000个:

新的问题来了:

image-20210725161637152

那个node来接收这些插槽??

显然是7004,那么7004节点的id是多少呢?

image-20210725161731738

复制这个id,然后拷贝到刚才的控制台后:

image-20210725161817642

这里询问,你的插槽是从哪里移动过来的?

  • all:代表全部,也就是三个节点各转移一部分
  • 具体的id:目标节点的id
  • done:没有了

这里我们要从7001获取,因此填写7001的id:

image-20210725162030478

填完后,点击done,这样插槽转移就准备好了:

image-20210725162101228

确认要转移吗?输入yes:

然后,通过命令查看结果:

image-20210725162145497

可以看到:

image-20210725162224058

目的达成。

4、故障转移

(1)自动故障转移

当集群中有一个master宕机会发生什么呢?

直接停止一个redis实例,例如7002:

redis-cli -p 7002 shutdown

1)首先是该实例与其它实例失去连接

2)然后是疑似宕机:

3)最后是确定下线,自动提升一个slave为新的master:

4)当7002再次启动,就会变为一个slave节点了:

(2)手动故障转移

利用 cluster failover 命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:

image-20210725162441407
redis-cli

cluster failover

这种failover命令可以指定三种模式:

  • 缺省:默认的流程,如图1~6歩
  • force:省略了对offset的一致性校验
  • takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见

5、RedisTemplate访问集群

RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

1)引入redis的starter依赖

2)配置分片集群地址

3)配置读写分离

与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

spring:
  redis:
    cluster:
      nodes:
        - 192.168.150.101:7001
        - 192.168.150.101:7002
        - 192.168.150.101:7003
        - 192.168.150.101:8001
        - 192.168.150.101:8002
        - 192.168.150.101:8003

五、慢查询

慢查询:在 Redis 执行时耗时超过某个阈值的命令,称为慢查询。不特指查询,新增修改删除可能也是慢查询。

慢查询的阈值可以通过配置指定

  • slowlog-log-slower-than:慢查询阈值,单位是微秒。默认 10000 ,建议改为1000

慢查询会被放入慢查询日志中,日志的长度有上限,可以通过配置指定:

  • ·slowlog-max-len·:慢查询日志(本质是一个队列)的长度,默认 128 ,建议 1000 ,应及时处理并清理

相关命令

redis-cli

# 查看慢查询日志最大长度
config get slowlog-max-len
# 查看慢查询阈值
config get slowlog-log-slower-than

# 修改慢查询日志最大长度
config set slowlog-max-len 1000
# 设置慢查询阈值
config set slowlog-log-slower-than 1000

查看慢查询日志列表

# 查询慢查询日志长度
slowlog len
# 读取 n 条慢查询日志
slowlog get [n]
# 清空慢查询列表
slowlog reset

image-20220328213608971

六、安全配置

  • 设置密码
  • 禁止线上使用下面命令:keys、flushall、flushdb、config set等,可以利用 rename-command 禁用。
# 修改配置文件 redis.conf
# 将 CONFIG 命令重命名为 abcdef ,以后就用 abcdef 替代 CONFIG
rename-command CONFIG abcdef
# 禁用 keys
rename-command KEYS ""
  • bind:限制网卡,禁止外网网卡访问
# 修改配置文件 redis.conf
bind 局域网网卡
  • 开启防火墙
  • 不要用 root 账号启动 redis
  • 不要使用默认端口

七、内存配置

当 redis 内存不足时,可能导致 key 频繁被删除、响应时间变长、QPS 不稳定等问题。当内存使用率达到 90% 以上时就需要我们警惕,并快速定位到内存占用的原因

内存占用 说明
数据内存 是redis最主要的部分,存储redis的键值信息。主要的问题是 BigKey 问题、内存碎片问题(重启解决)
进程内存 redis主进程本身运行肯定需要占用内存,如代码、常量池等;这部分内存大约几兆,在大多数生产环境中与 redis 数据占用的内存相比可以忽略
缓冲区内存 一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用 BigKey,可能导致内存溢出

查看内存分配状态

info memorymemory stats

内存缓冲区配置

内存缓冲区常见的有三种:

  • 复制缓冲区:主从复制的 repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。通过 repl-backlog-size 来设置,默认 1 mb
  • AOF缓冲区:AOF刷盘之前的缓冲区域,AOF执行 rewrite 的缓冲区。无法设置容量上限,但其中只存储redis的执行命令,一般不会占用过高
  • 客户端缓冲区:和客户端建立连接的缓冲区,分为输入和输出缓冲区,输入缓冲区最大 1 G且不能设置。输出缓冲区可以设置。

配置输出缓冲区

client-output-buffer-limit    
  • class:客户端类型
    • normal:普通客户端
    • replica:主从复制客户端
    • pubsub:发布订阅客户端
  • hard limit:缓冲区上限在超过 limit 后断开客户端
  • soft limitsoft seconds:缓冲区上限,在超过 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 

info client:查看所有客户端的输入输出缓冲区

client list:查看当前连接 reids 的客户端信息(主要看 omem参数)

  • omem : 输出缓冲区和输出列表占用的内存总量

八、缓存过期策略

键的过期方式有两种:被动方式 - 惰性删除,主动方式 - 定期删除。

redis的过期策略就是:定期删除 + 惰性删除

1、定期删除

定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。

假设redis里放了10W个key,都设置了过期时间,你每隔几百毫秒就检查全部的key,那redis很有可能就挂了,CPU负载会很高,都消耗在检查过期的key上。注意,这里不是每隔100ms就遍历所有设置过期时间的key,那样就是一场性能灾难。实际上redis是每隔100ms就随机抽取一些key来检查和删除的。

具体来说,如下 Redis 每秒 10 次: 1. 测试 20 个带有过期的随机键 2. 删除找到的所有已过期key 3. 如果超过 25% 的key已过期,从步骤 1 重新开始

这是一个微不足道的概率算法,基本上假设我们的样本代表整个key空间,继续过期,直到可能过期的key百分比低于 25%。 这意味着在任何给定时刻,使用内存的已过期的最大键量等于最大写入操作量/秒除以 4。

定期删除可能会导致很多过期的key到了时间并没有被删除掉。这个时候就可以用到惰性删除了。

2、惰性删除

惰性删除是指在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除,不会给你返回任何东西。

但即使是这样,依旧有问题。如果定期删除漏掉了很多过期的key,然后你也没及时去查,也就没走惰性删除。此时依旧有可能大量过期的key堆积在内存里,导致内存耗尽。

这个时候就需要内存淘汰机制了。

九、内存淘汰机制

redis内存淘汰机制有以下几个:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。这个一般很少用。
  • allkeys-lru:LRU算法,当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key,这个是最常用的。
  • allkeys-random:Random,当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:TTL,当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

end、注意事项

1、持久化配置建议

  • 用来做缓存的 Redis 实例尽量不要开启持久化配置
  • 建议关闭 RDB 持久化功能,使用 AOF
  • 利用脚本定期在 从节点 做 RDB ,实现数据备份
  • 设置合理的 rewrite 阈值,避免频繁的 bgrewrite
  • 配置 no-appendfsync-on-rewrite=yes,禁止在 rewrite 期间做AOF,避免线程阻塞,但是 rewrite 期间的数据无法进行 AOF 持久化,所以需要权衡利弊

2、部署相关建议

  • Redis 实例的物理机要预留足够的内存,应对 fork 和 rewrite
  • 单个 Redis 实例内存上限不要太大,例如 4G 或 8G 。可以加宽fork 的速度、减少主从同步、数据迁移压力
  • 不要与 CPU 密集型应用部署在一起
  • 不要与高硬盘负载应用一起部署。例如:数据库、消息队列

单体 Redis(主从 Redis) 已经能达到万级别的 QPS,并且具备很强的高可用性。尽量不搭建集群。


  目录