Redis(1.7)Redis高可用架构与数据库交互(理论篇)
【0】常用架构种类
(0.1)单机Redis
(0.2)单纯的Redis主从复制
(0.3)哨兵Sentinel+Redis主从复制集群(实现高可用自动故障转移)
(0.4)Redis Cluster 分布式数据库集群
(0.5)第三方中间件+Redis 主从复制
【1】Redis 主从复制
一般情况下,这种架构,主节点负责写数据,从节点负责读数据,主节点定期吧数据同步到从节点保证数据一致性。
常用的Redis主从拓扑:
(1)一主一从 (2)一主多从 (3)级联复制
【1.1】一主一从
主要是主库写,从库读。且主库不开启持久化,从库开启AOF即可。
【1.2】一主多从
实现读写分离,针对读较多的场景,主写从读,读有多个节点来分担,考虑到主库压力不建议从库超过3个,且受网络影响较大。
【1.3】级联复制
A->B->C/D,A复制到B,B复制C和D
【1.4】Redis主从复制过程
(1)基本过程
从库启动后:保存主节点信息;
主从建立连接;
从发送ping命令;
验证权限;
同步数据集;
持续复制;
(2)主从复制的缺点
【若主节点出现问题,则不能提供服务,需人工配置】
【主从复制主节点的写能力单机,能力有限】
【单机节点的存储能力有限】
【主从故障如何故障转移的问题】
1)主节点(master)0故障,从节点 slvae-1端执行 slaveof no one 后变成新主节点;
2)其他的节点成为新主节点的从节点,并从新节点辅助数据
3)需人工操作,无法实现高可用
当 master 关闭持久化时,复制的安全性
在使用 Redis 复制功能时的设置中,强烈建议在 master 和在 slave 中启用持久化。当不可能启用时,例如由于非常慢的磁盘性能而导致的延迟问题,应该配置实例来避免重置后自动重启。
为了更好地理解为什么关闭了持久化并配置了自动重启的 master 是危险的,检查以下故障模式,这些故障模式中数据会从 master 和所有 slave 中被删除:
-
我们设置节点 A 为 master 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据。
-
节点 A 崩溃,但是他有一些自动重启的系统可以重启进程。但是由于持久化被关闭了,节点重启后其数据集合为空。
-
节点 B 和 节点 C 会从节点 A 复制数据,但是节点 A 的数据集是空的,因此复制的结果是它们会销毁自身之前的数据副本。
【2】哨兵机制
(Sentinel)的 Redis主从复制集群
核心参考:https://www.cnblogs.com/bingshu/p/9776610.html
参考:https://www.jianshu.com/p/d6d2325a5ec7
拓扑,三台哨兵+主从复制集群(三台哨兵是为了实现哨兵之间的高可用)
哨兵机制的出现是为了解决主从复制的缺点
【2.1】哨兵机制的高可用原理
当主节点出现故障时,由 Redis Sentinel 自动完成故障发现和故障转移,并通知应用方,实现高可用。
其实整个过程只需要一个哨兵节点来完成,实现使用 Raft 算法(选举算法)实现选举机制,选出一个哨兵节点来完成转移和通知。
【2.2】哨兵是如何进行定时监控任务的?
》每个哨兵节点每10s 会向主节点和从节点发送info命令获取最新的拓扑结构图,哨兵配置时只需要对主节点的监控即可,通过向主节点发送info,获取从节点的信息,并当有心节点假如时可以马上感知到。
》每个哨兵节点每隔2S会向redis数据节点的指定频道上发送该哨兵节点对于主节点的判断以及当前哨兵节点的信息,同时每个哨兵节点也会订阅该频道,来了解其他哨兵节点的信息以及对主节点的判断,其实就是通过 Publish 和 subscribe 来完成的。(以此机制来投票选出处理问题的哨兵)
》每间隔1S每个哨兵会向主节点、从节点以及其余哨兵节点发送以此ping命令做一次心跳检测,这个也是哨兵用来判断节点是否正常的重要依据。
【2.3】Redis哨兵选举流程(3个中,谁是老大)
主观下线
哨兵(Sentinel)节点会每秒一次的频率向建立了命令连接的实例发送PING命令,如果在down-after-milliseconds
毫秒内没有做出有效响应包括(PONG/LOADING/MASTERDOWN)以外的响应。
哨兵就会将该实例在本结构体中的状态标记为SRI_S_DOWN
主观下线
主观下线 适用于所有 主节点 和 从节点。如果在 down-after-milliseconds
毫秒内,Sentinel
没有收到 目标节点 的有效回复,则会判定 该节点 为 主观下线。
客观下线
当一个哨兵节点发现主节点处于主观下线状态是,会向其他的哨兵节点发出询问,该节点是不是已经主观下线了。如果超过配置参数quorum
个节点认为是主观下线时,
该哨兵节点就会将自己维护的结构体中该主节点标记为SRI_O_DOWN
客观下线询问命令SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <run_id>
客观下线 只适用于 主节点。如果 主节点 出现故障,Sentinel
节点会通过 sentinel is-master-down-by-addr
命令,向其它 Sentinel
节点询问对该节点的 状态判断。
如果超过 <quorum>
个数的节点判定 主节点 不可达,则该 Sentinel
节点会判断 主节点 为 客观下线。
》每个在线的哨兵节点都可以称为领导者,但它确认(比如哨兵3)主节点下线时,会向其他哨兵发is-master-down-by-addr命令,征求判断并要求将自己设置为领导者,由领导者处理故障转移;
》当其他哨兵收到次命令时,可以同意或拒绝它称为领导者
》如果哨兵3发现自己的选举票大于50%时,将称为领导者,如果没有超过,继续换人选举。
【2.4】Resi哨兵故障转移步骤
》由Sentinel节点定期监控发现主节点是否出现了故障
》当主节点出现故障,此时3个Sentinel节点共同选举了 Sentinel3 节点为领导,负载处理主节点的故障转移。
》由Sentinel3领导者节点执行故障转移,过程和主从复制一样,但自己执行
1)主节点挂了,将 slave1 脱离原从节点,升级为主节点
2)将其他的从节点比如 slave-2/slave3 等指向新的主节点 slave1
3)通知客户端主节点已经更换成 slave1
4)原主节点修复恢复后,将原主节点变成从节点,指向新的主节点 slave1
【2.5】哨兵的常见配置参数
# redis进程是否以守护进程的方式运行,yes为是,no为否(不以守护进程的方式运行会占用一个终端)。 daemonize no # 指定redis进程的PID文件存放位置 pidfile /var/run/redis.pid # redis进程的端口号 port 6379 # 绑定的主机地址 bind 127.0.0.1 # 客户端闲置多长时间后关闭连接,默认此参数为0即关闭此功能 timeout 300 # redis日志级别,可用的级别有debug.verbose.notice.warning loglevel verbose # log文件输出位置,如果进程以守护进程的方式运行,此处又将输出文件设置为stdout的话,就会将日志信息输出到/dev/null里面去了 logfile stdout # 设置数据库的数量,默认为0可以使用select <dbid>命令在连接上指定数据库id databases 16 # 指定在多少时间内刷新次数达到多少的时候会将数据同步到数据文件 save <seconds> <changes> # 指定存储至本地数据库时是否压缩文件,默认为yes即启用存储 rdbcompression yes # 指定本地数据库文件名 dbfilename dump.db # 指定本地数据问就按存放位置 dir ./ # 指定当本机为slave服务时,设置master服务的IP地址及端口,在redis启动的时候他会自动跟master进行数据同步 slaveof <masterip> <masterport> # 当master设置了密码保护时,slave服务连接master的密码 masterauth <master-password> # 设置redis连接密码,如果配置了连接密码,客户端在连接redis是需要通过AUTH<password>命令提供密码,默认关闭 requirepass footbared # 设置同一时间最大客户连接数,默认无限制。redis可以同时连接的客户端数为redis程序可以打开的最大文件描述符,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回 max number of clients reached 错误信息 maxclients 128 # 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key。当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区 maxmemory<bytes> # 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no。 appendonly no # 指定跟新日志文件名默认为appendonly.aof appendfilename appendonly.aof # 指定更新日志的条件,有三个可选参数 - no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全), everysec:表示每秒同步一次(折衷,默认值); appendfsync everysec
【3】Redis Cluster
(3.1)Redis Cluster 介绍
分布式数据库集群
【3.1】RedisCluster是redis的分布式解决方案,支持分片,Redis3.0版本之前,
可以通过Redis Sentinel(哨兵)来实现高可用(HA),从3.0版本后,官方退出了Redis Cluster,它的主要用途是实现数据分片(Data Sharding),不过听杨可以实现HA,是官方当前推荐的方案,有效的解决了Redis分布式的需求,当一个服务挂了可以快速的切换到另外一个服务。
优势:去中心化,去中间件,集群中任意节点平等,任意节点可以获得全局的数据。(和Mysql MGR有点类似)
【3.2】分布式数据库把整个数据按分区规则映射到多个节点,即把数据划分到多个节点上,每个节点负责整体数据的一个子集。
自动将数据进行分片,每个master上放一部分数据 (2)提供内置的高可用支持,部分master不可用时,还是可以继续工作的
在redis cluster架构下,每个redis要放开两个端口号,比如一个是6379,另外一个就是加10000的端口号,比如16379
16379端口号是用来进行节点间通信的,也就是cluster bus的东西,集群总线。cluster bus的通信,用来进行故障检测,配置更新,故障转移授权
cluster bus用了另外一种二进制的协议,主要用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间
【3.2.1】案例:
比如有3000条用户数据,有3个redis节点,将3000条数据分成3份(分块分片了,并不是绝对的3个1000行数据),按分区规则分别存入到3个redis节点。
【3.2.2】分区规则:
Redis 集群使用了 哈希分区的 “虚拟槽分区” 方式(槽:slot),即哈希槽的概念
所有的键根据 哈希函数(CRC16[KEY]&16383) 即 HASH_SLOT(key)= CRC16(key) % 16384
映射到 0~16383槽内,共16384个槽位。每个节点维护部分槽及槽锁映射的键值数据。
Redis 集群有16384个哈希槽(slot),当需要在 Redis 集群中放置一个 key-value 时,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
【3.2.3】redis用虚拟槽分区的原因:
解耦数据与节点的关系,节点自身维护槽映射关系,分布式存储。
Redis 集群有16384个哈希槽(slot),当需要在 Redis 集群中放置一个 key-value 时,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
【3.2.4】RedisCluster的缺陷:
》键的批量操作支持有限,比如mset,mget,如果多个键映射在不同的槽,就不支持了
》不支持多数据库,只有0,select 0
》复制结构只支持单层结构,不支持树形结构(即不能级联复制了)
》键事务支持有限,当多个Key分部在不同节点时无法使用事务,同一节点是支持事务的
》键是数据分区的最小粒度,不能将一个很大的键值对 映射到不同的节点
(3.2)redis cluster的hash
hash算法的发展:
《1》算法最老土的hash算法和弊端(大量缓存重建)
比如cluster中有三台master机器,某一个master宕机了,就要重新对剩下2台master取模,再分布到其他的节点上去。
对于高并发的系统来说,是不可接受的,这会导致请求直接打到数据库上,从而将数据库压垮。
一台机器宕机后,全部请求过来会发现,要基于最新的master个数取模然后获取数据,这将导致几乎大部分的请求,无法拿到有效的缓存,大量的流量就会涌入数据库中。
《2》一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡)
一致性hash
一致性hash算法,可以保证任何一个master宕机,只有之前在那个master上的数据会受到影响,因为照着顺时针走,全部在之前的master上的数据找不到了,会顺时针去下一个master找,也会找不到。这样只有1/3的流量会涌入到数据库中重新查询。
虚拟节点
缓存热点的问题,可能集中在某个hash区间内的值特别多,那么会导致大量的数据库都涌入同一个master内,造成master的热点问题,性能出现瓶颈。
虚拟结点
《3》redis cluster的hash slot算法
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上去
移动hash slot的成本是非常低的
客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现
一、redis cluster核心原理分析
一、节点间的内部通信机制
1、集群基础通信原理(gossip)
(1)redis cluster节点间采取gossip协议进行通信
跟集中式不同,不是将集群元数据(节点信息,故障,等等)集中存储在某个节点上,而是互相之间不断通信,保持整个集群所有节点的数据是完整的
维护集群的元数据用得,集中式,一种叫做gossip
集中式:好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力
gossip:好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后
我们刚才做reshard,去做另外一个操作,会发现说,configuration error,达成一致
(2)10000端口
每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口
每隔节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong
(3)交换的信息
故障信息,节点的增加和移除,hash slot信息,等等
2、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给其他节点,通知其他节点,指定的节点宕机了
3、ping消息深入
ping很频繁,而且要携带一些元数据,所以可能会加重网络负担
每个节点每秒会执行10次ping,每次会选择5个最久没有通信的其他节点
当然如果发现某个节点通信延时达到了cluster_node_timeout / 2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了
比如说,两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题
所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送的频率
每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换
至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息
二、面向集群的jedis内部实现原理
开发,jedis,redis的java client客户端,redis cluster,jedis cluster api
jedis cluster api与redis cluster集群交互的一些基本原理
1、基于重定向的客户端
redis-cli -c,自动重定向
(1)请求重定向
客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会计算key对应的hash slot
如果在本地就在本地处理,否则返回moved给客户端,让客户端进行重定向
cluster keyslot mykey,可以查看一个key对应的hash slot是什么
用redis-cli的时候,可以加入-c参数,支持自动的请求重定向,redis-cli接收到moved之后,会自动重定向到对应的节点执行命令
(2)计算hash slot
计算hash slot的算法,就是根据key计算CRC16值,然后对16384取模,拿到对应的hash slot
用hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100}和set mykey2:{100}
(3)hash slot查找
节点间通过gossip协议进行数据交换,就知道每个hash slot在哪个节点上
2、smart jedis
(1)什么是smart jedis
基于重定向的客户端,很消耗网络IO,因为大部分情况下,可能都会出现一次请求重定向,才能找到正确的节点
所以大部分的客户端,比如java redis客户端,就是jedis,都是smart的
本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向
(2)JedisCluster的工作原理
在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池
每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点
如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved
如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存
重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException
jedis老版本,可能会出现在集群某个节点故障还没完成自动切换恢复时,频繁更新hash slot,频繁ping节点检查活跃,导致大量网络IO开销
jedis最新版本,对于这些过度的hash slot更新和ping,都进行了优化,避免了类似问题
(3)hashslot迁移和ask重定向
如果hash slot正在迁移,那么会返回ask重定向给jedis
jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存
已经可以确定说,hashslot已经迁移完了,moved是会更新本地hashslot->node映射表缓存的
三、高可用性与主备切换原理
redis cluster的高可用的原理,几乎跟哨兵是类似的
1、判断节点宕机
如果一个节点认为另外一个节点宕机,那么就是pfail,主观宕机
如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown
在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail
如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail
2、从节点过滤
对宕机的master node,从其所有的slave node中,选择一个切换成master node
检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master
这个也是跟哨兵是一样的,从节点超时过滤的步骤
3、从节点选举
哨兵:对所有从节点进行排序,slave priority,offset,run id
每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举
所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master
从节点执行主备切换,从节点切换为主节点
4、与哨兵比较
整个流程跟哨兵相比,非常类似,所以说,redis cluster功能强大,直接集成了replication和sentinal的功能
没有办法去给大家深入讲解redis底层的设计的细节,核心原理和设计的细节,那个除非单独开一门课,redis底层原理深度剖析,redis源码
对于咱们这个架构课来说,主要关注的是架构,不是底层的细节,对于架构来说,核心的原理的基本思路,是要梳理清晰的
【4】关于第三方中间件+Redis主从复制(3.0以后很少使用中间件了)
【4.1】Twemproxy
Redis client=》Twemproxy=》根据路由规则发送到对应的Redis实例=》Twemproxy把结果返回 Client
【4.2】Codis(如果真要用,用这个)
【4.3】基本架构
【5】Mysql与redis整合访问图解