写在前
持久化解决了单机redis的数据保存问题,但是redis还是存在以下两个问题:
- 假如某天这台redis服务器挂了,redis服务将彻底丧失
- redis的读和写都集中到一台机上,如果请求量比较大时,将可能被击溃
redis的多机数据库实现,主要分为以下三种:
- 主从(复制)模式(redis2.8版本之前的模式)
- 哨兵sentinel模式(redis2.8及之后的模式)
- redis cluster集群模式(redis3.0版本之后)
Redis主从复制
- 为了解决上述两个问题,redis提供了主从架构,在主从架构中,主服务器(master)可以进行读写操作,从服务器(slave)一般只进行读操作。缓解了单个redis服务器的压力;
- 主服务器将所有数据源源不断的同步到从服务器上,一旦主服务器挂了,还有从服务器可以提供服务,redis服务将不会间断。
- 和Mysql主从复制的原因一样,Redis的主从结构可以采用一主多从或者级联结构。
同步类型与同步过程
根据同步是否全量分为:全量同步和部分同步(增量同步)。
全量同步
指主服务器每次与从服务器同步都是同步全部数据。主服务器持久化数据为一个rdb文件,在此期间用缓存区把所有对主服务器的写操作命令存储起来,然后再rdb传给从服务器,再把储存起来的命令也传过去;从服务器从接收到的rdb文件加载数据,然后再加载传过来的命令。部分同步
指主服务器每次与从服务器同步都是只同步增量数据。
Redis的主从复制分为同步(sync)和命令传播两个操作。我们向从服务器发送SLAVEOF命令(异步命令),从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成。步骤如下(全量同步),一般发生在SLAVE初始化阶段,需要将MASTER的数据全部复制一份:
(1)从服务器连接主服务器,发送SYNC命令;
(2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件,并使用缓冲区记录此后执行的所有写命令;
(3)主服务器BGSAVE执行完后,向【所有】从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
(4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
(5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
(6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。
Redis命令传播是指同步操作完毕后,开始正常工作时主服务器发生的写操作同步到从服务器的过程。命令传播的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
- 主服务器通过向从服务器传播命令(主服务器执行的写命令)来更新从服务器的状态,保存主从服务器一致。
- 从服务器则通过向主服务器发送命令(每秒发送1次REPLCONF ACK命令)来进行心跳检测。以此来检测主从服务器的网络连接状态和检测命令丢失。
什么是部分重同步?
从Redis 2.8开始,如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步。它的工作原理是这样:
- 主服务器端为复制流维护一个内存缓冲区(replication backlog)。主从服务器都维护一个复制偏移量(replication offset)和服务器的运行id(run id)。
- 当连接断开时,从服务器会重新连接上主服务器,然后请求继续复制,假如主从服务器的两个run id相同,并且指定的偏移量在内存缓冲区中还有效,复制就会从上次中断的点开始继续。
- 如果其中一个条件不满足,就会进行完全重新同步(在2.8版本之前就是直接进行完全重新同步)。因为主运行id不保存在磁盘中,如果从服务器重启了的话就只能进行完全同步了。
- 部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令。Redis2.8版本可以检测出它所连接的服务器是否支持PSYNC命令,不支持的话使用SYNC命令。
主从复制的特点
- 一台主服务器(也称为master node)可以连接多台从服务器(也称为slave node)
- 从服务器也可以连接其他redis服务器,作为其他redis服务器的主服务器,从而形成一条链
- 从服务器在复制的时候,不会阻塞主服务器的正常工作,同时也不会阻塞自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
- 从服务器主要用来进行横向扩容,做读写分离,提高读的吞吐量
主从复制的优缺点
优点(开始的两个问题):
- 解决数据备份问题
- 做到读写分离,提高服务器性能
缺点:
- 每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接
- 主从模式下,如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作
- 无法实现动态扩容
哨兵Sentinel模式(基于主从模式)
使用哨兵模式进行主从替换与故障恢复!
Redis Sentinel 是一个分布式系统, 可以在一个架构中运行多个 Sentinel 进程(progress), 这些进程使用流言协议(gossip protocols)来接收关于主服务器是否下线的信息, 并使用投票协议(agreement protocols)来决定是否执行自动故障迁移, 以及选择哪个从服务器作为新的主服务器。
- 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程(独立的redis节点,不存储数据,只支持部分命令),实际上是一个运行在特殊模式下的一个redis服务器,独立运行。
- 哨兵与服务器之间会创建两个连接,命令连接和订阅连接,哨兵与哨兵之间只会创建命令连接。
- 其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
- 启动一个普通的redis服务器之后,通过--sentinel来启动Redis Sentinel
Redis 2.8版开始正式提供名为Sentinel的主从切换方案,通俗的来讲,Sentinel可以用来管理多个Redis服务器实例,可以实现一个功能上实现HA的集群,Sentinel主要负责三个方面的任务:
- 监控:通过发送命令,不间断的监控Redis服务器运行状态,包括主服务器和从服务器。
- 提醒:当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(核心任务):当哨兵监测到主服务器宕机,会自动在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器将其转换为主服务器(自动切换)。然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
多哨兵模式:一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,多哨兵可以防止误判并且使整个哨兵集合更加健壮。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
对于不可用描述术语总结:
- 主观下线(SDOWN):单个哨兵实例对主节点做出下线判断。
- 客观下线(ODOWN):多个哨兵实例对主节点做出下线判断。
- 主节点有主观和客观下线,从节点只有主观下线
领导者哨兵选举流程:
当确定redis服务器确实挂了以后,哨兵要进行故障转移,并且只能有一个哨兵去完成该操作,所以这时候就要选举出一名哨兵来当此重任。
- 每个在线的哨兵节点都可以成为领导者,当它确认(比如哨兵3)主节点下线时,会向其它哨兵发
is-master-down-by-addr
命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移; - 当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;
- 如果哨兵3发现自己在选举的票数大于等于num(sentinels)/2+1时,将成为领导者,如果没有超过,继续选举…………
讲讲slave->master选举算法(怎么完成故障迁移)
如果master被判odown了,大部分哨兵允许主备切换,那么需要选举一个slave,依次考虑如下:
- 看slave-priority:选择slave优先级最高的;
- 看offset:选择复制offset(偏移量)最大的,指复制最完整的从节点
- 看runid:程序id,就选runid最小的,越早开启的
自动发现机制(三个定时任务)
监控redis服务器的运行状态:
- 以10秒一次的频率,向被监控的master发送Info命令,根据回复获取当前master信息。这个任务达到两个目的:发现slave节点和确定主从关系
- 以1秒一次的频率,向所有的redis服务器包括 Sentinel 发送ping命令,通过回复判断服务器是否在线,这个其实是一个心跳检测,是失败判定的依据。
- 以2秒一次的频率,向master节点的channel交换信息(pub/sub)。master节点上有一个
发布订阅的频道(__sentinel__:hello)
。sentinel节点通过__sentinel__:hello
频道进行信息交换(对节点的"看法"和自身的信息),达成共识。
哨兵模式的优缺点
优点:
- 哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都有。
- 主从可以自动切换,系统更健壮,可用性更高
缺点:
- redis较难支持在线扩容,在集群容量达上限时在线扩容变的很复杂。
ps:“高可用性”(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。
1. redis 集群模式的工作原理能说一下么?在集群模式下,****redis的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
Redis Cluster集群模式
工作原理:
集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。一个Redis集群通常由多个节点组成;最初,每个节点都是独立的,需要将独立的节点连接起来才能形成可工作的集群。
Redis Cluster并没有使用一致性hash,而是采用hash slot(哈希槽) 算法,一共分成16384个槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽(每个节点对应若干个槽)。
关键点:
- 自动将数据进行分片(通过hash方式),每个 master 上放一部分数据(均分存储一定哈希槽区间的数据),先写入主节点,再写入从节点。注:同一分片多个节点间的数据不保持一致性
- 提供内置的高可用支持,部分 master 不可用时(注意:key找的是哈希槽),还是可以继续工作的,哈希槽会迁移。将请求发送到任意节点(这个key可能不在这个节点上),接收到请求的节点会将查询请求发送到正确的节点上执行(返回转向指令)。
redis cluster架构下的每个redis都要开放两个端口号,比如一个是6379,另一个就是加1w的端口号16379。
- 6379端口号就是redis服务器入口。
- 16379端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用的是一种叫gossip 协议的二进制协议,用于节点间高效的数据交换,占用更少的网络带宽和处理时间。
节点间的内部通信机制
写在前:任何文件系统中的数据分为数据和元数据。数据是指普通文件中的实际数据,而元数据指用来描述一个文件的特征的系统数据,诸如访问权限、文件拥有者以及文件数据块的分布信息(inode...)等等。
在集群文件系统中,分布信息包括文件在磁盘上的位置以及磁盘在集群中的位置。用户需要操作一个文件必须首先得到它的元数据,才能定位到文件的位置并且得到文件的内容或相关属性。
基本通信原理,集群元数据的维护有两种方式:集中式、Gossip 协议(分布式)。redis cluster 节点间采用 gossip 协议进行通信。
- 集中式管理:集中式是将集群元数据(节点信息、故障等等)集中存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
优缺点:元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;但可能会存在存储压力和单点失效问题。 - 分布式管理:redis 维护集群元数据采用另一个方式, gossip 协议:所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。
优缺点:元数据存储比较分散,降低了元数据存储压力;但元数据更新会延迟,可能导致集群中的一些操作会有一些滞后,实现较复杂,一致性维护复杂。
补充:
- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 ping 消息,同时其它几个节点接收到 ping 之后返回 pong。
- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。
gossip协议
gossip 协议包含多种消息,包含 ping,pong,meet,fail 等等。
- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。
- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
- pong:返回 ping 和 meet,包含自己的状态和其它信息,也用于信息广播和更新。
- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦
分布式寻址算法
在分布式系统中,对数据的准确定位以及整个系统的结构具有很高的要求。现代分布式寻址算法中,主要以下面三种算法为代表:
- hash 算法(大量缓存重建)
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
- redis cluster 的 hash slot 算法(也叫hash槽)
使用场景:
- hash算法比较适合固定分区或者分布式节点的集群架构。
- 一致性hash算法比较适合需要动态扩容的分布式架构以及一些动态负载均衡的分布式中间件和RPC中间件。
- hash slot是Redis对hash算法的一种实现。
hash 算法
- 对于不同的请求,比如来了一个 key,首先计算 hash 值,然后对节点数(服务器数)取模。
- 然后打在不同的 master 节点上。一旦某一个 master 节点宕机或者进行扩容,需要重新基于最新的master节点数计算hash值。当系统具有海量数据时将会是场灾难。
一致性hash算法(动态扩容和负载均衡)
- 一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。
- 这样就能确定每个节点在其哈希环上的位置。来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。
- 在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。
- 然而,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
ps:缓存热点问题解决方案
问题描述:同一时间访问同一个缓存key的请求数量过高,导致某台特定的redis服务器压力过大,而其他的redis服务器没有分担到压力。
举例说明:店铺活动查询的时候缓存key为店铺编码,value为店铺能够参加的活动编码信息,某个时候店铺搞活动瞬时redis访问命令数飙升,热点key 所在的redis服务器压力瞬间飙升。
解决方案:
- 热点数据推送到jvm内存,内存有则直接访问内存,内存不存在再去访问缓存
- 加随机数,将一份redis缓存数据通过key后面加随机数的方式生成多份分别分散到不同的redis服务器上,访问的时候随机访问其中的一份。
redis cluster 的 hash slot 算法
- 基本模型:记录和物理机之间引入了虚拟桶层,记录通过hash函数映射到虚拟桶,记录和虚拟桶是多对一的关系;第二层是虚拟桶和物理机之间的映射,同样也是多对一的关系,即一个物理机对应多个虚拟桶,这个层关系是通过内存表实现的。
- redis cluster 有固定的 16384 个 hash slot,对每个 key 进行CRC16检验 ,然后对 16384 取模,可以获取 key 对应的 hash slot。redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。
- hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去(不会造成节点阻塞)。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
redis cluster 的高可用与主备切换原理
- redis cluster 的高可用的原理,几乎跟哨兵是类似的。
- 如果一个节点认为另外一个节点宕机,那么就是 pfail,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown。
- 在 cluster-node-timeout 内,某个节点一直没有返回 pong,那么就被认为 pfail。如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中,ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail。
- 对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。每个从节点,都根据自己对 master 复制数据的 offset(偏移量),来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
- 所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。从节点执行主备切换,从节点切换为主节点。