惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
InfoQ
D
DataBreaches.Net
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI
Martin Fowler
Martin Fowler
Security Latest
Security Latest
Cisco Talos Blog
Cisco Talos Blog
MongoDB | Blog
MongoDB | Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
IT之家
IT之家
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园_首页
L
LINUX DO - 最新话题
Know Your Adversary
Know Your Adversary
S
Schneier on Security
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
T
Tenable Blog
G
GRAHAM CLULEY
Y
Y Combinator Blog
P
Palo Alto Networks Blog
L
LINUX DO - 热门话题
Hugging Face - Blog
Hugging Face - Blog
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
The Register - Security
The Register - Security
T
The Exploit Database - CXSecurity.com
MyScale Blog
MyScale Blog
M
MIT News - Artificial intelligence
Cyberwarzone
Cyberwarzone
雷峰网
雷峰网
T
Tailwind CSS Blog
V2EX - 技术
V2EX - 技术
T
Threat Research - Cisco Blogs
S
Secure Thoughts
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
O
OpenAI News
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
量子位
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
S
SegmentFault 最新的问题
小众软件
小众软件
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角 .NET Core 原生 DI 扩展之基于名称的注入实现 - 元视角
分布式丛林探险系列之 Redis 集群模式 - 元视角
飞鸿踏雪 · 2021-12-01 · via 元视角

时间终于来到了十二月,据说,《黑客帝国 4:矩阵重生》 将于本月在北美上映,正如同它的片名一样,黑客帝国系列在沉寂了十八年后,终于等来了一次矩阵重生的机会,不可不谓“有生之年”、“爷青回”。提及黑客帝国系列,这是一部公认的、具有划时代意义的科幻电影,除了精彩绝伦的打斗特效,最为影迷所津津乐道的,当属对于人和机器的关系这种颇具哲学意味的问题的探讨。在第二部中,The One 的部分代码被融合到了 Smith 身上,而这使得 Smith 发生变异,成为了可以自我复制的病毒。于是,我们在这里看到了 Neo 和 100 个 Smith 打斗的桥段,类似的桥段还有第三部里的雨中决斗。这些桥段或多或少地影响到了后来的电影,譬如,星爷的 《功夫》 里,阿星与斧头帮、火云邪神打斗的片段;吴京的第一部电影 《狼牙》 里,阿布雨夜大战黑衣人的片段等等。虽然,病毒的自我复制和分布式系统中的复制,是两个完全不同的概念,可当我们试图将电影和现实联系起来的时候,我们还是会不免会心一笑,因为 100 个 Smith ,大概就相当于一个 Smith 的集群;而吞噬了先知能力的 Smith ,大概就相当于这个集群中的 Leader。我们注意到,强如超人般的 Neo,一样架不住越来越多的 Smith ,最后不得不飞走,所谓:“双拳难敌四手”,这足以说明集群的重要性。好了,既然这里聊到了集群,那么我们这次来聊聊 Redis 中的集群模式。

Redis 集群概述

通过上一篇文章,我们了解到,主从复制的作用主要体现在数据冗余、故障恢复、负载均衡等方面。可很多时候,我们讲分布式,并不是说简单的复制就好啦!相信大家都听说过,水平扩展和垂直扩展这两个概念,特别是数据库的水平扩展,它天然地和分片(Sharding)联系在一起,这意味是我们希望在不同地数据库/表里存储不同地数据。此前,博主曾在 《浅议 EF Core 分库分表及多租户架构的实现》 一文里介绍过数据库的分库/表,作为类比,我们可以归纳出 Redis 集群模式的第一个特点,即:它本质上是一种服务器 Sharding 技术。因为纯粹的主从复制意味着,每台 Redis 服务器都存储相同的数据,显然这造成了资源的浪费,而让每台 Redis 服务器存储不同的数据,这就是 Redis 的集群模式。如下图所示,Redis 集群模式呈现出的一种网状结构,完全不同于主从复制间的单向流动:

Redis 集群模式示意图 Redis 集群模式示意图

从图中可以看出,6 台服务器组成了一个网状结构,任意两台服务器间都可以相互通信。也许,大家会好奇一个问题,为什么这里博主就画了 6 台服务器?其实,这一切都是有迹可循的,因为 Redis 官方规定:一个集群中至少需要有 3 个主服务器(Master)。所以,一个 Redis 集群至少需要 6 台服务器。如果从这个角度来审视集群的定义的话,你可以认为 Redis 集群就是由多个主从复制一起对外提供服务。此时,集群中的节点都通过 TCP 连接和一个被称为 Cluster Bus 的二进制协议来建立通信,这里的 Cluster Bus 你可以将其理解为 Kafka 或者 RabbitMQ 这样的支持“发布-订阅”(Pub-Sub)机制的东西,换句话说,集群中的每个节点都可以通过 Cluster Bus 与集群中的其它节点连接起来。节点们使用一种叫做 Gossip 的消息协议,据说,这是一种从瘟疫和社交网站上获得灵感消息传播方式。“六度分割”理论告诉我们,最多通过 6 个人你就能认识任何一个陌生人,同样地,最多通过 6 个节点你就可以把消息传递给任何一个节点。

