目录
单节点存在问题:
数据安全问题
:redis基于内存存储,如果主机损坏且没有进行数据备份的话,会造成数据丢失。即使进行备份,如果RDB后突然断电,那么在这期间后主机上的写数据将全部丢失并发能力问题
:虽然redis是基于内存存储,并发能力很强。但毕竟是单节点,并发能力有限故障恢复问题
:单节点一但出现故障,就会影响服务。redis负责的功能将不能自行恢复存储能力问题
:单个机器的内存是有限的
redis持久化
redis是基于内存存储的,内存的存储会导致:断电即失、数据不可迁移备份
RDB
RDB
即 Redis Database Backup file (redis数据备份文件) 也为 快照机制 默认开启
将某一时刻
的数据全部一起写入磁盘文件中 。redis重启后会读取磁盘文件的数据放入内存,使得恢复至那一时刻
。且备份文件可以移动,使得多个redis都可以恢复至那一刻,达到备份的效果。RDB在数据恢复时较快
备份命令:
save
- 缺点:此命令是由主进程进行执行的,在执行时会进行
阻塞
,在执行完成之前,其他命令都不可以执行。但保存到磁盘是一个IO
操作,又比较耗时 - 适用场景:在redis手动关机前进行备份
- 缺点:此命令是由主进程进行执行的,在执行时会进行
bgsave
- 开启子线程进行一步保存,避免主线程受到影响
- 适用场景:在redis自动备份时
RDB文件存放目录:默认存放在 运行redis时所在的目录,可在配置文件中修改
RDB 触发
redis在正常关机前会进行一次 save
- 每隔一段时间触发 ,频率可修改
- 在进行全量同步时会触发
若要关闭 RDB 可以将其注释并添加 save “”
RDB的配置
- 是否开启间隔保存?默认开启,关闭需要写入
save ""
- RDB文件名称:
- RDB是否进行压缩:压缩会耗费CPU资源和时间,但节省磁盘空间 通常选择不压缩,更多时候相比来说cpu资源(性能)更重要
- 保存RDB文件的路径
RDB异步持久化原理
- 执行bgsave命令时,主进程会fork一个子进程(此期间阻塞)用来执行异步IO保存主进程中的数据。但子进程如何访问主进程的数据呢?
主进程访问内存使用页表,将页表进行拷贝给子进程,从而使得子进程也能够操作同一个内存地址的数据,这时共享的内存数据将会变成只读数据,防止主进程在复制时数据发生修改。
- 如果这时主进程有写入,就会查找写入数据的地址然后进行单独复制一份进行写入(copy-on-write技术),防止直接在共享中内存写入导致数据不一致问题
但这样存在的一个极端条件下的问题:如果在子进程IO内存数据期间,所有数据都发生改动,会导致从共享数据中拷贝同样大小的那些数据用作修改,会导致内存突然翻倍,所以要留充足的内存,尽量避免内存溢出 - 将子进程写完的文件
替换
原有文件
RGB缺点:
io读写需要耗费性能与内存,不能过于频繁。但如果在每次备份间隔期间发生宕机,会导致在此时与上次备份这期间的新增或修改的数据全部丢失。数据可能会丢失一部分,不能用于RDB存储对数据安全有要求的。
AOF
AOF:Append Only File 每一个写命令都会被记录在AOF文件,相当于数据操作日志。在恢复时由于记录中之前所有增删改
命令,可以再次从头到尾执行这些命令,从而恢复数据 。默认不会开启
,由主进程进行操作
由于数据持久化频率比RDB频率高,因此可用作对RDB数据丢失风险的一种弥补。但AOF仍有频率间隔(默认每秒),在这期间仍然有可能发生数据丢失。
AOF记录的写数据的操作过程,而RDB记录的是最终数据结果。所以AOF文件会比RDB大很多
AOF触发
在开启Aop的条件下:
AOF的配置
- 开启AOF:
- 修改AOF文件名
- 修改AOF重写阀值
AOF重写
由于AOF记录写过程,文件会很大。但这些过程如果其中有对一个key多次进行修改那么只需要记录最后一次即可,其他关于key的记录将变得毫无意义。所以当记录一定数量命令时,要考虑去除无意义的命令,并考虑合并命令使得用最少的命令达到相同的效果
,减少文件体积
例如:
AOF可以使用命令 bgrewirteaof
异步执行重写
也可以配置 使得符合条件时,自动触发AOF重写
在重写期间产生的数据会放入缓冲区,当重写完毕后,进行追加。
AOF重写触发
如何解决AOF的子进程在重写aof文件时,数据发生变化?
Redis 引入了 AOF 重写缓冲区(aof_rewrite_buf_blocks),这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,它会同时将这个写命令追加到 AOF 缓冲区和 AOF 重写缓冲区。
二者对比
主从集群
主从数据一致,将读写进行分离,提升读并发能力
(不能提升 储存能力,因为空间并未扩容。不能提升 写能力,因为只有master能够写,而master数量没有变)
搭建集群
搭建三台集群步骤
规划 同一个主机通过更改端口运行三个redis:
IP | PORT | 角色 |
---|---|---|
本机 | 7001 | master |
本机 | 7002 | slave |
本机 | 7003 | slave |
-
创建三个目录 拟三个不同的主机 将原始配置文件拷给三个目录(不同主机可省略)
-
修改各自目录下的 端口号 和 工作目录 (不同主机可省略)
- 使用命令批量替换
sed -i -e 's/6379/目标端口/g' -e 's/dir .\//dir(原内容) (目标目录)/g' (要替换的目录 每级目录使用 \/ 相隔) (要修改的文件路径)
例 :
可一键完成当前文件对
端口
,以及工作目录
的修改 -
修改每个配置文件的ip ,即使是同一个主机下也要声明出来,避免自动读取的ip不一致
修改配置replica-announce-ip : 本机ip
sed -i '1a replica-announce-ip 主机ip' 配置文件路径
注意 :在搭建集群前,一定要将密码关闭 ,或者在从节点配置文件中 加上主节点的密码 “masterauth 密码”!!!
然后 将三个redis服务器启动
使用redis-cli -p 各自的端口 连接服务器
开启主从联系
此时,服务器间彼此毫无关系,独立的三个redis,要想形成主从集群,要将他们进行连接。只需slave指明是谁的从节点即可。
- 临时配置 在命令窗口中 输入
slaveof <masterIp> <masterPort>
- 永久配置 在redis配置文件 添加
slaveof <masterIp> <masterPort>
整体流程:先建立三个不同的redis,在通过 slaveOf masterIp masterPort 将他们进行主从连接
主从数据同步原理
第一次同步为全量同步,但怎样判断是不是第一次同步呢?
根据 replid
即 Replication Id :每一个master都有唯一的replid,而slave会继承master的replid,使得每一个数据集都会有唯一的replid。
所以,当有节点发起数据同步请求时,master只需要判断其replid与自己的是否一致。若不一致,说明是第一次同步,因为只要进行过同步就会一致。 若一致,说明与其在同一个数据集,已经不是第一次同步,无需将自己的全部数据都给与slave节点(全量同步
)。只需要判断salve与自己数据相差多少(偏移量 offset
) 。主机自己的偏移量会记录在repl_baklog(将要进行同步给salve的数据缓存在这里)中数据增多而增大,每次同步slave会记录自己同步数据的offset,当有新数据自己还未更新时,repl_backlog中的偏移量会大于slave的偏移量。
同步过程
- salve与master建立正常连接
- salve 会 发起
sync
同步请求,请求中携带自己的replid
和offset
给master节点。 - master节点接收到请求中的
replid
去判断与自己replid的是否一致。若不一致则是第一次同步,要进行全量同步
。若一致,则要进行增量同步
- 全量同步过程:性能较差
- master进行一次
bgsave
(fork一个子线程去做),将自己所有的数据写入一个RDB文件,发送给salve节点,salve节点接收到全量同步会将自己的数据全部清除,迎接新的数据 - 在写RDB时,将有关 写数据的命令 暂时放入到
repl_baklog
。 - 将RDB发送给salve后,再将在自己的repl_backlog中的数据持续传给salve节点,salve不断接收命令,不断去执行,使得主从之间数据能够一致(但此过程不叫增量同步,仍是一次连接)不是断开后再进行同步 就不是增量同步
增量同步(常发生在从节点宕机重连时)
可能是salve 发生宕机,使得与master已经连接过,再失去联系,等待其正常后,会再次向master发起以上三点 - master判断不是第一次请求,会向其发送一个
continue
命令(因为salve不知道自己是不是第一次) - 然后master将请求中的offset与自己的repl_backlog去做对比,判断是否相差的数据都在自己的repl_backlog中数据的范围内
- 若相差已经超过repl_backlog中的所有数据,导致缺失的数据被新数据覆盖,同步缓存中找不到,只能去内存中查找,此时重新进行增量同步 (从上面第4点开始)。若相差数据都在repl_backlog中,则将差的数据进行同步。
整体流程
数据同步优化
数据优化主要在 全量同步,因为需要进行阻塞fork、IO操作
- 在master中 ,同步时采用 无磁盘IO
repl-diskless-sync设置为yes
,将数据不再写入磁盘而是转为流通过网络进行传输,但要有足够的网络带宽(增大全量同步效率) - 尽量增大repl_backlog ,避免增量同步时,主节点缓存传输数据空间不足,数据被覆盖导致采用较慢的
全量同步
(减少使用全量同步) - 限制master节点管理slave节点的数量,避免过多的节点数据同步影响master节点的性能。若仍需要很多的从节点,可以采用master-salve-salve 使得从节点同步从节点,减轻master的压力
- 单个节点内存不要过高,过高会导致服务器后期数据量大,一次RDB,文件会非常大且耗时
哨兵机制
集群当前如果有master节点宕机,集群是不能自己进行恢复的 ,需要人为手动恢复。而引入哨兵,可以实现监控而自动恢复功能,可为客户端对集群的连接提供方便。
哨兵们(哨兵也要为集群) 检测
到master宕机后,从salve中选举
一个作为master,使得集群立即恢复
正常运转,并将宕机的master置为salve
哨兵作用:
状态监控
不断检测 master与slave的正常
- 基于心跳机制检测,每隔哨兵 每隔一秒向集群每一个节点发送一个ping,等待收到一个pong。若某个哨兵发送给某个节点的ping 超时了仍未收到恢复,则这个哨兵认为这个节点宕机了
主观下线
- 若其他哨兵也为收到此节点的恢复,且未收到回复的哨兵们已经超过
一半
(数量自己设置)了。则认为是客观下线
- 被认为是客观下线后,就要采取行动, 从slave中 选取一个节点作为master
故障恢复
master宕机后,要从slave中选出一个承担master的作用,实现集群的恢复
先去选举一个哨兵去做master选举
看谁先发宕机选举权就交给哪个哨兵
选取master的标准
- 先进行排除 :排除掉与master 失去连接过长的(down-after-milliseconds*10)。即使没有slave了,也不能使用,因为失去连接时间过长,可能有大量数据丢失,过旧
- 判断slave的优先级(slave-priority),是否已经人为指定顺序 .为0 则不参与选举
对比各个slave的offset ,值越大 越说明当前值与master越接近
,通常是选举中最有决定性的- 比较运行id大小,此时slave已经没有区别,只要有一个就行
哨兵们选取出哪一个slave作为master后,向其slave发送一个命令 slaveof no one
告诉其永不为奴,
然后发布通知
告诉所有slave ,master是谁,使得再次以恢复主从集群。
为了使得老master恢复后,不影响当前状态,在其配置文件中打上 slave印记,使其醒来后做slave
通知
集群主从关系发生了变化,负责写的master地址已经发生变化,有的节点甚至不可用。但客户端怎么知道呢?因此需要用到sentinel的一个通知功能(路由
)。相当于一个网关,客户端只连接哨兵,而不关心集群的具体主机,在访问主机资源时,只需要先去访问哨兵,又哨兵再找到具体对应的主机。这样即使master地址发生变化,也无需客户端关心。
但如果哨兵宕机了怎么办?哨兵采用的是集群,在客户端配置的也是集群中各个哨兵的地址。如果其中有一个宕机,还可以去问下一个
搭建哨兵集群
哨兵与节点不是一个共同体,是一个redis包下一个单独的应用
由于模拟搭建都在一个主机,通过更改其端口号,使其各自能够独立运行
整体规划
节点 | IP | PORT |
---|---|---|
s1 | 本机 | 27001 |
s2 | 本机 | 27002 |
s3 | 本机 | 27003 |
为每个哨兵单独创建一个目录,然后在目录中创建各自的配置文件 sentinel.conf
哨兵虽然监控集群每一个节点,但只要配置master节点,就可以知道其slave的信息
,每一个哨兵只有端口、工作目录 不同,将配置文件这两处进行修改,然后放各自的目录
启动哨兵集群
测试 :当master宕机,观察sentinel的变化
当老的master重新启动时,发现自己已经变成了slave
客户端连接主从集群
- 客户端不再去连接主节点的地址,而是去连接哨兵集群
- 指明集群环境下采用的读写策略 ,因为配置主从集群的目的就是实现读写分离
配置好哨兵集群后,哨兵会自动找到主节点与从节点,客户端无需关系集群内部信息
分片集群
解决单节点不能内存过大(RDB过大),且数据量很大时的存储问题
。解决高并发环境下 并发写的问题
,同时具有哨兵功能,实现高可用
主从集群数据是相同的,并未提升数据存储能力,且从节点都是用来读的,只有一个master节点负责写,未提升写的能力
分片集群集合主从集群可以解决并发写、并发读、高存储容量、高可用性
常用命令:
- 添加节点 redis-cli --cluster add-node 【主机Ip】:【端口】【集群中某一个主机Ip】:【其端口】 将一个独立运行的节点添加到集群
- 查看集群信息:
cluster nodes
- 分配插槽:
redis-cli --cluster reshard
【集群中任意一个主机ip】:【其端口】确保知道要分配哪个集群的插槽 - 删除一个节点
redis-cli --cluster del-node 【集群中任意一个主机ip】:【其端口】【要删除的节点id 】
在删除前,要注意其插槽 - 连接集群的主机 :要在普通连接命令中加上 -c
搭建分片集群
三个master之构成分片集群,同时每一个master有自己的主从集群
整体安排
IP | PORT | 角色 |
---|---|---|
本机 | 7001 | master |
本机 | 7002 | master |
本机 | 7003 | master |
本机 | 8001 | slave |
本机 | 8002 | slave |
本机 | 8003 | slave |
- 修改配置文件
将cluster-enabled
设为 yes,同时指定自动生成集群文件存放路径cluster-config-file /xxx/xxx/nodes.conf
(每个节点集群的一些信息会放在这里,要保证每一个节点都有自己的一分 nodes.conf)
例 :简化版的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
- 此时他们是具有集群功能的各个独立的主机,需要将他们组建成一个集群
使用命令redis-cli --cluster create --cluster-replicas 副本数量 master1地址及端口 master2地址及端口 ... slave1地址及端口 slave22地址及端口..
一键创建关系
由于指定了每个master从节点数量,先写master们地址,再写slave们地址 就会自动分辨出谁是谁的从节点
- 连接集群某一个节点是,不光要指定集群中某个主机的
ip
(-p),还要加上 集群模式 (-c)
散列插槽
采用分片集群提升了redis整体的存储能力,但redis是怎样将一个数据存入带有三个master集群的呢?数据是从一个存满再存下一个显然是不可能的,要将数据尽量均匀的分布在三个集群。但怎样进行分配呢?如何记录数据在哪个节点上呢?
使用散列插槽机制
。将集群中master的存储容量划分为 16384个插槽,将数据进行哈希散列后对16384取余
即可获取数据存放的插槽。有了插槽机制,使得集群中内存利用的更加充分,能够将数据进行较为均匀的分布,也能查找时,通过哈希计算得出数据存放位置,免得去保存每一个key所在位置。同时,也可以使得数据与插槽绑定,模块化 方便迁移
数据不是与节点进行绑定,而是与插槽进行绑定。数据可以随着插槽移动进行移动,方便查找数据的同时也方便集群的扩充和缩减
redis会将key的有效部分
进行计算(通过crc16
算法 获取哈希值 并处以16384),获取插槽位置
有效部分分为两种情况:
- 不带
{}
,将整个key都作为有效部分 - 带
{}
,只将{}中的内容作为有效部分,因此要想使得 不同的key能够放在同一个插槽(如果可能会连续记性访问,尽量放入一个插槽。如果不在一个主机,还需要进行重定向到另一个主机,耗费时间),所以尽量使得可能连续的key{}
中的内容相同
例:
get key 过程
总结:
如果有一个插槽无法正常使用 ,整个集群将不可用。例:只有一个插槽的master宕机,且没有从节点,无法自主恢复,整体集群将不可用
集群伸缩
- 添加节点 redis-cli --cluster add-node 【主机Ip】:【端口】【集群中某一个主机Ip】:【其端口】
在添加节点时需要指定要添加的节点信息以及要入集群的介绍人。不指定参数默认为master。要添加从节点需要添加--cluster-slave
,若要指定添加到哪个master,需要指定其master的id--cluster-master-id 【masterId】
在添加前,确保配置中已经打开集群模式,能够独立运行。然后使用 redis-cli --cluster
命令将其添加到集群
但后加入的节点没有插槽,要将原有插槽进行分配
- 分配插槽
redis-cli --cluster reshard
【集群中任意一个主机ip】:【其端口】确保知道要分配哪个集群的插槽
例:
- 先修改配置文件,开启集群模式,然后启动
- 将此节点添加到集群
- 为新加入的节点分配插槽,由于 key为num的值放在了 2765号插槽中,因此需要将此插槽移动到新master
- 分配插槽
将7001的 2800个插槽移动到 7004 中 。因为num在第2765个插槽,所以也会移动到新节点中。
例:删除7004节点
5. 将7004节点的所有插槽进行转移到其他节点
6. 将 7004 节点关机
7. 使用命令 redis-cli --cluster del-node 集群联系人 要删除的节点id
彻底删除一个节点
如果直接关机,如果7004没有slave且自己又有插槽数据,将会直接导致整个集群不可用
故障转移
与哨兵机制相同,可以看做在分片集群中,master之间集成了哨兵集群,具有相同的功能
有任何一台master宕机会立即被监测出来,使用它的slave来替代它
也可以在master完好的情况下手动完成 master
与 slave
的互换 常用于 升级master主机,向让新设备成为master的slave,在手动让二者身份互换。这样 老的master 就可以作为slave或停机。在其从节点
使用cluster failover
完成手动切换身份
failover 中文翻译:故障切换
具体细节:
可选参数
- 在其
cluster failover
后面添加上force
不会在移交期前 对offset进行校验,可能导致从节点数据不全 - 在其
cluster failover
后面添加上takeove
,将只有第6步,直接通知所有节点。忽略所有准备,类似于master宕机后,继位的从节点的行为。可能导致数据丢失,数据不一致。