欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

redis集群

程序员文章站 2022-05-21 23:53:07
...
redis集群

redis集群:http://www.redis.cn/topics/cluster-tutorial.html

redis cluster

   在哨兵sentinel机制中,可以解决redis高可用的问题,即当master故障后可以自动将slave提升为master从而可以保证redis服务的正常使用,但是哨兵机制无法解决redis单机写入的瓶颈问题,即单机的redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素,因此redis官方在redis 3.0版本之后推出了无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接,特点如下:

  • 所有Redis节点使用(PING机制)互联
  • ②集群中某个节点的失效,是整个集群中超过半数的节点监测都失效才算真正的失效
  • ③客户端不需要proxy即可直接连接redis,应用程序需要写全部的redis服务器IP
  • ④redis cluster把所有的redis node映射到0-16383个槽位(slot)上,读写需要到指定的redis node上进行操作,因此有多少个reids node相当于redis并发扩展了多少倍
  • ⑤Redis cluster预先分配16384个(slot)槽位,当需要在redis集群中写入一个key-value的时候,会使用CRC16(key) mod 16384之后的值,决定将key写入值哪一个槽位从而决定写入哪一个Redis节点上,从而有效解决单机瓶颈

redis cluster的实现方式

   redis集群与高可用有多重方式可以实现,比如高可用可以使用哨兵或者redis主从+keepalived的方式实现,redis集群支持多重方式,比如客户端分片、代理方式、redis cluster以及Coodis,每个实现方式都有自己的优缺点,具体方案及实现如下:

客户端分片:

   mysql、memcached以及redis等都可以通过客户端分片实现,其中mysql还可以通过客户端实现分库分表,客户端分片是在客户端将key进行hash按照不同的值保存到不同的redis 服务器,读取的话也是按照不同的位置进行读取,优势是比较灵活,不存在单点故障,缺点是添加节点需要重新配置分片算法,并且需要手动同步数据,在缓存场景客户端分片最适用于使用memcached,因为缓存是可以丢失一部分数据的,但是memcached可以做集群进行数据同步。

Redis Cluster:

   在3.0版本以后支持,无中心,在某种情况下会造成数据丢失,其也是通过算法将数据分片保存至某个redis服务器,即不再通过客户端计算key保存的redis服务器,redis服务器需要提前设置好自己所负责的槽位,比如redis A负责处理0-5000的哈希槽位数据,redis B负责处理5001-10000的hash槽位数据,redis C负责处理10001-16384的hash槽位数据,redis cluster需要特定的客户端,要求客户端必须支持集群协议 ,但是目前还没有比较好的客户端。

   这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:如果用户将新节点D添加到集群中,那么集群只需要将节点A、B、 C中的某些槽移动到节点D就可以了。

代理:

   例如Twemproxy,由proxy代替客户端换和服务端实现分片,可以使用在缓存场景中允许数据丢失的场景,其还支持memcached,可以为proxy配置算法,缺点为twemproxy是瓶颈,不支持数据迁移,官方github地址https://github.com/twitter/twemproxy/

Codis:

Codis:豌豆荚的开源方案,目前redis集群比较稳定的方案,豌豆荚gitlab地址https://github.com/pingcap:

  • 豌豆荚codis项目官方github地址https://github.com/CodisLabs/codis

  • 可以无缝迁移到codis

  • 可以动态扩容和缩容

  • 多业务完全透明,业务不知道运行的是codis

  • 支持多核心CPU,twemproxy只能单核

  • codis是有中心基于proxy的设计,是客户端像连接单机一样操作proxy

  • 有部分命令不能支持,比如keys *等

  • 支持group划分,组内可以设置一个主多个从,通过sentinel 监控redis主从,当主down了自动将从切换为主

  • 设置的进程要最大等于CPU的核心,不能超过CPU的核心数

  • 其基于zookeeper,里面保存的是key保存的redis主机位置,因此zookeeper要做高可用

  • 监控可以使用接口和dashboard

  • tidb豌豆荚团队实现的分布式mysql数据库,github地址https://github.com/pingcap/tidb

Redis Cluster介绍

Redis集群是一个提供在多个Redis间节点间共享数据的程序集。

Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令,Redis集群的优势:

  • 自动分割数据到不同的节点上。
  • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

Redis Cluster的数据分片

Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念.
Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

  这种结构很容易添加或者删除节点。比如如果我想新添加个节点D, 我需要从节点 A, B, C中得部分槽到D上。如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

哈希槽

  redis cluster采用数据分片的哈希槽来进行数据存储和数据的读取。redis cluster一共有2^14(16384)个槽,所有的master节点都会有一个槽区比如0~1000,槽数是可以迁移的。master节点的slave节点不分配槽,只拥有读权限。但是注意在代码中redis cluster执行读写操作的都是master节点,并不是你想的读是从节点,写是主节点。第一次新建redis cluster时,16384个槽是被master节点均匀分布的。

