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
相关文章
|
26天前
|
存储 算法 Linux
【数据结构和算法】---二叉树(1)--树概念及结构
【数据结构和算法】---二叉树(1)--树概念及结构
17 0
|
16天前
|
机器学习/深度学习 人工智能 自然语言处理
机器学习之深度学习算法概念
深度学习算法是一类基于人工神经网络的机器学习方法,其核心思想是通过多层次的非线性变换,从数据中学习表示层次特征,从而实现对复杂模式的建模和学习。深度学习算法在图像识别、语音识别、自然语言处理等领域取得了巨大的成功,成为人工智能领域的重要技术之一。
39 3
|
24天前
|
存储 算法 安全
深入理解SHA系列哈希算法:安全性的保障与演进
深入理解SHA系列哈希算法:安全性的保障与演进
|
26天前
|
存储 算法 C语言
二分查找算法的概念、原理、效率以及使用C语言循环和数组的简单实现
二分查找算法的概念、原理、效率以及使用C语言循环和数组的简单实现
|
1月前
|
存储 算法 Java
Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。
【6月更文挑战第21天】Java查找算法概览:二分查找适用于有序数组,通过比较中间元素缩小搜索范围;哈希查找利用哈希函数快速定位,示例中使用HashMap存储键值对,支持多值关联。简单哈希表实现未涵盖冲突解决和删除操作。
22 1
|
1月前
|
机器学习/深度学习 算法 C语言
详细介绍递归算法在 C 语言中的应用,包括递归的基本概念、特点、实现方法以及实际应用案例
【6月更文挑战第15天】递归算法在C语言中是强大力量的体现,通过函数调用自身解决复杂问题。递归涉及基本概念如自调用、终止条件及栈空间管理。在C中实现递归需定义递归函数,分解问题并设定停止条件。阶乘和斐波那契数列是经典应用示例,展示了递归的优雅与效率。然而,递归可能导致栈溢出,需注意优化。学习递归深化了对“分而治之”策略的理解。**
36 7
|
1月前
|
搜索推荐 算法
【排序】数据结构——排序算法概念及代码详解(插入、冒泡、快速、希尔)
【排序】数据结构——排序算法概念及代码详解(插入、冒泡、快速、希尔)
|
1月前
|
存储 算法 关系型数据库
【MySQL技术内幕】5.7- InnoDB存储引擎中的哈希算法
【MySQL技术内幕】5.7- InnoDB存储引擎中的哈希算法
21 1
|
1月前
|
存储 算法 Java
哈希算法篇 - 布隆过滤器
哈希算法篇 - 布隆过滤器
21 1
|
1月前
|
算法 数据挖掘 开发者
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】