08-Redis Cluster

nobility 发布于 2022-11-06 2431 次阅读


Redis Cluster

数据分布

单个数据库的容量已经无法满足容量需求,将数据按照一定规则分散到多台数据库服务器上,并且各个节点都是互相通讯的,客户端在存取数据时,根据要存取的数据也按照这个规则访问对应的数据库服务器,这样不仅扩大数据库容量,还能提高并发量,甚至能提升网络带宽,这就是分布式的基本原理

范围分区

顺序范围:根据数据的某个属性范围进行分区,属性可以是时间、索引字段(key)等,比如:今天的数据和明天的数据分布在不同节点上,索引范围是1到100、101到200分布在不同节点上

列表范围:根据数据库表中的多列内容相同情况下进行分区,比如:描述分类的字段,将不同类别的数据分布在不同节点上

优点:数据分布与业务相关,可顺序访问

缺点:数据分散容易倾斜

哈希分区

根据哈希函数计算出数据的哈希值,根据哈希值将数据分布在不同节点上

优点:数据分散度高不易倾斜

缺点:数据分布与业务无关,不可顺序访问

节点取余分区

计算出数据的哈希值后,将哈希值与节点个数进行取余,余数为几就将数据分布到编号为几的节点上

问题:若添加或删除一个节点,就需要对数据进行重新计算哈希并取余,数据迁移高达80%

解决:成倍伸缩节点,可减少数据迁移量到50%

一致性哈希分区

约定长度2^32^位的哈希环(可以理解为哈希值的范围,将范围看作一个环),将每个节点均匀分布到环上,计算出的哈希值一定落在环上,顺时针的去选择节点对该数据进行操作

解决的问题:若添加或删除一个节点时,会添加到环上,必然是在两个节点之间,所以只会影响到相邻的两个节点,不会影响其他节点

问题:任然存在少量的数据迁移,而且还会导致数据分布不均匀

解决:成倍伸缩节点,可以使数据分布均匀

虚拟槽(节点)分区

预设均匀的虚拟槽,每个槽映对应环上一个虚拟节点,再让用户决定虚拟节点到实际节点进行映射,达到分布情况由用户设置决定的目的,Redis采用CRC16哈希算法进行哈希值的计算,再对16383(即16383个槽)取模来决定节点归那个虚拟节点管,从而映射到实际节点上

优点:即时添加新节点,也不会立刻数据迁移,只有用户将虚拟槽分配给新节点才会发生数据迁移,从而减少数据迁移过程中丢数据的可能性

架构

Redis-Cluster

原理

智能客户端

redis-cli -c使用c参数可以开启集群模式下的客户端,当发生以下异常时会自动跳转

概念
  • moved重定向:随机选取节点进行命令的执行,若命中直接返回结果,否则会返回一个moved异常告知客户端应该访问的节点,即槽已经迁移,客户端再进行跳转
  • ask重定向:执行命令的节点正在进行槽的迁移,该节点会返回一个ask异常,告知客户端当前槽是在当前节点,但是数据已经迁移到了另一个节点,即槽在迁移过程中,数据已经迁移,客户端先给另一个节点发送asking命令,再执行要执行的命令
步骤
  1. 从集群中选一个可运行节点,使用clutser slots命令获取槽和节点之间的映射
  2. 将映射结果存储本地,为每个节点创建连接池
  3. 准备执行命令,即本地对key进行计算出对应的槽,从映射中获取槽对应的节点,再进行命令的执行
  4. 若连接出错有以下几种情况
    • 普通的连接超时:重连即可
    • ask异常:根据ask异常进行跳转即可
    • moved异常:先根据moved异常进行跳转,同时使用clutser slots命令刷新槽和节点之间的映射,再执行第3步,若期间连续多次出现了moved异常,则可能集群有问题
批量操作

集群中无法使用mgetmset的批量操作的,除非这些key都在一个槽中,这显然是不现实的

方案 原理 优点 缺点 网络IO
串行get/set 遍历所有的key并单个的执行get/set操作 编程简单,少量keys满足需求 大量keys请求延迟严重 O(keys)
串行IO 将所有访问相同节点的key进行汇总,使用管道进行命令传输 编程较简单,少量node满足需求 大量node延迟严重 O(nodes)
并行IO 多个线程并行的使用管道将汇总命令进行传输 由于是并行,延迟取决于最慢的节点 编程复杂,超时定位问题难 O(max_slow(node))
hash_tag 当一个key包含{}时,就会仅对{}包括的字符串做hash,就可以只访问一个节点 性能最高 读写增加tag维护成本,tag分布易出现数据倾斜 O(1)

故障转移

集群中各个节点进行互相监控

