Redis的分布式原理

1.为什么需要redis集群?

1.1 为什么需要集群?

1.1.1 性能

Redis本身的QPS已经很高了,但是如果在一些并发量非常高的情况下,性能还是会受到影响。这个时候我们希望有更多的Redis服务来完成工作。

1.1.2 扩展

第二个是出于存储的考虑。因为 Redis所有的数据都放在内存中,如果数据量大,很容易受到硬件的限制。升级硬件收效和成本比太低,所以我们需要有一种横向扩展的方法。

1.1.3 可用性

第三个是可用性和安全的问题。如果只有一个 Redis服务,一旦服务宕机,那么所有的客户端都无法访问,会对业务造成很大的影响。另一个,如果硬件发生故障,而单机的数据无法恢复的话,带来的影响也是灾难性的。
可用性,数据安全,性能都可以通过搭建多个Redis服务来实现,其中有一个主节点master,可以有多个从节点slave,主从之间数据同步,储存完全相同的数据,如果主节点发生故障,则把某个从节点改成主节点,访问新节点

Redis的主从复制(replication)

2.1主从复制配置

例如一台服务器主节点的是203,在每个从节点的redis.conf文件中增加一行
slaveof 192.xx.xx.203 6379;
//主从切换时,这个配置会被重写为
replicaof 192.xx.xx.203 6379;
或者在启动服务时通过参数的指定master节点
节点:
./redis-server–slaveof 192.xx.xx.203 6379
或在客户端直接执行slaveof xx xx,使该Redis实例成为从节点。
启动后,查看集群状态:
info replication
从节点不能写数据,只能读数据,只能从master节点上同步数据,get 成功,set失败;
主节点写入后,slave节点会自动从master同步数据
断开复制
slaveof no one;

2.2 主从复制原理

2.2.1 连接阶段

1.slave node 启动时(slaveof 命令),会在本地保存master node 的信息,包括master node的host和ip;
2.slave node 内部有个定时任务replicationCron,每隔1s中检查是否有新的master node 要连接和复制,如果发现,就跟master建立socket网络连接,如果连接成功,从该节点为socket建立一个专门处理复制工作的文件处理器,负责后续的复制工作,如接口RDB文件,传播广播等;

2.2.2 数据同步阶段

1.master node 第一次执行全量复制,通过bgsave命令在本地生成一份RDB快照,将快照文件发送给slave node(如果超时,会重连,可以调大repl-timeout的值),slave node 先要清除自己的旧数据,然后用RDB文件加载数据;
问题:生成RDB之间,master接受命令要怎么处理?
开始生成RDB时,master会把新的命令写到缓存在内存中,在slave node复制完成后,再将新的命令复制给从节点;

2.2.3 命令的传播阶段

1.master node持续写命令,异步复制给slave node;
repl-disable-tcp-nodelay no
当设置为yes,tcp会对包进行合并并减少带宽,但是发送频率会降低,从节点数据延迟降低,一致性变差;具体的发送频率与Linux内核有关,默认配置为40ms,当设置为no时,tcp立即将数据从主节点发送到从节点,带宽增加,延迟降低;一般来说,只有当应用对Redis数据不一致的容忍度比较高,且网络状态不好,才设置成yes,多数情况下设置为no。
问题:如果从节点一段时间断开了从主节点上复制数据,是不是要再复制一遍?
不用,通过master_repl_offset 记录偏移量;

2.3 主从复制的不足

主从复制解决了数据备份和性能(读写分离),但还存在一些问题:
1.RDB文件过大,同步非常消耗
2.在一主一从,一主多从的情况下,如果主服务器挂了,对外提供的服务器就不可用,单点问题没有解决,如果手动把之前的服务器切换成主服务器,这个比较费力,还会造成应用一定时间的不可用;

3.可用性保证之Sentinel

3.1Sentinel