Redis 集群模式与主从模式的联系 Redis 集群模式与主从模式的联系

目前,使用 Gossip 这一协议的项目有 Redis Cluster、ConsulApache Cassandra 等等, 关于这个协议以及“六度分割”理论更进一步的细节,大家可以通过搜索引擎来获取,对我们而言,我们只需要知道,Redis 集群需要借助这个协议来实现诸如发现新节点、发送 PING 包、发送集群消息这些功能,因为每个节点除了存储自身的数据以外,还需要记录集群的状态,需要知道哪些节点不可用,需要合适的时机推选出主节点。我们在主从复制这一篇文章中提到过故障恢复,这个概念在这里同样适用,如图所示,S1 是 M1 的从节点,它本身并不是为了扩展请求的并发量而存在的,它需要在主节点宕机的情况下能被提拔为主节点,所以,它主要起一个数据备份的作用。因此,Redis 集群中的读和写,实际上都是在M1、M2 和 M3 上进行的。那么,我们不妨来想这样一个问题,如果 M1 和 S1 都宕机了,此时 Redis 集群还能不能正常工作呢?答案是否定的,因为此时显然不满足最少 3 个主节点的要求。

一致性哈希算法

OK,我们知道,在面对数据库的水平拆分问题时,一个最为关键的问题是路由,换句话说,我怎么样可以找到到对应的库或者表。常见的思路有范围、哈希和配置等等,而在 Redis 的集群模式下,我们同样会面临这个问题,更一般地,任何需要负载均衡的场景都需要考虑这个问题,譬如,我们对多台服务器的 IP 地址求余,然后按照余数来进行路由,抑或者是按照不同的权重进行随机等等,这看起来像回到了熟悉的负载均衡算法,对吗?事实上,一般的负载均衡算法都会选择哈希算法,而哈希算法本质上就是一种散列函数,它可以把任意长度的输入转化为确定的、固定长度的输出,以最常见的求余为例,它具体是怎么工作的呢?我们来一起看下面的图:

经典哈希算法 经典哈希算法

可以注意到,首先,我们需要对 Key 计算出一个 hash 值,你可以理解为编程语言里的GetHashCode()方法,主要目的是将字符串类型的 Key 转化为整数型方便计算。接下来,按照主节点的数量来进行求余运算(统称为散列运算),例如,这里我们有 3 台主节点服务器,故而在上面的图示中 n 应该等于 3,而这就是经典的哈希算法啦!那么,Redis 的集群有没有采用这个算法呢?答案还是否定的,因为这个方案里最大的变数就是 n。具体来讲,如果你想要扩容,那么 n 会变大,此时 Key 与服务器间的映射关系会被打破,即使不扩容,一旦某台服务器宕机,Key 与服务器间的映射关系还是会被打破,如果出现散列“碰撞”,无疑会让问题变得更复杂。

经典哈希算法“碰撞”问题 经典哈希算法“碰撞”问题

可以注意到,当从对 3 取模变成对 2 取模以后,必然会出现两个 1,这就是所谓的散列“碰撞”。所以,对于经典的哈希算法而言,它无法抵消因为 n 的变化而带来的重新 hash 的问题。为了解决这个问题,业界普遍使用的是一致性哈希算法,相比经典的哈希算法,最大的变化在于它将 n 的取值固定下来,即按 2^32 取模,此时,结果会落在 0 到 2^32 - 1 这个区间内,一旦我们将这个区间抽象为一个圆环,利用 CRC16 算法计算出来的值就会落在圆环上的某个地方:

改良后的哈希一致性算法 改良后的哈希一致性算法

如图所示,假设我们有 A、B、C 三个 Redis 节点,它们均按照顺时针方向排列在整个圆环上,当我们对每一个 Redis 节点进行 hash() 运算以后按 2^32 取模,此时,这个值依然会落在 0 到 2^32 - 1 这个区间内,以此类推,理论上对于任意一台服务器 X,我们总能找到对应的一个值,当该值介于区间(m, n)中,表示它分配在节点 n 上,你可以认为,每个服务器节点,它负责的并不是哈希环上的一个点,而是一个范围。基于这种特性,当某一台 Redis 服务器宕机时,对应的这个区间会变大,相当于流量从这个节点转移到了它的下一个节点,这意味着我们只需要移动一部分数据。同理,如果要增加节点,只需要把移动该节点到它上一个节点间的数据。由此可见,它对原来数据的影响非常小,而这就是一致性哈希算法。

