Redis3.0版本之前,可以通过Redis Sentinel(哨兵)来实现高可用 ( HA ),从3.0版本之后,官方推出了Redis Cluster,它的主要用途是实现数据分片(Data Sharding),不过同样可以实现HA,是官方当前推荐的方案。

在Redis Sentinel模式中,每个节点需要保存全量数据,冗余比较多,而在Redis Cluster模式中,每个分片只需要保存一部分的数据,对于内存数据库来说,还是要尽量的减少冗余。在数据量太大的情况下,故障恢复需要较长时间,另外,内存实在是太贵了。。。

Redis Cluster的具体实现细节是采用了Hash槽的概念,集群会预先分配16384个槽,并将这些槽分配给具体的服务节点,通过对Key进行CRC16(key)%16384运算得到对应的槽是哪一个,从而将读写操作转发到该槽所对应的服务节点。当有新的节点加入或者移除的时候,再来迁移这些槽以及其对应的数据。在这种设计之下,我们就可以很方便的进行动态扩容或缩容,个人也比较倾向于这种集群模式。

  一个Redis Cluster由多个Redis节点构成。不同节点组服务的数据没有交集,也就是每一个节点组对应数据的一个分片。节点组内部分为主备两类节点,对应master和slave节点。两者数据准实时一致,通过异步化的主备复制机制来保证。一个节点组有且只有一个master节点,同时可以有0到多个slave节点,在这个节点组中只有master节点,同时可以有0到多个slave节点,在这个节点组中只有master节点对用户提供写服务,读服务可以由master或者slave提供。

  redis-cluster是基于gossip协议实现的无中心化节点的集群,因为去中心化的架构不存在统一的配置中心,各个节点对整个集群状态的认知来自于节点之间的信息交互。在Redis Cluster,这个信息交互是通过Redis Cluster Bus来完成的。

搭建集群:

Redis Cluster集群至少需要三个master节点,我将以3台主机的方式部署3个主节点及3个从节点。

1. 首先,在redis安装目录 /usr/local/redis/etc/下新建目录redisCluster,并在该目录下复制2个配置文件分别为6398,6399端口个,如下图:

  具体配置拿6398做例子,其余均按照该配置进行修改:

daemonize yes #开启后台运行
port 6398 #工作端口
protected-mode no
dir /usr/local/redis-cluster/6398/ #指定工作目录,rdb,aof持久化文件将会放在该目录下,不同实例一定要配置不同的工作目录
cluster-enabled yes #启用集群模式
cluster-config-file nodes-6398.conf #生成的集群配置文件名称,集群搭建成功后会自动生成,在工作目录下
cluster-node-timeout 5000 #节点宕机发现时间,可以理解为主节点宕机后从节点升级为主节点时间
appendonly yes #开启AOF模式
pidfile "/var/run/6398.pid" #pid file所在目录
logfile "6398.log"

2.将 3 台主机共 6 个服务全部启动:

3.由于创建集群需要用到redis-trib这个命令,它依赖Ruby和RubyGems,因此我们要先安装一下

yum -y install ruby ruby-devel rubygems rpm-build
gem install redis

  在这里可能会报如下错误:

  由于centos支持的ruby默认版本到2.0.0,因此需要安装RVM即Ruby的版本管理器:

yum install curl
curl -L get.rvm.io | bash -s stable
source /usr/local/rvm/scripts/rvm
rvm install 2.4.0
rvm use 2.4.0
gem install redis

  执行以上步骤完成 安装,其中第三步骤我遇到的问题可能是由于网速原因,在多次执行后才安装成功,会提示complete。

4.执行如下命令,完成 redis-cluster 集群配置,--replicas 1  这个1=主节点个数/从节点个数 。

