Redis核心技术与实战【学习笔记】 - 4.Redis 切片集群

前言

如果 Redis 要存 5000 万个键值对,每个键值对大约 512B,那么该如何选择主键的内存容量呢?

粗略估算下,所有键值对需要内存空间大约是 25GB。我们很自然的想到第一个方案,选择一台 32GB 的内存的主机来部署 Redis。

因为32GB 的内存可以保存所有数据,且还有7GB 预留,足以保证系统正常运行。同时,还可使用 RDB 持久化数据,确保 Redis 实例故障后,还能从 RDB 回复数据。

但是,在使用 Redis 进行持久化时, Redis 会 fork 子进程来完成,而 fork 操作的耗时和 Redis 的数据量成正比。所以在使用 RDB 对 25GB 进行持久化时,由于数据量较大,fork 创建子进程时阻塞的主进程,导致 Redis 的响应有时会非常慢。

显然,第一个方案不可行,我们须寻找其他的方案。 Redis 的切片集群方案是一个靠谱的方案。虽然切片集群比较麻烦,但是它可以保存大量数据,而且对 Redis 主线程的阻塞影响小。

切片集群也叫分片集群,是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据分成多份,每一份用一个实例来保存。

如上面的例子,可以把 25GB 的数据平均分成 5 份,这样每个实例只需要保存 5GB 的数据。
在这里插入图片描述


如何保存更多数据?

前言总,我们提到了使用大内存主机和切片集群方案,它们对应着 Redis 应对数据增多的两种方案:

  • 横向扩展:升级单个 Redis 实例的资源配置,包括加内存、加磁盘、使用高配 CPU。
  • 纵向扩展:横向增加当前 Redis 实例的个数。

首先横向扩展的好处是实施简单、直接,不过有两个潜在问题:

  1. 第一个问题,当使用 Redis 对数据进行持久化时,如果数据量增加,需要的内存也会增加,主线程 fork 子进程时可能会阻塞。不过,如果不需要持久化 Redis 数据,那么,纵向扩展会是一个不错的选择。
  2. 第二个问题,纵向扩展会受到硬件和成本的限制。这很好理解,把内存从 32GB 扩展到 64GB 还算容易,但是要扩充到 1TB,就会面临硬件容量和成本上的限制了。

与纵向扩展相比,横向扩展是一个扩展性更好的方案,因为想要保存更多的数据,只用增加 Redis 实例的个数就行了。在面向百万、千万级别的用户时,横向扩展的 Redis 切片集群是一个非常好的选择

切片集群不可避免的涉及多个实例的分布式管理问题:

  • 数据切片后,在多个实例之间如何分布?
  • 客户端怎么确定想要访问的数据在哪个实例上?

数据切片及实例的对应分布关系

切配集群是一种保存大量数据的通用机制,从 Redis 3.0 开始,官方提供 Redis Cluster 方案,用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。

Redis Cluster 方案采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,这些哈希槽就类似数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。

映射过程分为两大步:

  1. 首先根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值;
  2. 然后再将这个 16 bit 的值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个响应编号的哈希槽。

这些哈希槽是如何被映射到具体的 Redis 实例上呢?我们在部署 Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时 Redis 会 自动把这些槽平均分布到集群实例上。例如有 N 个 Redis 实例,那么,每个实例上的哈希槽数为 16384/N 个。

当日,我们也可以使用 cluster meet 命令手动创建实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数。

例如集群中不同 Redis 实例的内存大小不一样,如果均分哈希槽到各个实例上,内存小的实例就会有更大的容量压力。遇到这种情况,你可以根据不同的实例的资源配置清理,使用 cluster addslots 命令手动分配哈希槽。

画一张示意图来解释一下,数据、哈希槽、实例这三者的映射分布情况
在这里插入图片描述

图中的切片集群有3个实例,同时设有 5 个哈希槽,,首先通过下面的命令手动分配哈希槽:

  • 实例 1 保存哈希槽 0 和 1;
  • 实例 2 保存哈希槽 2 和 3;
  • 实例 3 保存哈希槽 4;