创建一台监控服务器来监控redis服务节点的状态,比如,master节点超过一段时间没有给监控服务器发送心跳报文,就把master标记为下线,然后把某个slave节点变成master节点,应从每次都从监控器上拿到master系欸但信息;
问题:如果监控器挂掉了,就拿不到应用了。
那我们在创建一台监控来监控;
Redis的Sentinel就是从过运行监控服务器来保证服务的可用性;
从Redis2.8后,提供了稳定版本的Sentinel(哨兵),用来解决高可用的问题;
我们会启动一个或者多个sentinel服务,它的本质是运行在特殊模式下的redis服务,Sentinel通过info命令得到被监听的redis机器的master,slave等信息。
在这里插入图片描述
为了保证服务的可用性,会对sentinel做集群的部署,sentinel即监控了所有的redis服务,sentinel之间也相互监控;
Sentinel本身没有主从之分;

3.1.1 服务下线

sentinel 默认每秒中1频率向redis服务节点发送ping命令,如果在down-after-milliseconds 内没有收到有效回复,sentinel会将该服务器标记为下线(主观下线)
这个时候sentinel会继续询问其他的sentinel,确认这个节点是否可用,,如果多数节点认为这个master下线,master才会真正被下线(客观下线),这个时候就需要重新选举master;

3.1.2 故障转移

如果master被标记为下线,就会开始故障转移流程,故障转移的第一步就是在sentinel中选出一个leader,由leader完成故障转移流程,sentinel从过Raft算法,实现sentinel选举

Raft算法

在分布式存储系统中,通常通过维护多个副本来提高系统的高可用性,那么多节点面对的问题是数据一致性问题,raft算法就是从过复制的方式,使所有节点达成一致,这么多节点以哪个节点为准呢?必须选出leader;
大体上有两个步骤,领导选举,数据复制等;
核心思想:先到先得,少数服从多数;
Sentinle的Raft 算法和Raft论文略有不同。
1,master客观下线后触发选举,而不是从过election timeout时间开始选举。
2.leader并不会把自己称为leader的消息转发给多个sentinel,其他待leader从slave节点中选出master后,检测到新的master正常工作后,就会客观去掉下线标识,从而不需要故障转移流程;

故障转移

问题:怎么让原来的slave成为主节点?
1.选出sentinel Leader后,由它向某个节点发送slaveof on one命令,让它独立节点;
2.然后向其他节点发送slaveof X.x.x.x xxxx ,让他们成为本机的从节点。故障转移完成;
问题:这么多节点,选谁做主节点?
关于主节点选举,一般有4个因素,分别是断开连接时长,优先级,排序,复制数量,进程id;
如果断开的比较久,超过某个阈值,就直接失去选举权,如果拥有选举权,那就看看谁的优先级比较高,这个配置文件中可以配置(replica-priority 100) ,数量越小,优先级越高;如果优先级相同,则看谁从master复制的数据量大,选最多的那个;

sentinel实战

配置

为了保证 Sentinel 的高可用,Sentinel 也需要做集群部署,集群中至少需要三个
Sentinel实例(推荐奇数个,防止脑裂)。
在这里插入图片描述
以Redis安装路径/usr/local/soft/redis-5.0.5/为例。
在204和205的src/redis.conf配置文件中添加
slaveof192.xx.x.xxx 6379
在203、204、205创建sentinel配置文件(安装后根目录下默认有sentinel.conf) :
cd/usr/local/soft/redis-5.0.5
mkdir logs
mkdir rdbs
mkdir sentinel-tmp
daemonize yes
port 26379
protected-modeno dir"/usr/local/soft/redis-5.0.5/sentinel-tmp"

sentinel monitor redis-master192.xx.x.xxx 63792
sentinel down-after-milliseconds redis-master 30000
sentinel failover-timeoutredis-master 180000
sentinel parallel-syncsredis-master1
上面出现了4 个’redis-master’,这个名称要统一,并且使用客户端(比如 Jedis)连接的时候名称要正确。
在这里插入图片描述
cd/usr/local/soft/redis-5.0.5/src
#启动 Redis 节点 ./redis-server…/redis.conf
#启动 Sentinel 节点 ./redis-sentinel…/sentinel.conf # 或者 ./redis-server…/sentinel.conf–sentinel
查看集群状态
redis>info replication
在这里插入图片描述

3.3.3 Sentinel 连接使用

master name来自于sentinel.conf的配置。

