七、Redis的高并发 & 高可用(集群)

一、为什么要做集群

当遇到单机内存、并发等瓶颈时,可以使用集群方案来解决这些问题。

二、RedisCluster

RedisCluster 是Redis提供的分布式解决方案,在Redis 3.0 版本之后推出,是一个多主多从的架构。

如果是Redis 3.0 之前的版本,想要做多主多从的高可用架构,就需要在应用程序客户端做数据分片,将数据存储到不同的Redis主从结构上。比如对key值进行hash计算,来确定它存放到哪个master节点上。但是还需要适配节点掉线、新增节点的问题,编写代码复杂,难以维护。

所以使用Redis 3.0之后原生支持的集群架构,可以用相对简单的配置,实现数据缓存的高并发、高可用。

三、Redis集群的数据分片

数据分区映射多节点

根据缓存数据的key,计算这条数据该存储到哪一个Redis节点上。

四、数据分片理论

1、顺序分片

原理:redis_1 存放最先进来的1000条数据,redis_2存放进来的1000~2000条数据,...。

顺序分片原理

缺陷:离散度不够。系统刚启动时,存储压力都在redis_1上,其他节点不能分担压力,不能实现均匀访问,造成数据倾斜。而且查询也不知道要从哪台redis节点上查询。不推荐使用。

2、节点取余分片

假设:有 3 台Redis数据节点。

原理:对进来的缓存数据的 key 进行计算 hash 值,使用hash值对Redis节点数(3)做取余运算,根据余数确定存储到哪台Redis节点上。

节点取余分片

缺陷:添加/减少Redis数据节点,对已存储的数据需要重新计算余数,再存储到对应的数据节点上。这时会造成大量的数据迁移,数据量大会很消耗时间。

优化:进行取余运算的时候,将Redis数据节点数 ✖ 2:【3 -> 6】,这样在添加一个数据节点时,余数 = 【0,1,2】的数据不用迁移,只需要迁移余数 = 【3,4,5】的数据。

3、一致性哈希分片

假设:有 3 台Redis数据节点。

原理:将3台Redis数据节点分布在一个Hash环上,这个Hash环的范围是 02^{32},根据Redis节点的IP或域名取Hash值,确定其在Hash环上的位置。这时对新进来的缓存数据的key取Hash值,从这个值的位置开始,顺时针方向寻找Redis节点,找到的第一个节点就是目标节点,存储在这个Redis数据节点上。

读取数据也是计算key的hash值去hash环上找Redis节点。

一致性哈希分片

优点:在添加/减少Redis数据节点时,只有少量的数据会发生迁移。

缺点:如果一台Redis节点掉线,那么它上面的所有数据都会被顺延到下一个Redis节点上,对这个节点造成很大的压力。

如果新增一个Redis节点,也只能分担下一个Redis节点的压力,没帮助到其他节点。

如果出现热点数据,压力都集中分布在少量Redis节点上,容易造成缓存击穿。

4、虚拟一致性哈希分片

核心思想:每个Redis数据节点都尽量分散它覆盖的区域。

原理:hash环上的节点都是虚拟的,指向真实的节点。这样每个Redis真实数据节点负责的区域更加分散,可以可以协助更多的节点分担压力。

虚拟一致性哈希分片

优点:应对的数据离散度更高,针对热点数据和节点下线的均衡性更好。

缺点:维护复杂。

五、Redis采用的数据分片算法

Redis采用虚拟槽分片,有16384个槽,所有的key根据Hash函数(CRC16[key] % 16384)映射到0-16383的槽内。是一个虚拟一致性哈希的变种。

1、虚拟槽

① 虚拟槽信息是如何保存的?

存储在一个bitMap数据类型中,记录着哪些槽被分配到了哪个Redis节点上。

② 虚拟槽信息保存在哪里?

保存在集群中的每一个节点上。

③ 如果有Redis节点新增/掉线,如何更新槽位信息?

保存槽位信息的bitMap会定时在集群的每个节点之间传输,如果出现节点新增/掉线就更新这个bitMap。

问题:为什么槽的数量是 16384?

主要有 2 点考虑:

网络资源:在Redis集群中,由于每个节点有若干定时任务在集群内和其他节点进行通信,这些内部通信所需要的带宽会随着节点数量的增加而增加,一般200个节点的集群会占用25M的带宽。另外Redis建议一个集群中的节点数量别超过1000台,否则内部通信所消耗的资源将会难以容忍。

计算资源:在Redis集群内部是使用 bitMap 数据类型保存槽位信息,预留槽位越多,这个bitMap就越长,在内部传输这个 bitMap 的过程中会对这个 bitMap 做压缩/解压处理,如果哈希槽数量过大会导致压缩/解压算法的效率下降,反而拉低了性能。

16384个槽在目前来看是一个很合适的值,针对集群中多主节点可以做到细粒度的数据分片,如果哈希槽太少会导致分片粒度太粗,如果哈希槽过多在执行取余运算会导致计算困难。

2、虚拟槽分片

原理:给Redis节点分配其负责的槽范围,使每个节点均匀负责一部分槽,进来的key以Hash函数计算后得到的值属于属于哪些槽就进入哪个Redis节点。

虚拟槽分片

六、集群的问题和缺陷

根据以往学习的计算机知识,我们每解决掉一些问题,往往就会出现一些新的问题。redis集群这么强大,所带来的缺陷有哪些呢?我们一起来看看。

1、对批量操作支持有限

比如:mset,mget。

原理:在集群环境下,需要对key进行hash运算,如果批量操作不同分片的key,就会导致操作失败,因为定位不到需要的redis节点。除非所有key的hash运算结构都在同一个Redis节点上。

延伸:Lua脚本,pipLine都是支持有限,还是看key的分布。

2、对事务的支持有限

原理:和上一个问题一样,当多个键分布在不同Redis节点上,没法确定事务在哪个Redis节点上执行,除非是在一台Redis节点上执行事务。

3、键是数据分区的最小粒度

描述:键是数据分片的最小粒度,不能将一个很大的键值对映射到不同的节点。

解释/人话:如果写入Hash类型的数据,只能以最外层的key做判断,内层的key不能作为分片依据。

4、不支持多数据库,只能使用0库

描述:Redis单节点本身支持多库,0 ~ 15库,但是在集群环境下只能使用0库。

5、复制结构只能支持单层结构,不能支持树状结构

描述:在Redis集群中,只能有一级从节点,从节点不能再有下级从节点了。

6、总结

由于Redis集群的性能消耗不低,难以维护,使用限制太多。所以一般情况下在生产环境单台Redis或者主从 + 哨兵可以应对数据缓存,就不会搭建Redis集群。

原理都讲清楚了,接下来我们搭建一个Redis集群玩一玩吧。

七、搭建Redis集群

1、搭建Redis集群的方式

依照Redis协议手工搭建,使用cluster meet,cluster addslots,cluster replicate命令。

Redis5.0 之前使用由 ruby 语言编写的 redis-trib.rb,在使用前需要安装 ruby 语言环境。

Redis5.0 及其以后将搭建集群的功能合并到了 redis-cli,但是没废弃redis-trib.rb。

2、尝试搭建一个这样的集群

懒得改这个图了,说明一下吧。

彩色的表示主节点:

        redis 1 的 端口设置为6900

        redis 2 的 端口设置为6901

        redis 3 的 端口设置为6902

灰色的表示从节点:

        slave 1 的 端口设置为6930

        slave 2 的 端口设置为6931

        slave 3 的 端口设置为6932

集群消息总线端口:

        配置文件参数:cluster-announce-bus-port

        在Redis集群中,节点之间的通讯依赖集群消息总线端口。

        集群消息总线端口通常是节点端口 + 10000偏移量,比如 6900节点的通讯端口是 16900。但是这个偏移量不总是固定的,也可以手动配置,具体看下redis.conf 文件的相关说明吧。所以这个6节点集群有6个通讯端口。

3、开始动手

动手也不能乱动手,先理顺顺序:

1、配置集群节点配置文件

2、启动集群节点

3、建立主从关系

另外我这里只安装了一台redis,通过配置多个配置文件来启动多个节点。

配置文件就是复制一份 redis.conf 改一改。

① 配置集群节点的配置文件

集群主节点配置文件

这里我为了方便截图,就把配置项挪到一起了。