主观下线与客观下线
  • 主观下线:单个节点对某个节点是否在线的看法
    • 在定时的心跳检测过程中,两次心跳超过cluster-node-timeout设置的时间就会标记为主观下线
  • 客观下线:半数以上有槽的主节点对某个节点是否在线的看法
    • 将其他节点发送的认为主观下线的消息存入一个故障链表中,节点可以知道有多少主节点主观下线
    • 故障链表是存在有效期,有效期为cluster-node-timeout 乘2,防止之前的主观下线消息长久存在于故障链表中
    • 当达有效的主观下线到达半数以上时,就将节点更新为客观下线,并向集群广播节点下线,并做故障转移
故障恢复
  1. 对从节点进行资格检查

    • 每个从节点检查与故障主节点的断线时间,超过cluster-node-timeoutcluster-slave-validity-factor(默认是10)的从节点会取消资格
  2. 准备选举时间,为了使偏移量最大的从节点更有机会成为主节点

    • 对各个符合资格的从节点按照偏移量进行排序,偏移量越大的节点准备选举时间越小,因为越早被选举,后续投票阶段可获得更多票数
  3. 所有可用的主节点对参加竞选的从节点进行选举投票,当半数以上主节点的票数时,就看升级为主节点

  4. 替换主节点

    1. 在选中的从节点上执行slaveof no one命令,即取消复制成为主节点
    2. 执行cluster del slot命令撤销故障主节点负责的槽,执行cluster add slot命令把这些槽分配给自己
    3. 向集群广播自己已经替换了故障从节点的消息

集群伸缩

对于主节点来说,不管是添加还是删除节点,都需要进行槽和数据的迁移,对于从节点来说就不需要进行槽和数据的迁移了,迁移前应该先指定槽的迁移计划,尽量的让槽分布均匀和小量的数据迁移

  1. 对目标节点发送cluster setslot 某个槽 importing 目标节点nodeid命令,让目标节点准备导入该槽中的数据,目标节点nodeid为了确认就是找这个节点
  2. 对源节点发送cluster set 某个槽 migrating 源节点nodeid命令,让源节点准备导出该槽中数据,源节点nodeid为了确认就是找这个节点
  3. 在源节点循环执行cluster setkeysinlot 某个槽 指定个数的key命令,获取指定个数个key后,在执行migrate 目标节点IP 目标节点端口 key 0 超时时间将数据迁移到目标节点上,直到所有key都迁移完
  4. 向集群内所有节点发送cluster setslot 某个槽 node 目标节点nodeid命令,告知集群内所有节点槽已经分配给目标节点
添加节点
  1. 以集群模式开启要添加的Redis节点,并且配置要与集群中的节点配置统一
  2. 在已启动的孤立的节点中执行cluster meet命令指定要连接的节点IP和端口,加入集群
  3. 若是主节点则需做槽和数据的迁移
删除节点
  1. 要下线节点若有槽需先做槽和数据的迁移
  2. 对所有节点执行cluster forget 要忘记节点的nodeid命令,通知所有节点删除该节点,需要在60s之内都通知到所有,否则无效

实现方式

在某个Redis节点上执行cluster nodes命令可进行查看单个Redis节点的信息

在任意Redis节点上执行cluster info命令可以查看整个集群的信息

在任意Redis节点上执行cluster slots命令可以查看到整个集群中槽分配的情况,主从节点应该分配的槽是一样的,在同一组

原生命令

  1. 以集群模式开启多个Redis节点

    配置项 含义 默认值
    cluster-enabled 节点是否开启集群模式 yes
    cluster-node-timeout 节点主观下线超时时间,单位毫秒 15000
    cluster-config-file 自动生成的集群节点配置文件名,也是cluster info命令的执行结果内容 nodes.conf
    cluster-require-full-coverage 集群完整性参数,是否需要所有节点全都可用,集群才能对外服务 yes
  2. 选择一个节点执行cluster meet命令指定要连接的节点IP和端口,连接一遍所有节点即可,因为Gossip协议会让其他节点互相感知

  3. 在每个节点上使用cluster addslots指定槽,该命令无法指定某个范围,可以通过shell脚本来指定范围

    port=$1	#第一个参数为Redis节点端口
    start=$2	#起始槽
    end=$3	#结束槽
    for slot in `seq ${start} ${end}`	#遍历起始到结束位置
    do
    	echo "slot:${slot}"
    	redis-cli -p ${port} cluster addslots ${slot}	#执行添加槽命令
    done
    
  4. 在从节点上使用cluster replicate指定nodeid,来设置对应的主节点

官方工具

