Redis持久化及集群架构原理剖析
Posted 猿华
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis持久化及集群架构原理剖析相关的知识,希望对你有一定的参考价值。
Redis持久化及哨兵架构搭建
RDB
save
所谓RDB快照文件,指的是redis会定期或者根据条件生成一个快照文件,可以凭借这个快照文件恢复redis数据;在重启redis服务端的时候会自动加载RDB文件,
redis数据库快照文件保存到dump.rdb文件中;
如何触发?-----可以通过设置 #Sava n秒 k个改动;即可让redis在n秒内根改了k个操作时触发save命令保存快照文件;
当然也可以手动执行save命令保存;每次执行都会覆盖原来的dump文件;变成一个新的快照文件;
save是同步操作,当需要保存的数据量过大时就会阻塞客户端请求,因为redis是基于单线程操作的,为了解决这个问题redis在1.1版本推出异步执行命令bgsave;
bgsave
redis不是单线程的嘛?怎么还能异步执行操作呢?这就需要借助操作系统提供的功能了:写时复制;简单来说就是主线程fork出一个子线程 ,子线程可以共享主线程的内存数据;bgsave 运行之后读取主线程内存数据,并写入dump文件;
两个线程操作互不影响;所以在读数据的时候都可以互相正常运行;但是如果主线程对数据进行写操作的话,就会将数据创建一个副本;bgsave操作这个副本将之写入文件;
两者对比
缺点:
- 数据的持久化会导致一些近期数据丢失;
AOF
Rdb生成的快照文件是一个二进制文件,数据量大之后文件会变得非常庞大;
AOF则是记录所有操作的命令;将这些命令写入文件,这样在重启服务是就可以一次执行文件中的命令即可恢复数据;文件形式如下:
1 *3 # 多少个参数----set zhuge 666
2 $3
3 set
4 $5 #KEY长度
5 zhuge
6 $3 #value长度
7 666
星号代表参数的个数;$表示长度;
如何开启:appendonly yes
,在配置文件中;
执行时机:共有三个选项可以选择:
1 appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
2 appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
3 appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
默认每秒同步一次;
时间久了就会出现一个问题:命令很多,但是许多过程是重复的,我们只在乎最后的结果而已
所以AOF还搭配了一个重写功能,相当与重建二叉树那样,调整文件命令,只取最终结果;
AOF重写
如何设置?
1 # auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就 很快,重写的意义不大
2 # auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
当然除了自动还有手动:通过bgrewirteaof即可实现;通过bg便可知道和bgsave一样通过子进程操作;
- 如何选择AOP和RDB?
可以看到各有各的优点,那么小孩子才做选择,大人当然是都要;redis在4.0后推出混合模式;
混合持久化
见名知意:RDB容易丢数据,但是体积小;那么使用RDB+ AOF不就ok了;
开启配置:前提是开启AOF;
# aof‐use‐rdb‐preamble yes //翻译:使用RDB做前缀;相当于把RDB文件数据放在前面;
实现思路:
某一刻:触发AOF,那么久将此刻之前的数据转为RDB放在AOF副本文件;在此刻新增的就使用AOF追加到文件末尾;在重写完成后覆盖原来的aof文件进行替换;
Redis主从架构
- 什么是主从架构?
redis服务端只有一个的话如果宕机了就没办法了;所以需要搭建集群;主从;保证一个结点挂了之后也能运行
搭建步骤:
单机环境下复制一份新的rdis.conf文件修改端口号
将相关配置修改为如下值:
port 6380 5 pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir /usr/local/redis‐5.0.3/data/6380 # 指定数据存放目录
# 需要注释掉bind 9 # bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通 过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
配置主从复制 12 replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica‐read‐only yes # 配置从节点只读
启动从节点 16 redis‐server redis.conf
连接从节点 19 redis‐cli ‐p 6380
测试在6379实例上写数据,6380实例是否能及时同步新修改数据
工作原理
如果一个master配置了一个slave;当这个slave连接上master之后,会发一个psync命令 给master请求复制数据;master收到命令后会在后台使用命令bgsave生成最新的rdb文件发送给slave,在持久化期间接受的客户端命令会缓存在一个buff中,持久化结束后再将其发送给slave;而slave在此期间一直在接受数据并生成rdb文件,之后加载到内存执行数据恢复;
可是这样有一个问题,那就是每次从结点连接主节点是都是全量复制数据,效率肯定不是很高;所以redis在2.6后推出部分复制,向文件的断点续传一样;这样就可以实现在网络断开一段时间后还能正常接受没同步的数据;
实现思路:主节点维护一个数据下标,标明从结点复制数据的位置;master将数据放到缓冲区,slave从缓冲区获取;其中记录了master的进程id供slave识别,如果id发生变化,也就说明master变了,从而全量复制,
管道及Lua脚本
客户端发送请求给服务端执行命令,只有收到回复之后才进行下一条,这样毋庸置疑耗费大量网络IO开销;那为什么不一次发送多条命令,再一次性返回呢;这就是管道出现的意义;
管道的目的是减少网络IO开销,就算命令执行失败也照常返回,并不是事务的功能;那么想要实现事务的功能该怎样实现呢?
- lua脚本
和管道类似,也是建立连接发送批量命令;到与管道不同的是,lua能实现事务的功能;脚本将会本视为一个整体来执行;是原子操作;
使用方法:
EVAL script numkeys key [key ...] arg [arg ...]
- script : 最后会执行的脚本
- numkeys : key的数量----通过KEYS[1]访问key;
- arg : key对应的值-----通过ARGS[1]访问参数
案例:
127.0.0.1:6379> eval "return KEYS[1],KEYS[2],ARGV[1]+ARGV[2]" 2 k1 k2 300 200
1) "k1"
2) "k2"
3) (integer) 500
解释:return key1 和 key2,对他们的值进行求和;
由此可知,可以在lua脚本中进行计算判断等操作,因此,不要再lua脚本中进行耗时的计算,不然客户端请求会被阻塞;
Redis哨兵高可用架构
由图可见,客户端不再直接访问master,而是首先访问哨兵集群,通过哨兵集群获得主节点master的信息后再访问之;建立访问关系之后,哨兵会实时监控结点集群信息;当master发生变动,哨兵会通过订阅功能发布通知给客户端;
- 哨兵的作用:
哨兵的目的就是为了监控redis-server集群的变动而存在的,当主节点挂了之后触发内部选举机制选举出一个新的主节点供客户端连接使用;那怎么样才能确定主节点挂了呢?当有一半的哨兵都认为master挂了之后才会触发选举;选举结束后通过订阅将消息通知给客户端,客户端监听消息从而动态切换访问;
了解一下即可,真正使用的还是cluster集群
- 缺点:集群不方便水平扩展;如果master节点异 常,则会做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现 一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持 很高的并发,且单个主节点内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的 效率
高可用集群Cluster
- 由图可见,该集群模式可用性极强,对客户端提供服务也多;同时水平扩展也很方便;配置也不复杂,总之优点多多
如何搭建不多赘述;
集群原理分析
redis Cluster将数据划分为16384个槽位;由每个结点占用其中一部分槽位,槽位信息存储在结点数据中;
这样当客户端连接集群是也会获得一份槽位信息,访问数据信息是会获得对应的槽位,根据槽位直接访问目标结点进行连接获取数据;
redis采用crc16算法进行hash得到一个整数值,然后用这个整数值对16384取模得到具体槽位;—HASH_SLOT = CRC16(key) mod 16384
如果向一个结点发送的key的槽位不属于该结点是会重定向到目标结点进行操作;
- 那么集群结点之间是如何进行通信的呢?
redis cluster结点之间采用 gossip进行通信;对于集群的元数据的维护:通常有集中式和gossip两种;
集中式:将元数据信息集中存放在一个地方,这样每个结点都访问这个地方获得结点和集群的元数据;有更新时也会更新这个位置,其他结点也能立马得到通知响应;很多中间件借助zookeper存储数据;
gossip:结点之间互相发送消息;交换元数据信息;
网络抖动:在真实网络环境中,因为网路抖动的缘故,使得某些结点会突然断开又很快重连,这样使得主从切换很频繁;可以通过配置cluster-node-timeout
当结点持续断连timeout之间后才开启选举
-
选举原理:
当slave发现自己的master变为FAIL状态之后;与该结点的其他slave竞争成为master;过程如下:
-
集群脑裂问题:
redis的集群脑裂是指因为网络问题,导致redis master节点跟redis slave节点和sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。此时存在两个不同的master节点,就像一个大脑分裂成了两个。
集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据,那么新的master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。规避方法可以在redis配置里加上参数(这种方法不可能百分百避免数据丢失,参考集群leader选举机制):
min‐replicas‐to‐write 1 //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如 集群总共三个节点可以配置1,加上leader就是2,超过了半数
目的就是让整个集群架构从结点最少同步数量超过一半才开始把多之前的主节点降级;
深入剖析Redis系列: Redis集群模式搭建与原理详解
前言
在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态。Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 Redis 在 分布式 方面的需求。当遇到 单机内存、并发、流量 等瓶颈时,可以采用 Cluster 架构方案达到 负载均衡 的目的。
本文将从 集群方案、数据分布、搭建集群、节点通信、集群伸缩、请求路由、故障转移、集群运维 等几个方面介绍 Redis Cluster。
正文
1. Redis集群方案
Redis Cluster 集群模式通常具有 高可用、可扩展性、分布式、容错 等特性。Redis 分布式方案一般有两种:
1.1 客户端分区方案
客户端 就已经决定数据会被 存储 到哪个 redis 节点或者从哪个 redis 节点 读取数据。其主要思想是采用 哈希算法 将 Redis 数据的 key 进行散列,通过 hash 函数,特定的 key会 映射到特定的 Redis 节点上。
客户端分区方案 的代表为 Redis Sharding,Redis Sharding 是 Redis Cluster 出来之前,业界普遍使用的 Redis 多实例集群 方法。Java 的 Redis 客户端驱动库 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及 结合缓存池 的 ShardedJedisPool。
- 优点
不使用 第三方中间件,分区逻辑 可控,配置 简单,节点之间无关联,容易 线性扩展,灵活性强。
- 缺点
客户端 无法 动态增删 服务节点,客户端需要自行维护 分发逻辑,客户端之间 无连接共享,会造成 连接浪费。
1.2. 代理分区方案
客户端 发送请求到一个 代理组件,代理 解析 客户端 的数据,并将请求转发至正确的节点,最后将结果回复给客户端。
- 优点:简化 客户端 的分布式逻辑,客户端 透明接入,切换成本低,代理的 转发 和 存储 分离。
- 缺点:多了一层 代理层,加重了 架构部署复杂度 和 性能损耗。
代理分区 主流实现的有方案有 Twemproxy 和 Codis。
1.2.1. Twemproxy
Twemproxy 也叫 nutcraker,是 twitter 开源的一个 redis 和 memcache 的 中间代理服务器 程序。Twemproxy 作为 代理,可接受来自多个程序的访问,按照 路由规则,转发给后台的各个 Redis 服务器,再原路返回。Twemproxy 存在 单点故障 问题,需要结合 Lvs 和 Keepalived 做 高可用方案。
- 优点:应用范围广,稳定性较高,中间代理层 高可用。
- 缺点:无法平滑地 水平扩容/缩容,无 可视化管理界面,运维不友好,出现故障,不能 自动转移。
1.2.2. Codis
Codis 是一个 分布式 Redis 解决方案,对于上层应用来说,连接 Codis-Proxy 和直接连接 原生的 Redis-Server 没有的区别。Codis 底层会 处理请求的转发,不停机的进行 数据迁移 等工作。Codis 采用了无状态的 代理层,对于 客户端 来说,一切都是透明的。
- 优点
实现了上层 Proxy 和底层 Redis 的 高可用,数据分片 和 自动平衡,提供 命令行接口 和 RESTful API,提供 监控 和 管理 界面,可以动态 添加 和 删除 Redis 节点。
- 缺点
部署架构 和 配置 复杂,不支持 跨机房 和 多租户,不支持 鉴权管理。
1.3. 查询路由方案
客户端随机地 请求任意一个 Redis 实例,然后由 Redis 将请求 转发 给 正确 的 Redis 节点。Redis Cluster 实现了一种 混合形式 的 查询路由,但并不是 直接 将请求从一个 Redis 节点 转发 到另一个 Redis 节点,而是在 客户端 的帮助下直接 重定向( redirected)到正确的 Redis 节点。
- 优点
无中心节点,数据按照 槽 存储分布在多个 Redis 实例上,可以平滑的进行节点 扩容/缩容,支持 高可用 和 自动故障转移,运维成本低。
- 缺点
严重依赖 Redis-trib 工具,缺乏 监控管理,需要依赖 Smart Client (维护连接,缓存路由表,MultiOp 和 Pipeline 支持)。Failover 节点的 检测过慢,不如 中心节点 ZooKeeper 及时。Gossip 消息具有一定开销。无法根据统计区分 冷热数据。
2. 数据分布
2.1. 数据分布理论
分布式数据库 首先要解决把 整个数据集 按照 分区规则 映射到 多个节点 的问题,即把 数据集 划分到 多个节点 上,每个节点负责 整体数据 的一个 子集。
数据分布通常有 哈希分区 和 顺序分区 两种方式,对比如下:
由于 Redis Cluster 采用 哈希分区规则,这里重点讨论 哈希分区。常见的 哈希分区 规则有几种,下面分别介绍:
2.1.1. 节点取余分区
使用特定的数据,如 Redis 的 键 或 用户 ID,再根据 节点数量 N 使用公式:hash(key)% N 计算出 哈希值,用来决定数据 映射 到哪一个节点上。
image
- 优点
这种方式的突出优点是 简单性,常用于 数据库 的 分库分表规则。一般采用 预分区 的方式,提前根据 数据量 规划好 分区数,比如划分为 512 或 1024 张表,保证可支撑未来一段时间的 数据容量,再根据 负载情况 将 表 迁移到其他 数据库 中。扩容时通常采用 翻倍扩容,避免 数据映射 全部被 打乱,导致 全量迁移 的情况。
- 缺点
当 节点数量 变化时,如 扩容 或 收缩 节点,数据节点 映射关系 需要重新计算,会导致数据的 重新迁移。
2.1.2. 一致性哈希分区
一致性哈希 可以很好的解决 稳定性问题,可以将所有的 存储节点 排列在 收尾相接 的 Hash 环上,每个 key 在计算 Hash 后会 顺时针 找到 临接 的 存储节点 存放。而当有节点 加入 或 退出 时,仅影响该节点在 Hash 环上 顺时针相邻 的 后续节点。
image
- 优点
加入 和 删除 节点只影响 哈希环 中 顺时针方向 的 相邻的节点,对其他节点无影响。
- 缺点
加减节点 会造成 哈希环 中部分数据 无法命中。当使用 少量节点 时,节点变化 将大范围影响 哈希环 中 数据映射,不适合 少量数据节点 的分布式方案。普通 的 一致性哈希分区 在增减节点时需要 增加一倍 或 减去一半 节点才能保证 数据 和 负载的均衡。
注意:因为 一致性哈希分区 的这些缺点,一些分布式系统采用 虚拟槽 对 一致性哈希 进行改进,比如 Dynamo 系统。
2.1.3. 虚拟槽分区
虚拟槽分区 巧妙地使用了 哈希空间,使用 分散度良好 的 哈希函数 把所有数据 映射 到一个 固定范围 的 整数集合 中,整数定义为 槽(slot)。这个范围一般 远远大于 节点数,比如 Redis Cluster 槽范围是 0 ~ 16383。槽 是集群内 数据管理 和 迁移 的 基本单位。采用 大范围槽 的主要目的是为了方便 数据拆分 和 集群扩展。每个节点会负责 一定数量的槽,如图所示:
当前集群有 5 个节点,每个节点平均大约负责 3276 个 槽。由于采用 高质量 的 哈希算法,每个槽所映射的数据通常比较 均匀,将数据平均划分到 5 个节点进行 数据分区。Redis Cluster 就是采用 虚拟槽分区。
- 节点1: 包含 0 到 3276 号哈希槽。
- 节点2:包含 3277 到 6553 号哈希槽。
- 节点3:包含 6554 到 9830 号哈希槽。
- 节点4:包含 9831 到 13107 号哈希槽。
- 节点5:包含 13108 到 16383 号哈希槽。
这种结构很容易 添加 或者 删除 节点。如果 增加 一个节点 6,就需要从节点 1 ~ 5 获得部分 槽 分配到节点 6 上。如果想 移除 节点 1,需要将节点 1 中的 槽 移到节点 2 ~ 5 上,然后将 没有任何槽 的节点 1 从集群中 移除 即可。
由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除 或者 改变 某个节点的 哈希槽的数量 都不会造成 集群不可用 的状态.
2.2. Redis的数据分区
Redis Cluster 采用 虚拟槽分区,所有的 键 根据 哈希函数 映射到 0~16383 整数槽内,计算公式:slot = CRC16(key)& 16383。每个节点负责维护一部分槽以及槽所映射的 键值数据,如图所示:
2.2.1. Redis虚拟槽分区的特点
- 解耦 数据 和 节点 之间的关系,简化了节点 扩容 和 收缩 难度。
- 节点自身 维护槽的 映射关系,不需要 客户端 或者 代理服务 维护 槽分区元数据。
- 支持 节点、槽、键 之间的 映射查询,用于 数据路由、在线伸缩 等场景。
2.3. Redis集群的功能限制
Redis 集群相对 单机 在功能上存在一些限制,需要 开发人员 提前了解,在使用时做好规避。
- key 批量操作 支持有限。
类似 mset、mget 操作,目前只支持对具有相同 slot 值的 key 执行 批量操作。对于 映射为不同 slot 值的 key 由于执行 mget、mget 等操作可能存在于多个节点上,因此不被支持。
- key 事务操作 支持有限。
只支持 多 key 在 同一节点上 的 事务操作,当多个 key 分布在 不同 的节点上时 无法 使用事务功能。
- key 作为 数据分区 的最小粒度
不能将一个 大的键值 对象如 hash、list 等映射到 不同的节点。
- 不支持 多数据库空间
单机 下的 Redis 可以支持 16 个数据库(db0 ~ db15),集群模式 下只能使用 一个 数据库空间,即 db0。
- 复制结构 只支持一层
从节点 只能复制 主节点,不支持 嵌套树状复制 结构。
3. Redis集群搭建
Redis-Cluster 是 Redis 官方的一个 高可用 解决方案,Cluster 中的 Redis 共有 2^14(16384) 个 slot 槽。创建 Cluster 后,槽 会 平均分配 到每个 Redis 节点上。
下面介绍一下本机启动 6 个 Redis 的 集群服务,并使用 redis-trib.rb 创建 3主3从 的 集群。搭建集群工作需要以下三个步骤:
3.1. 准备节点
Redis 集群一般由 多个节点 组成,节点数量至少为 6 个,才能保证组成 完整高可用 的集群。每个节点需要 开启配置 cluster-enabled yes,让 Redis 运行在 集群模式 下。
Redis 集群的节点规划如下:
注意:建议为集群内 所有节点 统一目录,一般划分三个目录:conf、data、log,分别存放 配置、数据 和 日志 相关文件。把 6 个节点配置统一放在 conf 目录下。
3.1.1. 创建redis各实例目录
$ sudo mkdir -p /usr/local/redis-cluster
$ cd /usr/local/redis-cluster
$ sudo mkdir conf data log
$ sudo mkdir -p data/redis-6379 data/redis-6389 data/redis-6380 data/redis-6390 data/redis-6381 data/redis-6391
3.1.2. redis配置文件管理
根据以下 模板 配置各个实例的 redis.conf,以下只是搭建集群需要的 基本配置,可能需要根据实际情况做修改。
# redis后台运行
daemonize yes
# 绑定的主机端口
bind 127.0.0.1
# 数据存放目录
dir /usr/local/redis-cluster/data/redis-6379
# 进程文件
pidfile /var/run/redis-cluster/${自定义}.pid
# 日志文件
logfile /usr/local/redis-cluster/log/${自定义}.log
# 端口号
port 6379
# 开启集群模式,把注释#去掉
cluster-enabled yes
# 集群的配置,配置文件首次启动自动生成
cluster-config-file /usr/local/redis-cluster/conf/${自定义}.conf
# 请求超时,设置10秒
cluster-node-timeout 10000
# aof日志开启,有需要就开启,它会每次写操作都记录一条日志
appendonly yes
- redis-6379.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6379
pidfile /var/run/redis-cluster/redis-6379.pid
logfile /usr/local/redis-cluster/log/redis-6379.log
port 6379
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6379.conf
cluster-node-timeout 10000
appendonly yes
- redis-6389.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6389
pidfile /var/run/redis-cluster/redis-6389.pid
logfile /usr/local/redis-cluster/log/redis-6389.log
port 6389
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6389.conf
cluster-node-timeout 10000
appendonly yes
- redis-6380.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6380
pidfile /var/run/redis-cluster/redis-6380.pid
logfile /usr/local/redis-cluster/log/redis-6380.log
port 6380
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6380.conf
cluster-node-timeout 10000
appendonly yes
- redis-6390.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6390
pidfile /var/run/redis-cluster/redis-6390.pid
logfile /usr/local/redis-cluster/log/redis-6390.log
port 6390
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6390.conf
cluster-node-timeout 10000
appendonly yes
- redis-6381.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6381
pidfile /var/run/redis-cluster/redis-6381.pid
logfile /usr/local/redis-cluster/log/redis-6381.log
port 6381
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6381.conf
cluster-node-timeout 10000
appendonly yes
- redis-6391.conf
daemonize yes
bind 127.0.0.1
dir /usr/local/redis-cluster/data/redis-6391
pidfile /var/run/redis-cluster/redis-6391.pid
logfile /usr/local/redis-cluster/log/redis-6391.log
port 6391
cluster-enabled yes
cluster-config-file /usr/local/redis-cluster/conf/node-6391.conf
cluster-node-timeout 10000
appendonly yes
3.2. 环境准备
3.2.1. 安装Ruby环境
$ sudo brew install ruby
3.2.2. 准备rubygem redis依赖
$ sudo gem install redis
Password:
Fetching: redis-4.0.2.gem (100%)
Successfully installed redis-4.0.2
Parsing documentation for redis-4.0.2
Installing ri documentation for redis-4.0.2
Done installing documentation for redis after 1 seconds
1 gem installed
3.2.3. 拷贝redis-trib.rb到集群根目录
redis-trib.rb 是 redis 官方推出的管理 redis 集群 的工具,集成在 redis 的源码 src 目录下,将基于 redis 提供的 集群命令 封装成 简单、便捷、实用 的 操作工具。
$ sudo cp /usr/local/redis-4.0.11/src/redis-trib.rb /usr/local/redis-cluster
查看 redis-trib.rb 命令环境是否正确,输出如下:
$ ./redis-trib.rb
Usage: redis-trib <command> <options> <arguments ...>
create host1:port1 ... hostN:portN
--replicas <arg>
check host:port
info host:port
fix host:port
--timeout <arg>
reshard host:port
--from <arg>
--to <arg>
--slots <arg>
--yes
--timeout <arg>
--pipeline <arg>
rebalance host:port
--weight <arg>
--auto-weights
--use-empty-masters
--timeout <arg>
--simulate
--pipeline <arg>
--threshold <arg>
add-node new_host:new_port existing_host:existing_port
--slave
--master-id <arg>
del-node host:port node_id
set-timeout host:port milliseconds
call host:port command arg arg .. arg
import host:port
--from <arg>
--copy
--replace
help (show this help)
For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
redis-trib.rb 是 redis 作者用 ruby 完成的。redis-trib.rb 命令行工具 的具体功能如下:
3.3. 安装集群
3.3.1. 启动redis服务节点
运行如下命令启动 6 台 redis 节点:
sudo redis-server conf/redis-6379.conf
sudo redis-server conf/redis-6389.conf
sudo redis-server conf/redis-6380.conf
sudo redis-server conf/redis-6390.conf
sudo redis-server conf/redis-6381.conf
sudo redis-server conf/redis-6391.conf
启动完成后,redis 以集群模式启动,查看各个 redis 节点的进程状态:
$ ps -ef | grep redis-server
0 1908 1 0 4:59下午 ?? 0:00.01 redis-server *:6379 [cluster]
0 1911 1 0 4:59下午 ?? 0:00.01 redis-server *:6389 [cluster]
0 1914 1 0 4:59下午 ?? 0:00.01 redis-server *:6380 [cluster]
0 1917 1 0 4:59下午 ?? 0:00.01 redis-server *:6390 [cluster]
0 1920 1 0 4:59下午 ?? 0:00.01 redis-server *:6381 [cluster]
0 1923 1 0 4:59下午 ?? 0:00.01 redis-server *:6391 [cluster]
在每个 redis 节点的 redis.conf 文件中,我们都配置了 cluster-config-file 的文件路径,集群启动时,conf 目录会新生成 集群 节点配置文件。查看文件列表如下:
$ tree -L 3 .
.
├── appendonly.aof
├── conf
│ ├── node-6379.conf
│ ├── node-6380.conf
│ ├── node-6381.conf
│ ├── node-6389.conf
│ ├── node-6390.conf
│ ├── node-6391.conf
│ ├── redis-6379.conf
│ ├── redis-6380.conf
│ ├── redis-6381.conf
│ ├── redis-6389.conf
│ ├── redis-6390.conf
│ └── redis-6391.conf
├── data
│ ├── redis-6379
│ ├── redis-6380
│ ├── redis-6381
│ ├── redis-6389
│ ├── redis-6390
│ └── redis-6391
├── log
│ ├── redis-6379.log
│ ├── redis-6380.log
│ ├── redis-6381.log
│ ├── redis-6389.log
│ ├── redis-6390.log
│ └── redis-6391.log
└── redis-trib.rb
9 directories, 20 files
3.3.2. redis-trib关联集群节点
按照 从主到从 的方式 从左到右 依次排列 6 个 redis 节点。
$ sudo ./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381 127.0.0.1:6389 127.0.0.1:6390 127.0.0.1:6391
集群创建后,redis-trib 会先将 16384 个 哈希槽 分配到 3 个 主节点,即 redis-6379,redis-6380 和 redis-6381。然后将各个 从节点 指向 主节点,进行 数据同步。
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:6379
127.0.0.1:6380
127.0.0.1:6381
Adding replica 127.0.0.1:6390 to 127.0.0.1:6379
Adding replica 127.0.0.1:6391 to 127.0.0.1:6380
Adding replica 127.0.0.1:6389 to 127.0.0.1:6381
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: ad4b9ffceba062492ed67ab336657426f55874b7 127.0.0.1:6379
slots:0-5460 (5461 slots) master
M: df23c6cad0654ba83f0422e352a81ecee822702e 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
M: ab9da92d37125f24fe60f1f33688b4f8644612ee 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
S: 25cfa11a2b4666021da5380ff332b80dbda97208 127.0.0.1:6389
replicates ad4b9ffceba062492ed67ab336657426f55874b7
S: 48e0a4b539867e01c66172415d94d748933be173 127.0.0.1:6390
replicates df23c6cad0654ba83f0422e352a81ecee822702e
S: d881142a8307f89ba51835734b27cb309a0fe855 127.0.0.1:6391
replicates ab9da92d37125f24fe60f1f33688b4f8644612ee
然后输入 yes,redis-trib.rb 开始执行 节点握手 和 槽分配 操作,输出如下:
Can I set the above configuration? (type ‘yes‘ to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join....
>>> Performing Cluster Check (using node 127.0.0.1:6379)
M: ad4b9ffceba062492ed67ab336657426f55874b7 127.0.0.1:6379
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: ab9da92d37125f24fe60f1f33688b4f8644612ee 127.0.0.1:6381
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 48e0a4b539867e01c66172415d94d748933be173 127.0.0.1:6390
slots: (0 slots) slave
replicates df23c6cad0654ba83f0422e352a81ecee822702e
S: d881142a8307f89ba51835734b27cb309a0fe855 127.0.0.1:6391
slots: (0 slots) slave
replicates ab9da92d37125f24fe60f1f33688b4f8644612ee
M: df23c6cad0654ba83f0422e352a81ecee822702e 127.0.0.1:6380
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: 25cfa11a2b4666021da5380ff332b80dbda97208 127.0.0.1:6389
slots: (0 slots) slave
replicates ad4b9ffceba062492ed67ab336657426f55874b7
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
执行 集群检查,检查各个 redis 节点占用的 哈希槽(slot)的个数以及 slot 覆盖率。16384 个槽位中,主节点 redis-6379、redis-6380 和 redis-6381 分别占用了 5461、5461 和 5462 个槽位。
3.3.3. redis主节点的日志
可以发现,通过 BGSAVE 命令,从节点 redis-6389 在 后台 异步地从 主节点 redis-6379 同步数据。
$ cat log/redis-6379.log
1907:C 05 Sep 16:59:52.960 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1907:C 05 Sep 16:59:52.961 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=1907, just started
1907:C 05 Sep 16:59:52.961 # Configuration loaded
1908:M 05 Sep 16:59:52.964 * Increased maximum number of open files to 10032 (it was originally set to 256).
1908:M 05 Sep 16:59:52.965 * No cluster configuration found, I‘m ad4b9ffceba062492ed67ab336657426f55874b7
1908:M 05 Sep 16:59:52.967 * Running mode=cluster, port=6379.
1908:M 05 Sep 16:59:52.967 # Server initialized
1908:M 05 Sep 16:59:52.967 * Ready to accept connections
1908:M 05 Sep 17:01:17.782 # configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH
1908:M 05 Sep 17:01:17.812 # IP address for this node updated to 127.0.0.1
1908:M 05 Sep 17:01:22.740 # Cluster state changed: ok
1908:M 05 Sep 17:01:23.681 * Slave 127.0.0.1:6389 asks for synchronization
1908:M 05 Sep 17:01:23.681 * Partial resynchronization not accepted: Replication ID mismatch (Slave asked for ‘4c5afe96cac51cde56039f96383ea7217ef2af41‘, my replication IDs are ‘037b661bf48c80c577d1fa937ba55367a3692921‘ and ‘0000000000000000000000000000000000000000‘)
1908:M 05 Sep 17:01:23.681 * Starting BGSAVE for SYNC with target: disk
1908:M 05 Sep 17:01:23.682 * Background saving started by pid 1952
1952:C 05 Sep 17:01:23.683 * DB saved on disk
1908:M 05 Sep 17:01:23.749 * Background saving terminated with success
1908:M 05 Sep 17:01:23.752 * Synchronization with slave 127.0.0.1:6389 succeeded
3.3.4. redis集群完整性检测
使用 redis-trib.rb check 命令检测之前创建的 两个集群 是否成功,check 命令只需要给出集群中 任意一个节点地址 就可以完成 整个集群 的 检查工作,命令如下:
$ ./redis-trib.rb check 127.0.0.1:6379
当最后输出如下信息,提示集群 所有的槽 都已分配到节点:
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
小结
本文介绍了 Redis 集群解决方案,数据分布 和 集群搭建。集群方案包括 客户端分区 方案,代理分区 方案 和 查询路由 方案。数据分布 部分简单地对 节点取余 分区,一致性哈希 分区以及 虚拟槽 分区进行了阐述和对比。最后对使用 Redis-trib 搭建了一个 三主三从 的 虚拟槽集群示例。
以上是关于Redis持久化及集群架构原理剖析的主要内容,如果未能解决你的问题,请参考以下文章