Redis分布式集群

前言

Redis3.0以后的版本支持了Cluster 那么我们首先会想到Cluster是解决怎样的应用场景

为了应对大流量访问下提供稳定的业务,集群化是存储的必然形态 之前的单点存储势必会有诸多隐患

而未来的发展趋势肯定是大数据和云计算的紧密集合 分布式架构也就可以很好的体现他的优势

分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率

解决方案

目前的Redis的集群化方案有三种

  • Twitter开发的twemproxy
  • 豌豆荚开发的codis
  • redis官方的redis-cluster

redis-cluster是三个里性能最强大的 因为他使用去中心化的思想 使用hash slot方式 将16348hash slot 覆盖到所有节点上

对于存储的每个key值 使用CRC16(KEY)&16348=slot 得到他对应的hash slot 并在访问key时就去找他的hash slot在哪一个节点上

然后由当前访问节点从实际被分配了这个hash slot的节点去取数据 节点之间使用轻量协议通信 减少带宽占用 性能很高

自动实现负载均衡与高可用 自动实现failover 并且支持动态扩展 官方已经玩到可以1000个节点 实现的复杂度低 因为他的去中心化思想免去了proxy的消耗 是全新的思路

基本介绍

Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施installation

Redis 集群通过分区partition来提供一定程度的可用性availability: 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

Redis集群提供了以下两个好处:

  • 将数据自动切分split到多个节点的能力。
  • 当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。

集群分区

常见的分区规则有哈希分区和顺序分区,redis集群使用了哈希分区。RedisCluster采用了哈希分区的’虚拟槽分区’方式(哈希分区节点取余,一致性哈希分区和虚拟槽分区)

RedisCluster采用嘘嘘你槽分区,所有的键根据哈希函数(CRC16[key]&16383)映射到0-16383槽内,一共16384个槽位。每个节点维护部分及槽所映射的键值数据

哈希函数: Hash() = CRC16[key]&16383 按位与

其中槽与节点的关系如下:

1

2

环境安装

本地安装Redis服务 我因为是mac环境 所以直接可以通过homebrew安装

1
$ brew install redis

为了搭建 目录这里新建了6个节点目录
3

redis安装包 执行bin目录分别copy到每个节点目录

可以看到redis bin目录下的一些redis执行命令文件

5

集群搭建

将本地redisredis-trib.rb复制到redis-cluster目录
6

复制redis的配置文件到每个节点 注意本机的conf文件的地址为 /usr/local/etc/redis.conf
7

完毕之后 集群文件的目录是这样的:
8

接下来就是针对每个节点的配置了 这里我们进入节点目录下的配置文件redis.conf

这里我们需要编辑的配置信息如下:

1
2
3
4
5
6
7
8
port 7001 //节点端口
daemonize yes //配置redis作为守护进程运行,默认情况下,redis不是作为守护进程运行的
bind 127.0.0.1 //默认127.0.0.1,需要改为其他节点可访问的地址
cluster-node-timeout 5000 //集群超时时间
cluster-enabled yes //redis 集群
cluster-config-file nodes-7001.conf //指定节点配置信息
appendonly yes //存储方式
dir /Users/gehuachun/Develop/redis-cluster/redis01 //指定本地数据库路径

节点启动

每个节点配置完毕之后开始依次启动每个节点 这里假设启动是06这个节点
9

启动完毕之后查看redis的服务我们可以看到
10

集群创建

节点启动完毕之后开始创建集群 集群文件目录终端执行

1
$ 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

其中–replices 1指定了为每个节点分配一个从节点

11

安装过程如果遇到插件报错 可以尝试:
12

那么在创建的过程中你将会看到如下信息:
13

最终集群创建成功时 你会看到
15

注意看后面的返回信息 这里指示了急群众主从节点的关系信息 比如:
16

可以看到70057001这个节点的从节点 因为这在创建集群的6个节点所生成的主从关系 当然你也可以单独指定主从节点

那么创建集群完毕之后再去查看集群节点目录 你会发现多了日志存储文件
17

集群连接

既然集群创建爱好了 我们开始尝试连接 这里我们先从7001节点开始并查看集群的信息

1
2
$ redis-cli -c -h 127.0.0.1 -p 7001
$ cluster nodes //查看集群信息

14

连接上7001这个节点 可以测试下与其他节点的通信 即与节点握手
18

就像之前说的 我们是可以手动建立主从关系的 我们再查看节点信息时 看好节点的id
19

槽的重新分配

集群创建完后 我们可以对所给我们的hash进行重新分配 比如:
20

21

all 表示节点的槽位全部重新洗牌

分配后的槽 可以看到7003这个节点多了500个槽 也就是 0-248 5461-5711 这之间的500
22

集群是为了处理一块业务 比如数据的存储 不过是通过不同的机器

如果没有重新分配槽 那么我这里新起了集群 最终的槽点的分布式这样的 注意主节点分配的信息
23

集群正常启动后,在每个redis.conf里加上

1
2
masterauth “12345678”
requiredpass “12345678”

当主节点下线时,从节点会变成主节点,用户和密码是很有必要的,设置成一致

集群节点之间的通信

1.节点之间采用Gossip协议进行通信,Gossip协议就是指节点彼此之间不断通信交换信息

当主从角色变化或新增节点,彼此通过ping/pong进行通信知道全部节点的最新状态并达到集群同步

  1. Gossip协议