redis-cli -h 172.16.19.3 -p 6379 cluster addslots  0,1
redis-cli -h 172.16.19.4 -p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 -p 6379 cluster addslots 4

在集群运行过程中,key1 和 key2 计算完 CRC16 值后,对哈希槽个数 5 取模,再根据各自的模数结果,就可以被映射到实例 1 和实例 3 上了。

另外,一定要注意!!!在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作

现在我们知道,切片集群实现了数据到哈希槽、哈希槽再到实例的分配。但是,即使实例有了哈希槽的映射信息,客户端又是怎么知道要访问的数据在哪个实例上呢?

客户端如何定位数据?

一般来说,客户端和机器实例建立连接后,实例会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。

客户端为什么可以访问任何一个实例时,能获得所有的哈希槽信息的?

这是因为,Redis 实例会把自己的哈希槽信息发送给和他相连接的其他实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射信息了。

客户端收到哈希槽信息后,会把哈希槽缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给响应的实例发送请求。

还有一个要注意的问题:在集群中,实例和哈希槽的对应关系不是一成不变的,最常见的的变化有两个:

  • 在集群中,实例有新增或删除的, Redis 需要重新分配哈希槽信息。
  • 为了负载均衡,Redis 需要把哈希槽在所有实例上重新分配一遍。

此时,实例之间可以相互传递消息,获得最新的哈希槽分配信息,但是客户端无法感知这些变化。这就导致哈希槽的分配信息和最新的信息不一致,这该怎么办?

Redis Cluster 方案提供了一种重定向机制。当客户端把一个键值对的操作请求发送给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么这个实例就会给客户端返回 MOVED 命令响应结果,这个结果中就包含了新势力的访问地址。

Get hello:key
(error) MOVED 13320 172.16.19.5:6379

其中,MOVED 命令表示,客户端请求的键值对所在的哈希槽 13320 是在 172.16.19.5 实例上。这样一来,客户端就可以直接和 172.16.19.5 连接,并发送操作请求了。

举例说明下整个过程:

  1. 由于负载均衡, Slot 2 中的数据已经从实例 2 迁移到了实例 3
  2. 但是客户端仍然记录着“Slot 2 在实例 2”的信息,所以客户端会给实例 2 发送命令
  3. 实例 2 给客户端返回一条 MOVED 命令,把 Slot 2 的最新位置(即实例 3),返回给客户端
  4. 客户端就会再次向实例 3 发送请求,同时还会更新本地缓存,把 Slot 2 与实例的对应关系更新过来
    在这里插入图片描述
    上述过程客户端给实例 2 发送命令时,Slot 2 中的数据已经全部迁移到了实例 3。在实际应用时,还可能存在一种情况:客户端向实例 2 发送请求,但此时, Slot 2 中的数据只有一部分迁移到了实例 3,还有部分数据没有迁移。在这种迁移部分完成的情况下,客户端会收到一条 ASK 报错信息,如下所示:
Get hello:key
(error) ASK 13320 172.16.19.5:6379
  1. 这个结果中的 ASK 命令就表示,客户端请求的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,且哈希槽上正在迁移。
  2. 此时,客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令,让这个实例允许执行客户端接下来发送的命令。
  3. 然后,客户端再向这个实例发送 GET 命令,以读取数据。

借助一个例子来解释下
在这里插入图片描述
Slot 正在从实例 2 往实例 3 迁移,key1 和 key2 已经迁移过去,key3 和 key4 还在实例 2。此时,客户端向实例 2 请求 key2 后,就会收到实例 2 返回的 ASK 命令。
ASK 命令表示两层含义:

  1. 表名 Slot 数据还在迁移中;
  2. ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后在再发送操作命令。

和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息。所以,在上图中,如果客户端再次请求 Slot 2 中的数据,它还是会给实例 2 发送请求。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/437739.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

江科大stm32学习笔记6——GPIO输入准备

一、按键消抖 由于按键内部使用的是机械式弹簧片,所以在按下和松开时会产生5~10ms的抖动,需要通过代码来进行消抖。 二、滤波电容 在电路中,如果见到一端接在电路中,一端接地的电容,则可以考虑它的作用为滤波电容&am…