/mysoft/redis-4.0.8/src/redis-trib.rb  create --replicas 1
192.168.254.136:6398 192.168.254.136:6399
192.168.254.137:6398 192.168.254.137:6399
192.168.254.138:6398 192.168.254.138:6399

  成功配置会显示如下信息:

  如果遇到如下图的错误:

  一直处于 waiting for the cluster to join ... 这种状态,需要开启redis集群总线端口,即redis服务节点的端口值+10000.将这些端口打开即可完成。

  这里还会遇到另外一个问题,那就是 [ERR] Node 192.168.254.137:6398 is not empty.报出这种错误。

  这个时候需要登录到每个节点,执行flushdb,然后删除每个节点的  .aof .rdb 以及对应的 nodes-6398.conf 。重启服务,这样子即可完成操作,然后重新分配。

  如果是使用redis-trib.rb工具构建集群,集群构建完成前不要配置密码,集群构建完毕再通过config set + config rewrite命令逐个机器设置密码。如果对集群设置密码,那么requirepass和masterauth都需要设置,否则发生主从切换时,就会遇到授权问题,可以模拟并观察日志。各个节点的密码都必须一致,否则Redirected就会失败。

  1. config set masterauth abc
  2. config set requirepass abc
  3. config rewrite
//增加节点的顺序是先增加Master主节点,然后在增加Slave从节点。
redis-trib.rb add-node ip:port //增加节点
redis-trib.rb reshard ip:port //重新分配槽
cluster replicate 71ecd970838e9b400a2a6a15cd30a94ab96203bf(主节点的ID) //将节点设置为从节点
//删除的顺序是先删除Slave从节点,然后在删除Master主节点
redis-trib.rb del-node 192.168.127.130: 991ed242102aaa08873eb9404a18e0618a4e37bd(该节点ID) // 删除节点

一致性哈希:

  如果是希望数据分布相对均匀的话,我们首先可以考虑哈希后取模。例如,hash(key)%N,根据余数,决定映射到那一个节点。这种方式比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的 N 发生变化,数据需要重新分布。为了解决这个问题,我们又有了一致性哈希算法。

  一致性哈希的原理:把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0 和 2^32-1 是重叠的。假设我们有四台机器要哈希环来实现映射(分布数据),我们先根据机器的名称或者 IP 计算哈希值,然后分布到哈希环中(红色圆圈)。

  现在有 4 条数据或者 4 个访问请求,对 key 计算后,得到哈希环中的位置(绿色圆圈)。沿哈希环顺时针找到的第一个 Node,就是数据存储的节点。

  在这种情况下,新增了一个 Node5 节点,不影响数据的分布。

  删除了一个节点 Node4,只影响相邻的一个节点。

  一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。比如:2 个节点,5 条数据,只有 1 条分布到 Node2,4 条分布到 Node1,不均匀。

  Node1 设置了两个虚拟节点,Node2 也设置了两个虚拟节点(虚线圆圈)。这时候有 3 条数据分布到 Node1,2 条数据分布到 Node2。

HashTags:

  通过分片手段,可以将数据合理的划分到不同的节点上,这本来是一件好事。但是有的时候,我们希望对相关联的业务以原子方式进行操作。举个简单的例子我们在单节点上执行MSET , 它是一个原子性的操作,所有给定的key会在同一时间内被设置,不可能出现某些指定的key被更新另一些指定的key没有改变的情况。但是在集群环境下,我们仍然可以执行MSET命令,但它的操作不在是原子操作,会存在某些指定的key被更新,而另外一些指定的key没有改变,原因是多个key可能会被分配到不同的机器上。

  所以,这里就会存在一个矛盾点,及要求key尽可能的分散在不同机器,又要求某些相关联的key分配到相同机器。这个也是在面试的时候会容易被问到的内容。怎么解决呢?从前面的分析中我们了解到,分片其实就是一个hash的过程,对key做hash取模然后划分到不同的机器上。所以为了解决这个问题,我们需要考虑如何让相关联的key得到的hash值都相同呢?如果key全部相同是不现实的,所以怎么解决呢?在redis中引入了HashTag的概念,可以使得数据分布算法可以根据key的某一个部分进行计算,然后让相关的key落到同一个数据分片

  举个简单的例子,加入对于用户的信息进行存储, user:user1:id、user:user1:name/ 那么通过hashtag的方式,user:{user1}:id、user:{user1}.name; 表示当一个key包含 {} 的时候,就不对整个key做hash,而仅对 {} 包括的字符串做hash。

重定向客户端:

  Redis Cluster并不会代理查询,那么如果客户端访问了一个key并不存在的节点,这个节点是怎么处理的呢?比如我想获取key为msg的值,msg计算出来的槽编号为6383,当前节点正好不负责编号为6383的槽,那么就会返回客户端下面信息:

(error) MOVED 6383 192.168.254.137:6398

  表示客户端想要的254槽由运行在IP为192.168.254.137,端口为6398的Master实例服务。如果根据key计算得出的槽恰好由当前节点负责,则当期节点会立即返回结果.

  redis客户端给我们提供了一个命令解决该问题

/mysoft/redis-4.0.8/src/redis-cli -h 192.168.254.138 -c -p 6398

   执行 set 方法后会显示如下信息:

  这里会帮我们重定向 指定的 槽点来完成数据的存储。

分片迁移:

  在一个稳定的Redis cluster下,每一个slot对应的节点是确定的,但是在某些情况下,节点和分片对应的关系会发生变更

  1. 新加入master节点

  2. 某个节点宕机

  也就是说当动态添加或减少node节点时,需要将16384个槽做个再分配,槽中的键值也要迁移。当然,这一过程,在目前实现中,还处于半自动状态,需要人工介入。新增一个主节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上。大致就会变成这样:节点A覆盖1365-5460,节点B覆盖6827-10922,节点C覆盖12288-16383,节点D覆盖0-1364,5461-6826,10923-12287

  删除一个主节点先将节点的数据移动到其他节点上,然后才能执行删除

槽迁移的过程:

  槽迁移的过程中有一个不稳定状态,这个不稳定状态会有一些规则,这些规则定义客户端的行为,从而使得RedisCluster不必宕机的情况下可以执行槽的迁移。下面这张图描述了我们迁移编号为1、2、3的槽的过程中,他们在MasterA节点和MasterB节点中的状态。

  简单的工作流程:

  1. 向MasterB发送状态变更命令,把Master B对应的slot状态设置为IMPORTING

  2. 向MasterA发送状态变更命令,将Master对应的slot状态设置为MIGRATING

  当MasterA的状态设置为MIGRANTING后,表示对应的slot正在迁移,为了保证slot数据的一致性,MasterA此时对于slot内部数据提供读写服务的行为和通常状态下是有区别的,

MIGRATING状态:

  1. 如果客户端访问的Key还没有迁移出去,则正常处理这个key

  2. 如果key已经迁移或者根本就不存在这个key,则回复客户端ASK信息让它跳转到MasterB去执行

IMPORTING状态

  当MasterB的状态设置为IMPORTING后,表示对应的slot正在向MasterB迁入,及时Master仍然能对外提供该slot的读写服务,但和通常状态下也是有区别的

  1. 当来自客户端的正常访问不是从ASK跳转过来的,说明客户端还不知道迁移正在进行,很有可能操作了一个目前还没迁移完成的并且还存在于MasterA上的key,如果此时这个key在A上已经被修改了,那么B和A的修改则会发生冲突。所以对于MasterB上的slot上的所有非ASK跳转过来的操作,MasterB都不会处理,而是通过MOVED命令让客户端跳转到MasterA上去执行这样的状态控制保证了同一个key在迁移之前总是在源节点上执行,迁移后总是在目标节点上执行,防止出现两边同时写导致的冲突问题。而且迁移过程中新增的key一定会在目标节点上执行,源节点也不会新增key,使得整个迁移过程既能对外正常提供服务,又能在一定的时间点完成slot的迁移。

总结 :

优势

  • 无中心架构。
  • 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
  • 可扩展性,可线性扩展到 1000 个节点(官方推荐不超过 1000 个),节点可动态添加或删除。
  • 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。
  • 降低运维成本,提高系统的扩展性和可用性。

不足

  • Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。
  • 节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的。
  • 数据通过异步复制,不保证数据的强一致性。
  • 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。

