在前面的学习中,我们已经了解了Redis的基本语法以及Redis持久化和事务的概念。而在这篇文章中我们继续来梳理管道、发布订阅、主从复制、哨兵监控和集群的知识,理解Redis主从复制到集群分片的演进过程,希望对正在学习的小伙伴有一定的帮助。
前言
一、管道
二、发布订阅
三、主从复制
3.1 概念
3.2 配置和指令
3.3 主从复制的工作流程
3.4 缺点
四、哨兵监控
4.1 基本概念
4.2 配置和命令
4.3 主观下线和客观下线
五、集群(cluster)
5.1 基本认知
5.2 集群算法
5.3 槽位映射的三种方案
5.3.1哈希取余分区
5.3.2 一致性哈希算法分区
5.3.3 哈希槽分区
5.4 集群环境开启
5.5 主从扩容
5.6 主从缩容
总结
管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间RTT。pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。换一种理解方式就是:管道其实就是一种批处理命令变种优化措施,类似于Redis的原生批命令mset等。
基本操作
首先把要执行的Redis的命令写入到一个txt文件中,使用pipeline来实现批处理
cat cmd.txt | redis-cli -a 111111 --pipe
管道与原生批命令的区别
事务与管道的区别
发布订阅是一种消息通信模式:发送者(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...]]
指退订给定的频道
同MySQL数据库一样,Redis也有主从复制的操作需求。在真实的业务场景中,我们必须要满足Redis高可用,因此不可能采用单机模式,必须同时使用多台Redis数据库服务器实现数据库的主从复制,同时实现读写分离的操作。在Redis中有两个对象:master以写为主,Slave以读为主,当master数据变化的时候,自动将新的数据异步同步到其它slave数据库。
使用主从复制优点
从库的配置
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
知识要点
两种从库连接方式如下
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
针对上述主从复制出现的问题,我们发现如果出现主机宕机后系统无法再执行写入的操作,因此我们引入了哨兵和集群来解决。
哨兵巡查监控后台master主机是否故障,如果故障了根据投票数自动将某一个从库转换为新主库,继续对外服务。哨兵的作用就是监控redis的运行状态,当master宕机后能自动将slave切换成新的master维持业务。
哨兵职责
配置redis中/opt文件下的sentinel.conf文件,记得对源文件复制保护
我们知道,网络是不可靠的,有时候一个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冲突。
主观下线(Subjectively Down)
所谓主观下线(Subjectively Down,简称SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。主观下线就是说如果服务器在sentinel down-after-milliseconds
客观下线(Objectively Down)
ODOWN需要一定数量的sentinel,多个哨兵达成一致意见才能认为一个master客观上已经宕掉,此时哨兵集群才会进行决策投票选出新的master。
故障切换
当主节点被判断客观下线以后,各个哨兵节点会进行协商,先选举出一个leader 哨兵节点,并由该领导者节点进行failover(故障迁移),也就是说新的master不是由所有哨兵节点共同投票来决策的,而是由哨兵节点推选出的leader哨兵节点推动故障切换的过程来决策出新的master。
哨兵之间是如何决策投票出新的leader哨兵节点的呢?
Raft算法
Rat算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者
故障切换流程:
使用建议
由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展。每个复制集只负责存储整个数据集一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。
简单理解:Redis集群是一个提供在多个Redis节点间共享数据的程序集,可以支持多个Master。
作用
集群的密钥空间被分成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算法的最大优势就是方便弹性扩容和数据分派查找。
2亿条记录就是2亿个k、v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点
简单有效,只需要规划好节点,使用hash算法让固定的一部分请求落到同一台服务器上,起到负载均衡和分而治之的效果。
缺点
原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。当某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
针对上文的哈希取余分区在服务器节点数量变动时会带来无法弹性扩容和hash映射关系的不可控的问题,一致性hash解决方案的提出解决方案,目的是当服务器个数发生变动时尽量减少影响客户端到服务器的映射关系。
步骤
一致性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计算出哈希值并确定此数据在环上的位置从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
优点:容错性和可扩展性
缺点:会出现数据倾斜问题,在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。
哈希槽实质就是一个数组,数组[0,2^14-1]形成hash slot空间。哈希槽的出现能有效解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(Slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用ky的哈希值来计算所在的槽,便于数据分配
为什么使用的槽位时16384而不是65536?
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令。
基本配置文件
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
将新增的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
首先检查集群请款并获得想要减小的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服务的正常运行。最后荔枝希望自己能在这个暑假技术上上升一个层次嘿嘿嘿。
今朝已然成为过去,明日依然向往未来!我是小荔枝,在技术成长的路上与你相伴,码文不易,麻烦举起小爪爪点个赞吧哈哈哈~~~ 比心心♥~~~