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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
相关文章
|
2月前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
54 3
|
4月前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
17天前
|
算法 安全
散列值使用相同的哈希算法
当使用相同的哈希算法对相同的数据进行散列时,所产生的散列值(也称为哈希值或摘要)总是相同的。这是因为哈希算法是一种确定性的函数,它对于给定的输入将始终产生相同的输出。 例如,如果你用SHA-256算法对字符串"hello world"进行哈希处理,无论何时何地,只要输入是完全一样的字符串,你都会得到相同的160位(40个十六进制字符)的SHA-256散列值。 但是,需要注意的是,即使是输入数据的微小变化也会导致产生的散列值完全不同。此外,不同的哈希算法(如MD5、SHA-1、SHA-256等)会对相同的数据产生不同的散列值。 哈希算法的一个关键特性是它们的“雪崩效应”,即输入中的一点小小
27 4
|
2月前
|
算法 索引
HashMap扩容时的rehash方法中(e.hash & oldCap) == 0算法推导
HashMap在扩容时,会创建一个新数组,并将旧数组中的数据迁移过去。通过(e.hash & oldCap)是否等于0,数据被巧妙地分为两类:一类保持原有索引位置,另一类索引位置增加旧数组长度。此过程确保了数据均匀分布,提高了查询效率。
45 2
|
2月前
|
存储 算法 C#
C#哈希查找算法
C#哈希查找算法
|
2月前
|
算法 安全 Go
Python与Go语言中的哈希算法实现及对比分析
Python与Go语言中的哈希算法实现及对比分析
49 0
|
2月前
|
存储 算法 C++
【算法】哈希映射(C/C++)
【算法】哈希映射(C/C++)
|
16天前
|
算法
基于WOA算法的SVDD参数寻优matlab仿真
该程序利用鲸鱼优化算法(WOA)对支持向量数据描述(SVDD)模型的参数进行优化,以提高数据分类的准确性。通过MATLAB2022A实现,展示了不同信噪比(SNR)下模型的分类误差。WOA通过模拟鲸鱼捕食行为,动态调整SVDD参数,如惩罚因子C和核函数参数γ,以寻找最优参数组合,增强模型的鲁棒性和泛化能力。
|
22天前
|
机器学习/深度学习 算法 Serverless
基于WOA-SVM的乳腺癌数据分类识别算法matlab仿真,对比BP神经网络和SVM
本项目利用鲸鱼优化算法(WOA)优化支持向量机(SVM)参数,针对乳腺癌早期诊断问题,通过MATLAB 2022a实现。核心代码包括参数初始化、目标函数计算、位置更新等步骤,并附有详细中文注释及操作视频。实验结果显示,WOA-SVM在提高分类精度和泛化能力方面表现出色,为乳腺癌的早期诊断提供了有效的技术支持。
|
2天前
|
供应链 算法 调度
排队算法的matlab仿真,带GUI界面
该程序使用MATLAB 2022A版本实现排队算法的仿真,并带有GUI界面。程序支持单队列单服务台、单队列多服务台和多队列多服务台三种排队方式。核心函数`func_mms2`通过模拟到达时间和服务时间,计算阻塞率和利用率。排队论研究系统中顾客和服务台的交互行为,广泛应用于通信网络、生产调度和服务行业等领域,旨在优化系统性能,减少等待时间,提高资源利用率。
下一篇
DataWorks