为了提高网站的响应速度,我们可以把经常用到的数据放到内存中进行缓存,而不是放到数据库中进行持久化.Redis是一个很好的Cache工具,当然了还有Memcached.在我们的分布式系统中,热点数据往往巨大,比如单点登录、用户浏览商品的信息、用户信息、用户收藏的商品信息、短信提醒数据等,也都用到了redis.如何使redis可以横向可伸缩扩展,这需要由多台机器协同提供服务,一台挂掉了,另一台马上顶上去,即分布式的redis集群,就对提升系统的性能非常重要.
Redis集群在启动的时候就自动在多个节点间分好片,同时提供了分片之间的可用性:当一部分redis节点故障或网络中断,集群也能继续工作.但是,当大面积的节点故障或网络中断(比如大部分的主节点都不可用了),集群就不能使用了.
所以,从实用性的角度来看,Redis集群提供以下功能:
1️⃣.自动把数据切分到多个Redis节点中;
2️⃣.当一部分节点挂了或不可达,集群依然能继续工作.

为了保证在部分节点故障或网络不通时集群依然能正常工作,集群使用了主从模型,每个哈希槽有一主节点 到 N个副本 (N-1个从节点). 在我们刚才的集群例子中,有A,B,C三个节点,如果B节点故障集群就不能正常工作了,因为B节点中的哈希槽数据没法操作.但是,如果我们给每一个节点都增加一个从节点,就变成了: A,B,C三个节点是主节点, A1, B1, C1 分别是他们的从节点,当B节点宕机时,我们的集群也能正常运作. B1节点是B节点的副本,如果B节点故障,集群会提升B1为主节点,从而让集群继续正常工作.但是,如果B和B1同时故障,集群就不能继续工作了.
1️⃣.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;
2️⃣.节点的fail是当集群中超过半数的节点检测失效时才生效;
3️⃣.客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群中所有的节点,连接集群中任何一个可用节点即可;
4️⃣.redis-cluster会把所有的物理节点映射到[0-16383]个slot哈希槽上,cluster负责维护哈希槽里的值.Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点.
1️⃣.Redis 的集群分片是将key所在的空间拆分成16384个槽位,每一个节点负责其中一部分槽位;
2️⃣.Redis提供一定程度的可用性,可以在某个节点宕机或者不可达的情况下继续处理命令;
3️⃣.Redis 集群中不存在中心(central)节点或者代理(proxy)节点,集群的其中一个主要设计目标是达到线性可扩展性(linear scalability);
4️⃣.Redis集群要想正常工作,必须要三个以上的主节点,在我们搭建的redis集群环境中,三个主节点每个都需要有一个从节点,所以一共有六个节点,也就是说需要开启6个redis服务.