Gossip协议的主要职责就是信息交换,信息交换的载体就是节点之间彼此发送的Gossip消息,常用的Gossip消息有ping消息、pong消息、meet消息、fail消息
24

meet消息:用于通知新节点加入,消息发送者通知接收者加入到当前集群,meet消息通信完后,接收节点会加入到集群中,并进行周期性ping pong交换

ping消息:集群内交换最频繁的消息,集群内每个节点每秒向其它节点发ping消息,用于检测节点是在在线和状态信息,ping消息发送封装自身节点和其他节点的状态数据;

pong消息,当接收到ping meet消息时,作为响应消息返回给发送方,用来确认正常通信,pong消息也封闭了自身状态数据;

fail消息:当节点判定集群内的另一节点下线时,会向集群内广播一个fail消息,

集群扩容

这也是分布式存储最常见的需求,当我们存储不够用时,要考虑扩容

启动新节点 这里启动70077008以备用
25

这里新增了两目录redis07redis08 复制之前的节点配置文件 需要修改的就是portdir数据存储的路径这两个参数

集群新增节点
26

./redis-trib.rb add-node 命令中 7007是新增的主节点 7001则是集群中已存在的节点

添加完毕后再查看下集群的节点信息
27

发现多了7007这个新的主节点

添加从节点 现在将7008添加作为7007的从节点 我们需要7007nodeid 可以通过clutser nodes查看

可以看到7c22cf36b86bc9efcd5d4cd8cab300462090b087 这个是7007的节点id --slave表示添加从节点
7008为需要加入到集群的从节点 7001为集群中已经存在的节点

现在可以再去查看集群各节点信息
28

OK 添加完毕

但是还没有结束 新添加的7007节点并没有为他分配槽 所以他还算不上主节点

和上面提过的一样 为7007这个节点重新分配节点 7007的节点id7c22cf36b86bc9efcd5d4cd8cab300462090b087

1
2
3
4
redis-trib.rb reshard 127.0.0.7   //为新主节点重新分配solt
How many slots do you want to move (from 1 to 16384)? 1000 //设置slot数1000
What is the receiving node ID? 7c22cf36b86bc9efcd5d4cd8cab300462090b087 //新节点node id
Source node #1:all //表示全部节点重新洗牌

新增完毕 再次查看集群信息
29

删除节点

删除已经占有hash槽的结点会失败 所以删除前需要把槽位分配出去即可

指定节点的id即可 比如这里删除7008

1
$ redis-trib.rb del-node 127.0.0.1:7008 2113ab21277cb952d77d269fe69f8a852000d949

30

查看删除后的集群信息
31

此时的节点也算是下线完成

故障处理

redis集群实现了高可用,当集群内少量节点出现故障时,通过故障转移可以保证集群正常对外提供服务。当集群里某个节点出现了问题,redis集群内的节点通过ping pong消息发现节点是否健康,是否有故障,其实主要环节也包括了 主观下线和客观下线;

主观下线:指某个节点认为另一个节点不可用,即下线状态,当然这个状态不是最终的故障判定,只能代表这个节点自身的意见,也有可能存在误判
32

下线流程:

  1. 节点a发送ping消息给节点b ,如果通信正常将接收到pong消息,节点a更新最近一次与节点b的通信时间;

2.如果节点a与节点b通信出现问题则断开连接,下次会进行重连,如果一直通信失败,则它们的最后通信时间将无法更新;

3.节点a内的定时任务检测到与节点b最后通信时间超过cluster_note-timeout时,更新本地对节点b的状态为主观下线(pfail)

客观下线:指真正的下线,集群内多个节点都认为该节点不可用,达成共识,将它下线,如果下线的节点为主节点,还要对它进行故障转移

假如节点a标记节点b为主观下线,一段时间后节点a通过消息把节点b的状态发到其它节点,当节点c接受到消息并解析出消息体时,会发现节点bpfail状态时,会触发客观下线流程

当下线为主节点时,此时redis集群为统计持有槽的主节点投票数是否达到一半,当下线报告统计数大于一半时,被标记为客观下线状态。
33

故障恢复

故障主节点下线后,如果下线节点的是主节点,则需要在它的从节点中选一个替换它,保证集群的高可用;转移过程如下:
34

1.资格检查:检查该从节点是否有资格替换故障主节点,如果此从节点与主节点断开过通信,那么当前从节点不具体故障转移;

2.准备选举时间:当从节点符合故障转移资格后,更新触发故障选举时间,只有到达该时间后才能执行后续流程;

3.发起选举:当到达故障选举时间时,进行选举;

4.选举投票:只有持有槽的主节点才有票,会处理故障选举消息,投票过程其实是一个领导者选举(选举从节点为领导者)的过程,每个主节点只能投一张票给从节点,当从节点收集到足够的选票(大于N/2+1)后,触发替换主节点操作,撤销原故障主节点的槽,委派给自己,并广播自己的委派消息,通知集群内所有节点。

RedisCluster 缺陷

1.键的批量操作支持有限,比如msetmget,如果多个键映射再不同的槽,就不支持了

2.键的事务支持有限,当多个key分布在不同的节点时无法使用事务,同一个节点时支持事务的

3.键是数据分区的最小粒度,不能讲一个很大的键值映射到不同的节点

4.不支持多数据库,只有0,select 0

5.复制结构只支持单层结构,不支持树状结构