Redis集群
Posted dirzzt_xiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis集群相关的知识,希望对你有一定的参考价值。
1 背景
这段时间项目需要对缓存进行集群,以下通过对redis集群研究与测试,对比直接采用单机内存缓存方式的性能。文章记录本人学习与实践redis集群的一些步骤与测试。
2 目标
1. redis集群研究与测试,分析其的可用性、可靠性、可运维性以及性能等方面。
2. 比较单机与集群的性能,内存占用等方面,为重构提供数据分析。
3 Redis集群简介
3.1 前言
为了提供程序的处理能力,通常会把热点数据保存在内存当中而不是直接从数据库中读取,从而减轻数据库压力与提高性能。Redis就是这样这个优秀的缓存中间件,由于它强大高效而又便捷的功能,得到了广泛的使用。
3.2 Redis集群方案
Redis集群三种常见的解决方案如下:
1、客户端分片:这种方案将分片工作放在业务程序端,程序代码根据预先设置的路由规则,直接对多个Redis实例进行分布式访问。这样的好处是,不依赖于第三方分布式中间件,实现方法和代码都自己掌控,可随时调整,不用担心踩到坑。这实际上是一种静态分片技术。Redis实例的增减,都得手工调整分片程序。基于此分片机制的开源产品,现在仍不多见。这种分片机制的性能比代理式更好(少了一个中间分发环节)。但缺点是升级麻烦,对研发人员的个人依赖性强——需要有较强的程序开发能力做后盾。如果主力程序员离职,可能新的负责人,会选择重写一遍。所以,这种方式下,可运维性较差。出现故障,定位和解决都得研发和运维配合着解决,故障时间变长。因此这种方案,难以进行标准化运维,不太适合中小公司(除非有足够的DevOPS)。
2、代理分片:这种方案,将分片工作交给专门的代理程序来做。代理程序接收到来自业务程序的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给业务程序。这种机制下,一般会选用第三方代理程序(而不是自己研发),因为后端有多个Redis实例,所以这类程序又称为分布式中间件。这样的好处是,业务程序不用关心后端Redis实例,运维起来也方便。虽然会因此带来些性能损耗,但对于Redis这种内存读写型应用,相对而言是能容忍的。这是我们推荐的集群实现方案。像基于该机制的开源产品Twemproxy,Codis便是其中代表,应用非常广泛。
3、服务器端分片:建立在基于无中心分布式架构之上(没有代理节点性能瓶颈问题)。Redis-Cluster即为官方基于该架构的解决方案。RedisCluster将所有Key映射到16384个Slot中,集群中每个Redis实例负责一部分,业务程序通过集成的RedisCluster客户端进行操作。客户端可以向任一实例发出请求,如果所需数据不在该实例中,则该实例引导客户端自动去对应实例读写数据。Redis Cluster的成员管理(节点名称、IP、端口、状态、角色)等,都通过节点之间两两通讯,定期交换并更新。
3.3 Redis集群方案具代表性中间件
中间件名称 |
特点 |
Twemproxy |
Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。这个方案顺理成章地解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived做高可用方案。这么些年来,Twemproxy是应用范围最广、稳定性最高、最久经考验的分布式中间件。只是,他还有诸多不方便之处。Twemproxy最大的痛点在于,无法平滑地扩容/缩容。这样增加了运维难度:业务量突增,需增加Redis服务器;业务量萎缩,需要减少Redis服务器。但对Twemproxy而言,基本上都很难操作。或者说,Twemproxy更加像服务器端静态sharding。有时为了规避业务量突增导致的扩容需求,甚至被迫新开一个基于Twemproxy的Redis集群。Twemproxy另一个痛点是,运维不友好,甚至没有控制面板。当然,由于使用了中间件代理,相比客户端直接连服务器方式,性能上有所损耗,实测结果降低了20%左右。 |
Codis |
Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。现已广泛用于豌豆荚的各种Redis业务场景,从各种压力测试来看,稳定性符合高效运维的要求。性能更是改善很多,最初比Twemproxy慢20%;现在比Twemproxy快近100%(条件:多实例,一般Value长度)。Codis具有可视化运维管理界面。Codis无疑是为解决Twemproxy缺点而出的新解决方案。因此综合方面会由于Twemproxy很多。目前也越来越多公司选择Codis。Codis引入了Group的概念,每个Group包括1个Redis Master及至少1个Redis Slave,这是和Twemproxy的区别之一。这样做的好处是,如果当前Master有问题,则运维人员可通过Dashboard“自助式”切换到Slave,而不需要小心翼翼地修改程序配置文件。为支持数据热迁移(Auto Rebalance),出品方修改了Redis Server源码,并称之为Codis Server。Codis采用预先分片(Pre-Sharding)机制,事先规定好了,分成1024个slots(也就是说,最多能支持后端1024个Codis Server),这些路由信息保存在ZooKeeper中。不足之处有对redis源码进行了修改,以及代理实现本身会有的问题。 |
Redis-cluster |
reids-cluster在redis3.0中推出,支持Redis分布式集群部署模式。采用无中心分布式架构。所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.节点的fail是通过集群中超过半数的节点检测失效时才生效.客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可,减少了代理层,大大提高了性能。redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key之间的关系。目前Jedis已经支持Redis-cluster。从计算架构或者性能方面无疑Redis-cluster是最佳的选择方案。(PS:虽然Redis-cluster从方案选型上面比较占据优势,而且官方推出后各方相应挺热,但是由于Redis-cluster刚推出不久,虽然官方宣传已经发布的是文档版本,但稳定性方面还有待验证)。 |
根据参考官方的更新来看,RedisClister不断趋向于普及与完善,社区的支持程度较热,本文以下主要采用Redis Clister实现集群,并进行测试。
3.4 Redis Clister
Redis3.0 最大的特点就是有了cluster的能力,使用redis-trib.rb工具可以轻松构建Redis Cluster。Redis Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。节点之间使用gossip协议传播信息以及发现新节点,这种结构和Cassandra很相似,Cassandra节点可以转发请求。Redis集群中节点不作为client请求的代理,client根据node返回的错误信息重定向请求。rediscluster在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
3.4.1 集群特性
Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。
Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误。
Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令. Redis 集群的优势:
自动分割数据到不同的节点上。
整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
3.4.2 分片
那么redis 是如何合理分配这些节点和数据的呢?
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。rediscluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16算法来取模得到所属的slot,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384。
假设现在有3个节点已经组成了集群,分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:
l 节点A覆盖0-5460;
l 节点B覆盖5461-10922;
l 节点C覆盖10923-16383.
这种哈希槽的分配方式有好也有坏,好处就是很清晰,比如我想新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上,我会在接下来的实践中实验。大致就会变成这样:
l 节点A覆盖1365-5460
l 节点B覆盖6827-10922
l 节点C覆盖12288-16383
l 节点D覆盖0-1364,5461-6826,10923-12287
同样删除一个节点也是类似,移动完成后就可以删除这个节点了。
所以redis cluster 就是这样的一个形状:
3.4.3 主从模式
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。
在我们例子中具有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。
然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点A1,B1,C1,那么整个集群便有三个master节点和三个slave节点组成,这样在节点B失败后,集群便会选举B1为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了。
不过当B和B1 都失败后,集群就不可用了。
redis-cluster 选举:
1. 领着选举过程是集群中所有master参与,如果半数以上master节点与master
节点通信超过(cluster-node-timeout),认为当前master节点挂掉。
2. 什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误
a: 如果集群任意master挂掉,且当前master没有slave。集群进入fail状态,也
可以理解成进群的slot映射[0-16383]不完成时进入fail状态。
b: 如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态。
3.4.4 一致性
Redis Cluster不提供强一致性。
例如cluster接受了一个写请求,给client返回ok,这个写请求的内容也可能丢失。因为其写流程如下:
1. master B接受了一个写请求;
2. B写成功,返回ok给client;
3. B把数据广播给slaves(B1、B2、B3)
如果第二步执行完毕后,B crash了,则会发生数据不一致现象。这与传统的DBMS类似,它们接收了写请求后,每隔1S才会把数据写入disk,这么做也是在性能和一致性之间做一个平衡。(如果用户对数据的一致性要求比较高,Redis日后可能也会兼顾这种需求,将来会提供相应的选项,让redis中的slave没用成功的接受数据之前不会给client返回ok给client。即先执行step 3,然后再执行step 2。)
一致性还有一种场景。假设有client Z,与cluster内各个node A and B and C,以及各个node的replica A1 and B1 and C1,Z与B之间连接正常,但是B与B1以及cluster内其他nodes连接失败。如果Z发起write request,那么B会给他返回ok,但是B1无法获取到相应的数据,这就要求写的时候也要把node与cluster内其他的成员的探活也要考虑在内。基本要求就是,写时间周期要大于探活时间周期(node timeout)。当node Btimeout之后,master B会自动进入failing状态,拒绝外部client的连接请求,而cluster则会选出slave B1来代替B。
3.4.5 集群配置参数
cluster-enabled<yes/no>: 是否开启集群
cluster-config-file<filename>:,Redis运行时保存配置的文件,自动生成,所以不能修改这个文件。
cluster-node-timeout<milliseconds>: 配置结点超时多久则认为它失效了。如果是主节点超时被认为失效,将由其从节点通过故障转移成为主节点。
cluster-slave-validity-factor<factor>: 失效校验次数。连接一个节点超时超过此配置的次数,才认为失效。例如配置为5,timeout为5秒,则从节点与主节点超时50秒后才会尝试故障转移。
cluster-migration-barrier <count>: 主节点至少从节点数。配置该参数,当另一个主节点没有了从节点覆盖,将对部分从节点进行迁移到该主节点。
cluster-require-full-coverage<yes/no>: 默认为yes,只要有结点宕机导致16384个槽没全被覆盖,整个集群就全部停止接受写入操作。如果设置为no,则仍然可以接收部分请求(仍被覆盖的槽对应keys子集)。
3.4.6 Cluster相关命令
集群 : CLUSTER INFO 打印集群的信息 CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。 节点 : CLUSTER MEET <ip> <port> 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。 CLUSTER FORGET <node_id> 从集群中移除 node_id 指定的节点。 CLUSTER REPLICATE <node_id> 将当前节点设置为 node_id 指定的节点的从节点。 CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。 槽(slot) : CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。 CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。 CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。 CLUSTER SETSLOT <slot> NODE <node_id> 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。 CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽 slot 迁移到 node_id 指定的节点中。 CLUSTER SETSLOT <slot> IMPORTING <node_id> 从 node_id 指定的节点中导入槽 slot 到本节点。 CLUSTER SETSLOT <slot> STABLE 取消对槽 slot 的导入(import)或者迁移(migrate)。 键 : CLUSTER KEYSLOT <key> 计算键 key 应该被放置在哪个槽上。 CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的键值对数量。 CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 个 slot 槽中的键。 |
这些命令是集群所独有的。执行上述命令要先登录:
redis-cli -c -p6382 -h 172.16.21.139 //登录
4 搭建并使用Redis集群
搭建集群的第一件事情我们需要一些运行在集群模式的Redis实例。这意味这集群并不是由一些普通的Redis实例组成的,集群模式需要通过配置启用,开启集群模式后的Redis实例便可以使用集群特有的命令和特性了。
n 1. 环境
要让集群正常工作至少需要3个主节点,如果不足,创建时会有以下提示:
在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
127.0.0.1:7004
127.0.0.1:7005
n 2. 下载编译
下载地址: http://download.redis.io/releases/redis-3.0.7.tar.gz (需要下载3.0.0以上的版本)
wget http://download.redis.io/releases/redis-3.0.7.tar.gz tar -zxvf redis-3.0.7.tar.gz make |
n 3. 创建集群需要的目录
mkdir /usr/local/redis-3.0.7/cluster cd /usr/local/redis-3.0.7/cluster mkdir 7000 mkdir 7001 mkdir 7002 mkdir 7003 mkdir 7004 mkdir 7005 |
n 4. 配置文件redis.conf
下面是一个最少选项的集群的配置文件,先拷贝一份作为作修改:
cp /usr/local/redis-3.0.7/redis.conf /usr/local/redis-3.0.7/cluster/ vi redis.conf |
port 7000 daemonize yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes |
##修改完 redis.conf 配置文件中的这些配置项之后把这个配置文件分别拷贝到
7000/7001/7002/7003/7004/7005目录下面:
cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7/cluster/7000 cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7/cluster/7001 cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7//cluster/7002 cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7/cluster/7003 cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7/cluster/7004 cp /usr/local/redis-3.0.7/cluster/redis.conf /usr/local/redis-3.0.7/cluster/7005 |
每个实例配置类似,修改一下都应端口、IP地址、文件名称即可。
Redis集群由多个运行在集群模式(cluster mode)下的Redis实例组成,实例的集群模式需要通过配置来开启,开启集群模式的实例将可以使用集群特有的功能和命令。要让集群正常运作至少需要三个主节点, 不过在刚开始试用集群功能时,强烈建议使用六个节点:其中三个为主节点,而其余三个则是各个主节点的从节点。
常用配置信息:
绑定地址:bind 192.168.XXX.XXX。不能绑定到127.0.0.1或localhost,否则指导客户端重定向时会报”Connection refused”的错误。 后台运行:daemonize yes 输出日志:logfile “./redis-7000.log” 监听端口:port 7000 Cluster:cluster-enabled yes cluster-config-file nodes-7000.conf cluster-node-timeout 15000 cluster-require-full-coverage yes |
n 5. 启动redis实例
cd /usr/local/redis-3.0.7/ ./src/redis-server cluster/7000/redis.conf ./src/redis-server cluster/7001/redis.conf … |
实例打印的日志显示, 因为 nodes.conf 文件不存在,所以每个节点都为它自身指定了一个新的 ID :
[82462]26 Nov 11:56:55.329 * No cluster configuration found, I‘m97a3a64667477371c4479320d683e4c8db5858b1
启动之后使用命令查看redis的启动情况 ps –ef | grep redis
n 6. 执行redis的创建集群命令创建集群
使用redis-trib.rb创建集群:
src/redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 # redis-trib.rb 的create子命令构建 # --replicas 则指定了为Redis Cluster中的每个Master节点配备几个Slave节点 # 节点角色由顺序决定,先master之后是slave |
如果执行上述命令报错的时候会报错,因为是执行的ruby的脚本,需要ruby的环境,错误内容:/usr/bin/env: ruby: No such file or directory
所以需要安装ruby的环境,这里推荐使用yuminstall ruby安装
yum install ruby |
然后再执行创建集群命令,还会报错,提示不能加载redis,是因为缺少redis和ruby的接口,
错误内容:
/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in`gem_original_require‘: no such file to load -- redis (LoadError)
from/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require‘
from./redis-trib.rb:25
使用yum安装:
gem sources -a http://rubygems.org #如果提示https://rubygems.org无法连接,采用http方式 gem install redis |
再执行创建集群命令成功后会展示:
输入yes,然后配置完成。
[OK] All16384 slots covered
这表示集群中的16384个槽都有至少一个主节点在处理,集群运作正常。redis集群即搭建成功!
备注:在utils/create-cluster下提供了一个create-cluster脚本,能够创建出一个集群,类似我们上面建立起的3主3从的集群。
n 7.检查集群状态
5 测试
5.1 测试说明
测试场景,分为几种场景测试,分别对单节点单redis实例方式与两台主机多个redis实例分别插入1千万条数据,查询记录等操作,进行性能分析,内存对比。以及对集群的可用性,分别对某个节点进行停止服务,验证其集群的可用性。上述测试都是基于内存方式进行测试。
5.2 本地虚拟主机配置
1、硬件配置:
? CPU:Intel(R) Core(TM) i5-4590 CPU @ 3.30GHz * 2(2核处理器)
? 内存:4G
? 磁盘:75G
2、软件配置:
? 操作系统:CentOSLinux release 7.1.1503(Linux version3.10.0-229.14.1.el7.x86_64)
? Redis:3.0.7
? JDK: 1.7.0_79
? Jedis: 2.8.1
5.3 测试单机单redis实例
5.3.1 测试场景1
测试场景:使用单机启动redis实例,插入1千万条记录(固定的key值)。
测试步骤:
1. 修改redis.conf,以全内存方式运行:
快照rdb保存方式部分全注释了,
#save 900 1
#save 300 10
#save 60 10000
使用aof 持久化方式设置为no
appendfsync no
2. 启动redis实例。
3 . 使用Jedis客户端写入1千万条记录(sadd方法),固定的key值,value值模拟规则生成(jedis.sadd("zimpes",s)),得出测试数据。
备注:这里插入的记录有是有一定的业务逻辑,所以时间上会有所损耗。因此耗时并不是直接插入redis的时间。
n 测试结果
新增记录1千万 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1055.999(s) |
1102.512(s) |
1075.976(s) |
内存消耗 |
955.70(M) |
955.70(M) |
955.70(M) |
5.3.2 测试场景2
测试场景:在测试场景1的基础上,执行查询记录1千万次,查询的值随机。
测试步骤:
1. 同1,以全内存方式启动redis实例。
2 . 使用Jedis客户端执行查询记录1千万次(sismember方法),查询值随机生成,得出测试数据。
n 测试结果
记录1千万,查询次数1千万次 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1207.424(s) |
1075.278(s) |
1024.245(s) |
5.3.3 测试场景3
测试场景:使用单机启动redis实例,插入1千万条记录(模拟随机生成key值)。
测试步骤跟测试场景1相同:
1 . 使用Jedis客户端写入1千万条记录(sadd方法),模拟随机生成key与value值模(jedis.sadd(s, s)),得出测试数据。
n 测试结果
新增记录1千万 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1365.425(s) |
1366.234(s) |
1427.347(s) |
内存消耗 |
1044.34(M) |
1044.30(M) |
1044.30(M) |
5.3.4 测试场景4
测试场景:在测试场景3的基础上,执行查询记录1千万次,查询的值随机。
测试步骤:
1. 同3,以全内存方式启动redis实例。
2 . 使用Jedis客户端执行查询记录1千万次(sismember方法),查询值随机生成,得出测试数据。
n 测试结果
记录1千万,查询次数1千万次 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1452.37(s) |
1287.173(s) |
1408.166(s) |
5.4 测试多台主机多个redis实例集群
5.4.1 测试场景1
测试场景:使用两台主机,同上述配置,插入1千万条记录。
测试步骤:
1. 分别在两台机器上各启动3个redis实例,分别为:
172.16.21.139:
172.16.21.131:
2. 执行../src/redis-trib.rbcreate --replicas 1 172.16.21.139:7000 172.16.21.139:7001 172.16.21.139:7002172.16.21.131:7003 172.16.21.131:7004 172.16.21.131:7005,启动集群:
从启动日志可以看出,三个主节点为:
M: 8092602c5491fad21ab71670a3c178f72964b2ba172.16.21.139:7000
M: e27291b9e2449c857373d4fd60f7b07f37886eed172.16.21.139:7001
M: 22d5c7cce58d559839c2914c34846afc23170320172.16.21.131:7003
三个从节点为:
S: 576e6ea7e133510dfa5e90d086e1330846f6fdd7172.16.21.139:7002
S: e46fc51206f05160dff40a7a585908bd55e9e9b5172.16.21.131:7004
S: 356a53b3146e0f8918d3530eba3d463f94397350172.16.21.131:7005
3 . 使用Jedis客户端写入1千万条记录(sadd方法),测试发现如果采用相同的key值时,只存储到同一个节点,这样测试并没有多大意义,因此这里测试采用的key与value使用的都是一样,都是模拟随机生成的,从而得出测试数据。
主机一:
主机二:
n 测试结果
新增记录1千万 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1014.444(s) |
952.875(s) |
962.408(s) |
内存消耗 (主机1) |
661.45+661.52+660.9 =1983.87(M) |
661.51+661.86+660.41 =1983.78(M) |
660.8+660.68+660.29 =1981.77(M) |
内存消耗 (主机2) |
661.9+660.47+660.5 =1982.87(M) |
661.4+660.49+660.82 =1982.71(M) |
661.31+661.77+661.72 =1984.8(M) |
内存消耗 (总) |
3966.74(M) |
3966.49(M) |
3966.57(M) |
5.4.2 测试场景2
测试场景:在测试场景1的基础上,执行查询记录1千万次,查询的值随机。
测试步骤:
1. 同1,以全内存方式启动redis实例。
2 . 使用Jedis客户端执行查询记录1千万次(sismember方法),查询值随机生成,得出测试数据。
n 测试结果
主机一:
主机二:
记录1千万,查询次数1千万次 |
|||
测试次数 |
第一次 |
第二次 |
第三次 |
耗时 |
1910.489(s) |
1971.054(s) |
1942.552(s) |
5.4.3 可用性、可运维性测试
测试场景:使用两台主机,同上述配置,起分别6个redis实例并对其进行集群配置,分别测试添加新节点,Resharding,故障转移,移除节点等实验。
实验主要通过redis-trib.rb命令进行操作,可以查询到相关命令信息:
[[email protected] src]# ./redis-trib.rb help |
5.4.3.1 添加节点
1. 参考之前章节4搭建并使用Redis集群,在已经启动6个节点集群的基础上,在139机器上建立7010文件夹和相应修改端口为7010的redis.conf文件;在131机器上建立7020文件夹和相应修改端口为7020的redis.conf文件。并分别启动两个Redis实例:
[[email protected] cluster]# ../src/redis-server ./7010/redis.conf |
[[email protected] cluster]# ../src/redis-server ./7020/redis.conf |
当前的集群信息:
2. 使用程序模拟随机生成记录,保持到redis,数据分布如下:
3. 使用redis-trib.rb add-node分别将两个新结点添加到集群中,一个作为Master,一个作为其Slave。
添加Master节点:
[[email protected] src]# ./redis-trib.rb add-node 172.16.21.139:7010 172.16.21.139:7000 |
新添加的7010节点已经作为master节点,没有包含任何数据, 因为它没有包含任何 slot。,当集群需要将某个从节点升级为新的主节点时,这个新节点不会被选中。
添加Slaver节点:
./redis-trib.rb add-node --slave --master-id 6dc6b0be6f1eb4b0316c25856ea2a08361f2cf05 172.16.21.131:7020 172.16.21.139:7000 |
注意:在线添加slaver时,需要dump整个master进程,并传递到slaver,再由slaver
加载 rdb 文件到内存,rdb 传输过程中Master可能无法提供服务,整个过程消耗大量 io,小心操作。
5.4.3.2 Resharding
新添加的节点仍不能提供服务,需要为其分配slot后才行。通过redis-trib.rbreshard可以交互式地迁移Slot。下面的例子将4000个Slot从其他master节点迁移到7010节点上。也可以通过./redis-trib.rb reshard <host>:<port> --from<node-id> --to <node-id> --slots --yes在程序中自动完成迁移。
1. reshard:
./redis-trib.rb reshard 172.16.21.139:7000 |
途中会进行询问:
需要迁移的slot数量
选择要接受这些迁移slot的node-id
选择slot来源,all表示从所有的master重新分配,或者数据要提取 slot 的 master 节点 id,最后用 done 结束
打印被移动的 slot 后,输入 yes 开始移动 slot 以及对应的数据
此时7010已成功分配4000个slot:
2. 迁移完成后,查看之前保存的测试记录的分布情况,可以看到部分Key已经迁移到了新的结点7010上。
5.4.3.3 故障转移
停掉主节点:
Redis Cluster重用了Sentinel的代码逻辑,不需要单独启动一个Sentinel集群,RedisCluster本身就能自动进行Master选举和Failover切换。
1. 先查看7010目前存储的数据记录:
2. 停掉7010节点,之后可以看到结点状态变成了fail,而Slave 7011被选举为新的Master。
3. 再次查询之前保存在7010节点上的数据,可以看到7020节点顶替了7010节点成为主节点后继续提供服务,整个集群没有受到影响。
4. 重新启动7010,可以发现自动重新恢复到集群中。
5. 然后尝试用程序不断写入记录,中途停止7020节点。
此时自动切换7010为master节点:
程序仍可以进行写入,表示集群故障转移是成功的。
重启7020节点的实例,连接上去查看现在数据量与7010节点上的数据量,发现是一致的,表示数据主从同步成功。
注意:这里使用的是Jedis,初始化JedisCluster时有几个参数需要重点关注的:
connectionTimeout
soTimeout socket timeout
maxRedirections 尝试重定向次数
以上connectionTimeout与soTimeout需要大于redis配置failover检测主节点,最终从节点成功替代原主节点的时间段。不然会抛出CLUSTER DOWN异常。
另外maxRedirections尝试重定向次数将包含在上面两个配置的时间段内,如果插入条数速度比较快此值需要设置比较大,不然在从节点生效之前,已经到达最大重定向次数的话,将会抛出Too many Cluster redirections?异常。
5.4.3.4 删除节点
1. 删除节点前,查询当前集群信息:
2. 分别进行删除从节点与删除主节点测试
删除从节点:
./redis-trib.rb del-node 172.16.21.131:7020 fe79dcbac250b97ec9df4102983fd2901f4823c6 |
执行完成后查看集群状态与7020节点实例,发现都已经没有了:
删除主节点:
使用同样的方法移除主节点,不过在移除主节点前,需要确保这个主节点是空的。如果不是空的,需要将这个节点的数据重新分片到其他主节点上。
将7010节点的slot(4000)迁到7000上:
./redis-trib.rb reshard 172.16.21.139:7000 |
输入要迁移的slot数量,这里是4000
输入要接受这些slot的node-id,这里是7000节点的node-id
输入被分配的源节点node-id,这里需要被删除7010节点的node-id
输入done之后可以看到后台输出moving的信息:
完成后可以查询到7010节点的slots迁移到7000中了:
最后就可以执行删除主节点操作
./redis-trib.rb del-node 172.16.21.139:7010 6dc6b0be6f1eb4b0316c25856ea2a08361f2cf05 |
执行完成后查看集群状态与7020节点实例,发现都已经没有了:
5.4.3.5 迁移从节点
1. 在特定的场景下,不需要系统管理员的协助下,自动将一个从节点从当前的主节点切换到另一个主节的自动重新配置的过程叫做复制迁移(从节点迁移),从节点的迁移能够提升整个Redis集群的可用性:
概况一下从节点迁移
集群会在有从节点数量最多的主节点上进行从节点的迁移
要在一个主节点上添加多个从节点
参数来控制从节点迁移replica-migration-barrier:你可以仔细阅读redis.conf。
2. 先查询当前集群的信息:
3. 启动7020节点实例,并添加到集群中作为7000的从节点:
./redis-trib.rb add-node --slave --master-id d3e9e5fac92b43aee69f3f29d4679842ee7e979b 172.16.21.131:7020 172.16.21.139:7000 |
4. 添加成功后可以看到,7020已作为7000的从节点,此时7000的从节点有7004和7020两个:
5. 7003只有一个从节点7002,尝试删除7002从节点:
./redis-trib.rb del-node 172.16.21.139:7002 559dee1a662ecdc508efcf402c4f19294e3b42dd |
6. 删除后已经没有了7002实例,与集群中7003已没有从节点:
7. 使用replicate转移7020从节点到7003主节点,成功后如下:
5.4.3.6 手动故障转移
有的时候在主节点没有任何问题的情况下强制手动故障转移也是很有必要的,比如想要升级主节点的Redis进程,我们可以通过故障转移将其转为slave再进行升级操作来避免对集群的可用性造成很大的影响。
Redis集群使用 CLUSTER FAILOVER命令来进行故障转移,不过要被转移的主节点的从节点上执行该命令 手动故障转移比主节点失败自动故障转移更加安全,因为手动故障转移时客户端的切换是在确保新的主节点完全复制了失败的旧的主节点数据的前提下下发生的,所以避免了数据的丢失。
执行手动故障转移时从节点日志如下:
# Manual failover user request accepted.
# Received replication offset for pausedmaster manual failover: 347540
# All master replication stream processed,manual failover can start.
# Start of election delayed for 0milliseconds (rank #0, offset 347540).
# Starting a failover election for epoch7545.
# Failover election won: I‘m the newmaster.
其基本过程如下:客户端不再链接我们淘汰的主节点,同时主节点向从节点发送复制偏移量,从节点得到复制偏移量后故障转移开始,接着通知主节点进行配置切换,当客户端在旧的master上解锁后重新连接到新的主节点上。
例如当前7002是7003的从节点,通过登录7002执行failover,主动实现故障迁移:
执行后,可以看到7002已经成为主节点,7003成为其从节点:
5.5 结果分析
1. 单机上保存1千万条记录(当前生成规则:key固定值与value模拟生成规则值)占用内存大小约为955M,比较稳定,因此可以估计到1亿条记录大约至少需要9.4G内存。
2. 单机上保存1千万条记录(当前生成规则:key与value都是模拟生成规则值)占用内存大小约为1044.30M,比较稳定,因此可以估计到1亿条记录大约至少需要10.2G内存。插入数据的性能比第一种情况下降15%-28%,查询的性能比第一种情况下降15%-25%。
3. 集群的情况下保存1千万条记录(当前生成规则:key与value都是模拟生成规则值),平均每台机占用总内存大小约为1984M,共约3966.6M,因此可以估计到1亿条记录大约至少共需要38.7G内存。
4. 同样的添加记录条件下(当前生成规则:key与value都是模拟生成规则值),新增记录1千万条,单机约为1400s,集群约为977s,集群性能提升大约30%。
5. 同样的查询记录条件下(当前生成规则:key与value都是模拟生成规则值),存量记录1千万条,查询1千万次,单机约为1382s,集
以上是关于Redis集群的主要内容,如果未能解决你的问题,请参考以下文章