redis内容
redis.mindnode.zip
redis
数据类型
string
最基本类型,做简单的kv缓存
hash
类似map的结构,比如一个对象给缓存在redis里,每次读写的时候可以操作对象里的某个属性
list
有序列表,例如:微博某个大V的粉丝,就可以以list的格式放在redis里去缓存
通过list存储一些列表型的数据结构,类似粉丝列表了、文章的评论列表了之类的东西
通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询
可以搞个简单的消息队列,从list头怼进去,从list尾巴那里弄出来
set
无序集合,自动去重
可以基于set玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁
sorted set
排序的set,去重但是可以排序
比如说你要是想根据时间对数据排序,那么可以写入进去的时候用某个时间作为分数,人家自动给你按照时间排序了
数据分布算法
解决把整个数据集按照分区的规则映射到多个节点的问题。即把数据划分到多个节点上,每个节点负责整体数据的一个子集
哈希分区
使用特定的数据(包括redis的键或用户ID),再根据节点数量N,使用公式:hash(key)%N计算出一个0~(N-1)值,用来决定数据映射到哪一个节点上。即哈希值对节点总数取余。余数x,表示这条数据存放在第(x+1)个节点上
优点
简单
缺点
当节点数量N变化时(扩容或者收缩),数据和节点之间的映射关系需要重新计算,这样的话,按照新的规则映射,要么之前存储的数据找不到,要么之前数据被重新映射到新的节点(导致以前存储的数据发生数据迁移)。这是难以接受的
一致性哈希分区
把所有的数据包括节点数据都放在一个哈希环上,我们除了需要计算要存储的数据的key的hash之外,还要计算节点的hash,然后在存储时,选择一个跟key的hash最接近的节点(顺时针找到第一个大于等于该哈希值的节点),存储进去
优点
加入和删除节点只影响哈希环中相邻的节点,对其他的节点无影响
缺点
加减节点,会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景
当使用少量节点时,节点变化将大范围影响哈希环中数据映射,因此这种方式不适合少量数据节点的分布式方案
普通的一致性哈希分区在增加节点时需要增加一倍或减去一半节点才能保证数据和负载的均衡
虚拟槽分区
虚拟槽分区就是为了解决一致性哈希分区的不足而被创造的。虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,比如redis Cluster槽的范围是0-16383,一共16384个槽。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽(虚拟节点),之前我们在介绍一致性哈希的时候,是将物理节点直接通过哈希运算得到其hash值,而后数据的key计算出来之后,与节点的哈希进行比较,决定存放在哪个节点中.而现在,我们用几个槽(虚拟节点)代表一个物理节点.不同槽(虚拟节点)的hash是通过不同的哈希函数计算出来的。以redis为例子,假设现在我们有5个节点,一共分配16384个槽(虚拟节点)
redis把数据分布到不同节点带来集群功能的限制
key批量操作支持有限
如mset,mget,目前只支持 具有相同slot值得key执行批量操作。对于映射为不同slot值得key由于执行mset,mget等操作可能存在于多个节点上因此不被支持
key事物操作支持有限
同理只支持多个key在同一个节点上的事物操作,当多个key分布在不同的节点上时无法使用事物功能
key作为数据分区的最小粒度
因此不能将一个大的键值对象如hash,list等映射到不同的节点
不支持多数据库空间
单机下的redis可以支持16个数据库,集群模式下只能使用一个数据空间,即db 0
复制结构只支持一层
从节点只能复制主节点,不支持嵌套树状复制结构
线程模型
文件事件处理器
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型
文件事件处理器是单线程模式运行的,但是通过IO多路复用机制监听多个socket,可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了redis内部的线程模型的简单性
多个socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,但是会将socket放入一个队列中排队,每次从队列中取出一个socket给事件分派器,事件分派器把socket给对应的事件处理器,然后一个socket的事件处理完之后,IO多路复用程序才会将队列中的下一个socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理
如果是客户端要连接redis,那么会为socket关联连接应答处理器 如果是客户端要写数据到redis,那么会为socket关联命令请求处理器
如果是客户端要从redis读数据,那么会为socket关联命令回复处理器
文件事件
AE_READABLE
当socket变得可读时(比如客户端对redis执行write操作,或者close操作),或者有新的可以应答的socket出现时(客户端对redis执行connect操作),socket就会产生一个AE_READABLE事件
AE_WRITABLE
当socket变得可写的时候(客户端对redis执行read操作),socket会产生一个AE_WRITABLE事件
IO多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件,要是一个socket同时产生了AE_READABLE和AE_WRITABLE两种事件,那么文件事件分派器优先处理AE_READABLE事件,然后才是AE_WRITABLE事件
客户端与redis通信的一次流程
在redis启动初始化的时候,redis会将连接应答处理器跟AE_READABLE事件关联起来,接着如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来
当客户端向redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在socket产生一个AE_READABLE事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理
接着redis这边准备好了给客户端的响应数据之后,就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入socket,供客户端来读取
命令回复处理器写完之后,就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系
为啥单线程模型效率也能那么高
纯内存操作
核心基于非阻塞IO多路复用机制
单线程避免了多线程的频繁上下文切换问题
缓存过期策略
定时删除
在设置key的过期时间的同时,创建一个定时器(timer),让定时器在key的过期时间来临时,立即执行对key的删除操作
优点
对内存友好,保证内存被尽快释放
缺点
对CPU时间最不友好。若过期key过多时,删除过期key会占用很多的CPU时间,(当内存不紧张而CPU紧张时)会对服务器的响应时间和吞吐量造成影响
惰性删除
放任key过期不管,但是每次去数据库获取key的时候去检查是否过期,若过期,则删除,返回null.
优点
对CPU时间友好。程序只会在取key的时候对key进行过期检查,这可以保证删除过期key的操作只会在非做不可的情况下进行(如果此时还不删除的话,我们就会获取到了已经过期的key),并且删除的目标仅限于当前处理的key
缺点
若大量的过期的key没有再次被访问,从而不会被清除,这样会占用大量内存,而且服务器不会自己去释放它们
定期删除
每隔一段时间,程序就对数据库进行一次检查,删除里面过期的key
优点
定时删除占用太多CPU时间,影响服务器的响应时间和吞吐量
惰性删除浪费太多内存,有内存泄漏的危险
定期删除就是前两种策略的折中。
服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。
淘汰策略
noeviction
不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )
allkeys-lru
所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key
allkeys-random
所有key通用; 随机删除一部分 key
volatile-lru
只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key
volatile-random
只限于设置了 expire 的部分; 随机删除一部分 key
volatile-ttl
只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key
持久化
持久化意义
主要是做灾难恢复,数据恢复,防止数据丢失
RDB
工作原理
对redis中的数据执行周期性的持久化,生成redis内存中的数据的一份完整快照
优点
RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备
RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子 进程执行磁盘IO操作来进行RDB持久化即可
相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速
缺点
如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟, 或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据
RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒
AOF
工作原理
对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启 的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集
优点
AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据
AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复
AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可
AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据, 只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
缺点
对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多
RDB与AOF如何选择
1、不要仅仅使用RDB,因为那样会导致你丢失很多数据
2、也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
3、综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复
4、如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整
redis实现高并发
读写分离
一主多从,主负责写,从负责读,从节点可以进行水平扩容实现高并发
主从架构
redis replication基本原理
redis replication --> 主从架构 --> 读写分离架构 --> 水平扩容支撑读高并发架构
redis replication核心机制
redis通过异步方式将数据复制到slave
一个master配置多个slave,slave做复制的时候,不阻塞master node正常工作,不会阻塞自己的查询操作
slave node用来横向扩容,做读写分离,扩容的slave node可以提供读的吞吐量
主从复制核心原理
启动一个slave node的时候,它会发送一个PSYNC命令给master node 如果是slave重连master,master仅仅会复制slave部分缺少的数据
如果是第一次连接,那么会触发master的full resynchronization全量同步
全量复制
master执行bgsave,在本地生成一份rdb快照文件
master node将rdb快照文件发送给slave node,如果rdb复制时间超过60秒(repl-timeout),那么slave node就会认为复制失败,可以适当调大这个参数
master node在生成rdb时,会将所有新的写命令缓存在内存中,在salve node保存了rdb之后,再将新的写命令复制给salve node
client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区持续消耗超过64MB,或者一次性超过256MB,那么停止复制,复制失败
slave node接收到rdb之后,清空自己的旧数据,然后重新加载rdb到自己的内存中,同时基于旧的数据版本对外提供服务
如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF
增量复制
如果全量复制过程中,master-slave网络连接断掉,那么salve重新连接master时,会触发增量复制
master直接从自己的backlog中获取部分丢失的数据,发送给slave node,默认backlog就是1MB
msater就是根据slave发送的psync中的offset来从backlog中获取数据的
数据同步机制
master和slave都维护一个offset
master会在自身不断累加offset,slave也会在自身不断累加offset slave每秒都会上报自己的offset给master,同时master也会保存每个slave的offset
通过master和slave中的offset的差异性,才能知道数据不一致的情况
backlog
master node有一个backlog,默认1MB大小 master node给slave node复制数据时,也会将数据在backlog中同步一份
backlog主要是用来做全量复制中断后的增量复制
master run id
info server命令,可以看到master run id 根据host+ip定位master node不靠谱,如果master node重启或者数据发生了变化,slave node应该根据run id来区分,run id 不同就做全量复制
psync
从节点使用psync从master node进行复制,psync runid offset master node会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,可能是CONTINUE触发增量复制
断点续传
master node会在内存中维护一个backlog,master和slave都会保存一个replica offset还有一个master id,replica offset保存在backlog中,如果master和slave连接中断,恢复网络后,slave会让master从上一次的replica offset开始继续复制,如果找不到对应的offset,那么就会执行一次full resynchronization
redis实现高可用
redis哨兵架构
哨兵
redis高可用架构,叫做故障转移,failover,也可以叫做主备切换
主要功能
集群监控,负责监控redis master和slave进程是否工作
消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
故障转移,如果master node挂掉了,那么会自动转移到slave node上
配置中心,如果故障转移发生了,通知client新的master地址
哨兵集群
故障转移时,判断一个master node是宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了
核心知识
哨兵至少需要3个实例,来保证自己的健壮性
哨兵 + redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性
对于哨兵 + redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练
为什么哨兵集群只有2个节点无法正常工作?
如果哨兵集群仅仅部署了个2个哨兵实例,quorum=1
master宕机,s1和s2中只要有1个哨兵认为master宕机就可以执行切换,同时s1和s2中会选举出一个哨兵来执行故障转移
同时这个时候,需要majority,也就是大多数哨兵都是运行的,2个哨兵的majority就是2,2个哨兵都运行着,就可以允许执行故障转移
但是如果整个M1和S1运行的机器宕机了,那么哨兵只有1个了,此时就没有majority来允许执行故障转移,虽然另外一台机器还有一个R1,但是故障转移不会执行
3节点哨兵集群
如果M1所在机器宕机了,那么三个哨兵还剩下2个,S2和S3可以一致认为master宕机,然后选举出一个来执行故障转移
同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移
哨兵主备切换数据丢失问题
异步复制导致数据丢失
master -> slave的复制是异步的,所以可能有部分数据还没复制到slave,master就宕机了,此时这些部分数据就丢失了
脑裂导致的数据丢失
脑裂:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着 此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master
这个时候,集群里就会有两个master,也就是所谓的脑裂
虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了 因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据
数据丢失处理方式
配置参数:要求至少有1个slave,数据复制和同步的延迟不能超过10秒 min-slaves-to-write 1
min-slaves-max-lag 10
数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了
减少异步复制的数据丢失
有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了, 那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内
减少脑裂的数据丢失
如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave发送数据, 而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求
哨兵底层原理
sdown和odown转换机制
sdown和odown两种失败状态 sdown是主观宕机,就一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机
odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机
sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机
sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那个master是sdown了,那么就认为是odown了,客观认为master宕机
哨兵集群的自动发现机制
哨兵互相之间的发现,是通过redis的pub/sub系统实现的,每个哨兵都会往__sentinel__:hello这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在 每隔两秒钟,每个哨兵都会往自己监控的某个master+slaves对应的__sentinel__:hello channel里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置
每个哨兵也会去监听自己监控的每个master+slaves对应的__sentinel__:hello channel,然后去感知到同样在监听这个master+slaves的其他哨兵的存在
每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步
slave配置的自动纠正
哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据; 如果slave连接到了一个错误的master上,比如故障转移之后,那么哨兵会确保它们连接到正确的master上
slave->master选举算法
如果一个master被认为odown了,而且majority哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来
考虑slave的一些信息
跟master断开连接的时长
slave优先级
复制offset
run id
对slave进行排序
按照slave优先级进行排序,slave priority越低,优先级就越高
)如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
如果上面两个条件都相同,那么选择一个run id比较小的那个slave
quorum和majority
每次一个哨兵要做主备切换,首先需要quorum数量的哨兵认为odown,然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权,才能正式执行切换 如果quorum < majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换
但是如果quorum >= majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换
configuration epoch
哨兵会对一套redis master+slave进行监控,有相应的监控的配置 执行切换的那个哨兵,会从要切换到的新master(salve->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号
configuraiton传播
哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub消息机制 这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的
其他的哨兵都是根据版本号的大小来更新自己的master配置的
redis cluster集群模式
集群架构模式
支撑N个redis master node,每个master node都可以挂载多个slave node
读写分离的架构,对于每个master来说,写就写到master,然后读就从mater对应的slave去读
高可用,因为每个master都有salve节点,那么如果mater挂掉,redis cluster这套机制,就会自动将某个slave切换成master
多master + 读写分离 + 高可用
与redis replication+哨兵对比
数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个G,单机足够了
replication,一个mater,多个slave,要几个slave跟你的要求的读吞吐量有关系,然后自己搭建一个sentinal集群,去保证redis主从架构的高可用性
redis cluster,主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,那么建议就用redis cluster
核心原理
节点间的内部通信机制
基础通信原理
redis cluster节点间采取gossip协议进行通信 跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的
维护集群的元数据用得,集中式,一种叫做gossip
集中式:好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力
gossip:好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后
我们刚才做reshard,去做另外一个操作,会发现说,configuration error,达成一致
10000端口
每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口
每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong
交换的信息
故障信息,节点的增加和移除,hash slot信息,等等
gossip协议
gossip协议包含多种消息,包括ping,pong,meet,fail,等等 meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信
redis-trib.rb add-node
其实内部就是发送了一个gossip meet消息,给新加入的节点,通知那个节点去加入我们的集群
ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据
每个节点每秒都会频繁发送ping给其他的集群,ping,频繁的互相之间交换数据,互相进行元数据的更新
pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新
fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
ping消息深入
ping很频繁,而且要携带一些元数据,所以可能会加重网络负担 每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点
当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了
比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题
所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率
每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换
至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息
高可用性与主备切换原理
判断节点宕机
如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机 如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown
在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail
如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail
从节点过滤
对宕机的master node,从其所有的slave node中,选择一个切换成master node 检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master
这个也是跟哨兵是一样的,从节点超时过滤的步骤
从节点选举
哨兵:对所有从节点进行排序,slave priority,offset,run id 每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举
所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master
从节点执行主备切换,从节点切换为主节点
与哨兵比较
整个流程跟哨兵相比,非常类似,所以说,redis cluster功能强大,直接集成了replication和sentinal的功能
缓存带来的问题
缓存与数据库双写不一致
缓存血崩
描述
缓存层由于某些原因整体崩溃掉,导致大量请求到达后端数据库,从而导致数据库崩溃,整个系统崩溃
发生的原因
Redis服务器宕机,所有的请求都走存储层
对设置过期时间的数据,在某段时间内大量的数据同时失效。对于这种情况可以在缓存的时候加上一个随机值,这样会大幅度减少缓存在同一时间过期
解决方法
保证缓存层服务高可用性。如主从架构、Sentinel或者Redis Cluster都实现了高可用
降级、熔断和限流
降级:服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
限流:限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
熔断:一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果
缓存穿透
描述
查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致不存在的数据每次请求都要到数据库去查询,失去了缓存保护后端存储的意义
解决方法
缓存空对象
如果一个查询返回的数据为空,就将这个空结果进行缓存,设置5分钟的过期时间
缓存空对象的存在的问题:
(1) 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间(如果是攻击,问题更严重),比较有效的方法就是针对这类数据设置一个较短的过期时间,让其自动剔除。
(2) 缓存层和存储层会有一段时间窗口的不一致,可能对业务有一定的影响。可以通过设置过期时间或者利用消息系统清除缓存中的空对象
布隆过滤器
描述
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。如果一个要查询的key在布隆过滤器中判断肯定不存在,则不会查询直接返回
基本原理及要点
位数组+k个独立的hash函数, 一个值通过多个哈希函数计算得到在数组中对应的位,并将该位的标识改为1。如果请求查询一个数,先通过哈希函数计算,如果得到的位存在标识有0,说明这个值肯定不存在,这样可以避免不必要的数据库查询。
适用:利用布隆过滤器减少磁盘IO或网络请求,因为一旦一个值必定不存在的话,就可以不用进行后续昂贵的查询操作。
优点:查询快,占用空间小。
缺点:存在误判,删除困难
如何减少布隆过滤器的误判:增大位数组的长度,增加哈希函数的数量
redis并发竞争问题