Redis集群不是使用一致性哈希,而是使用哈希槽,整个Redis集群有16384个哈希槽.决定一个key应该分配到哪个槽的算法是:计算该key的CRC16结果再模16384 .
集群中的每个节点负责一部分哈希槽,比如集群中有3个节点,则:
节点A存储的哈希槽范围是: 0 – 5500;
节点B存储的哈希槽范围是:5501 – 11000;
节点C存储的哈希槽范围是: 11001 – 16384.
这样的分布方式方便节点的添加和删除.比如,此时如果需要新增一个节点D,只需要把A、B、C中的部分哈希槽数据移到D节点.同样,如果希望在集群中删除A节点,只需要把A节点上哈希槽的数据移到B和C节点,当A节点的数据全部被移走后,A节点就可以完全从集群中删除.
因为把哈希槽从一个节点移到另一个节点是不需要停机的,所以增加或删除节点,或更改节点上的哈希槽,也是不需要停机的.
如果多个key都属于一个哈希槽,集群支持通过一个命令(或事务,或lua脚本)同时操作这些key.通过“哈希标签”的概念,用户可以让多个key分配到同一个哈希槽.哈希标签在集群详细文档中有描述,这里做个简单介绍: 如果key含有大括号”{}”,则只有大括号中的字符串会参与哈希,比如”this{foo}”和”another{foo}”这2个key会分配到同一个哈希槽,所以可以在一个命令中同时操作他们.
Redis集群中的每个节点都需要建立2个TCP连接,所以需要监听2个端口: 一个端口称之为“客户端端口”,用于接受客户端指令,与客户端交互,比如6379;另一个端口称之为“集群总线端口”,是在客户端端口号上加10000,比如16379,用于节点之间通过二进制协议通讯.
各节点通过集群总线检测宕机节点、更新配置、故障转移验证等.客户端只能使用客户端端口,不能使用集群总线端口.请确保你的防火墙允许打开这两个端口,否则Redis集群没法工作.客户端端口和集群总线端口之间的差值是固定的,集群总线端口比客户端端口高10000.
注意,关于集群的2个端口:
1️⃣.客户端端口(一般是6379)需要对所有客户端和集群节点开放,因为集群节点需要通过该端口转移数据;
2️⃣.集群总线端口(一般是16379)只需对集群中的所有节点开放.
这2个端口必须打开,否则集群没法正常工作.
集群节点之间通过集群总线端口交互数据,使用的协议不同于客户端的协议,是二进制协议,这可以减少带宽和处理时间.
Redis集群不能保证强一致性.一些已经向客户端确认写成功的操作,会在某些不确定的情况下丢失.
产生写操作丢失的第一个原因,是因为主从节点之间使用了异步的方式来同步数据.
一个写操作是这样一个流程:
1️⃣.客户端向主节点B发起写的操作;
2️⃣.主节点B回应客户端写操作成功;
3️⃣.主节点B向它的从节点B1,B2,B3同步该写操作.
从上面的流程可以看出来,主节点B并没有等从节点B1,B2,B3写完之后再回复客户端这次操作的结果.所以,如果主节点B在通知客户端写操作成功之后,但同步给从节点之前,主节点B故障了,其中一个没有收到该写操作的从节点会晋升成主节点,该写操作就这样永远丢失了.
就像传统的数据库,在不涉及到分布式的情况下,它每秒写回磁盘.为了提高一致性,可以在写盘完成之后再回复客户端,但这样就要损失性能.这种方式就等于Redis集群使用同步复制的方式.
基本上,在性能和一致性之间,需要一个权衡.
如果真的需要,Redis集群支持同步复制的方式,通过WAIT指令来实现,这可以让丢失写操作的可能性降到很低.但就算使用了同步复制的方式,Redis集群依然不是强一致性的.在某些复杂的情况下,比如从节点在与主节点失去连接之后被选为主节点,不一致性还是会发生.
这种不一致性发生的情况是这样的,当客户端与少数的节点(至少含有一个主节点)网络联通,但他们与其他大多数节点网络不通.比如6个节点,A,B,C是主节点,A1,B1,C1分别是他们的从节点,一个客户端称之为Z1.
当网络出问题时,他们被分成2组网络,组内网络联通,但两个组之间的网络不通.假设A/B/C,A1/B1/C1彼此之间是联通的,另一边,B和Z1的网络是联通的.Z1可以继续往B发起写操作,B也接受Z1的写操作.当网络恢复时,如果这个时间间隔足够短,集群仍然能继续正常工作.如果时间比较长,以致B1在大多数的这边被选为主节点,那刚才Z1发给B的写操作都将丢失.
注意,Z1给B发送写操作是有一个限制的,如果时间长度达到了大多数节点那边可以选出一个新的主节点时,少数这边的所有主节点都不接受写操作.
这个时间的配置,称之为节点超时(node timeout),对集群来说非常重要,当达到了这个节点超时的时间之后,主节点被认为已经宕机,可以用它的一个从节点来代替.同样,在节点超时时,如果主节点依然不能联系到其他主节点,它将进入错误状态,不再接受写操作.

