Redis集群,集群的概念 三种主流分片方式1.哈希求余 一致性哈希算法:方案三:哈希槽分区算法问题一Redis集群是最多有16384个分片吗问题二:为什么是16384个,集群扩容:1.新的主节点

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis集群,集群的概念 三种主流分片方式1.哈希求余 一致性哈希算法:方案三:哈希槽分区算法问题一Redis集群是最多有16384个分片吗问题二:为什么是16384个,集群扩容:1.新的主节点

集群的概念

广义的集群:多个机器,构成了分布式系统,都可以称作为集群

狭义的集群:redis提供的集群模式,这个集群模式下,主要是解决存储空间不足的问题(拓展存储空间)

哨兵模式提高系统的可用性,哨兵模式中,本质上还是redis主从节点存储数据,其中就要求一个主节点/从节点,存储整个数据的全集,引入多台机器,每台机器存储一部分数据。

设有1TB数据需要存储,

拿两台机器来存储,每个机器只需要存储512GB即可

拿四台机器来存储,每个机器只需要存储256GB即可

把数据分成多分,如何分?

三种主流分片方式

1.哈希求余

借鉴了哈希表的基本思想,借助hash函数,把一个key映射到整数,再针对数组的长度求余,就可以得到一个数组下标

三个分片,编号0,1,2

此时就可以针对要插入的数据的key(redis都是键值对结构的数据)计算hash值(比如,使用md5)再把hash值余上分片个数,就得到了一个下标,此时就可以把这个数据放到该下标对应的分片中了 hash(key)%N==0 此时这个key存储在0号分片中,后续查询key的时候,也是同样的算法,key是一样,hash函数是一样的,得到的分片是值就是一样的。

分片主要目的:提高存储能力,分片越多,能存储的数据越多,成本也更高。一旦服务器集群需要扩容,就需要更高的成本了。一般是少搞几个先,随着业务的逐渐成长,数据变多了,3个分片不够了,需要引入新的分片N就改变了。

当hash函数和key都不变的情况下,如果N改变了,整体的分片结果都会改变。

搬运量是巨大的,上述扩容,开销是极大的,往往不能再生产环境上操作,只能通过替换的方式来扩容。(原来三组不变,找其他机器构成这四个,然后生产环境导入到这些机器里面)这也会找成依赖的机器更多,成本更高。

一致性哈希算法:

为了降低上述的搬运开销,能够更高效扩容,key映射到分片的序号不是简单的求余,而是改成以下过程,插入三号分片,现在属于把原先哈希的交替的(要一直算余数,这个是连在一起的,连续出现,这样搬运成本就会小了)

方案三:哈希槽分区算法

redis真正采用的分区算法:

hash_slot=crc16(key)%16384 相当于是16*1024(用来计算hash值的算法)

会进一步把上述这些哈希槽,分配到不同的分片上。 虽然不是严格意义上的均匀,但是此时三个分片上的数据就是比较均匀的了,这里只是一种可能的分片方式,实际上分片十分灵活,每个分片持有槽位号,可以连续,也可以不连续

0号分片[0,5461]共5462个槽位

1号分片[5463,10923]共5462个槽位=

2号分片[10924,16383]共5460个槽位

此处,每个分片都会使用位图这样的数据结构,表示出当前有多少槽位号16384个bit位,用每一个0/1来区分自己这个分片当前是否持有该槽位号 2048->2kb

0号分片[0,4095]

1号[5462 9557]

2号[10924,15019]

3号[4096,5461]+[9558,10923]+[15019,16383]共4096个槽位

针对某个分片,上面的槽位号,不一定非得是连续区间

redis,当前某个分片包含哪些个槽位,都是可以手动配置的

问题一

Redis集群是最多有16384个分片吗

每个分片上就只有一个槽位,如果每个分片包含的槽位比较多,如果槽位个数相当,就可以认为包含的key数量相当

如果每个分片包含的槽位非常少,槽位个数不一定能直观的反映到key的数目

实际上Redis作者建议分片最多不超过1000,

问题二:为什么是16384个槽位

这个是因为使用位图这样的数据结构表示的,16384(16k,个数基本够用)个slots,需要的是2kb大小,如果给定的slots数量更多了,则需要消耗更多的空间,假如65536 那么他就是(8kb) 心跳包是周期性通信的(非常频繁,吃网络带宽)

基于redis搭建出一个redis集群

注意:docker compose必须要在对应的目录下,才能够操作

配置文件期间,注意要把docker(运行的redis和哨兵)停掉

在linux以.sh为后缀,成为shell脚本,使用命令操作,非常适合吧命令写到一个文件中,批量化执行,还可以加入,条件,循环,函数等机制.