若是3.0版本redis需要在Redis源码包的src目录中找到名为redis-trib的ruby脚本,若是5.0及以上版本以及集成到redis-cli中,无需以下操作

  1. 安装Ruby环境,使用yum install ruby安装,默认安装的是2.0版本
  2. 安装Ruby的redis客户端,使用gem install redis -v 3.0.7安装
  3. 以集群模式开启多个Redis节点
  4. 执行redis-trib脚本
#3.0版本
./redis-trib.rb create --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		#从节点,一一对应


#5.0版本
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		#从节点,一一对应

客户端使用

jedisCluster
Set<HostAndPort> nodeList = new HashSet<>();  //存放所有节点
nodeList.add(new HostAndPort("127.0.0.1", 6379));
nodeList.add(new HostAndPort("127.0.0.1", 6380));
nodeList.add(new HostAndPort("127.0.0.1", 6381));
nodeList.add(new HostAndPort("127.0.0.1", 6382));
nodeList.add(new HostAndPort("127.0.0.1", 6383));
nodeList.add(new HostAndPort("127.0.0.1", 6384));
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //若不进行配置,则使用的是默认配置
JedisCluster jedisCluster = new JedisCluster(nodeList, jedisPoolConfig);
//之后直接使用jedisCluster执行命令即可,无需关心连接池归还,内部已经封装
批量操作
Map<String, JedisPool> jedisPoolMap = jedisCluster.getClusterNodes();  //获取所有节点的连接池
for (Map.Entry<String, JedisPool> entry : jedisPoolMap.entrySet()) {  //遍历所有连接池
  Jedis jedis = null;
  try {
    jedis = entry.getValue().getResource(); //每个连接池中获取一个连接
    //jedis.auth("redis");  //操作数据前设置的密码
    if (jedis.info("replication").contains("master")) {
      //若是主节点才进行操作
    }
  } finally {
    if (jedis != null) {//需手动归还Jedis连接
      jedis.close();
    }
  }
}

节点运维

数据迁移

#3.0版本
./redis-trib.rb reshard 127.0.0.1:6379

#指定集群中的任意一个节点即可

#首先会打印出集群信息,并提示需要迁移多个槽

#接着会提示需要将槽迁移到哪个节点上,需要填目标的nodeid

#之后会提示槽从哪些节点中迁出,若填写all则待迁移的槽在剩余节点中平均分配,也可从指定节点中迁出
./redis-trib.rb info 127.0.0.1:6379

#指定集群中的任意一个节点即可,查看集群中数据和槽的分布情况
./redis-trib.rb rebalance 127.0.0.1:6379

#指定集群中的任意一个节点即可,将未均匀分配槽和数据的集群均匀分配


#5.0版本
redis-cli --cluster reshard 127.0.0.1:6379
redis-cli --cluster info
redis-cli --cluster rebalance 127.0.0.1:6379

节点下线

#3.0版本
./redis-trib.rb del-node 127.0.0.1:6385 9eba8a6900357b84be7746dfb45b7e20f44430d7

#第一个是要删除的节点,第二个是要删除的节点的nodeid


#5.0版本
redis-cli --cluster del-node 127.0.0.1:6385 9d183bfc9bcbf135573c5891f23aae635b1e864a

节点上线

#3.0版本
./redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379

 #第一个是要添加的新节点,第二个是已存在集群中的任意节点
./redis-trib.rb add-node --slave --master-id a2370763fcf1ff70e3b6cf08d12c677f7c126fd7 127.0.0.1:6385 127.0.0.1:6379

#添加从节点时需要指定--slave参数

#--slave和--master-id必须写在前面,否则会报错

# 若不设置--master-id,则会随机选择主节点


#5.0版本
redis-cli --cluster add-node 127.0.0.1:6385 127.0.0.1:6379
redis-cli --cluster add-node --cluster-slave --cluster-master-id 7a735a2093c4f46e958aade004fe562ae2cf03fe 127.0.0.1:6385 127.0.0.1:6379

集群VS单机

对比项 集群 单机
带宽消耗 集群规模越大越消耗带宽 带宽消耗小
发布订阅 集群内全部节点都会发布消息,会加大带宽 不会加大带宽
数据/请求倾斜 可能会发生 不会发生
bigKey分区 不支持,尽量少使用大key,减少数据倾斜 支持
数据迁移 只能从单机到集群,使用./redis-trib.rb import --from 源 目标集群任意节点命令 持久化文件
批量操作 支持有限,在单个节点可以 支持
事务操作 支持有效,在单个节点可以 支持
主从复制 每个主节点只支持一层从节点 支持多层
多数据库 不支持 支持

综上所述分布式Redis不一定好,大多数情况下Redis Sentinel已经满足需求

此作者没有提供个人介绍
最后更新于 2022-11-06