redis-cluster集群搭建的更多相关文章

  1. Redis Cluster集群搭建<原>

    一.环境配置 一台window 7上安装虚拟机,虚拟机中安装的是centos系统. 二.目标     Redis集群搭建的方式有多种,根据集群逻辑的位置,大致可以分为三大类:基于客户端分片的Redis ...

  2. Redis Cluster集群搭建与配置

    Redis Cluster是一种服务器sharding分片技术,关于Redis的集群方案应该怎么做,请参考我的另一篇博客http://www.cnblogs.com/xckk/p/6134655.ht ...

  3. Redis Cluster集群搭建与应用

    1.redis-cluster设计 Redis集群搭建的方式有多种,例如使用zookeeper,但从redis 3.0之后版本支持redis-cluster集群,redis-cluster采用无中心结 ...

  4. Ubuntu 16.04下Redis Cluster集群搭建(官方原始方案)

    前提:先安装好Redis,参考:http://www.cnblogs.com/EasonJim/p/7599941.html 说明:Redis Cluster集群模式可以做到动态增加节点和下线节点,使 ...

  5. 【Redis】Redis cluster集群搭建

    Redis集群基本介绍 Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施installation. Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行 ...

  6. Redis Cluster集群搭建后,客户端的连接研究(Spring/Jedis)(待实践)

    说明:无论是否已经搭建好集群,还是使用什么样的客户端去连接,都是必须把全部IP列表集成进去,然后随机往其中一个IP写. 这样做的好处: 1.随机IP写入之后,Redis Cluster代理层会自动根据 ...

  7. centos8平台redis cluster集群搭建(redis5.0.7)

    一,规划 redis cluster 1,cluster采用六台redis,3主3从 redis1    : ip: 172.17.0.2 redis2    : ip: 172.17.0.3 red ...

  8. Redis Cluster 集群搭建与扩容、缩容

    说明:仍然是伪集群,所有的Redis节点,都在一个服务器上,采用不同配置文件,不同端口的形式实现 前提:已经安装好了Redis,本文的redis的版本是redis-6.2.3 Redis的下载.安装参 ...

  9. redis cluster 集群搭建步骤和注意事项

    1.安装Ubuntu ,修改root的密码. sudo passwd  (apt-get update 更新系统) 2.安装 Gcc 和G++  sudo apt-get install build- ...

  10. Ubuntu 16.04 下Redis Cluster集群搭建

    实际操作如下: 准备工作 版本:4.0.2 下载地址:https://redis.io/download 离线版本:(链接: https://pan.baidu.com/s/1bpwDtOr 密码: ...

随机推荐

  1. 第25月第8天 100-Days-Of-ML-Code

    1.100-Days-Of-ML-Code https://github.com/Avik-Jain/100-Days-Of-ML-Code https://github.com/llSourcell ...

  2. vue WepApp 音乐App实战以及各个知识点

    1.组件初始化(scoped 貌似属于局域css样式) 2.组件使用三部曲(当然第三步可以<MHeader></MHeader> 也不报错) 3. 配置 路由 ①.在route ...

  3. Javascript - Jquery - 筛选

    筛选(JQuery Selector) JQuery是一个JavaScript库,它极大地简化了JavaScript编程.整个JQuery库的方法都在$这个工厂函数里,我们只需要使用$函数,而$函数会 ...

  4. SpringSecurity项目中如何在多个模块中配置认证信息

    ⒈在SpringSecurity项目中创建AuthorizeConfigProvider接口用于配置认证信息 package cn.coreqi.ssoserver.authorize; import ...

  5. nc替代技术方案

    powershell $client = New-Object System.Net.Sockets.TCPClient('127.0.0.1',4444);$stream = $client.Get ...

  6. python中模块的__all__属性

    python模块中的__all__属性,可用于模块导入时限制,如:from module import *此时被导入模块若定义了__all__属性,则只有__all__内指定的属性.方法.类可被导入. ...

  7. Hash算法【转】

    转自:http://www.cnblogs.com/wangjy/archive/2011/09/08/2171638.html Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度 ...

  8. C++ 解析Json——jsoncpp

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,和xml类似,本文主要对VS2008中使用Jsoncpp解析json的方法做一下记录.Jsoncpp是个跨 ...

  9. BZOJ3224/LOJ104 普通平衡树 pb_ds库自带红黑树

    您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x2. 删除x(若有多个相同的数,因只删除一个)3. 查询x的排名(若有多个相同的数,因输出最小的排名)4. 查询排名为x的数5. ...

  10. ffmpeg 加载双语字幕

    set infile=in.mp4 set subfile1=cn.srt set subfile2=en.srt set subvf1="subtitles=%subfile1%:forc ...