需要创建11个redis节点,这些redis配置文件大同小异,此时可以用脚本来批量生产。(不用脚步就一个一个改),注意这里的ip地址,要去看根据端口号,101,102,103···109

for port in $(seq 1 9); \
do \
mkdir -p redis$(port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
 
for port in $(seq 10 11); \
do \
mkdir -p redis$(port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done

/是续行符,把下一行的内容和当前行,合并成一行,shell默认情况是所有代码写到一行里面的,使用续行符号来进行换行

对于shell中,用for都是使用do/done开始和结束的,预期效果,得到11个目录,每个目录里都有一个配置文件,配置文件中,ip地址各不相同

cluster-enabled yes.  开启集群

cluster-config-file nodes.conf. 需要多个节点之间进行交互,保持联络

cluster-node-timeout 5000     5s之内没有心跳包,则说明该节点出现问题

cluster-announce-ip 172.30.0.10${port}:该redis节点,自己所在的主机的ip地址

cluster-announce-port 6379     port是redis节点自身绑定的端口(容器内的端口)不同的容器内部可以有相同的端口,后续进行端口映射,再把这些容器内的端口映射到容器外的不同端口即可

cluster-announce-bus-port 16379

业务端口:用来进行业务数据通信的 ->响应redis客户端请求的

管理端口:为了完成一些管理上的任务来进行通信的 ->如果某个分片中的redis主节点挂了,就要让从节点成为主接待您,就需要通过刚才管理端口来完成对应操作

一个服务器,可以绑定多个端口号

for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
 
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
 
~                                                                                              
~                                                                                              
~                                                                                              
~                                                                                              

我们可以看到下面是上述脚本的执行结果

此处为了后续创建静态ip,此时要先手动创建出网络,同时给这个网段也分配ip

网络原理-IP协议-IP地址规则

IP地址=网络号+主机号

使用子网掩码的方式来区分主机号和网络号

使用ifconfig查看网段,每个人主机上已有的网段,具体不一定一样

/24的意思是子网掩码,左边24是1,右边8位是0

相当于255.255.0      172.30.0网络号 ip是内网ip,

什么是内网ip,10*,172.16-172.31.*,192.168.*

注意,配置静态ip网络号部分要和前面的网段保持一致,主机号部分,可以随便配置(1-255,保证不重复)

必须要保证都关闭之后,再去启动集群

version: '3.7'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24
 
 
services:
  redis1:
   image: 'redis:5.0.9'
   container_name: redis1
   restart: always
   volumes:
      - ./redis1/:/etc/redis/
   ports:
      - 6371:6379
      - 16371:16379
   command:
      redis-server /etc/redis/redis.conf
   networks:
     mynet:
       ipv4_address: 172.30.0.101
  redis2:
   image: 'redis:5.0.9'
   container_name: redis2
   restart: always
   volumes:
      - ./redis2/:/etc/redis/
   ports:
      - 6372:6379
      - 16372:16379
   command:
      redis-server /etc/redis/redis.conf
   networks:
      mynet:
        ipv4_address: 172.30.0.102
redis3:
    image: 'redis:5.0.9'
    container_name: redis3
    restart: always
    volumes:
      - ./redis3/:/etc/redis/
    ports:
       - 6373:6379
       - 16373:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.103
  redis4:
    image: 'redis:5.0.9'
    container_name: redis4
    restart: always
    volumes:
      - ./redis4/:/etc/redis/
    ports:
       - 6374:6379
       - 16374:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.104
 redis5:
    image: 'redis:5.0.9'
    container_name: redis5
    restart: always
    volumes:
      - ./redis5/:/etc/redis/
    ports:
       - 6375:6379
       - 16375:16379
    command:
        redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.105
  redis6:
    image: 'redis:5.0.9'
    container_name: redis6
    restart: always
    volumes:
      - ./redis6/:/etc/redis/
    ports:
       - 6376:6379
       - 16376:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.106
 redis7:
    image: 'redis:5.0.9'
    container_name: redis7
    restart: always
    volumes:
      - ./redis7/:/etc/redis/
    ports:
       - 6377:6379
       - 16377:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.107
  redis8:
    image: 'redis:5.0.9'
    container_name: redis8
    restart: always
    volumes:
      - ./redis8/:/etc/redis/
    ports:
       - 6378:6379
       - 16378:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.108
 redis9:
    image: 'redis:5.0.9'
    container_name: redis9
    restart: always
    volumes:
      - ./redis9/:/etc/redis/
    ports:
       - 6379:6379
       - 16379:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.109
  redis10:
    image: 'redis:5.0.9'
    container_name: redis10
    restart: always
    volumes:
      - ./redis10/:/etc/redis/
    ports:
       - 6380:6379
       - 16380:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
       ipv4_address: 172.30.0.110
 redis11:
    image: 'redis:5.0.9'
    container_name: redis11
    restart: always
    volumes:
      - ./redis11/:/etc/redis/
    ports:
       - 6381:6379
       - 16381:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.111
                                                                             163,10 

列出每个参与构建集群的ip和端口,端口都是写容器内部的端口号

描述集群的每个节点,应该有2个从节点

redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379  172.30.0.103:6379  172.30.0.104:6379  172.30.0.105:6379  172.30.0.106:6379  172.30.0.107:6379   172.30.0.108:6379  172.30.0.109:6379 --cluster-replicas 2   

可以看到,敲出来之后弹出一堆信息

先去生成每个redis节点的配置文件

使用docker创建出11个节点,并且启动容器

使用redis-cli执行创造集群命令

从101到109九个节点,现在是一个整体,使用客户端连接任意一个节点,本质上都是等价的

cluseter nodes查看当前集群的信息

使用集群来存储,前面使用的redis基础命令大部分适用的

我们发现这个会错误,发现这个是来自于103的分片,所以我们在启动redis-cli的时候,加上-c的选项,可以根据当前key实际算出来的槽位号,自动找到匹配的分片主机,进一步就可以完成操作。

此时还是计算k1,属于103,然后后面这块给他重定向,重新转发给103这个节点(当然这个109也是从节点,不可以处理写的请求,103是主节点,能处理写的请求)

集群故障处理

使用集群之后,之前的命令大部分能用,但是有些指令是操作多个key,此时在不同分片上,是无法这么操作

当然也有特殊的方式,hash tag

如果集群中,有节点挂了,如果挂的是从节点,没事,如果是主节点?,主节点才能处理写操作,从节点不可以写

101挂了之后,我们发现105当上主节点了

集群机制,也能处理故障转移,此处故障转移和哨兵这里不同

1.故障判定

识别出某个节点是否挂了

1.节点A给B发送ping包,B给A返回一个pong包,ping和pong除了message type属性之外,其他部分都是一样的,这里包含了集群的配置信息(该节点的id,该节点属于哪个分片,是主节点还是从节点,从属于谁,持有哪些slots的位图)

2.每个节点每秒钟,都会给一些随机节点发送ping,而不是全发一遍,这样设定是为了避免在节点很多的时候,心跳包也非常大多

3.当节点给B发ping包,B不能如期回应的时候,此时A尝试重置B的tcp连接,看是否能够连接成功,如果仍然连接是比啊,A会给B设置为PFAIL状态(相当于主管下线)

4.A判定B为PFAIL之后,会通过redis内置的Gossip协议,和其他节点进行沟通,向其他节点确认B的状态(每个节点都会维护自己的下线列表,由于视角不同,每个节点的下线列表也不一定相同)

5.此时A发现其他很多节点,也认为B为PFAIL,并且数目超过总集群个数的一半,那么A就会把B标记成FAIL(相当于客观下线),并且把这个消息同步给其他节点(其他节点收到后,也把B标记成FAIL)

2.故障迁移

如果B是从节点,那么不需要进行故障迁移

如果B是主节点,那么会由B的从节点(如C,D)触发故障迁移了,类似于哨兵,

具体流程如下:

1.从节点判定自己是否有参选资格,如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太大了),时间超过阈值,就是去竞选资格

2.具有资格的节点,如果C和D,就会先休眠一定时间,休眠时间=500ms基础时间+[0-500ms]随机时间+排名*1000ms ,offset的值越大,则排名越靠前(越小)->(offset越大,数据越接近主节点,排名越要靠前,休眠时间就更短)

3.比如c的休眠时间到了,C就会给其他所有集群中的节点,进行拉票操作,但是只有主节点才有投票资格

4.主节点就会把自己的票投给C(每个主节点只有1票),当C收到的票数超过主节点数目的一半,C就会晋升成主节点(C自己负责slaveof no one,并且让D执行slaveof C)

5.同时,C还会把自己成为主节点的消息,同步给其他集群的节点,大家也都会更新自己保存的集群结构信息

谁休眠时间短,大概率就是新的主节点了,

以下三种情况会出现集群宕机:

1.某个分片,所有的主节点和从节点全挂了-该分片无法提供数据服务了

2.某个分片主节点挂了,但是没有从节点

3.超过半数的master节点都挂了 ->此时master挂了,但是后面还有slave做补充,如果突然一系列的master挂了,说明集群遇到了非常严重的现象,此时就得赶紧停下来,检查检查是不是有啥问题。

如果集群中有个节点挂了,无论什么节点,程序员都要尽快处理好(最晚第二天,上班之前,处理好)

集群扩容:

101-109 9个主机构成三主,六从,以110和111页加入到集群中

把数据分片从3->4. 集群扩容操作,是风险较高,成本很大的操作。

1.新的主节点110加入到集群中

前面的表示被新增的节点是啥    后面是集群中的任意一个节点,表示要把新节点加入到哪个集群中

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

我们添加过后可以看到,110这个小可怜一个也没有跟他连接到,

2.重新分配slots

把之前三组master上面的slots拎出来一些,分配给新的master,重新切分他会让你手动,写重新切分多少个,此时会先打印当前集群每个机器的情况,并且要求用户输入要移动多少个redis

预期是1/4,16384 ->4096,会问你哪个节点接收

输入要从哪些节点移动redis

1)all,表示从其他每个持有slots的master都拿一点