我是在一台虚拟机上启动集群,所以相关的日志文件、运行文件、持久化文件都需要单独配置,避免冲突。

redis集群节点配置文件

我一共有3台主节点和3台从节点,所以要配置6次这样的配置文件。后缀为m的代表主节点,后缀为s的代表从节点。

② 启动集群节点

# 启动redis-server 以刚才配置的配置文件启动
./redis-server ../../conf/cluster_m_6900.conf

使用 ps 命令查询redis运行情况

可以看到使用的端口是我们配置的6900,且标识为:cluster,表示以集群启动。

接着我们再启动其他节点:

看到都成功启动了。

下面我们来查看日志文件,我们之前并没有手动创建.log日志文件,redis会自动创建.log日志文件存放到我们指定的目录。我这里指定到了 /usr/local/redis/logs下了。

查看6900的目录:

 再来看看集群节点的配置文件,发现也自动创建了。

③ 随机创建集群主从节点

# 随机创建集群主从节点
./redis-cli --cluster create 节点1 节点2 节点3 节点4 ... --cluster-replicas 1

命令解析

./redis-cli:启动redis客户端。

--cluster:以集群方式创建。

create 节点列表(127.0.0.1:6900):在这些redis服务中创建主从节点。

--cluster-replicas 1:表示每个主节点有 1 个从节点,参数是几就表示一个主节点有几个从节点。

④ 指定集群主节点

# 指定创建主节点
./redis-cli --cluster create 节点1 节点2 节点3

也是使用上面那个命令,只是最后不设置cluster-replicas参数,表示把所有节点都创建为主节点。

启动成功啦!可以看到日志打印了槽位信息。总槽位数、每个节点分配的槽位范围都和前面理论部分对上了。

⑤ 指定集群从节点

# 集群新增从节点,并且指定挂到哪个主节点下
./redis-cli --cluster add-node 127.0.0.1:6930 127.0.0.1:6900 
--cluster-slave --cluster-master-id 2b64503b05c4cee05f5a00125e8c091b4f5658ef

命令解析:

./redis-cli:启动redis客户端

--cluster:以集群方式启动

add-node 127.0.0.1:6930 127.0.0.1:6900:新增节点,第一个表示待新增的节点。第二个表示加入这个节点所在的集群,换成集群中其他的节点也行。

--cluster-slave:表示新增从节点

--cluster-master-id run_id: 指定主节点的run_id,在log文件可以查询到。

添加从节点执行结果

⑥ 集群信息查看

查询集群基本信息
# 查询集群信息
./redis-cli --cluster info 127.0.0.1:6900

我们来看一下执行结果:

结果很清晰吧!基本不用解释,我这里还没启动从节点,所以是0个slaves。

现在加上从节点,都变成1 slaves啦!

查询集群详细信息
# 查询集群详细信息
./redis-cli --cluster check 127.0.0.1:6900

展示的信息更加丰富。

到这里集群就成功搭建啦!

4、Redis集群的其他命令

[root@localhost src]# ./redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          <host:port> or <host> <port> - separated by either colon or space
                 --cluster-search-multiple-owners
  info           <host:port> or <host> <port> - separated by either colon or space
  fix            <host:port> or <host> <port> - separated by either colon or space
                 --cluster-search-multiple-owners
                 --cluster-fix-with-unreachable-masters
  reshard        <host:port> or <host> <port> - separated by either colon or space
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      <host:port> or <host> <port> - separated by either colon or space
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
                 --cluster-only-masters
                 --cluster-only-replicas
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-from-user <arg>
                 --cluster-from-pass <arg>
                 --cluster-from-askpass
                 --cluster-copy
                 --cluster-replace
  backup         host:port backup_directory
  help           

For check, fix, reshard, del-node, set-timeout, info, rebalance, call, import, backup you can specify the host and port of any working node in the cluster.

Cluster Manager Options:
  --cluster-yes  Automatic yes to cluster commands prompts

八、Redis集群的使用

Redis集群搭建好啦,接下来我们玩一下感受感受。

1、直接插入一条数据

错误示范

这里直接连接6900主节点插入一条数据,但是报错了。Redis提示计算的槽位是9898,应该把数据插入到6901节点上。

