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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: 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
相关文章
|
21天前
|
存储 运维 NoSQL
Redis Cluster集群模式部署
Redis Cluster集群模式部署
49 4
|
9天前
|
缓存 负载均衡 NoSQL
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
Redis系列学习文章分享---第十四篇(Redis多级缓存--封装Http请求+向tomcat发送http请求+根据商品id对tomcat集群负载均衡)
18 1
|
16天前
|
NoSQL Redis
redis集群教程
redis集群教程
|
9天前
|
存储 监控 NoSQL
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
Redis系列学习文章分享---第十二篇(搭建哨兵集群+RedisTemplate连接哨兵+搭建分片集群+-散列插槽+集群伸缩 +故障转移+RedisTemplate访问分片集群)
25 0
|
9天前
|
NoSQL Redis
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
11 0
|
12天前
|
存储 NoSQL 算法
Redis集群
Redis集群
19 0
|
12天前
|
存储 缓存 NoSQL
redis的集群架构
规避方法可以在redis配置里加上参数(这种方法不可能百分之百的避免数据丢失,参数集群leader选举机制)
15 0
|
2天前
|
机器学习/深度学习 算法 调度
Matlab|基于改进鲸鱼优化算法的微网系统能量优化管理matlab-源码
基于改进鲸鱼优化算法的微网系统能量管理源码实现,结合LSTM预测可再生能源和负荷,优化微网运行成本与固定成本。方法应用于冷热电联供微网,结果显示经济成本平均降低4.03%,提高经济效益。代码包括数据分段、LSTM网络定义及训练,最终展示了一系列运行结果图表。
|
7天前
|
算法 安全 数据库
基于结点电压法的配电网状态估计算法matlab仿真
**摘要** 该程序实现了基于结点电压法的配电网状态估计算法,旨在提升数据的准确性和可靠性。在MATLAB2022a中运行,显示了状态估计过程中的电压和相位估计值,以及误差随迭代变化的图表。算法通过迭代计算雅可比矩阵,结合基尔霍夫定律解决线性方程组,估算网络节点电压。状态估计过程中应用了高斯-牛顿或莱文贝格-马夸尔特法,处理量测数据并考虑约束条件,以提高估计精度。程序结果以图形形式展示电压幅值和角度估计的比较,以及估计误差的演变,体现了算法在处理配电网状态估计问题的有效性。
|
4天前
|
数据采集 存储 算法
基于BP算法的SAR成像matlab仿真
**摘要:** 基于BP算法的SAR成像研究,利用MATLAB2022a进行仿真。SAR系统借助相对运动合成大孔径,提供高分辨率图像。BP算法执行回波数据预处理、像素投影及图像重建,实现精确成像。优点是高精度和强适应性,缺点是计算量大、内存需求高。代码示例展示了回波生成、数据处理到插值显示的全过程。