哈希槽和一致性哈希相比:

  • 哈希槽并不是闭合的,key的定位规则是根据CRC-16(key)%16384的值来判断属于哪个槽区,从而判断该key属于哪个节点,而一致性哈希是根据hash(key)的值来顺时针找第一个hash(ip)的节点,从而确定key存储在哪个节点。

  • 一致性哈希是创建虚拟节点来实现节点宕机后的数据转移并保证数据的安全性和集群的可用性的。redis cluster是采用master节点有多个slave节点机制来保证数据的完整性的,master节点写入数据,slave节点同步数据。当master节点挂机后,slave节点会通过选举机制选举出一个节点变成master节点,实现高可用。但是这里有一点需要考虑,如果master节点存在热点缓存,某一个时刻某个key的访问急剧增高,这时该mater节点可能操劳过度而死,随后从节点选举为主节点后,同样宕机,一次类推,造成缓存雪崩。

Redis cluster基本架构

假如三个主节点分别是:A, B, C 三个节点,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot区间是:

  • 集群自动分配是均分的:
  • 节点A覆盖 0-5460
  • 节点B覆盖 5461-10922
  • 节点C覆盖 10923-16383
    redis集群

Redis cluster的架构虽然解决了并发的问题,但是又引入了一个新的问题,每个Redis master的高可用如何解决?

  • 在每个节点上增加一个slave节点,以实现redis cluster的高可用
    redis集群
部署redis集群

部署redis cluster集群的前提:

1.每个redis node节点采用相同的硬件配置、相同的密码、相同的redis版本。

2.每个节点必须开启的参数

  • cluster-enabled yes #必须开启集群状态,开启后redis 进程会有cluster显示
  • cluster-config-file nodes-6380.conf #此文件有redis cluster集群自动创建和维护,不需要任何手动操作

3.所有redis服务器必须没有任何数据

4.先启动为单机redis且没有任何key value

5集群节点时间同步.

注:集群的通信端口在原服务的监听端口上加10000

部署拓扑图

  • 生产环境建议直接6台服务器
    redis集群

  • 实验主机:centos7.6

(1)首先在每台主机上部署redis
  • 在其中一台编译安装之后,将其打包发送至其他主机,也可以考虑使用ansible。
(2)创建集群
  • 注:只需在其中一台主机上安装集群管理工具redis-trib.rb即可,然后在这一台主机管理redis cluster
(2-1)创建集群:redis版本3和版本4

Redis 3和4版本:

   需要使用到集群管理工具redis-trib.rb,redis-trib.rb是redis官方推出的管理redis集群的工具,集成在redis的源码src目录下,是基于redis提供的集群命令封装成简单、便捷、实用的操作工具,redis-trib.rb是redis作者用ruby开发完成的,centos 系统yum安装的ruby存在版本较低问题,如下:

  • redis-trib.rb工具在redis源码包路径中,但是redis-trib.rb工具的运行需要依赖于ruby环境和redis模块(gem install redis安装)
# cp /usr/local/src/redis-4.0.14/src/redis-trib.rb /usr/local/bin/
  • ①解决ruby环境问题,编译ruby:
src]# wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.5.tar.gz
src]# tar xf ruby-2.5.5.tar.gz
src]# cd ruby-2.5.5
ruby-2.5.5]# yum -y install gcc zlib-devel curl-devel openssl-devel
ruby-2.5.5]# ./configure
ruby-2.5.5]# make -j 2
ruby-2.5.5]# make install
ruby-2.5.5]# gem install redis