2、以集群方式插入一条数据

正确示范

换一个连接客户端的命令,在最后加一个 -c ,表示以集群方式连接Redis客户端。这时Redis集群会自动寻找到正确的Redis节点,将数据保存到目标节点上。这里提示在6900节点上将数据保存到了6901节点上。

这里的set命令分两步执行,先计算key对应的槽位,再通过槽位连接正确的节点保存数据。

3、smart 客户端

我们发现,在使用Redis集群方式存取数据时,如果计算出的槽位不在本节点上时,还需要再去连接正确的节点,形成二次开销,这势必会降低Redis的性能。

Jedis 的扩展:JedisCluster 可以保存Redis集群中的槽位信息,计算key的槽位后直接发送给对应节点,避免二次连接。

九、Redis集群的动态扩容/缩容

1、动态扩容

这里我们来尝试扩容,增加一个6903的主节点,和一个6933的从节点。也是添加两个配置文件,以这两个配置文件启动Redis服务。

启动新的集群节点

向原来的集群中添加一个主节点 6903

相关命令:  ./redis-cli --cluster add-node 127.0.0.1:6903 127.0.0.1:6900

这里最后的 127.0.0.1:6900 节点可以替换为集群中任意节点 ip+端口。

给 6903 添加一个从节点 6933
添加完成查看集群信息

最后这里可以看到,扩容进来的节点没分配槽位,还不能使用,需要分配槽位才能使用。

2、迁移槽位

顾名思义就是给Redis集群节点重新分配它们负责的槽位。

./redis-cli --cluster reshard 
--cluster-from 2b64503b05c4cee05f5a00125e8c091b4f5658ef 
--cluster-to 5f797e494bc482d9985799bdf286d30adec662eb 
--cluster-slots 1365 
127.0.0.1:6900

命令解析:这里我为了写清楚加了换行,其实是一行命令。

./redis-cli --cluster:不用解释

reshard:重新分配槽位

--cluster-from run_id:表示从哪个节点取出一些槽位。从哪取?

--cluster-to run_id:表示取出的槽位放到哪个节点。放到哪?

--cluster-slots 1365:表示迁移多少槽位

127.0.0.1:6900 :标识哪个集群,不是固定的ip+端口。只要在集群中随便取一个节点即可。

迁移完成啦!我这里是经过人工计算每一个节点的迁出槽位数,最后得到的槽位数比较平均。

3、动态缩容

先迁移槽位,把缩容节点的槽位迁移给其他节点。查看集群信息:

我这里把6903的全部槽位迁移出去后,发现6903主节点没了,6902竟然有3个从节点。

连上6902客户端看看:

可以看到有 6932、6903、6933 这三个从节点,但是 6933 一开始是 6903 的从节点,先前我们也提到RedisCluster中不能出现树形从节点,有点不放心,连接6933节点看看信息:

看到6933的上级节点变成了6902,Redis还挺智能,我这里用的是Redis 7.0多,可能版本之间也有差异。

这时候也基本实现了Redis的节点下线,主节点变成从节点后,我们开发人员的自由度变得更高了,数据没那么容易丢失。

节点槽位清空 且 降级后,将其移出集群:

./redis-cli --cluster del-node 127.0.0.1:6900 run_id

命令解析:

del-node 127.0.0.1:6900:表示从127.0.0.1:6900所在的集群删除节点

run_id:目标节点的run_id

成功移出集群啦!

不过这两个Redis节点还在运行中,如果不需要了记得关掉,避免浪费资源!

十、数据平衡

1、Redis的数据平衡

由于在实际生产环境中,Redis集群保存的数据可能会出现数据倾斜,大量key所属槽位都集中到一两个Redis节点上,导致集群节点压力不均衡。Redis提供了原生的数据平衡支持,我们来看一下。

./redis-cli --cluster rebalance 
--cluster-weight
run_id_1=3
run_id_2=3
run_id_3=4
--cluster-simulate
127.0.0.1:6900

命令解析:这里表示以权重分配每个节点的槽位数,权重越大,负责的槽位数就越多。相当于一次重新洗牌,将数据均衡地分布在每一个节点上。

十一、在应用程序中使用配置好的Redis集群

 

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值