黄东旭解析 TiDB 的核心优势
1000
2023-05-21
一文把Redis主从复制、哨兵、Cluster三种模式摸透
概述
Redis作为缓存的高效中间件,在我们日常的开发中被频繁的使用,今天就来说一说Redis的四种模式,分别是「单机版、主从复制、哨兵、以及集群模式」。
可能,在一般公司的程序员使用单机版基本都能解决问题,在Redis的官网给出的数据是10W QPS,这对于应付一般的公司绰绰有余了,再不行就来个主从模式,实现读写分离,性能又大大提高。
但是,我们作为有抱负的程序员,仅限于单机版和主从模式的crud是不行的,至少也要了解「哨兵」和「集群模式」的原理,这样面试的时候才能和面试官扯皮啊。
之前对于Redis方面也是写了比较多的文章,如:「Redis的基本数据类型和底层的实现原理、事务、持久化、分布式锁、订阅预发布」等,可以说是比较全面的教程了,这篇讲完基本就全了,我会把文章系统的整理成pdf,分享给大家。
先来个整理的Redis大纲,可能还有不完整的地方,若是有不完整的,可以在留言区补充,我后续会加进去。
单机
单机版的Redis就比较简单了,基本90%的程序员都是用过,官网推荐操作Redis的第三方依赖库是Jedis,在SpringBoot项目中,引入下面依赖就可以直接使用了:
优点
单机版的Redis也有很多优点,比如实现实现简单、维护简单、部署简单、维护成本非常低,不需要其它额外的开支。
缺点
但是,因为是单机版的Redis所以也存在很多的问题,比如最明显的单点故障问题,一个Redis挂了,所有的请求就会直接打在了DB上。
并且一个Redis抗并发数量也是有限的,同时要兼顾读写两种请求,只要访问量一上来,Redis就受不了了,另一方面单机版的Redis数据量存储也是有限的,数据量一大,再重启Redis的时候,就会非常的慢,所以局限性也是比较大的。
实操搭建
上面这个教程讲的非常的详细,环境的搭建本来是运维的工作,但是作为程序员尝试自己去搭建环境还是有必要的,而且搭建环境这种东西,基本就是一劳永逸,搭建一次,可能下次换电脑或者重装虚拟机才会再次搭建。
这里也放出redis常用的redis.conf的配置项,并且附带注释,看我是不是很暖男:
由于,单机版的Redis在并发量比较大的时候,并且需要较高性能和可靠性的时候,单机版基本就不适合了,于是就出现了「主从模式」。
主从模式
原理
主从的原理还算是比较简单的,一主多从,「主数据库(master)可以读也可以写(read/write),从数据库仅读(only read)」。
但是,主从模式一般实现「读写分离」,「主数据库仅写(only write)」,减轻主数据库的压力,下面一张图搞懂主从模式的原理:
主从模式原理就是那么简单,那他执行的过程(工作机制)又是怎么样的呢?再来一张图:
当开启主从模式的时候,他的具体工作机制如下:
当slave启动后会向master发送SYNC命令,master节点收到从数据库的命令后通过bgsave保存快照(「RDB持久化」),并且期间的执行的些命令会被缓存起来。
然后master会将保存的快照发送给slave,并且继续缓存期间的写命令。
slave收到主数据库发送过来的快照就会加载到自己的数据库中。
最后master讲缓存的命令同步给slave,slave收到命令后执行一遍,这样master与slave数据就保持一致了。
优点
之所以运用主从,是因为主从一定程度上解决了单机版并发量大,导致请求延迟或者redis宕机服务停止的问题。
从数据库分担主数据库的读压力,若是主数据库是只写模式,那么实现读写分离,主数据库就没有了读压力了。
另一方面解决了单机版单点故障的问题,若是主数据库挂了,那么从数据库可以随时顶上来,综上来说,主从模式一定程度上提高了系统的可用性和性能,是实现哨兵和集群的基础。
主从同步以异步方式进行同步,期间Redis仍然可以响应客户端提交的查询和更新的请求。
缺点
主从模式好是好,他也有自己的缺点,比如数据的一致性问题,假如主数据库写操作完成,那么他的数据会被复制到从数据库,若是还没有即使复制到从数据库,读请求又来了,此时读取的数据就不是最新的数据。
若是从主同步的过程网络出故障了,导致主从同步失败,也会出现问题数据一致性的问题。
主从模式不具备自动容错和恢复的功能,一旦主数据库,从节点晋升为主数据库的过程需要人为操作,维护的成本就会升高,并且主节点的写能力、存储能力都会受到限制。
实操搭建
下面的我们来实操搭建一下主从模式,主从模式的搭建还是比较简单的,我这里一台centos 7虚拟机,使用开启redis多实例的方法搭建主从。
redis中开启多实例的方法,首先创建一个文件夹,用于存放redis集群的配置文件:
mkdir redis
然后粘贴复制redis.conf配置文件:
cp /root/redis-4.0.6/redis.conf /root/redis/redis-6379.conf cp /root/redis-4.0.6/redis.conf /root/redis/redis-6380.conf cp /root/redis-4.0.6/redis.conf /root/redis/redis-6381.conf
复制三份配置文件,一主两从,6379端口作为主数据库(master),6380、6381作为从数据库(slave)。
首先是配置主数据库的配置文件:vi redis-6379.conf:
然后,就是修改从数据库的配置文件,在从数据库的配置文件中加入以下的配置信息:
slaveof 127.0.0.1 6379 # 配置master的ip,port masterauth 123456 # 配置访问master的密码 slaveof-serve-stale-data no
接下来就是启动三个redis实例,启动的命令,先cd到redis的src目录下,然后执行:
./redis-server /root/redis/6379.conf ./redis-server /root/redis/6380.conf ./redis-server /root/redis/6381.conf
通过命令ps -aux | grep redis,查看启动的redis进程:
如上图所示,表示启动成功,下面就开始进入测试阶段。
测试
我这里使用SecureCRT作为redis连接的客户端,同时启动三个SecureCRT,分别连接redis1的三个实例,启动时指定端口以及密码:
./redis-cli -p 6379 -a 123456
启动后,在master(6379),输入:set name 'ldc',在slave中通过get name,可以查看:
数据同步成功,这有几个坑一个是redis.conf中没有设置对bind,会导致非本机的ip被过滤掉,一般配置0.0.0.0就可以了。
另一个是没有配置密码requirepass 123456,会导致IO一直连接异常,这个是我遇到的坑,后面配置密码后就成功了。
还有,就是查看redis的启动日志可以发现有两个warning,虽然不影响搭建主从同步,看着挺烦人的,但是有些人会遇到,有些人不会遇到。
但是,我这个人比较有强迫症,百度也是有解决方案的,这里就不讲了,交给你们自己解决,这里只是告诉你有这个问题,有些人看都不看日志的,看到启动成功就认为万事大吉了,也不看日志,这习惯并不好。
哨兵模式
原理
哨兵模式是主从的升级版,因为主从的出现故障后,不会自动恢复,需要人为干预,这就很蛋疼啊。
在主从的基础上,实现哨兵模式就是为了监控主从的运行状况,对主从的健壮进行监控,就好像哨兵一样,只要有异常就发出警告,对异常状况进行处理。
所以,总的概括来说,哨兵模式有以下的优点(功能点):
「监控」:监控master和slave是否正常运行,以及哨兵之间也会相互监控「自动故障恢复」:当master出现故障的时候,会自动选举一个slave作为master顶上去。
哨兵模式的监控配置信息,是通过配置从数据库的sentinel monitor 来指定的,比如:
// mymaster 表示给master数据库定义了一个名字,后面的是master的ip和端口,1表示至少需要一个Sentinel进程同意才能将master判断为失效,如果不满足这个条件,则自动故障转移(failover)不会执行 sentinel monitor mymaster 127.0.0.1 6379 1
节点通信
当然还有其它的配置信息,其它配置信息,在环境搭建的时候再说。当哨兵启动后,会与master建立一条连接,用于订阅master的_sentinel_:hello频道。
该频道用于获取监控该master的其它哨兵的信息。并且还会建立一条定时向master发送INFO命令获取master信息的连接。
「当哨兵与master建立连接后,定期会向(10秒一次)master和slave发送INFO命令,若是master被标记为主观下线,频率就会变为1秒一次。」
并且,定期向_sentinel_:hello频道发送自己的信息,以便其它的哨兵能够订阅获取自己的信息,发送的内容包含「哨兵的ip和端口、运行id、配置版本、master名字、master的ip端口还有master的配置版本」等信息。
以及,「定期的向master、slave和其它哨兵发送PING命令(每秒一次),以便检测对象是否存活」,若是对方接收到了PING命令,无故障情况下,会回复PONG命令。
所以,哨兵通过建立这两条连接、通过定期发送INFO、PING命令来实现哨兵与哨兵、哨兵与master之间的通信。
这里涉及到一些概念需要理解,INFO、PING、PONG等命令,后面还会有MEET、FAIL命令,以及主观下线,当然还会有客观下线,这里主要说一下这几个概念的理解:
INFO:该命令可以获取主从数据库的最新信息,可以实现新结点的发现
PING:该命令被使用最频繁,该命令封装了自身节点和其它节点的状态数据。
PONG:当节点收到MEET和PING,会回复PONG命令,也把自己的状态发送给对方。
MEET:该命令在新结点加入集群的时候,会向老节点发送该命令,表示自己是个新人
FAIL:当节点下线,会向集群中广播该消息。
上线和下线
当哨兵与master相同之后就会定期一直保持联系,若是某一时刻哨兵发送的PING在指定时间内没有收到回复(sentinel down-after-milliseconds master-name milliseconds 配置),那么发送PING命令的哨兵就会认为该master「主观下线」(Subjectively Down)。
因为有可能是哨兵与该master之间的网络问题造成的,而不是master本身的原因,所以哨兵同时会询问其它的哨兵是否也认为该master下线,若是认为该节点下线的哨兵达到一定的数量(「前面的quorum字段配置」),就会认为该节点「客观下线」(Objectively Down)。
若是没有足够数量的sentinel同意该master下线,则该master客观下线的标识会被移除;若是master重新向哨兵的PING命令回复了客观下线的标识也会被移除。
选举算法
当master被认为客观下线后,又是怎么进行故障恢复的呢?原来哨兵中首先选举出一个老大哨兵来进行故障恢复,选举老大哨兵的算法叫做「Raft算法」:
发现master下线的哨兵(sentinelA)会向其它的哨兵发送命令进行拉票,要求选择自己为哨兵大佬。若是目标哨兵没有选择其它的哨兵,就会选择该哨兵(sentinelA)为大佬。若是选择sentinelA的哨兵超过半数(半数原则),该大佬非sentinelA莫属。如果有多个哨兵同时竞选,并且可能存在票数一致的情况,就会等待下次的一个随机时间再次发起竞选请求,进行新的一轮投票,直到大佬被选出来。
选出大佬哨兵后,大佬哨兵就会对故障进行自动回复,从slave中选出一名slave作为主数据库,选举的规则如下所示:
所有的slave中slave-priority优先级最高的会被选中。若是优先级相同,会选择偏移量最大的,因为偏移量记录着数据的复制的增量,越大表示数据越完整。若是以上两者都相同,选择ID最小的。
通过以上的层层筛选最终实现故障恢复,当选的slave晋升为master,其它的slave会向新的master复制数据,若是down掉的master重新上线,会被当作slave角色运行。
优点
哨兵模式是主从模式的升级版,所以在系统层面提高了系统的可用性和性能、稳定性。当master宕机的时候,能够自动进行故障恢复,需不要人为的干预。
哨兵与哨兵之间、哨兵与master之间能够进行及时的监控,心跳检测,及时发现系统的问题,这都是弥补了主从的缺点。
缺点
哨兵一主多从的模式同样也会遇到写的瓶颈,已经存储瓶颈,若是master宕机了,故障恢复的时间比较长,写的业务就会受到影响。
增加了哨兵也增加了系统的复杂度,需要同时维护哨兵模式。
实操搭建
最后,我们进行一下哨兵模式的搭建,配置哨兵模式还是比较简单的,在上面配置的主从模式的基础上,同时创建一个文件夹用于存放三个哨兵的配置文件:
mkdir /root/redis-4.0.6/sentinel.conf /root/redis/sentinel/sentinel1.conf mkdir /root/redis-4.0.6/sentinel.conf /root/redis/sentinel/sentinel2.conf mkdir /root/redis-4.0.6/sentinel.conf /root/redis/sentinel/sentinel3.conf
分别在这三个文件中添加如下配置:
daemonize yes # 在后台运行 sentinel monitor mymaster 127.0.0.1 6379 1 # 给master起一个名字mymaster,并且配置master的ip和端口 sentinel auth-pass mymaster 123456 # master的密码 port 26379 #另外两个配置36379,46379端口 sentinel down-after-milliseconds mymaster 3000 # 3s未回复PING就认为master主观下线 sentinel parallel-syncs mymaster 2 # 执行故障转移时,最多可以有2个slave实例在同步新的master实例 sentinel failover-timeout mymaster 100000 # 如果在10s内未能完成故障转移操作认为故障转移失败
配置完后分别启动三台哨兵:
./redis-server sentinel1.conf --sentinel ./redis-server sentinel2.conf --sentinel ./redis-server sentinel3.conf --sentinel
然后通过:ps -aux|grep redis进行查看:
可以看到三台redis实例以及三个哨兵都已经正常启动,现登陆6379,通过INFO Repliaction查看master信息:
当前master为6379,然后我们来测试一下哨兵的自动故障恢复,直接kill掉6379进程,然后通过登陆6380再次查看master的信息:
可以看到当前的6380角色是master,并且6380可读可写,而不是只读模式,这说明我们的哨兵是起作用了,搭建成功,感兴趣的可以自行搭建,也有可能你会踩一堆的坑。
Cluster模式
最后,Cluster是真正的集群模式了,哨兵解决和主从不能自动故障恢复的问题,但是同时也存在难以扩容以及单机存储、读写能力受限的问题,并且集群之前都是一台redis都是全量的数据,这样所有的redis都冗余一份,就会大大消耗内存空间。
集群模式实现了Redis数据的分布式存储,实现数据的分片,每个redis节点存储不同的内容,并且解决了在线的节点收缩(下线)和扩容(上线)问题。
集群模式真正意义上实现了系统的高可用和高性能,但是集群同时进一步使系统变得越来越复杂,接下来我们来详细的了解集群的运作原理。
数据分区原理
集群的原理图还是很好理解的,在Redis集群中采用的是虚拟槽分区算法,会把redis集群分成16384 个槽(0 -16383)。
比如:下图所示三个master,会把0 -16383范围的槽可能分成三部分(0-5000)、(5001-11000)、(11001-16383)分别数据三个缓存节点的槽范围。
当客户端请求过来,会首先通过对key进行CRC16 校验并对 16384 取模(CRC16(key)%16383)计算出key所在的槽,然后再到对应的槽上进行取数据或者存数据,这样就实现了数据的访问更新。
之所以进行分槽存储,是将一整堆的数据进行分片,防止单台的redis数据量过大,影响性能的问题。
节点通信
节点之间实现了将数据进行分片存储,那么节点之间又是怎么通信的呢?这个和前面哨兵模式讲的命令基本一样。
首先新上线的节点,会通过 Gossip 协议向老成员发送Meet消息,表示自己是新加入的成员。
老成员收到Meet消息后,在没有故障的情况下会恢复PONG消息,表示欢迎新结点的加入,除了第一次发送Meet消息后,之后都会发送定期PING消息,实现节点之间的通信。
通信的过程中会为每一个通信的节点开通一条tcp通道,之后就是定时任务,不断的向其它节点发送PING消息,这样做的目的就是为了了解节点之间的元数据存储情况,以及健康状况,以便即使发现问题。
数据请求
上面说到了槽信息,在Redis的底层维护了unsigned char myslots[CLUSTER_SLOTS/8] 一个数组存放每个节点的槽信息。
因为他是一个二进制数组,只有存储0和1值,如下图所示:
这样数组只表示自己是否存储对应的槽数据,若是1表示存在该数据,0表示不存在该数据,这样查询的效率就会非常的高,类似于布隆过滤器,二进制存储。
比如:集群节点1负责存储0-5000的槽数据,但是此时只有0、1、2存储有数据,其它的槽还没有存数据,所以0、1、2对应的值为1。
并且,每个redis底层还维护了一个clusterNode数组,大小也是16384,用于储存负责对应槽的节点的ip、端口等信息,这样每一个节点就维护了其它节点的元数据信息,便于及时的找到对应的节点。
当新结点加入或者节点收缩,通过PING命令通信,及时的更新自己clusterNode数组中的元数据信息,这样有请求过来也就能及时的找到对应的节点。
有两种其它的情况就是,若是请求过来发现,数据发生了迁移,比如新节点加入,会使旧的缓存节点数据迁移到新结点。
请求过来发现旧节点已经发生了数据迁移并且数据被迁移到新结点,由于每个节点都有clusterNode信息,通过该信息的ip和端口。此时旧节点就会向客户端发一个MOVED 的重定向请求,表示数据已经迁移到新结点上,你要访问这个新结点的ip和端口就能拿到数据,这样就能重新获取到数据。
倘若正在发正数据迁移呢?旧节点就会向客户端发送一个ASK 重定向请求,并返回给客户端迁移的目标节点的ip和端口,这样也能获取到数据。
扩容和收缩
扩容和收缩也就是节点的上线和下线,可能节点发生故障了,故障自动回复的过程(节点收缩)。
节点的收缩和扩容时,会重新计算每一个节点负责的槽范围,并发根据虚拟槽算法,将对应的数据更新到对应的节点。
还有前面的讲的新加入的节点会首先发送Meet消息,详细可以查看前面讲的内容,基本一样的模式。
以及发生故障后,哨兵老大节点的选举,master节点的重新选举,slave怎样晋升为master节点,可以查看前面哨兵模式选举过程。
优点
集群模式是一个无中心的架构模式,将数据进行分片,分布到对应的槽中,每个节点存储不同的数据内容,通过路由能够找到对应的节点负责存储的槽,能够实现高效率的查询。
并且集群模式增加了横向和纵向的扩展能力,实现节点加入和收缩,集群模式是哨兵的升级版,哨兵的优点集群都有。
缺点
缓存的最大问题就是带来数据一致性问题,在平衡数据一致性的问题时,兼顾性能与业务要求,大多数都是以最终一致性的方案进行解决,而不是强一致性。
并且集群模式带来节点数量的剧增,一个集群模式最少要6台机,因为要满足半数原则的选举方式,所以也带来了架构的复杂性。
slave只充当冷备,并不能缓解master的读的压力。
实操搭建
集群模式的部署比较简单,只要在redis.conf加入下面的配置信息即可:
port 6379# 本示例6个节点端口分别为6379、6380、6381、6382、6383、6384 daemonize yes # r后台运行 pidfile /var/run/redis_6379.pid # 分别对应6379、6380、6381、6382、6383、6384 cluster-enabled yes # 开启集群模式 masterauth 123456# 如果设置了密码,需要指定master密码 cluster-config-file nodes_6379.conf # 集群的配置文件,同样对应6379、6380、6381、6382、6383、6384六个节点 cluster-node-timeout 10000 # 请求超时时间
同时开启这六个实例,通过下面的命令将这六个实例以集群的方式运行
./redis-cli --cluster create --cluster-replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 -a 123456
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。