注:ruby编译的安装环境需要安装以上包,否则也能安装ruby成功,但是好像很多功能无法使用
# gem install redis执行此命令时,可能报错:
    ERROR:  Loading command: install (LoadError)
	    cannot load such file -- zlib
    ERROR:  While executing gem ... (NoMethodError)
        undefined method `invoke_with_build_args' for nil:NilClass
# 然后去网上搜出现此错误的解决办法:进入ruby源码目录逐个安装,特别麻烦
  • ②解决redis模块(gem install redis安装)
# gem install redis  #如果无法在线安装,可以下载reids模块安装包离线安装
  • 测试redis-trib.rb工具可用
[aaa@qq.com ~]# redis-trib.rb  然后会列出此命令的使用帮助则表示此命令可以使用啦
redis-trib.rb help
Usage: redis-trib <command> <options> <arguments ...>

#创建集群
create          host1:port1 ... hostN:portN  
                  --replicas <arg> #带上该参数表示是否有从,arg表示从的数量
#检查集群
check           host:port
#查看集群信息
info            host:port
#修复集群
fix             host:port
                  --timeout <arg>
#在线迁移slot  
reshard         host:port       #个是必传参数,用来从一个节点获取整个集群信息,相当于获取集群信息的入口
                  --from <arg>  #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
                  --to <arg>    #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入。
                  --slots <arg> #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
                  --yes         #设置该参数,可以在打印执行reshard计划的时候,提示用户输入yes确认后再执行reshard
                  --timeout <arg>  #设置migrate命令的超时时间。
                  --pipeline <arg> #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10。
#平衡集群节点slot数量  
rebalance       host:port
                  --weight <arg>
                  --auto-weights
                  --use-empty-masters
                  --timeout <arg>
                  --simulate 不会真正迁移,测试用的
                  --pipeline <arg> 一次迁移多少分数据
                  --threshold <arg>
#将新节点加入集群 
add-node        new_host:new_port existing_host:existing_port
                  --slave
                  --master-id <arg>
#从集群中删除节点
del-node        host:port node_id
#设置集群节点间心跳连接的超时时间
set-timeout     host:port milliseconds
#在集群全部节点上执行命令
call            host:port command arg arg .. arg
#将外部redis数据导入集群
import          host:port
                  --from <arg>
                  --copy
                  --replace

   #修改密码redis登录密码

  • 因为在redis.conf配置文件中设置了requirepass 123456,直接创建集群将会报错
[aaa@qq.com src]# vim /usr/local/lib/ruby/gems/2.5.0/gems/redis-4.1.3/lib/redis/client.rb

redis集群

创建redis cluster
[aaa@qq.com src]# redis-trib.rb create --replicas 1 192.168.38.17:6379 192.168.38.27:6379 192.168.38.37:6379 192.168.38.47:6379 192.168.38.57:6379 192.168.38.67:6379

redis集群
redis集群

查看各主机redis的状态:

> info
# Cluster
cluster_enabled:1    #cluster_enabled 要为1

==> 至此cluster集群配置完成

(2-2)创建集群:redis版本5
  • redis版本5 统一使用redis-cli管理集群
[aaa@qq.com ~]# redis-cli -a 123456 --cluster create  192.168.38.17:6379 192.168.38.27:6379 192.168.38.37:6379 192.168.38.47:6379 192.168.38.57:6379 192.168.38.67:6379 --cluster-replicas 1
redis集群的节点维护

   集群运行时间长久之后,难免由于硬件故障、网络规划、业务增长等原因对已有集群进行相应的调整, 比如增加Redis node节点、减少节点、节点迁移、更换服务器等。

增加节点和删除节点会涉及到已有的槽位重新分配及数据迁移

redis cluster查看状态

  • redis-trib.rb 命令查看
[aaa@qq.com src]# redis-trib.rb info 192.168.38.17:6379
192.168.38.17:6379 (1de670f4...) -> 0 keys | 5461 slots | 1 slaves.
192.168.38.27:6379 (f76df109...) -> 0 keys | 5462 slots | 1 slaves.
192.168.38.37:6379 (c4b91bbf...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:6379> CLUSTER info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:610
cluster_stats_messages_pong_sent:649
cluster_stats_messages_sent:1259
cluster_stats_messages_ping_received:644
cluster_stats_messages_pong_received:610
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1259

集群维护之动态添加节点

增加Redis node节点,需要与之前的Redis node版本相同、配置一致,然后分别启动两台Redis node,因为一主一从。

  • 将192.168.38.77 导入至集群中,然后对集群槽位重新划分。

添加节点到集群:

  • redis-trib.rb add-node
add-node    new_host:new_port   existing_host:existing_port 
            要添加的新redis节点IP和端口 添加到的集群中的master IP:端口,新的node节点加到集群之后默认是 master节点,但是没有slots数据,需要重新分配。
[aaa@qq.com src]# redis-trib.rb add-node 192.168.38.77:6379 192.168.38.17:6379

redis集群

重新分配槽位:

  • 注:重新分配槽位时,每个节点均不能有数据
  • redis-trib.rb reshard host:port
[aaa@qq.com src]# redis-trib.rb  reshard 192.168.38.17:6379
.....
[OK] All nodes agree about slots configuration. 
>>> Check for open slots... 
>>> Check slots coverage... 
[OK] All 16384 slots covered. 
How many slots do you want to move (from 1 to 16384)? 4096                  #分配多少个槽位  
What is the receiving node ID? 886338acd50c3015be68a760502b239f4509881c     #接收slot的服务器ID 

Please enter all the source node IDs. 
    Type 'all' to use all the nodes as source nodes for the hash slots. 
    Type 'done' once you entered all the source nodes IDs. 
Source node #1: all                                                         #将哪些源主机的槽位进行重分

==> all是自动在所有的redis node选择划分,如果是从redis cluster删除主机可以使用此方式将主机上的槽位全部移动到别的redis主机

redis集群

当前集群:
[aaa@qq.com src]# redis-trib.rb info 192.168.38.17:6379
192.168.38.17:6379 (1de670f4...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.27:6379 (f76df109...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.37:6379 (c4b91bbf...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.77:6379 (95c1150d...) -> 0 keys | 4096 slots | 0 slaves.

为新的master添加slave节点

  • 刚添加和重新分配槽位的192.168.38.77主机没有slave节点,因此为了实现高可用,需要给其增加一个slave节点
[aaa@qq.com src]# redis-trib.rb add-node 192.168.38.87:6379 192.168.38.27:6379

[aaa@qq.com src]# redis-cli -h 192.168.38.87
192.168.38.87:6379> AUTH 123456
OK
192.168.38.87:6379> CLUSTER REPLICATE 95c1150d5910e2dd0950acda55383e1616b4c589
OK
  • 此时每个节点均有一个slave
  • redis-trib.rb info 192.168.38.17:6379
[aaa@qq.com ~]# redis-trib.rb info 192.168.38.17:6379
192.168.38.17:6379 (1de670f4...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.27:6379 (f76df109...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.87:6379 (493aca4e...) -> 0 keys | 4096 slots | 1 slaves.
192.168.38.77:6379 (95c1150d...) -> 0 keys | 4096 slots | 1 slaves.
[OK] 0 keys in 4 masters.
0.00 keys per slot on average.
[aaa@qq.com ~]#

集群维护之动态删除节点

   添加节点的时候是先添加node节点到集群,然后分配槽位,删除节点的操作与添加节点的操作正好相反,是先将被删除的Redis node上的槽位迁移到集群中的其他Redis node节点上,然后再将其删除,如果一个Redis node节点上的槽位没有被完全迁移,删除该node的时候会提示有数据且无法删除。

  • ①先将被删除的Redis node上的槽位迁移
  • ②将下线节点从集群中删除

集群节点下线:

①将新机加入节点
[aaa@qq.com src]# redis-trib.rb add-node 192.168.38.87:6379 192.168.38.17:6379


②槽位迁移至新机
[aaa@qq.com src]# redis-trib.rb  reshard 192.168.38.17:6379
    将192.168.38.37的4096个槽位迁至192.168.38.87

redis集群

  • 从集群删除服务器: redis-trib.rb del-node host:port node_id
③槽位迁移之后,服务器IP信息还在集群当中,因此还需要将IP信息从集群删除

[aaa@qq.com src]# redis-trib.rb del-node 192.168.38.37:6379 c4b91bbfc83610901126e83b0f174bca5d722799
>>> Removing node c4b91bbfc83610901126e83b0f174bca5d722799 from cluster 192.168.38.37:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.
[aaa@qq.com src]#
  • redis-trib.rb check 192.168.38.17:6379
[aaa@qq.com src]# redis-trib.rb check 192.168.38.17:6379
>>> Performing Cluster Check (using node 192.168.38.17:6379)
M: 1de670f4c8cccfd4670a643a32748cc53c5d0f2d 192.168.38.17:6379
   slots:1365-5460 (4096 slots) master
   1 additional replica(s)
S: b36dd413b27985ebadba6274dd2a3d1c076f97cd 192.168.38.67:6379
   slots: (0 slots) slave
   replicates f76df1092d5775f72196e8039be7843d3fbb8955
M: f76df1092d5775f72196e8039be7843d3fbb8955 192.168.38.27:6379
   slots:6827-10922 (4096 slots) master
   1 additional replica(s)
S: 6be42ff12a9f0dd89c98327784c0e281e72fb142 192.168.38.47:6379
   slots: (0 slots) slave
   replicates 493aca4ed8b6faf452781f8c078c8c6063086b79
M: 493aca4ed8b6faf452781f8c078c8c6063086b79 192.168.38.87:6379
   slots:12288-16383 (4096 slots) master
   1 additional replica(s)
S: 78f0bc9884fe3158178bf3b42e8d653639e39281 192.168.38.57:6379
   slots: (0 slots) slave
   replicates 1de670f4c8cccfd4670a643a32748cc53c5d0f2d
M: 95c1150d5910e2dd0950acda55383e1616b4c589 192.168.38.77:6379
   slots:0-1364,5461-6826,10923-12287 (4096 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[aaa@qq.com src]#

==> 至此192.168.38.37 redis node已经下线(有个shutdown),然后与redis集群无关了。

数据导入
将外部redis数据导入集群
  • redis-trib.rb import --from 源 --replace cluster
# redis-trib.rb import --from 172.18.200.107:6382 --replace 192.168.38.17:6379