2023年:个人年度成长与团队协作成就

文章目录 个人职业发展的喜悦团队成就的辉煌公众号CSDN申请了移动安全领域新星创作者获得6月城市之星北京TOP 10获得23年博客之星TOP 41年度总结 知识星球 开拓新领域的决心免费知识大陆付费知识大陆 展望未来福利时间知识星球会员一年知识星球立减88券 在这个充满挑战与机遇的…

【使用New Bing的Dall-e-3绘画(完全免费,无需账号)】

前言 New Bing 的 DALL-E-3 是一款由 OpenAI 开发的 AI 绘画工具,它能够根据用户提供的文本提示生成高质量的图片。与其他 AI 绘画工具不同,DALL-E-3 最大的特点在于能够生成更精确的英文字符。此外,DALL-E-3 还具有以下优点: 1…

根据两个主键查询变更日志 抽屉时间线降序

时间戳例子&#xff1a; <div class"block"><el-timeline><el-timeline-item timestamp"2018/4/12" placement"top"><el-card><h4>更新 Github 模板</h4><p>王小虎 提交于 2018/4/12 20:46</p>…

浅析透明图片显示

1、理解图片构成 上面是一个飞机的透明图片&#xff0c;每个图片都是有一个个像素点构成的&#xff0c;每个像素点都是一个颜色&#xff0c;在内存中占4个字节&#xff0c;由透明度、红、绿、蓝构成。如下图&#xff1a; 该飞机图片飞机图片长51像素&#xff0c;宽63像素。就是…

echarts多个折线图共用X轴,实现tooltip合并和分离

echarts共享X轴案例&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</…

STM32之IIC总线控制ATC24C04

一、存储器介绍 1、电子密码存储概述 单片机的电子密码存储是一种将密码信息以电子形式存储在单片机内部的技术。它通常用于需要保护敏感信息或限制访问权限的应用程序&#xff0c;如安全系统、门禁系统、电子锁等。 电子密码存储可以通过多种方式实现&#xff0c;以下是其中…

黑马程序员-瑞吉外卖-day6

目录 做下面两个功能: 1.写实体类 2. Mapper 3.Service 4.controller 5.将分类管理表的进行分页查询返回给前端 6.套餐分类 7.修改套餐分类 后台系统中可以管理分类信息&#xff0c;分类包括两种类型&#xff0c;分别是 **菜品分类** 和 **套餐分类** 。当我们在后台…

Nginx负载均衡下的webshell连接

一、上传AntSword-Labs-master搭建负载均衡实验环境 搭建好docker环境&#xff0c;并且配置好docker-compose 我的Redhat的docker版本&#xff1a; 查看当前环境下的文件是否正确&#xff1a; 接着执行docker compose up -d 拉取环境 访问成功页面&#xff1a; 进入docker容器…

图形绘制-仪表盘(2)

本章节我们介绍如何如何绘制刻度对应的数字及指针。效果如下&#xff1a; 关于通过继承重写QWidget的绘制事件paintEvent()&#xff0c;来绘制仪表盘的基础操作&#xff0c;请看上一章节《图形绘制-仪表盘&#xff08;1&#xff09;-CSDN博客》介绍。 在paintEvent()中继续写以…

【JAVE SE】---运算符和程序逻辑控制语句

1.运算符 算数运算符 - * / % 注意&#xff1a;1.Java的%符号左右两边可以是小数&#xff0c;也可以是负数 //运算符float a1.0f;float b2.0f;float c-1.5f;System.out.println(a%b); //1.0System.out.println(a%c); //1.0 2.Java中除数不可以为0&#xff0c…

一些著名的软件都用什么语言编写?

1、操作系统 Microsoft Windows &#xff1a;汇编 -> C -> C 备注&#xff1a;曾经在智能手机的操作系统&#xff08;Windows Mobile&#xff09;考虑掺点C#写的程序&#xff0c;比如软键盘&#xff0c;结果因为写出来的程序太慢&#xff0c;实在无法和别的模块合并&…