在默认情况下,redis将内存数据,快照保存在名字未dump.rdb的二进制快照文件中.
//你可以对redis.conf进行修改配置
//声明在60秒至少有1000键被改动,则自动保存一次快照集(关闭注释掉所以RDB快照配置即可)
save 60 1000
还可以手动执行命令生成RDB快照,进入redis客户端执行命令save或bgsave可以生成dump.rdb文件,
每次命令执行都会将所有redis内存快照到一个新的rdb文件里,并覆盖原有rdb快照文件。
字面意思,另起一个线程,进行(Copy-On-Write,COW)复制写入技术,在生成RDB快照文件的时候,仍然可以进行redis数据读写操作
简单来说,bgsave是由主线程fork生成的,享有主线程所有数据,并行,不影响主线程读写操作,
在bgsave执行的时候,会抓取主线程内存数据,并且把他写入RDB的快照文件,如果这个时候主线程在执行读操作,
那么主线程和bgsave子进程互不影响,但是如果主线程执行写操作的话,那么这块数据会被复制一份,生成一份副本,
然后bgsave会把该副本数据写入到RDB二进制文件中.而这个过程中,主线程依旧可以修改原来的数据
每次执行save或者bgsave都会把redis最新所有内存数据生成RDB文件,覆盖掉原来旧数据(所有说redis内存不应该配置过大)
命令 | save | bgsave |
---|---|---|
IO类型 | 阻塞 | 异步 |
是否阻塞redis其他命令 | 是 | 否 |
复杂度 | O(n) | O(n) |
优点 | 不会额外消耗内存 | 不会阻塞客户端命令 |
缺点 | 阻塞客户端命令 | fork子进程,消耗内存资源 |
默认生成RDB的配置方式是bgsave
RDB快照功能保存数据并不耐久(最新数据得不到很好的保障),存在redis挂掉之后最新数据丢失,旧数据修改无法及时保存修改操作等问题,
所有从1.1版本开始redis加入了AOF持久化的功能极大程度保证了数据耐久的问题(某些配置可以达到完全耐久的操作,消耗性能就是了)
AOF持久化:将修改的每一条记录写入appendonly.aof中(先写入os cache)根据自己的配置fsync到磁盘
比如 set user 666 aof文件里会记录如下数据
*3 //表示命令由多少个参数 set user 666(3个)
$3 //表示下面的参数字符长度
set
$4 //表示下面的参数字符长度
user
$3 //表示下面的参数字符长度
666
这个是一直resp的协议格式数据
注意,如果执行带过期时间的set命令,aof文件里记录的是并不是执行的原始命令,而是记录key过期的时间戳
比如执行“set date 888 ex 1000”,对应aof文件里记录如下
*3
$3
set
$4
date
$3
888
*3
$9
PEXPIREAT
$4
date
$13
1604249786301
可以通过修改redis.conf的文件中的
appendonly yes //开启aof持久化
从现在开始, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。
这样的话, 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。
你可以配置 Redis 多久才将数据 fsync 到磁盘一次。
appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。只有写入到磁盘之后才会返回告知客户端
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐使用 appendfsync everysec 配置既保证效率的同时也一定程度保住了数据的安全性
关于aof的重写机制,它存在就是为了优化之前的aof文件从而节省aof文件占用的磁盘空间,我们可以从上面的aof文件格式上面可以看出来,它这种写法虽然可读性高,但是很占空间
类似这种原子加的操作
127.0.0.1:6379> incr readcount
(integer) 1
127.0.0.1:6379> incr readcount
(integer) 2
127.0.0.1:6379> incr readcount
(integer) 3
127.0.0.1:6379> incr readcount
(integer) 4
aof的文件长这样
$4
incr
$9
readcount
*2
$4
incr
$9
readcount
*2
$4
incr
$9
readcount
*2
$4
incr
$9
readcount
其实目的不就是给readcount加4而已(看看执行重写后的aof文件长啥样)
//得先把 redis.conf文件修改下 aof-use-rdb-preamble no //默认yes,不修改的话,会被重写成aof的二进制文件
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
--------------------------------
*3
$3
SET
$2
readcount
$1
4
是不是简单很多
如下两个配置可以控制AOF自动重写频率
auto-aof-rewrite-min-size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就很快,重写的意义不大
auto-aof-rewrite-percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写(下一次触发是128m)
当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF
注意,AOF重写redis会fork出一个子进程去做(与bgsave命令类似),不会对redis正常命令处理有太多影响
重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志恢复,但是恢复 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。
通过如下配置可以开启混合持久化(必须先开启aof):
appendonly yes //开启aof
aof-use-rdb-preamble yes //这个就是执行aof重写之后aof会写成rdb的二进制文件内容
如果开启了混合持久化,那么redis重写数据的时候,就会把aof中的resp的协议格式数据写rdb的二进制数据格式,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。后续的其他命令会以aof的形式追加的rdb文件下面
如图
redis重启的时候,可以先加载aof文件中rdb的内容,然后在执行aof部分文件,这样可以大大提高redis重启的速度
写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
每次copy备份的时候,都把太旧的备份给删了
每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
准备两台服务器
1、配置主从复制
replicaof 192.168.3.60 6379 # 从6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica-read-only yes # 配置从节点只读
protected-mode no #关闭保护模式,开启的话,只有本机才可以访问redis
# 需要注释掉bind
#bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,
代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可,这里搭配不好会影响集群之间互相调用)
4、启动从节点
redis-server redis.conf
5、连接从节点
redis-cli -p 6379
6、测试在主节点实例上写数据,从节点实例是否能及时同步新修改数据
7、可以自己再配置一个193.168.3.62:6379的从节点
1.如果你的master配置了一个slave,不管这个slave是不是第一次连接master,它都会向master发送一个psync命令请求同步最新的一份数据(由slave向master发起socket连接)
2.当master收到由一个或多个slave的psync命令之后,都之后将自己内存中的数据,生成一份rdb快照文件,在持久化生成期间,如果有新的命令请求master的话,他会将这些命令缓存在自己的内存中,并不会一起加入持久化,等持久化结束之后,master会将最新的rdb文件通过socket长连接形式回传给请求连接的slave,salve收到这份新的数据之后,会清空自己原来的数据并且执行最新的rdb文件,将数据写入自己的内存中
3.最后master将之前缓存在内存中的指令,通过socket以指令形式发给slave,slave收到指令之后,执行指令
4.当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
1.当master和slave断开重连之后,2.8之前的版本一般都会对重新获取一份master数据进行全量更新,但是2.8版本开始,redis支持断点续传的功能,可以通过数据下标offset和master的进程id的方式定位最新文件位置方便重连之后可以实现部分数据续传功能(断点续传)
2.master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所以的slave都维护了复制的数据下标offset和master进程id,因此,当socket断开重连之后,slave会对master进行请求恢复数据,master会根据所记录的offset下标开始,如果master进程id改变或者下标太旧,无法追踪到缓存队列中的数据,则会进行全量更新一份最新数据
如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。
需要注意到是用管道(pipeline)方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。(说白了就是比较耗内存,因为要先缓存所有命令结果,然后客户端在一次性读取)
打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。
注意:pipeline中发送的每个command都会被server立即执行,哪怕失败了也会继续执行管道中后续指令,但是结果可以从后续的返回消息中获取,如果要实现事务的原子操作,可以用redis官方推荐的Redis Lua脚本
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过redis的批量操作命令(类似mset)是原子的。
3、替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。
从Redis2.6.0版本开始,通过内置的Lua解释器,可以使用EVAL命令对Lua脚本进行求值。EVAL命令的格式如下:
EVAL script numkeys key [key ...] arg [arg ...]
script参数是一段Lua脚本程序,它会被运行在Redis服务器上下文中,这段脚本不必(也不应该)定义为一个Lua函数。numkeys参数用于指定键名参数的个数。键名参数 key [key …] 从EVAL的第三个参数开始算起,表示在脚本中所用到的那些Redis键(key),这些键名参数可以在 Lua中通过全局变量KEYS数组,用1为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在Lua中通过全局变量ARGV数组访问,访问的形式和KEYS变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。例如
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
其中 “return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}” 是被求值的Lua脚本,数字2指定了键名参数的数量, key1和key2是键名参数,分别使用 KEYS[1] 和 KEYS[2] 访问,而最后的 first 和 second 则是附加参数,可以通过 ARGV[1] 和 ARGV[2] 访问它们。
在 Lua 脚本中,可以使用redis.call()函数来执行Redis命令
注意,不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点,将获取到的master节点的数据缓存在本地内存中,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端,client端在去将之前缓存在内存中的主节点数据进行更新(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
1、复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf
2、将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.3.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到
3、启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf
4、查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从
5、可以自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:
sentinel known-replica mymaster 192.168.3.60 6380 #代表redis主节点的从节点信息
sentinel known-replica mymaster 192.168.3.60 6381 #代表redis主节点的从节点信息
sentinel known-sentinel mymaster 192.168.3.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.3.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件的集群元数据信息,比如6379的redis如果挂了,假设选举出的新主节点是6380,则sentinel文件里的集群元数据信息会变成如下所示:
sentinel known-replica mymaster 192.168.3.60 6379 #代表主节点的从节点信息
sentinel known-replica mymaster 192.168.3.60 6381 #代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.3.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.3.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
同时还会修改sentinel文件里之前配置的mymaster对应的6379端口,改为6380
sentinel monitor mymaster 192.168.3.60 6380 2
当6379的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点加入集群
当一个redis的master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader(领导)进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟redis集群的master选举很类似。
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。只要有一个哨兵存活(唯一的leader),是允许对redis进行选举的
不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。