前言
在前面的学习中,我们已经了解了Redis的基本语法以及Redis持久化和事务的概念。而在这篇文章中我们继续来梳理管道、发布订阅、主从复制、哨兵监控和集群的知识,理解Redis主从复制到集群分片的演进过程,希望对正在学习的小伙伴有一定的帮助。
文章目录
一、管道
管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间RTT。pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。换一种理解方式就是:管道其实就是一种批处理命令变种优化措施,类似于Redis的原生批命令mset等。
基本操作
首先把要执行的Redis的命令写入到一个txt文件中,使用pipeline来实现批处理
cat cmd.txt | redis-cli -a 111111 --pipe
管道与原生批命令的区别
- 原生批量命令是原子性(例如:mset,mget),pipeline,是非原子性
- 原生批量命令一次只能执行一种命令,pipeline支持批量执行不同命令
- 原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成
事务与管道的区别
- 事务具有原子性,管道不具有原子性
- 管道一次性将多条命令发送到服务器,事务是一条一条的发,事务只有在接收到exec命令后才会执行
- 执行事务时会阻塞其他命令的执行,而执行管道中的命令时不会
- 事务中主要的是一个事务队列,而管道强调的是批处理
二、发布订阅
发布订阅是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE)接收消息,可以实现进程间的消息传递。Redis可以实现消息中间件MQ的功能,通过发布订阅实恐消息的引导和分流。对于PUB和SUB,其实是Redis对标于MQ、kafka等消息中间件来实现消息订阅等功能所提出的第一代产品,在Redis5.0之后提出了使用Stream来替代该功能。
常见命令
PSUBSCRIBE pattern[pattern...]
订阅一个或多个符合给定模式的领道
PUBSUB subcommand [argument [argument._]]
查看订阅与发布系统状态
PUBLISH channel message
将信息发送到指定的频道
PUNSUBSCRIBE [pattern[pattern..]]
退订所有给定模式的频道
SUBSCRIBE channel[channel...]
订阅给定的一个或多个频道的信息
UNSUBSCRIBE [channel [channel...]]
指退订给定的频道
三、主从复制
3.1 概念
同MySQL数据库一样,Redis也有主从复制的操作需求。在真实的业务场景中,我们必须要满足Redis高可用,因此不可能采用单机模式,必须同时使用多台Redis数据库服务器实现数据库的主从复制,同时实现读写分离的操作。在Redis中有两个对象:master以写为主,Slave以读为主,当master数据变化的时候,自动将新的数据异步同步到其它slave数据库。
使用主从复制优点
- 实现读写分离,减轻Redis主机负担
- 实现数据库的容灾恢复,读写分离中主库从库保持数据一致性,并实现了数据备份
- 水平扩容实现高并发
3.2 配置和指令
从库的配置
master如果配置了requirepass参数,需要密码登陆,而且slave就要配置masterauth来设置校验密码,否则的话master会拒绝slave的访问请求。
基本操作命令
//查看复制节点的主从关系和配置信息
info replication
//一般写入进redis.conf,配从库不配主库
replicaof 主库IP 主库端口
//在运行期间修改slave节点的信息,如果该数据库已经是某个主数据库的从数据库,那么会停止和原主数据库的同步关系转而和新的主数据库同步,简单来说就是手动配置修改主从关系
slaveof 主库IP 主库端口
//停止与当前数据库与其它的数据库的同步,转成主数据库
slaveof no one
配置文件redis.conf的修改
1.开启redis后台服务
daemonize yes
2.注释掉bind 127.0.0.1 -::1
3.关闭保护模式
protected-mode no
4.指定端口
port 6379
5.指定当前工作目录
dir /myredis
6.pid文件名字,默认其实就可以了
pidfile /var/run/redis_6379.pid
7.log文件名字-日志文件
logfile "/myredis/6379.log"
8.主库要设置密码
requirepass 123456
9.dump.rdb文件名字
dbfilename dump6379.rdb
10.是否使用AOF,这里可以不用
appendonly no
11.从机访问主机的必须设置通行密码
masterauth 密码
12.从机配置主机的IP和端口
replicaof 192.168.111.185 6379
3.3 主从复制的工作流程
知识要点
- 主机可以执行读和写命令,而从机只能执行读命令!
- 只要master还启动着,即使slave掉队重启,也会同步master中的数据保持数据一致性。
- 在主从关系中,如果主机shutdown,则从机slave会原地待命直至主机重启归来,从机数据可以继续使用。
- 主机重启后,其保持的主从关系依旧存在!
两种从库连接方式如下
slave第一次连接上master后会发送一个sync命令,首次连接会自动执行全量复制,slave在接收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化,其自身原有的数据会被覆盖清除;在保持通信的过程中,Master继续将新的收集到的写操作自动传给Slave以完成同步。在从机下线并重启后,master会检查backlog里面的offset,master和slave都会保存一个复制的offset还有一个masterId,offset是保存在backlog中的。Master只会把已经复制的offset/后面的数据复制给Slave,类似断点续传。
全量复制过程
master节点收到sync命令后会开始在后台保存快照(即RDB持久化,主从复制时会触发RDB),同时收集所有接收到的用于修改数据集命令缓存起来,master节点执行RDB持久化完后,master将rdb快照文件和所有缓存的命令发送到所有slave,以完成一次完全同步
心跳包
master需要按照设定的时间间隔来发出ping包以维持主从复制的关系:repl-ping-replica-period 10
3.4 缺点
- 复制延时,信号衰减:由于所有的写操作都是先在Master.上操作,然后同步更新到Slave上,所以从Master|同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
- master出现宕机:整个系统只能读取而不能写入
针对上述主从复制出现的问题,我们发现如果出现主机宕机后系统无法再执行写入的操作,因此我们引入了哨兵和集群来解决。
四、哨兵监控
4.1 基本概念
哨兵巡查监控后台master主机是否故障,如果故障了根据投票数自动将某一个从库转换为新主库,继续对外服务。哨兵的作用就是监控redis的运行状态,当master宕机后能自动将slave切换成新的master维持业务。
哨兵职责
- 主从监控:监控主从redis库运行是否正常
- 消息通知:哨兵可以将故障转移的结果发送给客户端
- 故障转移:如果Master异常,则会进行主从切换,将其中一个Slave作为新Master
- 配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址
4.2 配置和命令
配置redis中/opt文件下的sentinel.conf文件,记得对源文件复制保护
- bind:服务监听地址,用于客户端连接,默认本机地址
- daemonize:是否以后台daemon方式运行
- protected-mode:安全保护模式
- port:端口
- logfile:日志文件路径
- pidfile:pid文件路径
- dir:工作目录
我们知道,网络是不可靠的,有时候一个sentinel会因为网络堵塞而误以为一个master redis己经死掉了,在sentinel集群环境下需要多个sentinel互相沟通来确认某个master是否真的死了,quorum这个参数是进行客观下线的一个依据,意思是至少有quorum个sentineli认为这个master有故障,才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因,导致无法连接master,而此时master并没有出现故障,所以,这就需要多个sentinel都一致认为该master有问题,才可以进行下一步操作,这就保证了公平性和高可用。
//启动集群
redis-sentinel /myredis/sentinel.conf
redis-server /myredis/sentinel.conf --sentinel
需要注意:
在一个主从关系中,如果原来的master宕机了,sentinel会重新投票选出新的master以维护原有的数据。这时候即使原来的master重连了,它的主从关系也将会被重写而不会依旧成为master从而不会出现双master冲突。
4.3 主观下线和客观下线
主观下线(Subjectively Down)
所谓主观下线(Subjectively Down,简称SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。主观下线就是说如果服务器在sentinel down-after-milliseconds <masterName> <timeout>给定的毫秒数之内没有回应PING命令或者返回一个错误消息,那么这个Sentinel会主观的(单方面的)认为这个master不可以用了。
客观下线(Objectively Down)
ODOWN需要一定数量的sentinel,多个哨兵达成一致意见才能认为一个master客观上已经宕掉,此时哨兵集群才会进行决策投票选出新的master。
故障切换
当主节点被判断客观下线以后,各个哨兵节点会进行协商,先选举出一个leader 哨兵节点,并由该领导者节点进行failover(故障迁移),也就是说新的master不是由所有哨兵节点共同投票来决策的,而是由哨兵节点推选出的leader哨兵节点推动故障切换的过程来决策出新的master。
哨兵之间是如何决策投票出新的leader哨兵节点的呢?
Raft算法
Rat算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者
故障切换流程:
- redis.conf文件中,优先级slave-priority或者replica-priority:最高的从节点(数字越小优先级越高)复制偏移位置offset最大的从节点
- 最小Run ID的从节点。
- 字典顺序,ASCII码 ,最小的选中
使用建议
- 哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用
- 哨兵节点的数量应该是奇数
- 各个哨兵节点的配置应一致
- 如果哨兵节点部署在Docker等容器里面,尤其要注意端口的正确映射
- 哨兵集群+主从复制,并不能保证数据零丢失,因为哨兵的决策是需要时间的
五、集群(cluster)
5.1 基本认知
由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展。每个复制集只负责存储整个数据集一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。
简单理解:Redis集群是一个提供在多个Redis节点间共享数据的程序集,可以支持多个Master。
作用
- Redis集群支持多个Master,每个Master又可以挂载多个Slave,支持读写分离、数据的高可用和海量数据的读写存储操作
- 由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵
- 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可
- 槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系
5.2 集群算法
集群的密钥空间被分成16384个槽,有效地设置了16384个主节点的集群大小上限(建议的最大节点大小约为1000个节点)集群中的每个主节点处理16384个哈希槽的一个子集。当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。当集群稳定时,单个哈希槽将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并且可以用于扩展读取陈旧数据是可接受的操作)
槽位hlot
Redis集群没有使用一致性hash索引,而是引入哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
分片
使用Redis集群时我们会将存储的数据分散到多台dis机器上,这称为分片。简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。
HASH_SLOT = CRC16(key) mod 16384
如何找到给定key的分片?
我们对Key进行CRC16(key)算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。
使用槽位、分片和CRC16算法的最大优势就是方便弹性扩容和数据分派查找。
5.3 槽位映射的三种方案
5.3.1哈希取余分区
2亿条记录就是2亿个k、v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点
简单有效,只需要规划好节点,使用hash算法让固定的一部分请求落到同一台服务器上,起到负载均衡和分而治之的效果。
缺点
原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。当某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
5.3.2 一致性哈希算法分区
针对上文的哈希取余分区在服务器节点数量变动时会带来无法弹性扩容和hash映射关系的不可控的问题,一致性hash解决方案的提出解决方案,目的是当服务器个数发生变动时尽量减少影响客户端到服务器的映射关系。
步骤
- 算法构建一致性hash环
- Redis的服务器IP节点映射
- key落到服务器的落键规则
一致性hash环
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0=2^32),这样让它逻辑上形成了一个环形空间,这就是hash环。
换句话说,我们前面的取余公式的分母会变成2^32,通过取余操作将所有的key都映射到这一个hash虚拟圆环上,这个圆环也就是hash环。
服务器IP的节点映射
将集群中各个IP节点映射到环上的某一个位置。将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。
落键规则
当我们需要存储一个kv键值对时,首先计算key的hash值,将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
优点:容错性和可扩展性
缺点:会出现数据倾斜问题,在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。
5.3.3 哈希槽分区
哈希槽实质就是一个数组,数组[0,2^14-1]形成hash slot空间。哈希槽的出现能有效解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(Slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用ky的哈希值来计算所在的槽,便于数据分配
为什么使用的槽位时16384而不是65536?
- 出于带宽考虑:redis和节点需要每个一段时间就发送一次心跳包,心跳包的消息头太大会浪费带宽
- redis的集群主节点数量基本不可能超过1000个
- 槽位越小,节点少的情况下,压缩比高,容易传输
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令。
5.4 集群环境开启
基本配置文件
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 5000
使用redis-cli命令手动为6台Redis服务器构建集群关系
redis-cli -a password --cluster create --cluster-replicas 1 192.168.111.175:6381 192.168.111.175:6382 192.168.111.172:6383 192.168.111.172:6384 192.168.111.174:6385 192.168.111.174:6386
--cluster-replicas 1 表示为每个master创建一个slave节点
查看集群节点之间的关系
cluster nodes
查看某一个节点的在集群中的角色
info replication
如果此时正常启动redis连接,会发现一些key只能在特定的Redis服务器上才能set和get。当然这是不对的,出现的问题就是因为不同的key在集群中已经被划分出了范围。 要解决这个问题就必须进行路由到位,防止路由失效就需要在启动连接的时候加上一个-c的参数,这时候路由就会重定向。
redis-cli -a 123456 -p 6385 -c
集群从属调整
当一个Redis服务器宕机后重启并进入集群,其原有的从属关系可以通过如下命令来修复。
cluster failover
5.5 主从扩容
将新增的Redis节点作为master节点加入新集群
redis-cli -a password --cluster add-node 实际IP:实际port 集群中的一个master的IP:port
重新分配槽号
redis-cli -a password --cluster reshard 集群中的一个master的IP:port
为新加入的master节点添加slave
redis-cli-a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点id
5.6 主从缩容
首先检查集群请款并获得想要减小的Master的slave的节点ID
redis-cli -a password --cluster check IP:PORT
清除从机
redis-cli -a 密码 --cluster del-node ip:从机端口 从机节点ID
清空Master的所有槽号并分配给指定的Redis服务器,根据节点ID执行集群的重新hash
redis-cli -a 111111 --cluster reshard 指定IP:指定端口
执行后原有的master节点下的槽位就会给到指定IP和端口下运行的Redis服务器,同时原来的Master会变成Slave。
根据节点ID删除原来的master:redis-cli -a 密码 --cluster del-node 指定ip:指定端口 指定节点ID
注意:
不在同一个slot槽位下的键值无法使用mset、mget等多键操作,可以通过{ }来定义同一个组的概念,使key中{ }内相同内容的键值对放到一个slot槽位去。
三个重要的配置命令
//是否保证集群完整才能对外服务
cluster-require-full-coverage yes
//检查槽位是否被占用
CLUSTER COUNTKEYSINSLOT 槽位数字编号
//对key返回对应槽位编号
CLUSTER KEYSLOT 键名称
总结
Redis缓存中间件的第一阶段的学习就到这里啦,后续荔枝会结合项目进行梳理和介绍,感觉一个流程学下来其实收获还是很多的。整个主从复制、哨兵和集群主要就是围绕一个问题来引出的:在高并发场景下我们如何保持Redis服务的正常运行。最后荔枝希望自己能在这个暑假技术上上升一个层次嘿嘿嘿。
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~