redis cluster在设计的时候,就考虑到了去中心化,去中间件.也就是说,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态.每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据.
Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽(hash slot)的方式来分配数据.redis cluster 默认分配了 16384 个slot,当我们设置一个 key 的时候,redis集群会先用CRC16算法对key计算出一个结果,然后利用该结果来对16384取模得到所属的slot位,最后将这个key 分到哈希槽区间所在的节点上.具体算法就是: CRC16(key) % 16384 .所以我们在测试的时候看到set 和 get 的时候,直接跳转到了7001端口所在的节点.
Redis 集群会把数据存在一个 master 节点上,然后在这个 master 节点和其对应的 slave 节点之间进行数据同步.当读取数据时,会根据一致性哈希算法得到对应的 master 节点来获取数据,只有当一个master 挂掉之后,才会启动一个对应的 slave 节点,将其提升为master .
必须要创建3个或以上的主节点,否则在创建集群时会失败,并且当存活的主节点数小于总节点数的一半时,整个集群就无法提供服务了.
Redis集群中至少应该有三个节点,要保证集群的高可用,需要每个节点有一个备份机,所以一个Redis集群至少需要6台服务器.
搭建伪分布式集群,可以使用一台虚拟机运行6个redis实例,然后修改redis的端口号7001-7006(自定义端口,只要不和其他程序重复).
一般情况下,我们需要6台redis服务器,搭建出一个分布式的redis集群.在我们的案例中,是用一台虚拟机模拟出6个节点,分别创建出3个master节点,3个slave节点,这几个服务器分别运行在不同的端口7001-7006上,以此构建出一个分布式的伪集群环境.
介绍一下redis集群中redis.conf文件中的常用参数.
cluster-enabled :如果配置”yes”则开启集群功能,此redis实例作为集群的一个节点,否则,它是一个普通的单一的redis实例;
cluster-config-file :注意:虽然此配置的名字叫“集群配置文件”,但是此配置文件不能人工编辑,它是集群节点自动维护的文件,主要用于记录集群中有哪些节点、他们的状态以及一些持久化参数等,方便在重启时恢复这些状态.通常是在收到请求之后这个文件就会被更新.
cluster-node-timeout : 这是集群中的节点能够失联的最大时间,超过这个时间,该节点就会被认为故障.如果主节点超过这个时间还是不可达,则用它的从节点将启动故障迁移,升级成主节点.注意,任何一个节点在这个时间之内如果还是没有连上大部分的主节点,则此节点将停止接收任何请求.
cluster-slave-validity-factor : 如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节点.如果设置成正数,则cluster-node-timeout
乘以cluster-slave-validity-factor
得到的时间,是从节点与主节点失联后,此从节点数据有效的最长时间,超过这个时间,从节点不会启动故障迁移.假设cluster-node-timeout=5,cluster-slave-validity-factor=10,则如果从节点跟主节点失联超过50秒,此从节点不能成为主节点.注意,如果此参数配置为非0,将可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作.在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作.
cluster-migration-barrier :主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移.更详细介绍可以看本教程后面关于副本迁移到部分.
cluster-require-full-coverage :在部分key所在的节点不可用时,如果此参数设置为”yes”(默认值),则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读操作.
在/url/local/目录下创建一个redis-cluster目录.
mkdir redis-cluster
然后在redis-cluster目录下,创建出名为redis01,redis02,redis03,redis04,redis05,redis06的6个目录,并将/etc/redis/redis.conf这个配置文件拷贝到这6个目录中.
#切换到redis-cluster目录
cd /usr/local/redis-cluster
#创建6个文件夹
mkdir redis01 redis02 redis03 redis04 redis05 redis06
#切换到/etc/redis/目录
cd /etc/redis/
#复制redis.conf配置文件
cp redis.conf redis-cluster/redis01/
cp redis.conf redis-cluster/redis02/
cp redis.conf redis-cluster/redis03/
cp redis.conf redis-cluster/redis04/
cp redis.conf redis-cluster/redis05/
cp redis.conf redis-cluster/redis06/
1️⃣. redis01/redis.conf内容
port 7001
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes01.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
2️⃣. redis02/redis.conf内容
port 7002
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes02.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
3️⃣. redis03/redis.conf内容
port 7003
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes03.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
4️⃣. redis04/redis.conf内容
port 7004
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes04.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
5️⃣. redis05/redis.conf内容
port 7005
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes05.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
6️⃣. redis06/redis.conf内容
port 7006
cluster-enabled yes
#注意:该文件一定不能同名
cluster-config-file nodes06.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
一个节点一个节点的启动命令:
sudo redis-server /usr/local/redis-cluster/redis01/redis.conf
sudo redis-server /usr/local/redis-cluster/redis02/redis.conf
sudo redis-server /usr/local/redis-cluster/redis03/redis.conf
sudo redis-server /usr/local/redis-cluster/redis04/redis.conf
sudo redis-server /usr/local/redis-cluster/redis05/redis.conf
sudo redis-server /usr/local/redis-cluster/redis06/redis.conf
如果我们一个节点一个节点进行启动,会特别麻烦,所以我们接下来创建一个脚本文件,用这个脚本文件同时启动所有的redis节点.
为了方便每个redis的启动,可以创建启动redis服务的shell脚本.
#切换到该目录
cd /usr/local/redis-cluster/
#创建一个文件
touch start-all-redis.sh
#注意:以下内容是shell脚本的内容.
#!/bin/sh
redis-server /usr/local/redis-cluster/redis01/redis.conf
redis-server /usr/local/redis-cluster/redis02/redis.conf
redis-server /usr/local/redis-cluster/redis03/redis.conf
redis-server /usr/local/redis-cluster/redis04/redis.conf
redis-server /usr/local/redis-cluster/redis05/redis.conf
redis-server /usr/local/redis-cluster/redis06/redis.conf
#更改shell脚本的权限
chmod +x start-all-redis.sh
#切换到redis-cluster目录
cd /usr/local/redis-cluster/
#创建一个脚本文件
touch stop-all-redis.sh
#!/bin/sh
redis-cli -h 127.0.0.1 -p 7001 shutdown
redis-cli -h 127.0.0.1 -p 7002 shutdown
redis-cli -h 127.0.0.1 -p 7003 shutdown
redis-cli -h 127.0.0.1 -p 7004 shutdown
redis-cli -h 127.0.0.1 -p 7005 shutdown
redis-cli -h 127.0.0.1 -p 7006 shutdown
#更改shell脚本的权限
chmod +x stop-all-redis.sh
apt-get install ruby
apt-get install rubygems
gem install redis
Redis 官方提供了redis-trib.rb这个工具,使用下面这个命令即可完成redis集群安装.
find / -name "redis-trib.rb"
发现redis-trib.rb文件在/usr/share/doc/redis-tools/examples/redis-trib.rb
位置上.

