1.redis-cluster设计
Redis集群搭建的方式有多种,例如使用zookeeper,但从redis 3.0之后版本支持redis-cluster集群,redis-cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。其redis-cluster架构图如下:
其结构特点
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
- redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。
- Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
redis cluster节点分配
例如分配三个主节点分别是:7000, 7001, 7002。三个从节点分别是7003,7004,7005。它们可以是一台机器上的六个端口,也可以是六台不同的服务器。采用哈希槽 (hash slot)的方式来分配16384个slot 的话,六个节点分别承担的slot 区间如同所示:
获取数据: 如果存入一个值,按照redis cluster哈希槽的算法: CRC16('key')%16384 = 6782。 就会把这个key 的存储分配到7001 上了。同样,当连接(7000,7001,7002)任何一个节点想获取'key'这个key时,也会这样的算法,然后内部跳转到7001节点上获取数据 。
2.redis-cluster主从模式
redis cluster为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉。主从模式具有如下特点:
- 集群有7000,7001,7002三个主节点, 如果这3个节点都没有加入从节点,如果7001挂掉了,就无法访问整个集群,7000和7002的slot也无法访问。
- 主从节点同时挂掉后,如节点7001和7004同时挂了,Redis集群将无法继续正确地提供服务。
- 为每个主节点设置从节点, 比如像这样, 集群包含主节点7000,7001,7002 以及从节点7003,7004,7005, 那么即使7001挂掉系统也可以继续正确工作。7004节点替代了7001节点,所以Redis集群将会选择7004节点作为新的主节点,集群将会继续正确地提供服务。 当7001重新开启后,它就会变成7004的从节点。
2.redis-cluster集群搭建
搭建环境
本次测试采用虚拟机模式,在本机上做测试,虚拟机环境为Centos7.0。搭建集群需要如下相关依赖软件,下载地址为“
- rubygems软件包下载:https://rubygems.org/pages/download
- ruby软件包下载:http://www.ruby-lang.org/en/downloads/
- redis-3.2.2.gem依赖包下载:https://rubygems.global.ssl.fastly.net/gems/redis-3.2.2.gem
-
openssl软件包下载: http://www.openssl.org/source/
下载文件包截图如下:
redis集群与大多数分布式中间件一样,redis的cluster也是依赖选举算法来保证集群的高可用,所以类似zookeeper一样,一般是奇数个节点(可以允许N/2以下的节点失效),再考虑到每个节点做Master-Slave互为备份,所以一个redis cluster集群最少也得6个节点。
步骤1:安装redis
下载最新版redis并安装在linux系统中。具体操作可参考网址:Redis介绍及Jedis基础操作
步骤2:新建集群文件夹目录
新建一个根目录data/cluster/。并在cluster目录下面建立6个子目录:mkdir
7000 7001 7002 7003 7004 7005。
步骤3:修改redis.conf配置文件
修改redis的配置文件redis.conf,复制原有解压redis文件中的redis.conf文件到7000目录中,操作指令如:cp /usr/software/redis-4.0.6/redis.conf /data/cluster/7000。修改redis.conf文件中的配置字段,修改字段如下:
daemonize yes #后台启动 port 7000 #修改端口号,从7000到7005 cluster-enabled yes #开启cluster,去掉注释 cluster-config-file nodes-7000.conf cluster-node-timeout 15000 appendonly yes pidfile /var/run/redis_7000.pid
相同操作处理其他五个文件夹,配置文件redis.conf中,将7000替换为对应的值。比如:7001文件下替换为7001。处理完成后,一次运行定义的每个文件夹下面的redis,查看是否启动成功。
步骤4:安装Ruby环境
安装Ruby环境。网上很多博客都是采用yum模式安装的,但考虑到FQ等限制条件,本文采用离线模式安装。
Ruby简介
Ruby是一种纯粹的面向对象编程语言。它由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)创建于1993年。可以在 www.ruby-lang.org 的 Ruby 邮件列表上找到松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)的名字。在 Ruby 社区,松本也被称为马茨(Matz)。Ruby 是"程序员的最佳朋友"。Ruby 的特性与 Smalltalk、Perl 和 Python 类似。Perl、Python 和 Smalltalk 是脚本语言。Smalltalk 是一个真正的面向对象语言。Ruby,与 Smalltalk 一样,是一个完美的面向对象语言。使用 Ruby 的语法比使用 Smalltalk 的语法要容易得多。
Ruby离线安装
- Ruby下载地址:http://www.ruby-lang.org/en/downloads/ ,最新的版本为2.5.0。
- Ruby安装参考网址:Linux 安装Ruby详解(在线和离线安装)
- 在安装之前,请确保使用账号具有Root权限,将下载的Ruby安装包上传到服务器当中,通过xtfp5工具进行文件上传:解压上传文件, tar -zxvf ruby-2.5.0.tar.gz,进入到ruby-2.5.0目录执行指令make && make install安装,安装成功可通过指令:ruby -v查看版本号:
步骤5:安装RubyGems环境
安装RubyGems环境。网上很多博客都是采用yum模式安装的,但考虑到FQ等限制条件,本文采用离线模式安装。
RubyGems简介
RubyGems 是 Ruby 的一个包管理器,它提供一个分发 Ruby 程序和库的标准格式,还提供一个管理程序包安装的工具。RubyGems 旨在方便地管理 gem 安装的工具,以及用于分发 gem 的服务器。这类似于 Ubuntu 下的apt-get, Centos 的 yum,Python 的 pip。RubyGems大约创建于2003年11月,从Ruby 1.9版起成为Ruby标准库的一部分。
离线安装RubyGems
如果你的 Ruby 低于 1.9 版本,也可以通过手动安装:
- RubyGems下载地址:https://rubygems.org/pages/download
- RubyGems安装参考网址:Linux 离线安装Rubygems详解
-
解压下载文件并进入目录,解压指令tar -zxvf rubygems-2.7.4.tgz,执行命令:ruby setup.rb
步骤6:安装openssl
使用gem install 安装 ruby redis。直接操作会报如下错误,查看原因是因为缺少openssl。
离线安装openssl
- openssl下载地址:http://www.openssl.org/source/
- openssl安装参考网址:配置群集时# gem install redis 报错:Unable to require openssl, install OpenSSL and rebuild ruby
- 解压下载文件并进入目录,解压指令如: tar -xzvf openssl-1.0.2n.tar.gz ,执行以下命令:
tar -xzvf openssl-1.0.2n.tar.gz cd openssl-1.0.2n ./config -fPIC --prefix=/usr/local/openssl enable-shared ./config -t make && make install
- 执行以上命令安装openssl,安装后查看版本号如下:
- 解决ssl.h文件找不到的问题,配置ruby文件,# ruby extconf.rb --with-openssl-include=/usr/local/openssl/include/ --with-openssl-lib=/usr/local/openssl/lib
- 设置软链接:ln -s /usr/local/src/ruby-2.2.3/include /
- 再次编译安装,成功后如下图所示:
步骤7: 安装redis-trib.rb运行依赖的ruby的包redis-3.2.2.gem
- redis-3.2.2.gem下载地址:https://rubygems.global.ssl.fastly.net/gems/redis-3.2.2.gem
- 下载完成后上传到服务器上面,执行安装命令如:gem install /usr/software/redis-3.2.2.gem
步骤8: 使用redis-trib.rb创建集群
使用create命令 --replicas 1 参数表示为每个主节点创建一个从节点,其他参数是实例的地址集合。可利用命令:.
/redis-trib
.rb help查看使用介绍。
运行集群创建shell脚本,cluster就创建成功了。最终的结果是后面的192.168.210.128:7000~192.168.210.128:7005中,会有3个会指定成master,而其它3个会指定成slave。
注:利用redis-trib创建cluster的操作,只需要一次即可,假设系统关机,把所有6个节点全关闭后,下次重启后,即自动进入cluster模式,不用再次redis-trib.rb create。
查看redis进程启动状态,并开放防火墙中的对应端口。
查看节点分配指令为:./redis-trib.rb check 192.168.210.128:7002 (任意一个集群的ip地址)
3.redis-cluster集群节点选举,扩容与删除
集群选举
现在模拟将7002节点挂掉,按照redis-cluster原理会选举将 7002的从节点7005选举为主节点。直接关闭7002的进程,在重新check可发现7005已经被自动选举为主节点。当启动7002后,7002将作为7005的从节点。
新增主节点
新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到新增点上,也可设置从指定部分节点获取。举例如:在A,B,C节点上新增节点D。
- 节点A覆盖1365-5460
- 节点B覆盖6827-10922
- 节点C覆盖12288-16383
- 节点D覆盖0-1364,5461-6826,10923-12287
新增一个节点7006作为主节点,操作步骤如下:
- 修改配置文件,新建一个对应的文件7006,并把复制配置文件redis.conf放入到文件7006下面,并修改配置文件,把端口修改为7006,其他配置信息也参考前面的案例对应修改。节点配置信息成功后,启动7006下面的redis。
- 将7006加入到现有的集群中,输入指令:./redis-trib.rb add-node 192.168.210.128:7006 192.168.210.128:7002。指令说明:dd-node是加入集群节点,192.168.210.128:7006为要加入的节点,192.168.210.128:7002 表示加入的集群的一个节点,用来辨识是哪个集群,理论上那个集群的节点都可以。
- 目前cluster已经定义7006为主节点,但是Cluster并未给7006分配哈希卡槽(0 slots)。
- redis-cluster在新增节点时并未分配卡槽,需要操作者手动对集群进行重新分片迁移数据,需要重新分片命令:reshard。操作如:redis-trib.rb reshard 192.168.210.128:7002。指令说明:这个命令是用来迁移slot节点的,后面的192.168.210.128:7002是表示是哪个集群,端口填[7000-7006]都可以,执行后:它提示需要迁移多少slot到7006上平分16384个哈希槽给4个节点:16384/4 = 4096,可移动4096个槽点到7006上。填写7006的id:如ee3efb90e5ac0725f15238a64fc60a18a71205d7。
- redis-trib 会向你询问重新分片的源节点(source node),即,要从特定的哪个节点中取出 4096 个哈希槽,还是从全部节点提取4096个哈希槽, 并将这些槽移动到7006节点上。如果不打算从特定的节点上取出指定数量的哈希槽,那么可以向redis-trib输入 all,这样的话, 集群中的所有主节点都会成为源节点,redis-trib从各个源节点中各取出一部分哈希槽,凑够4096个,然后移动到7006节点上。操作命令为:Source node #1:all 。
- 确认之后,redis-trib就开始执行分片操作,将哈希槽一个一个从源主节点移动到7006目标主节点。重新分片结束后可以check以下节点的分配情况。指令为:./redis-trib.rb check 192.168.210.128:7002。可查看扩容主节点是否成功。
新增从节点
- 新增一个节点7007作为从节点修改配置文件,新建一个对应的文件7007,并把复制配置文件redis.conf放入到文件7007下面,并修改配置文件,把端口修改为7007,其他配置信息也参考前面的案例对应修改。节点配置信息成功后,启动7007下面的redis并加入到现有集群中。
- redis-trib增加从节点的命令为:./redis-trib.rb add-node --slave --master-id $[nodeid] 192.168.210.128:7007 192.168.210.128:7000 。操作指令含义:nodeid为要加到master主节点的node id,192.168.210.128:7007为新增的从节点,192.168.210.128:7000为集群的一个节点(集群的任意节点都行),用来辨识是哪个集群;如果没有给定那个主节点--master-id的话,redis-trib将会将新增的从节点随机到从节点较少的主节点上。
- 从节点不存在分片操作,与主节点对应的片一致。
移除主节点
-
移除节点使用redis-trib的del-node命令,redis-trib del-node 192.168.210.128:7002 ${node-id} 。操作指令含义: 192.168.210.128:7000为指定集群,node-id为要删除的主节点。 和添加节点不同,移除节点node-id是必需的。
-
测试删除7001主节点,redis cluster提示7001已经有数据了,不能够被删除,需要将他的数据转移出去,也就是和新增主节点一样需重新分片。
- 分区指令: ./redis-trib.rb reshard 192.168.210.128:7002
- 输入提示的需要移动的分片大小,分配给7001的slots为4096,输入需要移动的片为4096。
- 输入这些移除的slots如何分配给其他node,可指定一个具体node的id或者选择所有。
- 最后确认后,开始移除节点。
移除从节点
- 移除节点使用redis-trib的del-node命令,redis-trib del-node 192.168.210.128:7002 ${node-id} 。操作指令含义: 192.168.210.128:7000为指定集群,node-id为要删除的节点。 和添加节点不同,移除节点node-id是必需的。
- 从节点不存在分片问题,直接执行命令,确认移除即可。
4.redis-cluster集群与分布式连接池区别
ShardedJedisPool是redis没有集群功能之前客户端实现的一个数据分布式方案,redis3.0提供集群之后,客户端则采用JedisCluster实现连接redis集群环境。 ShardedJedisPool使用的是JedisShardInfo的instance的顺序或者name来做的一致性哈希,JedisCluster使用的是CRC16算法来做的哈希槽。
集群环境各个服务之间的数据是隔离的。无论是ShardedJedisPool的一致性哈希算法还是JedisCluster的CRC16哈希槽算法,都是把所有的服务叠加然后进行均匀的分割,分割出来的每一个段或槽都是不重复的,所以导致存储的数据彼此之间也是处于隔离状态的。
jediscluster通过在客户端调用捕捉异常,可实现集群环境下的高可用。Jedis还提供了对jedis sentinel pool的封装,所以ShardedJedisPool发生主从切换的时候,web server都不需要重新配置和deploy。高可用性的极佳体现啊。
5.java客户端调用redis-cluster
更新pom文件中redis-clients的版本,低版本会报错。<redis-clients.version>2.9.0</redis-clients.version>
java客户端调用redis-cluster可通过在java代码中直接填写地址或通过spring配置文件填写,具体可参考上传的代码。在java中调用集群案例代码如下:
/** * Description: redis cluster 测试 * Copyright: 2018 CSNT. All rights reserved. * Company:CSNT * * @author wangling * @version 1.0 */ public class RedisClusterTestDemo { @Test public void testRedisCluster() throws Exception { JedisPoolConfig poolConfig = new JedisPoolConfig(); Set<HostAndPort> nodes = new HashSet<HostAndPort>(); HostAndPort hostAndPort = new HostAndPort("192.168.210.128", 7000); HostAndPort hostAndPort1 = new HostAndPort("192.168.210.128", 7001); HostAndPort hostAndPort2 = new HostAndPort("192.168.210.128", 7002); HostAndPort hostAndPort3 = new HostAndPort("192.168.210.128", 7003); HostAndPort hostAndPort4 = new HostAndPort("192.168.210.128", 7004); HostAndPort hostAndPort5 = new HostAndPort("192.168.210.128", 7005); nodes.add(hostAndPort); nodes.add(hostAndPort1); nodes.add(hostAndPort2); nodes.add(hostAndPort3); nodes.add(hostAndPort4); nodes.add(hostAndPort5); JedisCluster jedisCluster = new JedisCluster(nodes,5000,1000); jedisCluster.set("jedisKey","wangling test jedisKey"); //redis内部会创建连接池,从连接池中获取连接使用,然后再把连接返回给连接池 String string = jedisCluster.get("jedisKey"); System.out.println(string); } }
//基于配置文件调用,完整配置信息参考上传的源代码 /** * Description: redis cluster 测试 * Copyright: 2018 CSNT. All rights reserved. * Company:CSNT * * @author wangling * @version 1.0 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:redis-cluster-context.xml") public class JedeisClusterTest { @Autowired private JedisCluster jedisCluster; @Test public void testJedisCluster(){ jedisCluster.set("jedisCluster", "wangling test jedisCluster"); String val = jedisCluster.get("jedisCluster"); System.out.println(val); } }
软件运行截图如下:
6.参考网址
- 分布式缓存技术redis学习系列(七)——spring整合jediscluster
- Linux 安装Ruby详解(在线和离线安装)
- 解决方法:配置群集时gem install redis 报错
- redis 学习笔记(6)-cluster集群搭建
- Redis介绍及Jedis基础操作