2)手动指定,从某个或者某几个节点来移动slots(以done为结尾)

输入all之后,会给出搬运的计划(还没真正搬运)当程序员输入yes之后,搬运才真开始,不仅是slots重新划分,也会把slots上对应的数据,也搬运到新的主机上

比较重量的操作!,谨慎!

此时再去查看,发现110,持有三个区间,一人薅一点。

如果搬运slots/key的过程中,此时客户端能否访问我们redis集群呢?

搬运key,大部分的key不用搬运的,针对这些未搬运的key,此时可以正常访问的,针对这些正在搬运中的key,是有可能出现访问出错的情况。

假设客户端访问k1,集群通过分片算法,得到k1是第一个分片的数据,就会重定向到第一个分片的节点,就有可能在重定向过去之后,正好k1被搬走了,自然也无法访问

因此不可以随意扩容,一般都是晚上(很明显,更高可用,搞多机器,多集群,倒入数据)

3.把从节点添加到集群中

111添加进来,作为110的从节点

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-slave --cluster-master-id [xx的nodeId]

注意是在客户端的外面操作

小结:

1)集群是什么,解决了什么问题?

2)数据分片算法[重点/考点]

 a)哈希求余

 b)一致性哈希算法

 c)哈希槽分区算法(redis)

3)搭建redis集群