#切换目录
cd /usr/share/doc/redis-tools/examples/
#复制redis-trib.rb文件
cp redis-trib.rb /usr/local/redis-cluster/

./redis-trib.rb create --replicas 1 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 127.0.0.1:7006
注意:安装过程中需要输入 yes ,然后出现如下内容,说明安装成功.

./start-all-redis.sh
ps -ef | grep redis
root 61020 1 0 02:14 ? 00:00:01 redis-server 127.0.0.1:7001 [cluster]
root 61024 1 0 02:14 ? 00:00:01 redis-server 127.0.0.1:7002 [cluster]
root 61029 1 0 02:14 ? 00:00:01 redis-server 127.0.0.1:7003 [cluster]
#监听端口
netstat -tnlp | grep redis
tcp 0 0 127.0.0.1:17001 0.0.0.0:* LISTEN 61020/redis-server
tcp 0 0 127.0.0.1:17002 0.0.0.0:* LISTEN 61024/redis-server
tcp 0 0 127.0.0.1:17003 0.0.0.0:* LISTEN 61029/redis-server
tcp 0 0 127.0.0.1:7001 0.0.0.0:* LISTEN 61020/redis-server
tcp 0 0 127.0.0.1:7002 0.0.0.0:* LISTEN 61024/redis-server
tcp 0 0 127.0.0.1:7003 0.0.0.0:* LISTEN 61029/redis-server
比如在第一台机器上连接集群的7003端口所在的节点,在另外一台机器上连接7005节点,连接方式为 redis-cli -h 127.0.0.1 -c -p 7003,加参数 -c 可连接到集群,因为上面 redis.conf 将 bind 改为了ip地址,所以 -h 参数不可以省略.
注意:一定要添加 -c 参数,才能连接到redis集群!!!
说明集群运作正常.
解决办法: 检查端口号是否有错,我一开始创建的redis文件夹,并有把redis.conf下的端口号改为7000,但是测试的时候只能连接6379的端口,明明是修改过了,所以个人猜想,可能redis这个文件名,系统会默认打开6379端口,所以我改名为redis1,端口号改为7001;
解决办法: 杀死改redis进程, 用./redis.conf 配置文件方式登录这个节点,然后执行flushall和cluster reset即可,然后重新执行群集脚本命令: ./redis-trib.rb create --replicas 1 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 127.0.0.1:7006
./redis-trib.rb create --replicas 1 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 127.0.0.1:7006
再创建集群,否则会出这个错:
[ERR] Node 127.0.0.1:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
说明上次创建的集群还在,却不为空,直接redis1/redis-cli -h 127.0.0.1 -p 7002 -c进入连接即可.
如果你要重新创建一个新的集群,就要进去上次的那个集群连接中,使用flushdb,清除数据库,在执行创建.
原因是因为当前的数据不是存储在这个节点上,你需要get key获得该数据,并跳往那个存储数据的真正节点,再执行flushdb命令.
原因是没办法到达存储数据的那个节点,因为你客户端连接时没有指明 -c 参数,所以连接的是单个节点.
要执行这个 redis1/redis-cli -h 127.0.0.1 -p 7002 -c
命令.
要确保使用 ./redis-trib.rb create --replicas 1
命令创建redis集群成功.