Redis原理详解及部署方式
一、Redis的线程模型
Redis 基于 reactor 模式开发了网络事件处理器你,这个处理器叫做文件事件处理器,file event headler。其是单线程的,因此 redis 才叫做单线程的模型。采用 IO多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的时间处理器来处理这个事件。如果被监听的 socket 准备好执行 accept、read、write、close 等操作的时候,跟操作对应的文件事件就会产生,这时候文件事件处理器就会调用之前关联好的事件处理器来处理这个事件。文件事件处理器是单线程模式运行的,但是通过IO多路复用机制监听多个 sokcet,可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 redis 内部的线程模型的简单性。
文件事件处理器的结构包含 4 个部分:
- 多个socket
- IO多路复用程序
- 文件事件分派器
- 事件处理器
- 命令请求处理器:写数据到 redis
-命令回复处理器:客户端要从 redis 读数据
-连接应答处理器:客户端要连接 redis
多个 socket 可能并发地产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 sokcet,会讲socket放入一个队列中排队,每次从队列中取出一个 socket 给事件分派器,事件分派器再把 socket 给对应的事件处理器。然后一个 socket 的时间处理完之后,IO多路复用程序才会将队列中的下一个 socket 给事件分派器。文件事件分派器会根据每个 socket 当前产生的事件,来选择对应的事件处理器来处理。
二、Redis的持久化
1.RDB
默认在Redis退出之前进行持久化操作(save),也可以在redis.conf文件中做触发机制save 900 1等
底层原理:bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据,完成fork后读取内存数据写入RDB文件
2.AOF
Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。
AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:
AOF的命令记录的频率也可以通过redis.conf文件来配置:
配置项说明:
两者对比:
RDB和AOF各有自己的优缺点,如果对数据安全要求较高,在实际开发中往往会结合两者来使用。
三、Redis过期删除策略(默认使用1、2的组合策略)
1. 惰性删除:设置key的过期时间,在查询的时候判断这个key是否过期,过期了就删掉
缺点:对内存不友好,如果这些key不使用,就一直在内存中,占用空间
2. 定期删除:每隔一段时间对key进行检查,如果key过期了就删掉
缺点:不能控制删除的时长和频率,对内存和CPU都不太友好
四、Redis内存淘汰策略
当内存达到上限时,可以设置LRU算法来释放无效的key
从设置了过期时间的key里扫描,以hash桶为维度扫描,最多扫20个key,一旦扫到某个hash桶必须扫完这个桶里的所有key,如果删除比例超过10%,会继续扫描并删除过期的key,但是最多扫描16次。
Redis中提供了8种内存淘汰策略:
- volatile-lru:针对设置了过期时间的key,使用LRU算法进行淘汰
- allkeys-lru:针对所有key使用LRU算法进行淘汰
- volatile-lfu:针对设置了过期时间的key,使用LFU算法进行淘汰
- allkeys-lfu:针对所有key使用LFU算法进行淘汰
- volatile-random: 从设置了过期时间的key中随机删除
- allkeys-random: 从所有key中随机删除
- volatile-ttl:删除生存时间最近的一个键
- noeviction(默认策略):不删除键,返回错误OOM,只能读取不能写入
通过修改 maxmemory-policy 配置来设置淘汰策略,默认noeviction 核心本质:选择哪些数据进行清理,选择什么方式去清理数据
五、Redis部署方式:
5.1 Redis主从部署
主从部署结构
1.全量同步
- Slave节点与Master建立连接,请求增量同步
- Master判断repid,发现不一致,拒绝增量同步并返回主节点repid和offset
- Master将完整内存数据生成RDB文件,并发送RDB文件到Salve
- Slave清空本地内存数据,加载Master发送的RDB文件
- Master将在RDB期间的命令记录在repl_baklog中,并持续将log中的命令发送给Slave
- Slave执行接收到的命令,保持与Master之间的同步
2.增量同步
- Slave节点与Master建立连接,请求增量同步
- Master判断repid,发现一致
- Master去repl_backlog中获取offset后的数据
- Master发送offset后的命令给Slave
- Slave执行接收到的命令,保持与Master之间的同步
3.repl_backlog原理
master怎么知道slave与自己的数据差异在哪里呢?
这就要说到全量同步时的repl_baklog文件了。
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset:
slave与master的offset之间的差异,就是salve需要增量拷贝的数据了。
随着不断有数据写入,master的offset逐渐变大,slave也不断的拷贝,追赶master的offset:
直到数组被填满:
此时,如果有新的数据写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。
但是,如果slave出现网络阻塞,导致master的offset远远超过了slave的offset:
如果master继续写入新数据,其offset就会覆盖旧的数据,直到将slave现在的offset也覆盖:
棕色框中的红色部分,就是尚未同步,但是却已经被覆盖的数据。此时如果slave恢复,需要同步,却发现自己的offset都没有了,无法完成增量同步了。只能做全量同步。
针对增量同步的优化:
- 可以从以下几个方面来优化Redis主从就集群:
- 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘10。
- Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
- 适当提高repl baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
5.2 Redis的哨兵机制
哨兵的结构如图:
Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵的结构和作用如下:
- 监控:Sentinel会不断检查您的master和slave是否按预期工作
- 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
- 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端
![在这里插入图片描述](https://img-blog.csdnimg.cn/ae6d10f271fa471fa62447a6da63b060.png#pic_center
1.集群监控原理
Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:
主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
2.集群故障恢复原理
一旦发现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 192.168.150.101 7002 命令,让这些slave成为新master的从节点,开始从新的master上同步数据。
- 最后,sentinel将故障节点标记为slave,当故障节点恢复后会自动成为新的master的slave节点
5.3 Redis分片集群
主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:
- 海量数据存储问题
- 高并发写的问题
使用分片集群可以解决上述问题,如图:
分片集群特征:
集群中有多个master,每个master保存不同数据
每个master都可以有多个slave节点
master之间通过ping监测彼此健康状态
客户端请求可以访问集群任意节点,最终都会被转发到正确节点
1.散列插槽
Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上,查看集群信息时就能看到:
数据key不是与节点绑定,而是与插槽绑定。redis会根据key的有效部分计算插槽值,分两种情况:
- key中包含"{}",且“{}”中至少包含1个字符,“{}”中的部分是有效部分
- key中不包含“{}”,整个key都是有效部分
例如:key是num,那么就根据num计算,如果是{key1}num,则根据key1计算。计算方式是利用CRC16算法得到一个hash值,然后对16384取余,得到的结果就是slot值。
如上图,在7001这个节点执行set a 1时,对a做hash运算,对16384取余,得到的结果是15495,因此要存储到103节点。
到了7003后,执行get num
时,对num做hash运算,对16384取余,得到的结果是2765,因此需要切换到7001节点
2.转移插槽
我们要将num存储到7004节点,因此需要先看看num的插槽是多少:
如上图所示,num的插槽为2765.
我们可以将0~3000的插槽从7001转移到7004,命令格式如下:
具体命令如下:
建立连接:
得到下面的反馈:
询问要移动多少个插槽,我们计划是3000个:
新的问题来了:
哪个node来接收这些插槽??
显然是7004,那么7004节点的id是多少呢?
复制这个id,然后拷贝到刚才的控制台后:
这里询问,你的插槽是从哪里移动过来的?
- all:代表全部,也就是三个节点各转移一部分
- 具体的id:目标节点的id
- done:没有了
这里我们要从7001获取,因此填写7001的id:
填完后,点击done,这样插槽转移就准备好了
然后,通过命令查看结果:
3. 自动故障转移
集群初识状态是这样的:
其中7001、7002、7003都是master,我们计划让7002宕机。
直接停止一个redis实例,例如7002:
redis-cli -p 7002 shutdown
- 首先是该实例与其它实例失去连接
- 然后是疑似宕机:
- 最后是确定下线,自动提升一个slave为新的master:
- 当7002再次启动,就会变为一个slave节点了:
4.手动故障转移
利用cluster failover命令可以手动让集群中的某个master宕机,切换到执行cluster failover命令的这个slave节点,实现无感知的数据迁移。其流程如下:
这种failover命令可以指定三种模式:
- 缺省:默认的流程,如图1~6歩
- force:省略了对offset的一致性校验
- takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见