总结:
1、简介
Redis Cluster是一个高性能高可用的分布式系统。由多个Redis实例组成的整体,数据按照Slot存储分布在多个Redis实例上,通过Gossip协议来进行节点之间通信。
1.1Redis集群核心的目标:
在官方文档Cluster Spec中,作者详细介绍了Redis集群为什么要设计成现在的样子。最核心的目标有三个:
1、性能:这是Redis赖以生存的看家本领,增加集群功能后当然不能对性能产生太大影响,所以Redis采取了P2P而非Proxy方式、Master与slave之间使用异步复制(异步replication)、客户端重定向等设计,而牺牲了部分的一致性、使用性。
2、水平扩展:集群的最重要能力当然是扩展,文档中称可以线性扩展到1000结点。
3、可用性:在Cluster推出之前,可用性要靠Sentinel保证。有了集群之后也自动具有了Sentinel的监控和自动Failover能力。只要集群中大多数master可达、且失效的master至少有一个slave可达时,集群都可以继续提供服务;同时“replicas migration”可以将那些拥有多个slaves的master的某个slave,迁移到没有slave的master下,即将slaves的分布在整个集群相对平衡,尽力确保每个master都有一定数量的slave备份。
4、数据一致性:客户端容忍一定程度的数据丢失,集群尽可能保存Client write操作的数据,保证数据一致性。集群将会尽可能(best-effort)保存客户端write操作的数据;通常在failover期间,会有短暂时间内的数据丢失(因为异步replication引起);当客户端与少数派的节点处于网络分区时(network partition),丢失数据的可能性会更高。(因为节点有效性检测、failover需要更长的时间)
Redis Cluster功能特点如下:
1)节点自动发现:所有的节点相互连接
2)集群消息通信通过集群总线通信,,集群总线端口大小为客户端服务端口 10000,这个10000是固定值
3)节点与节点之间通过二进制协议进行通信
4)客户端和集群节点之间通信和通常一样,通过文本协议进行
5)集群节点不会代理查询
6)数据按照Slot存储分布在多个Redis实例上
7)集群节点挂掉会自动故障转移
8)可以相对平滑扩/缩容节点
1.2 架构变化与CAP理论:
Redis Cluster集群功能推出已经有一段时间了。在单机版的Redis中,每个Master之间是没有任何通信的,所以我们一般在Jedis客户端或者Codis这样的代理中做Pre-sharding。按照CAP理论来说,单机版的Redis属于保证CP(Consistency & Partition-Tolerancy)而牺牲A(Availability),也就说Redis能够保证所有用户看到相同的数据(一致性,因为Redis不自动冗余数据)和网络通信出问题时,暂时隔离开的子系统能继续运行(分区容忍性,因为Master之间没有直接关系,不需要通信),但是不保证某些结点故障时,所有请求都能被响应(可用性,某个Master结点挂了的话,那么它上面分片的数据就无法访问了)。
有了Cluster功能后,Redis从一个单纯的NoSQL内存数据库变成了分布式NoSQL数据库,CAP模型也从CP变成了AP。也就是说,通过自动分片和冗余数据,Redis具有了真正的分布式能力,某个结点挂了的话,因为数据在其他结点上有备份,所以其他结点顶上来就可以继续提供服务,保证了Availability。然而,也正因为这一点,Redis无法保证曾经的强一致性了。这也是CAP理论要求的,三者只能取其二。
1.3 和单点操作变化
Cluster不能进行跨Nodes操作,也没有nodes提供merge层代理。
Cluster中实现了一个称为“hash tags”的概念,每个key都可以包含一个自定义的“tags”,那么在存储时将根据tags计算此key应该分布在哪个nodes上(而不是使用key计算,但是存储层面仍然是key);此特性,可以强制某些keys被保存在同一个节点上,以便于进行“multikey”操作,比如“foo”和“{foo}.student”将会被保存在同一个node上。不过在人工对slots进行resharding期间,multikey操作可能不可用。
在Cluster环境下,将不支持SELECT命令,所有的key都将保存在默认的database中。
1.4 客户端与Server角色
集群中nodes负责存储数据,保持集群的状态,包括keys与nodes的对应关系(内部其实为slots与nodes对应关系)。nodes也能够自动发现其他的nodes,检测失效的节点,当某个master失效时还应该能将合适的slave提升为master。
为了达成这些行为,集群中的每个节点都通过TCP与其他所有nodes建立连接Nodes之间使用gossip协议(参见下文备注)向其他nodes传播集群信息,以达到自动发现的特性,通过发送ping来确认其他nodes工作正常,也会在合适的时机发送集群的信息。当然在Failover时(包括人为failover)也会使用Bus来传播消息。
Nodes之间使用gossip协议(参见下文备注)向其他nodes传播集群信息,以达到自动发现的特性,通过发送ping来确认其他nodes工作正常,也会在合适的时机发送集群的信息。当然在Failover时(包括人为failover)也会使用Bus来传播消息。
理论上,Client可以将请求发送给任意一个nodes,然后根据在根据错误信息转发给合适的node,客户端可以不用保存集群的状态信息,当然这种情况下性能比较低效,因为Client可能需要2次TCP调用才能获取key的结果,通常客户端会缓存集群中nodes与slots的映射关系,并在遇到“Redirected”错误反馈时,才会更新本地的缓存。上,Client可以将请求发送给任意一个nodes,然后根据在根据错误信息转发给合适的node,客户端可以不用保存集群的状态信息,当然这种情况下性能比较低效,因为Client可能需要2次TCP调用才能获取key的结果,通常客户端会缓存集群中nodes与slots的映射关系,并在遇到“Redirected”错误反馈时,才会更新本地的缓存。
2、集群配置安装
2.1 Redis Cluster 3.2.3安装:
Redis的安装很简单:
代码语言:javascript复制mkdir /mnt/src && cd /mnt/src ;
wget http://download.redis.io/releases/redis-3.2.3.tar.gz
tar xzf redis-3.2.3.tar.gz
cd redis-3.2.3
make && make install
2.2 单机启动:
代码语言:javascript复制#启动redis
redis-server /mnt/redis-7001/redis.conf
#连接redis
redis-cli -a passwd -p 7001
#查看版本
redis-cli -a passwd -p 7001 info|grep 'redis_version'
#测试是否成功
登录后执行:ping
收到:pong
2.3 redis集群配置
要想开启Redis Cluster模式,有几项配置是必须的。此外为了方便使用和后续的测试,我还额外做了一些配置:
- 绑定地址:bind 192.168.XXX.XXX。不能绑定到127.0.0.1或localhost,否则指导客户端重定向时会报”Connection refused”的错误。
- 开启Cluster:cluster-enabled yes :如果想在特定的Redis实例中启用Redis群集支持就设置为yes。 否则,实例通常作为独立实例启动。
- 集群配置文件:cluster-config-file nodes-7000.conf。这个配置文件不是要我们去配的,而是Redis运行时保存配置的文件,所以我们也不可以修改这个文件。Redis群集节点每次发生更改时自动保留群集配置(基本上为状态)的文件,以便能够 在启动时重新读取它。 该文件列出了群集中其他节点,它们的状态,持久变量等等。 由于某些消息的接收,通常会将此文件重写并刷新到磁盘上。
- 集群超时时间:cluster-node-timeout 15000。结点超时多久则认为它宕机了。如果主节点超过指定的时间不可达,它将由其从属设备进行故障切换。 此参数控制Redis群集中的其他重要事项。 值得注意的是,每个无法在指定时间内到达大多数主节点的节点将停止接受查询。
- 槽是否全覆盖:cluster-require-full-coverage no。默认是yes,只要有结点宕机导致16384个槽没全被覆盖,整个集群就全部停止服务,所以一定要改为no。 如果将其设置为yes,则默认情况下,如果key的空间的某个百分比未被任何节点覆盖,则集群停止接受写入。 如果该选项设置为no,则即使只处理关于keys子集的请求,群集仍将提供查询。
- 后台运行:daemonize yes
- 输出日志:logfile “./redis.log”
- 监听端口:port 7000
- cluster-slave-validity-factor <factor>:如果设置为0,无论主设备和从设备之间的链路保持断开连接的时间长短,从设备都将尝试故障切换主设备。 如果该值为正值,则计算最大断开时间作为节点超时值乘以此选项提供的系数,如果该节点是从节点,则在主链路断开连接的时间超过指定的超时值时,它不会尝试启动故障切换。 例如,如果节点超时设置为5秒,并且有效因子设置为10,则与主设备断开连接超过50秒的从设备将不会尝试对其主设备进行故障切换。 请注意,如果没有从服务器节点能够对其进行故障转移,则任何非零值都可能导致Redis群集在主服务器出现故障后不可用。 在这种情况下,只有原始主节点重新加入集群时,集群才会返回可用。
- cluster-migration-barrier <count>:主设备将保持连接的最小从设备数量,以便另一个从设备迁移到不受任何从设备覆盖的主设备。有关更多信息,请参阅本教程中有关副本迁移的相应部分。
配置好后,根据我们的集群规模,拷贝出来几份同样的配置文件,唯一不同的就是监听端口,可以依次改为7001、7002… 因为Redis Cluster如果数据冗余是1的话,至少要3个Master和3个Slave,所以我们拷贝出6个实例的配置文件。为了避免相互影响,为6个实例的配置文件建立独立的文件夹。
2.4 redis-trib管理器
Redis作者应该是个Ruby爱好者,Ruby客户端就是他开发的。这次集群的管理功能没有嵌入到Redis代码中,于是作者又顺手写了个叫做redis-trib的管理脚本。redis-trib依赖Ruby和RubyGems,以及redis扩展。可以先用which命令查看是否已安装ruby和rubygems,用gem list –local查看本地是否已安装redis扩展。
最简便的方法就是用apt或yum包管理器安装RubyGems后执行gem install redis。如果网络或环境受限的话,可以手动安装RubyGems和redis扩展
代码语言:javascript复制#安装rub管理工具rvm
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L get.rvm.io | bash -s stable
find / -name rvm -print
#加载rvm
source /usr/local/rvm/scripts/rvm
#查看ruby版本
rvm list known
#安装ruby,这里我们选择安装2.5.1
rvm install 2.5.1
rvm use 2.5.1 --default
#安装redis-trib.rb即redis集群工具
gem install redis -v 3.3.5
#安装完成后的目录为:
/mnt/src/redis-4.0.9/src/redis-trib.rb
2.5 启动集群服务
首先,启动我们配置好的6个Redis实例。我们创建相关目录,主文件夹是redis-cluster,在此文件夹下建立6个子文件夹,名称分别是:7001,7002,7003,7004,7005,7006 该目录以我们将在任何给定目录内运行的实例的端口号命名。
mkdir -p /data/redis-cluster-{7001..7006} && touch /data/redis-cluster-{7001..7006}/redis.log #启动redis redis-server /data/redis-7001/redis.conf
此时6个实例还没有形成集群,现在用redis-trb.rb管理脚本建立起集群。可以看到,redis-trib默认用前3个实例作为Master,后3个作为Slave。因为Redis基于Master-Slave做数据备份,而非像Cassandra或Hazelcast一样不区分结点角色,自动复制并分配Slot的位置到各个结点。
代码语言:javascript复制#启动集群
/mnt/src/redis-4.0.9/src/redis-trib.rb create --replicas 1 10.26.25.115:7001 10.26.25.115:7002 10.26.25.115:7003 10.26.25.115:7004 10.26.25.115:7005 10.26.25.115:7006
代码语言:javascript复制至此,集群就已经建立成功了!“贴心”的Redis还在utils/create-cluster下提供了一个create-cluster脚本,能够创建出一个集群,类似我们上面建立起的3主3从的集群。
2.6 简单测试
我们连接到集群中的任意一个结点,启动redis-cli时要加-c选项,存取两个Key-Value感受一下Redis久违的集群功能。
仔细观察能够注意到,redis-cli根据指示,不断在7000和7002结点之前重定向跳转。如果启动时不加-c选项的话,就能看到以错误形式显示出的MOVED重定向消息。
2.7 集群重启
目前redis-trib的功能还比较弱,需要重启集群的话先手动kill掉各个进程,然后重新启动就可以了。这也有点太… 网上有人重启后会碰到问题,我还比较幸运,这种“土鳖”的方式重启试了两次还没发现问题。
代码语言:javascript复制[root@8gVm redis-3.0.4]# ps -ef | grep redis | awk '{print $2}' | xargs kill