private static JedisSentinelPool createJedisPool(){ StringmasterName="redis-master"; Set<String>sentinels=newHashSet<String>(); sentinels.add("192.168.8.203:26379"); sentinels.add("192.168.8.204:26379"); sentinels.add("192.168.8.205:26379"); pool=newJedisSentinelPool(masterName,sentinels); returnpool; }

Spring Boot连接Sentinel

spring.redis.sentinel.master=redis-master spring.redis.sentinel.nodes=192.168.8.203:26379,192.168.8.204:26379,192.168.8.205:26379

3.4 哨兵机制的不足

主从切换过程会丢失数据,因为只有一个master.
只能单点写,没有水平解决扩容问题;
如果数据量非常大,这个时候我们需要多个master组,把数据分布到不同的group中;
那么问题来了?数据怎么分片,分片后,怎样实现路由?

4.redis分布是方案

如果要实现分片,我们有三种不同的方案,第一种实在客户端,实现相关逻辑,例如取模或者一致性hash,查询和修改先判断key的路由;
第二种是把分片抽取出来单独做成一个代理,有客户端连接到这个代理,再由这个代理服务做出请求转发;
第三种就是基于服务端的实现

4.1 客户端sharing

在这里插入图片描述

publicclassShardingTest{ publicstaticvoidmain(String[]args){ JedisPoolConfigpoolConfig=newJedisPoolConfig();//Redis 服务器 JedisShardInfoshardInfo1=newJedisShardInfo("127.0.0.1",6379); JedisShardInfoshardInfo2=newJedisShardInfo("192.168.8.205",6379);// 连接池 List<JedisShardInfo>infoList=Arrays.asList(shardInfo1,shardInfo2); ShardedJedisPooljedisPool=newShardedJedisPool(poolConfig,infoList);
ShardedJedisjedis=null; try{ jedis=jedisPool.getResource(); for(inti=0;i<100;i++){ jedis.set("k"+i,""+i); } for(inti=0;i<100;i++){ System.out.println(jedis.get("k"+i)); }}finally{ if(jedis!=null){ jedis.close();
4.2 代理

在这里插入图片描述
典型的代理分区方案有Twitter开源的Twemproxy和国内的豌豆荚开源的Codis。
在这里插入图片描述
优点:比较稳重,可用性高;
不足:
1.出现故障不能自动转移,架构复杂;
2.扩缩容需要修改配置,不能实现平滑的扩缩容;

4.2 codis

在这里插入图片描述
在这里插入图片描述
分片原理:codis把所有key分成n个槽,每个槽由对应的一个分组,一个分组对应有一个或者一组redis实例,codis对key进行了crc32运算,得到一个32为数字,然后模拟n个槽,得到的余数,就是这个key所对应的槽,槽后面就是redis实例,比如4个槽
在这里插入图片描述
codis的槽位映射关系是保存再proxy中,如果要解决单间问题,codies也要做集群,, 多个Codis节点怎么同步槽和实例的关系呢?需要运行一个Zookeeper (或者etcd/本地文件)。

4.3Redis Cluster

redis3.0版本后,用来解决分布式问题,同时也可以实现高可用,客户端可以连接到任意一个可用节点;
数据分片有几个关键的问题需要解决?

  • 数据如何相对均匀的分布
  • 客户端怎么样访问到相应的节点和数据
  • 重新分配的过程中,如果保证正常服务
4.3.1 架构

redis cluster 可以看成是由多个Redis实例组成的数据集合,客户端不需要再关注数据子集到底存放到哪个节点,只需要关注集合整体
以三主三从为例,节点之间两两交互,共享数据分片,节点状态等;
在这里插入图片描述

4.3.2搭建

配置 启动 进入客户端: redis-cli-p7291 redis-cli-p7292 redis-cli-p7293 批量插入数据

在这里插入图片描述

4.3.3 数据分布

如果希望数据分布的均匀,可以考虑hash和取模

  • 哈希后取模
    例如,hash(key)%N,根据余数,决定映射到那一个节点。这种方式比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的N发生变化,数据需要重新分布。
    为了解决这个问题,我们又有了一致性哈希算法。
  • 一致性hash
    把所有的哈希值空间组织成一个虚拟环,整个空间按照顺时针方向组织,因为是环空间,0和2^32-1是重叠的。
    假设我们有4台集器要用哈希环来实现映射,我们根据集器的ip来计算哈希值,然后分布到哈希环中
    在这里插入图片描述
    现在有4条数据访问请求,对key进行计算后,得到的是哈希环中绿色的位置,沿哈希环顺时针找到的第一个Node,就是数据存储的节点。
    在这里插入图片描述
    在这种情况下,新增了一个Node5节点,不影响数据的分布。
    在这里插入图片描述
    删除了一个节点Node4,只影响相邻的一个节点。
    在这里插入图片描述
    一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。
    但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。
    在这里插入图片描述
    Node1设置了两个虚拟节点,Node2也设置了两个虚拟节点(虚线圆圈)。
    这时候有3条数据分布到Node1,1条数据分布到Node2。
    在这里插入图片描述
redis虚拟槽分配

Redis创建了16384个槽,每个节点负责一定的空间,比如Node1负
责0-5460,Node2负责5461-10922,Node3负责10923-16383。
在这里插入图片描述
Redis的每一个master节点维护一个16384位(2048Kb==2kb)的位序列;
对象分布到Redis节点上,对key进行CRC16算法后再%16384,得到槽的位置,数据落到负责这个槽的节点上;
redis>cluster keyslot qingshan;
问题:怎么让相关数据落到一个槽上?
比如有些multikey操作是不能跨节点的,如果要让某些数据分布到一个节点上,例如用户2673的基本信息和金融信息,怎么办?
在key里面加入{hash tag}即可。Redis在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。
问题:客户连接到那台服务器上?访问数据不在该节点上,怎么办?
服务端返回moved,也就是根据key计算槽的位置,服务端会告诉你需要去那台机器上访问,这个时候 更换端口,用redis-cli -p 7239才会返回ok,或者用./redis-cli -c-p port 的命令(c代表cluster)。这样客户端需要连接两次。
jedis客户端会在本地维护一份slot–node的映射关系,大部分时候不需要定向;
问题:新增master或者下线了,数据怎么迁移和重新分配?
因为key和slot的关系永远不会变的,当新增节点,需要把原来相关slot的数据迁移过来就行;添加新节点(新增一个7297):
redis-cli–cluster add-node 127.0.0.1:7291 127.0.0.1:7297
新增的节点没有哈希槽,不能分布数据,在原来的任意一个节点上执行:
redis-cli–cluster reshard 127.0.0.1:7291
输入需要分配的哈希槽的数量(比如500),和哈希槽的来源节点(可以输入all或者id)。
问题:一个主节点挂了,从节点怎么变成主节点?
当slave发现自己的master变成fail时,便尝试惊醒FailOver,以期待成为行动master,由于挂掉的master有多个slave节点,从而slave竞争master节点的过程如下:

  • slave发现自己的master变成FAIL;
  • 将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息
  • 其他节点收到该信息,只有master响应,判断请求的合法性,并发送一个FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
  • 尝试failover的slave收集FAILOVER_AUTH_ACK
  • 超过半数后就变成新的Master
  • 广播Pong通知其他集群节点
    Redis cluster 既能实现主从分配的角色,又能实现主从切换,相当于继承了repilcation和sentinel;

4.7.3 优缺点

优点:

  • 没有中心框架
  • 数据按照slot分布多个数据节点,节点之间数据共享,可动态调整数据分布
  • 可扩展性,可扩展到1000个节点,节点可动态的添加或者删除
  • 高可用,部分节点不可用了,集群仍然可用,增加slave做数据副本,能够实现故障自动failover,节点间通过gossip协议交换状态信息,用投票机制完成slaver到master选举
  • 降低运营成本,提高系统的高可用性和扩展性
    不足:
  • Client实现太复杂,驱动要求实现Smart Client,缓存slotsmapping信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。
  • 节点会因为某些原因发生阻塞,被判断位下线,这种failover没有必要
  • 数据通过异步复制,不能保证数据的强一致性
  • 多个业务使用同一套集群,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值