4)集群容灾,故障转移

5)集群扩容

6)代码的连接集群

使用的库,得能支持集群模式

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
监控 NoSQL Redis
看完这篇就能弄懂Redis的集群的原理了
看完这篇就能弄懂Redis的集群的原理了
133 0
|
24天前
|
NoSQL 数据处理 Redis
Redis 分区
10月更文挑战第22天
11 1
|
3月前
|
NoSQL Redis
Redis——单机迁移cluster集群如何快速迁移
Redis——单机迁移cluster集群如何快速迁移
137 0
|
3月前
|
NoSQL Linux Redis
使用docker-compose搭建redis-cluster集群
使用docker-compose搭建redis-cluster集群
552 0
|
28天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
13天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
|
14天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
15天前
|
存储 算法 决策智能
基于免疫算法的TSP问题求解matlab仿真
旅行商问题(TSP)是一个经典的组合优化问题,目标是寻找经过每个城市恰好一次并返回起点的最短回路。本文介绍了一种基于免疫算法(IA)的解决方案,该算法模拟生物免疫系统的运作机制,通过克隆选择、变异和免疫记忆等步骤,有效解决了TSP问题。程序使用MATLAB 2022a版本运行,展示了良好的优化效果。
|
14天前
|
机器学习/深度学习 算法 芯片
基于GSP工具箱的NILM算法matlab仿真
基于GSP工具箱的NILM算法Matlab仿真,利用图信号处理技术解析家庭或建筑内各电器的独立功耗。GSPBox通过图的节点、边和权重矩阵表示电气系统,实现对未知数据的有效分类。系统使用MATLAB2022a版本,通过滤波或分解技术从全局能耗信号中提取子设备的功耗信息。
|
14天前
|
机器学习/深度学习 算法 5G
基于MIMO系统的SDR-AltMin混合预编码算法matlab性能仿真
基于MIMO系统的SDR-AltMin混合预编码算法通过结合半定松弛和交替最小化技术,优化大规模MIMO系统的预编码矩阵,提高信号质量。Matlab 2022a仿真结果显示,该算法能有效提升系统性能并降低计算复杂度。核心程序包括预编码和接收矩阵的设计,以及不同信噪比下的性能评估。
34 3
下一篇
无影云桌面