Redis 哈希槽

截止到现在,我们知道了,一致性哈希算法相对经典哈希算法的优势,可这个算法真的没有问题吗?我们应该会想到,节点在哈希环上的分布是不均匀的,这意味着,每个节点上对应的 Key 的数量各不相同,更进一步,我们可以说,每台 Redis 上存储的数据量不同,这样,原本期待着负载均衡的我们,此刻被光速地打脸。因此,在实际应用中,为了让哈希环分布更均匀一点,会设置所谓的“虚拟节点”。不过,就算你考虑到了这种地步,Redis 还是没有采用这种哈希一致性的方案,你说这是不是有点气人?事实上,它采用的是一种被称为哈希槽的方案:

Redis 哈希槽示意图 Redis 哈希槽示意图

如图所示,Redis 集群中总共有 16384 个槽位,它是怎么算出来的呢?因为 CRC16 算法产生的 hash 值有 16 个 bit,因此,它可以产生 2^16 即 65536 个值,理论上我们完全可以分配 65536 个槽位,不过考虑到网络带宽、节点数目,作者觉得 16384 个槽位完全够用了,前提是你集群内的节点不超过 1000 个!是不是听起来觉得非常离谱?可有些时候,工程上的事情,还真不能按科学一板一眼的来,不然,你又怎么会见到各种各样的“敏捷开发”、”项目管理”呢?

Redis 哈希槽槽位分配 Redis 哈希槽槽位分配

OK,对于 Redis 中的每一个 Key,它经过计算以后会落到某个具体的槽位内,至于槽位会路由到哪个机器上,这完全取决于每台 Redis 服务器的配置,硬盘大就多分一点,硬盘小就少分一点,“能者多劳”,听起来就很科学,对吧!所以,从这个角度来看,相比哈希环,哈希槽可以更加灵活地控制槽位的分布。可世间万物,无一不是双刃剑,你选择了自由,那么需要操心的事情就变多了,至少 Redis 集群不会帮你转移和分配槽位;你选择了秩序,一切井井有条,那么你内心又会期盼自由。“小而美”,想要面面俱到;“大而全”,想要细致入微,难怪刺客组织要和圣殿骑士间的斗争永无止息啊,哈哈……因此,Redis 集群的高可用,实际上非常依赖主从节点的主从复制和故障切换,我承认,我点题了,你发现了吗?

Redis 集群实战

好了,和上一篇一样,我们先礼后兵,等所有的理论知识都讲完了,再来着眼实际的过程,因为在博主看来,这是一个互相印证的过程,写流水账、记下每一步的操作步骤,这固然可以让你快速上手,但你真的只愿意到此为止吗?博主以前学过 CAD,近来又开始学习 Blender,一个深刻的感受就是,这些软件都需要大量的重复练习,甚至于形成肌肉记忆。可是我们学习知识的过程,是一个反复提炼、内化的过程,我们并不是为了成为某种“熟练工”,我发现有很多朋友,特别喜欢问某软件/工具怎么安装/配置类似的问题,虽然说这是技术人员日常生活里的一部分,但我觉得这不应该成为你特别关注的问题,所以,如果你发现我越来越不像是在写教程,恭喜你,发现了我的改变,谢谢你啊!回到正题,我们还是准备一个最小的集群,即“三主三从”的一个集群,依然使用 docker-compose 进行服务编排。首先,准备下面的目录结构:

Clusters
|-- docker-compose.yaml
|-- redis-1
|   |-- redis.conf
|-- redis-2
|   |-- redis.conf
|-- redis-3
|   |-- redis.conf
|-- redis-4
|   |-- redis.conf
|-- redis-5
|   |-- redis.conf
|-- redis-6
|   |-- redis.conf

此时,针对集群模式的 Redis 配置文件,相比主从复制模式要精简许多,以其中一台服务器7001为例:

# 端口号
port 7001
# 启用集群
cluster-enabled yes
cluster-config-file nodes_7001.conf
cluster-node-timeout 5000
appendonly yes

照葫芦画瓢,我们准备好剩下的70027003700470057006 这 5台服务器即可。接下来,我们准备服务编排文件docker-compose.yaml:

version: '3.1'
services:
  redis1:
    image: redis:latest
    container_name: redis-1
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-1/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  redis2:
    image: redis:latest
    container_name: redis-2
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-2/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  redis3:
    image: redis:latest
    container_name: redis-3
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-3/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  redis4:
    image: redis:latest
    container_name: redis-4
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-4/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  redis5:
    image: redis:latest
    container_name: redis-5
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-5/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
  redis6:
    image: redis:latest
    container_name: redis-6
    restart: always
    network_mode: "host"
    volumes:
      - ./redis-6/redis.conf:/usr/local/etc/redis/redis.conf
    command: ["redis-server", "/usr/local/etc/redis/redis.conf"]

接下来,我们运行 docker-compose up命令,就可以看到 6 台 Redis 服务器都运行起来,此时,我们需要选择任意一台 Redis 服务器对应的容器内,执行下列命令:

redis-cli --cluster create 
  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 \
  --cluster-replicas 1

该命令表示创建一个由 6 台服务器组成的集群,其中,--cluster-replicas 参数表示每个主节点分配一个从节点。此时此刻,当我们按下回车后,就可以看到下面的画面:

通过 redis-cli 创建 Redis 集群 通过 redis-cli 创建 Redis 集群

可以注意到,当前集群有 3 台主服务器节点:700170027003,有 3 台从服务器节点:700470057006,这符合我们一开始设想的“三主三从”,Redis 内部会为每个节点分配一个唯一的 Id,这个 Id 会和每个节点的 IP 地址、端口号一起保存下来。当我们没有显式地分配每台服务器对应多少个槽位时,它会按照主节点的数量平均地分割。在这里我们只有 3 台主节点服务器,无法被 16384 整除,所以,7002 这个服务器会多分配一个槽位。显然,Redis 需要我们来确定这个集群划分,此时,我们输入yes即可,这样我们就完成了 Redis 集群的搭建。接下来,我们通过 redis-cli 写入信息来看看集群实际的运行效果:

# 进入 Redis 容器
docker exec -it df5cae3c6004 sh
# 连接 7001 服务器
redis-cli -p 7001 -c
# 写入一个 key
127.0.0.1:7001> set name yuanpei

我们会发现一个现象,就是 Redis 会告诉你这里有一次重定向,如图所示,它从 7001 重定向到了 7002,这是因为按照 CRC16 算法计算出的结果,name 这个键应该被分配到 7002 上存储:

通过 redis-cli 向 Redis 集群写入值 通过 redis-cli 向 Redis 集群写入值

同理,在我们已经知道 name 这个键存储在 7002上的情况下,我们依然可以从集群中的其它节点上读取到这个值:

通过 redis-cli 向 Redis 集群读出值 通过 redis-cli 向 Redis 集群读出值

可以注意到,集群依然可以自动地从 7004 重定向到 7002,这表明,我们可以从集群中的任何一个节点上查询数据,并且每个节点上都存储着不同的数据,这可不就是数据分片吗?这恰恰印证了我一开始的观点,即:Redis 的集群模式,本质上是一种服务器 Sharding 技术。果然,博主诚不欺人也,哈哈!

Redis 集群投票示意图 Redis 集群投票示意图

到目前为止,我们已经搭建出了一个基本的 Redis 集群,你可能会问,如果集群中某个节点发生宕机该怎么办呢?此时,我们要注意区分,这是主观宕机还是客观宕机。什么是主观宕机呢?就是某个节点认为你挂了;什么是客观宕机呢?就是集群内超过半数的节点认为你挂了。事实上,只有客观宕机会触发故障转移,而所谓的故障转移,其实就是从故障节点的从节点中挑选一个出来继续提供服务,此时,需要有超过半数以上的主节点给这个从节点投票,这样,Redis 集群就会让它当选为新的主节点,谁能想到,在以绝对理性著称的技术世界里,居然会有如此民主而科学的做法呢?可少数服从多数,真的就是对的吗?

本文小结

坦白讲,这篇文章写起来非常费劲,因为它里面关联的知识点非常密集,从经典哈希算法到一致性哈希算法,再到 Redis 集群里的哈希槽,这是一个循序渐进的认知过程,我们由此认识到,Redis 的集群模式,本质上是由多个主从复制模式组成的服务器分片技术,它通过哈希槽来管理集群内的节点和数据,而节点间则是通过 TCP 协议互相通信,这使得我们可以从任意节点上查询数据,因为 Redis 集群会帮助我们实现连接的重定向。与此同时,Redis 集群内通过投票来实现故障转移,其触发机制是超过半数的节点认为该节点宕机,即客观宕机,此时,就需要从该节点的从节点中筛选一个继续提供服务,当然,这个筛选还是投票决定的,谁能说这不是一种现实世界的投影呢?好了,以上就是这篇博客的全部内容啦,关于 Gossip 协议等更细节的东西,留到以后有机会再写吧…..!