【转】Redis 分布式——可用性保证之 Sentinel(实战篇)
前言
在上个篇章我们阐述了Sentinel的原理,可能大家还是云里雾里,需要来点实战性的东西,那这个篇章我们来个实战篇吧~话不多说,我们开始今天的吹牛皮~
正文
Sentinel 实战
Sentinel 配置
为了保证 Sentinel 的高可用,Sentinel 也需要做集群部署,集群中至少需要三个 Sentinel 实例(推荐奇数个,防止脑裂)。
hostname | IP 地址 | 节点角色&端口 |
---|---|---|
master | 192.168.8.203 | Master:6379 / Sentinel : 26379 |
slave1 | 192.168.8.204 | Slave :6379 / Sentinel : 26379 |
Slave2 | 192.168.8.205 | Slave :6379 / Sentinel : 26379 |
以 Redis 安装路径/usr/local/soft/redis-5.0.5/为例。
在 204 和 205 的 src/redis.conf 配置文件中添加
slaveof 192.168.8.203 6379
在 203、204、205 创建 sentinel 配置文件(安装后根目录下默认有 sentinel.conf):
cd /usr/local/soft/redis-5.0.5
mkdir logs
mkdir rdbs
mkdir sentinel-tmp
vim sentinel.conf
三台服务器内容相同:
daemonize yes
port 26379
protected-mode no
dir "/usr/local/soft/redis-5.0.5/sentinel-tmp"
sentinel monitor redis-master 192.168.8.203 6379 2
sentinel down-after-milliseconds redis-master 30000
sentinel failover-timeout redis-master 180000
sentinel parallel-syncs redis-master 1
上面出现了 4 个’redis-master’,这个名称要统一,并且使用客户端(比如 Jedis)连接的时候名称要正确。
hostname | IP 地址 |
---|---|
protected-mode | 是否允许外部网络访问 |
dir | sentinel 的工作目录 |
sentinel monitor | sentinel 监控的 redis 主节点 |
down-after-milliseconds(毫秒) | master 宕机多久,才会被 Sentinel 主观认为下线 |
sentinel failover-timeout(毫秒) |
1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间。 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到slave 被纠正为向正确的 master 那里同步数据时。 3. 当想要取消一个正在进行的 failover 所需要的时间。 4. 当进行 failover 时,配置所有 slaves 指向新的 master 所需的最大时间。 |
parallel-syncs | 这个配置项指定了在发生 failover 主备切换时最多可以有多少个 slave 同时对新的 master 进行 同步,这个数字越小,完成 failover 所需的时间就越长,但是如果这个数字越大,就意味着越多的 slave 因为 replication 而不可用。可以通过将这个值设为 1 来保证每次只有一个 slave 处于不能处理命令请求的状态。 |
Sentinel 验证
启动 Redis 服务和 Sentinel
cd /usr/local/soft/redis-5.0.5/src#启动 Redis 节点./redis-server ../redis.conf#启动 Sentinel 节点./redis-sentinel ../sentinel.conf#或者./redis-server ../sentinel.conf --sentinel查看集群状态:redis> info replication
203
204 和 205
模拟 master 宕机,在 203 执行:
redis> shutdown
205 被选为新的 Master,只有一个 Slave 节点。
注意看 sentinel.conf 里面的 redis-master 被修改了!
模拟原 master 恢复,在 203 启动 redis-server。它还是 slave,但是 master 又有两个 slave 了。
slave 宕机和恢复省略。
Sentinel 连接使用
Jedis 连接 Sentinel(ck-jedis:JedisSentinelTest.java)
master name 来自于 sentinel.conf 的配置。
private static JedisSentinelPool createJedisPool() {String masterName = "redis-master";Set<String> sentinels = new HashSet<String>();sentinels.add("192.168.8.203:26379");sentinels.add("192.168.8.204:26379");sentinels.add("192.168.8.205:26379");pool = new JedisSentinelPool(masterName, sentinels);return pool;
}
Spring Boot 连接 Sentinel(springboot-redis:RedisAppTest.java)
spring.redis.sentinel.master=redis-master
spring.redis.sentinel.nodes=192.168.8.203:26379,192.168.8.204:26379,192.168.8.205:26379
无论是 Jedis 还是 Spring Boot(2.x 版本默认是 Lettuce),都只需要配置全部哨兵的地址,由哨兵返回当前的 master 节点地址。
哨兵机制的不足
主从切换的过程中会丢失数据,因为只有一个 master。
只能单点写,没有解决水平扩容的问题。
如果数据量非常大,这个时候我们需要多个 master-slave 的 group,把数据分布到不同的 group 中。
- 问题来了,数据怎么分片?分片之后,怎么实现路由?
Redis 分布式方案
如果要实现 Redis 数据的分片,我们有三种方案:
- 在客户端实现相关的逻辑,例如用取模或者一致性哈希对 key 进行分片,查询和修改都先判断 key 的路由。
- 把做分片处理的逻辑抽取出来,运行一个独立的代理服务,客户端连接到这个代理服务,代理服务做请求的转发。
- 基于服务端实现。
客户端 Sharding
Jedis 客户端提供了 Redis Sharding 的方案,并且支持连接池。
ShardedJedis
public class ShardingTest {public static void main(String[] args) {JedisPoolConfig poolConfig = new JedisPoolConfig();// Redis 服务器JedisShardInfo shardInfo1 = new JedisShardInfo("127.0.0.1", 6379); JedisShardInfo shardInfo2 = new JedisShardInfo("192.168.8.205", 6379);//连接池List<JedisShardInfo> infoList = Arrays.asList(shardInfo1, shardInfo2); ShardedJedisPool jedisPool = new ShardedJedisPool(poolConfig, infoList);ShardedJedis jedis = null;try{jedis = jedisPool.getResource();for(int i=0; i<100; i++){jedis.set("k"+i, ""+i);}for(int i=0; i<100; i++){System.out.println(jedis.get("k"+i));}}finally{if(jedis!=null) {jedis.close();}}}
}
- Sharded 分片的原理?怎么连接到某一个 Redis 服务?
使用 ShardedJedis 之类的客户端分片代码的优势是配置简单,不依赖于其他中间件,分区的逻辑可以自定义,比较灵活。但是基于客户端的方案,不能实现动态的服务增减,每个客户端需要自行维护分片策略,存在重复代码。
第二种思路就是把分片的代码抽取出来,做成一个公共服务,所有的客户端都连接到这个代理层。由代理层来实现请求和转发。
代理 Proxy
典型的代理分区方案有 Twitter 开源的 Twemproxy 和国内的豌豆荚开源的 Codis。
Twemproxy
two-em-proxy
点击跳转two-em-proxy GitHub地址
Twemproxy 的优点:比较稳定,可用性高。
不足:
- 出现故障不能自动转移,架构复杂,需要借助其他组件(LVS/HAProxy + Keepalived)实现 HA
- 扩缩容需要修改配置,不能实现平滑地扩缩容(需要重新分布数据)。
Codis
点击跳转Codis GitHub地址
Codis 是一个代理中间件,用 Go 语言开发的。
功能:客户端连接 Codis 跟连接 Redis 没有区别。
proxy | Codis | Tewmproxy | Redis Cluster |
---|---|---|---|
重新分片不需要重启 | Yes | No | Yes |
pipeline | Yes | Yes | |
多 key 操作的 hash tags {} | Yes | Yes | Yes |
重新分片时的多 key 操作 | Yes | - | No |
客户端支持 | 所有 | 所有 | 支持 cluster 协议的客户端 |
分片原理:
Codis 把所有的 key 分成了 N 个槽(例如 1024),每个槽对应一个分组,一个分组对应于一个或者一组 Redis 实例。
Codis 对 key 进行 CRC32 运算,得到一个 32 位的数字,然后模以 N(槽的个数),得到余数,这个就是 key 对应的槽,槽后面就是 Redis 的实例。
比如 4 个槽:
Codis 的槽位映射关系是保存在 Proxy 中的,如果要解决单点的问题,Codis 也要做集群部署,多个 Codis 节点怎么同步槽和实例的关系呢?需要运行一个 Zookeeper(或者 etcd/本地文件)。
在新增节点的时候,可以为节点指定特定的槽位。Codis 也提供了自动均衡策略。
Codis 不支持事务,其他的一些命令也不支持。
不支持的命令
获取数据原理(mget):在 Redis 中的各个实例里获取到符合的 key,然后再汇总到 Codis 中。
Codis 是第三方提供的分布式解决方案,在官方的集群功能稳定之前,Codis 也得到了大量的应用。
Redis Cluster
Redis Cluster介绍
Redis Cluster 是在 Redis 3.0 的版本正式推出的,用来解决分布式的需求,同时也可以实现高可用。跟 Codis 不一样,它是去中心化的,客户端可以连接到任意一个可用节点。
数据分片有几个关键的问题需要解决:
- 数据怎么相对均匀地分片
- 客户端怎么访问到相应的节点和数据
- 重新分片的过程,怎么保证正常服务
架构
Redis Cluster 可以看成是由多个 Redis 实例组成的数据集合。
客户端不需要关注数据的子集到底存储在哪个节点,只需要关注这个集合整体。
以 3 主 3 从为例,节点之间两两交互,共享数据分片、节点状态等信息。
搭建
点击跳转redis三主三从搭建教程
- 配置
- 启动
- 进入客户端:
redis-cli -p 7291
redis-cli -p 7292
redis-cli -p 7293
批量插入数据
类型 | 命令 |
---|---|
集群 |
cluster info :打印集群的信息 cluster nodes :列出集群当前已知的所有节点(node),以及这些节点的相关信息。 |
节点 |
cluster meet :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。 cluster forget <node_id> :从集群中移除 node_id 指定的节点(保证空槽道)。 cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点。 cluster saveconfig :将节点的配置文件保存到硬盘里面。 |
槽(slot) |
cluster addslots [slot …] :将一个或多个槽(slot)指派(assign)给当前节点。 cluster delslots [slot …] :移除一个或多个槽对当前节点的指派。 cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。 cluster setslot node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。 cluster setslot migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。 cluster setslot importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。 cluster setslot stable :取消对槽 slot 的导入(import)或者迁移(migrate)。 |
键 |
cluster keyslot :计算键 key 应该被放置在哪个槽上。 cluster countkeysinslot :返回槽 slot 目前包含的键值对数量。 cluster getkeysinslot :返回 count 个 slot 槽中的键 |
- 问题:Cluster 解决分片的问题,数据怎么分布?
数据分布
如果是希望数据分布相对均匀的话,我们首先可以考虑哈希后取模。
哈希后取模
例如,hash(key)%N,根据余数,决定映射到那一个节点。
这种方式比较简单,属于静态的分片规则。但是一旦节点数量变化,新增或者减少,由于取模的 N 发生变化,数据需要重新分布。
为了解决这个问题,我们又有了一致性哈希算法。
一致性哈希
一致性哈希的原理:
把所有的哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织。因为是环形空间,0 和 2^32-1 是重叠的。
假设我们有四台机器要哈希环来实现映射(分布数据),我们先根据机器的名称或者 IP 计算哈希值,然后分布到哈希环中(红色圆圈)。
现在有 4 条数据或者 4 个访问请求,对 key 计算后,得到哈希环中的位置(绿色圆圈)。沿哈希环顺时针找到的第一个 Node,就是数据存储的节点。
在这种情况下,新增了一个 Node5 节点,不影响数据的分布。
删除了一个节点 Node4,只影响相邻的一个节点。
谷歌的 MurmurHash 就是一致性哈希算法。
在分布式系统中,负载均衡、分库分表等场景中都有应用。
一致性哈希解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没有影响。
但是这样的一致性哈希算法有一个缺点,因为节点不一定是均匀地分布的,特别是在节点数比较少的情况下,所以数据不能得到均匀分布。解决这个问题的办法是引入虚拟节点(Virtual Node)。
比如:2 个节点,5 条数据,只有 1 条分布到 Node2,4 条分布到 Node1,不均匀。
Node1 设置了两个虚拟节点,Node2 也设置了两个虚拟节点(虚线圆圈)。
这时候有 3 条数据分布到 Node1,1 条数据分布到 Node2。
Redis 虚拟槽分区
Redis 既没有用哈希取模,也没有用一致性哈希,而是用虚拟槽来实现的。
Redis 创建了 16384 个槽(slot),每个节点负责一定区间的 slot。比如 Node1 负责 0-5460,Node2 负责 5461-10922,Node3 负责 10923-16383。
Redis 的每个 master 节点维护一个 16384 位(2048bytes=2KB)的位序列,比如:序列的第 0 位是 1,就代表第一个 slot 是它负责;序列的第 1 位是 0,代表第二个 slot 不归它负责。
对象分布到 Redis 节点上时,对 key 用 CRC16 算法计算再%16384,得到一个 slot 的值,数据落到负责这个 slot 的 Redis 节点上。
查看 key 属于哪个 slot:
redis> cluster keyslot qingshan
注意:key 与 slot 的关系是永远不会变的,会变的只有 slot 和 Redis 节点的关系。
问题:
- 怎么让相关的数据落到同一个节点上?
- 比如有些 multi key 操作是不能跨节点的,如果要让某些数据分布到一个节点上,例如用户 2673 的基本信息和金融信息,怎么办?
在key 里面加入{hash tag}即可。Redis 在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽。
user{2673}base=…
user{2673}fin=…
127.0.0.1:7293> set a{qs}a 1
OK
127.0.0.1:7293> set a{qs}b 1
OK
127.0.0.1:7293> set a{qs}c 1
OK
127.0.0.1:7293> set a{qs}d 1
OK
127.0.0.1:7293> set a{qs}e 1
OK
- 问题:客户端连接到哪一台服务器?访问的数据不在当前节点上,怎么办?
客户端重定向
比如在 7291 端口的 Redis 的 redis-cli 客户端操作:
127.0.0.1:7291> set qs 1
(error) MOVED 13724 127.0.0.1:7293
服务端返回 MOVED,也就是根据 key 计算出来的 slot 不归 7191 端口管理,而是归 7293 端口管理,服务端返回 MOVED 告诉客户端去 7293 端口操作。
这个时候更换端口,用 redis-cli –p 7293 操作,才会返回 OK。或者用./redis-cli -c -p port 的命令(c 代表 cluster)。这样客户端需要连接两次。
Jedis 等客户端会在本地维护一份 slot——node 的映射关系,大部分时候不需要重定向,所以叫做 smart jedis(需要客户端支持)。
问题:新增或下线了 Master 节点,数据怎么迁移(重新分配)?
数据迁移
因为 key 和 slot 的关系是永远不会变的,当新增了节点的时候,需要把原有的 slot 分配给新的节点负责,并且把相关的数据迁移过来。
添加新节点(新增一个 7297):
redis-cli --cluster add-node 127.0.0.1:7291 127.0.0.1:7297
新增的节点没有哈希槽,不能分布数据,在原来的任意一个节点上执行:
redis-cli --cluster reshard 127.0.0.1:7291
输入需要分配的哈希槽的数量(比如 500),和哈希槽的来源节点(可以输入 all 或者 id)。
问题:只有主节点可以写,一个主节点挂了,从节点怎么变成主节点?
高可用和主从切换原理
当 slave 发现自己的 master 变为 FAIL 状态时,便尝试进行 Failover,以期成为新的 master。由于挂掉的 master 可能会有多个 slave,从而存在多个 slave 竞争成为 master 节点的过程, 其过程如下:
- slave 发现自己的 master 变为 FAIL
- 将自己记录的集群 currentEpoch 加 1,并广播 FAILOVER_AUTH_REQUEST 信息
- 其他节点收到该信息,只有 master 响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个 epoch 只发送一次 ack
- 尝试 failover 的 slave 收集 FAILOVER_AUTH_ACK
- 超过半数后变成新 Master
- 广播 Pong 通知其他集群节点。
Redis Cluster 既能够实现主从的角色分配,又能够实现主从切换,相当于集成了 Replication 和 Sentinal 的功能。
总结:
优势
- 无中心架构。
- 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
- 可扩展性,可线性扩展到 1000 个节点(官方推荐不超过 1000 个),节点可动态添加或删除。
- 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做 standby 数据副本,能够实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave 到 Master 的角色提升。
- 降低运维成本,提高系统的扩展性和可用性。
不足
- Client 实现复杂,驱动要求实现 Smart Client,缓存 slots mapping 信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。
- 节点会因为某些原因发生阻塞(阻塞时间大于 clutser-node-timeout),被判断下线,这种 failover 是没有必要的。
- 数据通过异步复制,不保证数据的强一致性。
- 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
By the way
有问题?可以给我留言或私聊
有收获?那就顺手点个赞呗~
想找工作机会也可以联系我噢~
当然,也可以到我的公众号下「6曦轩」,
回复“学习”,即可领取一份
【Java工程师进阶架构师的视频教程】~
回复“面试”,可以获得:
【本人呕心沥血整理的 Java 面试题】
回复“MySQL脑图”,可以获得
【MySQL 知识点梳理高清脑图】
还有【阿里云】【腾讯云】的购买优惠噢~具体请联系我
曦轩我是科班出身的程序员,php,Android以及硬件方面都做过,不过最后还是选择专注于做 Java,所以有啥问题可以到公众号提问讨论(技术情感倾诉都可以哈哈哈),看到的话会尽快回复,希望可以跟大家共同学习进步,关于服务端架构,Java 核心知识解析,职业生涯,面试总结等文章会不定期坚持推送输出,欢迎大家关注~~~
【转】Redis 分布式——可用性保证之 Sentinel(实战篇)相关推荐
- redis分布式锁及秒杀系统实战
本文分为两部分: 一.介绍redis分布式锁的原理和使用方法: 二.使用redis分布式锁实现一个简单的秒杀系统. 注意:本文使用java1.8,最后的例子为springboot项目. 目录 redi ...
- Redis分布式锁—SETNX+Lua脚本实现篇
前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...
- Redis中的可用性保证之Sentinel 原理
如何实现主从的自动切换?我们的思路: 创建一台监控服务器来监控所有Redis 服务节点的状态,比如,master 节点超过一定时间没有给监控服务器发送心跳报文,就把master 标记为下线,然后把某一 ...
- Redis中的可用性保证之Sentinel的Ratf 算法
在分布式存储系统中,通常通过维护多个副本来提高系统的可用性,那么多个节点之间必须要面对数据一致性的问题.Raft 的目的就是通过复制的方式,使所有节点达成一致,但是这么多节点,以哪个节点的数据为准呢? ...
- Redis中的可用性保证之Sentinel服务下线
Sentinel 默认以每秒钟1 次的频率向Redis 服务节点发送PING 命令.如果在down-after-milliseconds 内都没有收到有效回复,Sentinel 会将该服务器标记为下线 ...
- Redis中的可用性保证之Sentinel故障转移
如果master 被标记为下线,就会开始故障转移流程. 既然有这么多的Sentinel 节点,由谁来做故障转移的事情呢? 故障转移流程的第一步就是在Sentinel 集群选择一个Leader,由Lea ...
- Redis分布式锁相关总结
文章目录 分布式锁 Expire命令 为什么要设置过期时间 过期时间的精度与持久 Redission看门狗机制 redis淘汰过期key Redis过期KEY的删除策略: 模式: 导航 session ...
- Redis分布式锁 Spring Schedule实现任务调度
一看到标题就知道,这一篇博客又是总结分布式工作环境中集群产生的问题,个人觉得分布式没有那么难以理解,可能也是自己见识比较浅,对我来说,分布式只是一种后端业务演进时的一种工作方式,而真正实现这种工作方式 ...
- 集群部署中解决定时任务重复执行的问题-redis分布式锁应用
背景描述 有小伙伴私信我,关于存在定时任务的项目在集群环境下部署如何解决重复执行的问题,PS:定时任务没有单独拆分. 概述:之前的项目都是单机器部署,所以定时任务不会重复消费,只会执行一次.而在集群环 ...
最新文章
- MATLAB双纵坐标绘图(重要)
- android动态32位so,Android:动态库(.so)调试技巧
- android 清除所有计时器,android-如何从onTick方法中清除CountDownTimer?
- 关于php调用.net的web service 踩过的坑
- UltraEdit打开就报错,文件找不到
- jdbc连接带密码的access数据库
- 华为手机隐藏代码大全!一秒查出你的手机使用记录
- IT资产管理流程及规范
- 用python画生日蛋糕-祝你生日快乐
- Linux内核编译依赖
- pandas中使用fillna函数填充NaN值
- 北京工商大学计算机考研818真题,北京工商大学考研真题汇总
- python期末大作业 邮箱小程序
- 人工生命全景图:如何创造出超越人工智能的生命系统
- 齐二TK6916/20/26/32系列数控落地铣镗床简介2
- java fuoco2_音乐术语《piu mosso con fuoco》是什么意思
- 庄家出货前兆5种情况(风险必看)【转】
- 这本C语言入门书被翻译成五种语言出版,如今迎来了中文版第17版
- influxdb基础(三)——influxdb按时间分片存储数据(shard和shard group)
- 半小时拿下Python数据处理之Matplotlib篇
热门文章
- 计算机万金油专业,为什么说自动化专业是万金油专业
- 如何配置java环境变量?
- GCC 编译器警告——【-Wunused-variable】【-Wunused-parameter】
- thinkphp6下载汉字转拼音包以及其使用方法
- 一路走来的飞控设计研发之路
- BZOJ3827[Poi2014] Around the world
- 软通python编程题_软通动力笔试(带参考答案)
- RabbitMQ原理名词解释
- DR钻戒能给“真爱”定价,但难救股价
- Ubuntu新旧版本下载地址