35.Redis 7.0简介

news2024/11/24 2:05:49

 2022 年 2 月初,Redis 7.0 迎来了首个候选发布(RC)版本。这款内存键值数据库迎来了“重大的性能优化”和其它功能改进,性能优化包括降低写入时复制内存的开销、提升内存效率,改进 fsync 来避免大量的磁盘写入和优化延迟表现。

Redis 7.0-rc1 的其它一些变动,包括将“Redis 函数”作为新的服务器端脚本功能,细粒度 / 基于键的权限、改进子命令处理 / Lua 脚本 / 各种新命令。

此外也提供了一些安全改进。我们从分析 Redis 主从复制中的内存消耗过多和堵塞问题,以及 Redis 7.0 (尚未发布) 的共享复制缓冲区方案是如何解决这些问题的。

1.Redis 主从复制原理

Redis 的主从复制主要分 为两种情况:

全量同步

主库通过 fork 子进程产生内存快照,然后将数据序列化为 RDB 格式同步到从库,使从库的数据与主库某一时刻的数据一致。

命令传播

当从库与主库完成全量同步后,进入命令传播阶段,主库将变更数据的命令发送到从库,从库将执行相应命令,使从库与主库数据持续保持一致。

1.1Redis 复制缓存区相关问题分析

多从库时主库内存占用过多

对于 Redis 主库,当用户的写请求到达时,主库会将变更命令分别写入所有从库复制缓冲区(OutputBuffer),以及复制积压区(ReplicationBacklog)。全量同步时依然会执行该逻辑,所以在全量同步阶段经常会触发 client-output-buffer-limit,主库断开与从库的连接,导致主从同步失败,甚至出现循环持续失败的情况。该实现一个明显的问题是内存占用过多,所有从库的连接在主库上是独立的,就是说每个从库 OutputBuffer 占用的内存空间也是独立的,那么主从复制消耗的内存就是所有从库缓冲区内存大小之和。如果我们设定从库的client-output-buffer-limit 为 1GB,如果有三个从库,则在主库上可能会消耗 3GB 的内存用于主从复制。另外,真实环境中从库的数量不是确定的,这也导致 Redis 实例的内存消耗不可控。

1.2 OutputBuffer 拷贝和释放的堵塞问题

Redis 为了提升多从库全量复制的效率和减少 fork 产生 RDB 的次数,会尽可能的让多个从库共用一个 RDB,从代码(replication.c)上看:

当已经有一个从库触发 RDB BGSAVE 时,后续需要全量同步的从库会共享这次 BGSAVE 的 RDB,为了从库复制数据的完整性,会将之前从库的

OutputBuffer 拷贝到请求全量同步从库的 OutputBuffer 中。其中的 copyClientOutputBuffer 可能存在堵塞问题,因为 OutputBuffer 链表

上的数据可达数百 MB 甚至数 GB 之多,对其拷贝可能使用百毫秒甚至秒级的时间,而且该堵塞问题没法通过日志或者 latency 观察到,但对 Redis 性能影响却很大。

同样地,当 OutputBuffer 大小触发 limit 限制时,Redis 就是关闭该从库链接,而在释放 OutputBuffer 时,也需要释放数百 MB 甚至数 GB 的数据,其耗时对 Redis 而言也很长。

1.3 ReplicationBacklog 的限制

复制积压缓冲区 ReplicationBacklog 是 Redis 实现部分重同步的基础,如果从库可以进行增量同步,则主库会从 ReplicationBacklog 中拷贝从库

缺失的数据到其 OutputBuffer。拷贝的数据量最大当然是 ReplicationBacklog 的大小,为了避免拷贝数据过多的问题,通常不会让该值过大,一般百兆左右。但在大容量实例中,为了避免由于主从网络中断导致的全量同步,又希望该值大一些,这就存在矛盾了。而且如果重新设置 ReplicationBacklog 大小时,会导致 ReplicationBacklog 中的内容全部清空,所以如果在变更该配置期间发生主从断链重连,则很有可能导致全量同步。

2. Redis7.0 共享复制缓存区的设计与实现

2.1 简述

每个从库在主库上单独拥有自己的 OutputBuffer,但其存储的内容却是一样的,一个最直观的想法就是主库在命令传播时,将这些命令放在一个全局的复制数据缓冲区中,多个从库共享这份数据,不同的从库对引用复制数据缓冲区中不同的内容,这就是『共享复制缓存区』方案的核心思想。实际上,复制积压缓冲区(ReplicationBacklog)中的内容与从库 OutputBuffer 中的数据也是一样的,所以该方案中,ReplicationBacklog 和从库一样共享一份复制缓冲区的数据,也避免了 ReplicationBacklog 的内存开销。『共享复制缓存区』方案中复制缓冲区 (ReplicationBuffer) 的表示采用链表的表示方法,将 ReplicationBuffer 数据切割为多个 16KB 的数据块(replBufBlock),然后使用链表来维护起来。为了维护不同从库的对ReplicationBuffer 的使用信息,在 replBufBlock 中存在字段:

refcount:block 的引用计数

id:block 的唯一标识,单调递增的数值

repl_offset:block 开始的复制偏移

ReplicationBuffer 由多个 replBufBlock 组成链表,当 复制积压区 或从库对某个 block 使用时,便对正在使用的 replBufBlock 增加引用计数,上图中可以看到,复制积压区正在使用的 replBufBlock refcount 是 1,从库 A 和 B 正在使用的 replBufBlock refcount 是 2。当从库使用完当前的 replBufBlock(已经将数据发送给从库)时,就会对其 refcount 减 1 而且移动到下一个 replBufBlock,并对其 refcount 加 1。

2.2 堵塞问题和限制问题的解决

多从库消耗内存过多的问题通过共享复制缓存区方案得到了解决,对于OutputBuffer 拷贝和释放的堵塞问题和 ReplicationBacklog 的限制问题是否解决了呢?

首先来看 OutputBuffer 拷贝和释放的堵塞问题问题, 这个问题很好解决,因为 ReplicationBuffer 是个链表实现,当前从库的 OutputBuffer 只需要维护共享 ReplicationBuffer 的引用信息即可。所以无需进行数据深拷贝,只需要更新引用信息,即对正在使用的 replBufBlock refcount 加 1,这仅仅是一条简单的赋值操作,非常轻量。

OutputBuffer 释放问题呢?

在当前的方案中释放从库OutputBuffer 就变成了对其正在使用的 replBufBlock refcount 减 1,也是一条赋值操作,不会有任何阻塞。

对于 ReplicationBacklog 的限制问题也很容易解决了,因为ReplicatonBacklog 也只是记录了对 ReplicationBuffer 的引用信息,对ReplicatonBacklog 的拷贝也仅仅成了找到正确的 replBufBlock,然后对其refcount 加 1。这样的话就不用担心 ReplicatonBacklog 过大导致的拷贝堵塞问题。而且对 ReplicatonBacklog 大小的变更也仅仅是配置的变更,不会清掉数据。

2.3 ReplicationBuffer 的裁剪和释放

ReplicationBuffer 不可能无限增长,Redis 有相应的逻辑对其进行裁剪,简单来说,Redis 会从头访问 replBufBlock 链表,如果发现 replBufBlock refcount 为 0,则会释放它,直到迭代到第一个 replBufBlock refcount 不为 0 才停止。所以想要释放 ReplicationBuffer,只需要减少相应 ReplBufBlock 的 refcount,会减少 refcount 的主要情况有:

1、当从库使用完当前的 replBufBlock 会对其 refcount 减 1;

2、当从库断开链接时会对正在引用的 replBufBlock refcount 减 1,无论是因为超过 client-output-buffer-limit 导致的断开还是网络原因导致的断开;

3、当 ReplicationBacklog 引用的 replBufBlock 数据量超过设置的该值大小时,会对正在引用的 replBufBlock refcount 减 1,以尝试释放内存;

不过当一个从库引用的 replBufBlock 过多,它断开时释放的 replBufBlock 可能很多,也可能造成堵塞问题,所以 Redis7 里会限制一次释放的个数,未及时释放的内存在系统的定时任务中渐进式释放。

2.4 数据结构的选择

当从库尝试与主库进行增量重同步时,会发送自己的 repl_offset,主库在每个 replBufBlock 中记录了该其第一个字节对应的 repl_offset,但如何高效地从数万个 replBufBlock 的链表中找到特定的那个?从链表的性质我们知道,链表只能直接从头到位遍历链表查找对应的replBufBlock ,这个操作必然会耗费较多时间而堵塞服务。有什么改进的思路?

可以额外使用一个链表用于索引固定区间间隔的 replBufBlock,每 1000 个replBufBlock 记录一个索引信息,当查找 repl_offset 时,会先从索引链表中查起,然后再查找 replBufBlock 链表,这个就类似于跳表的查找实现。Redis 的 zset就是跳表的实现:

在极端场景下可能会查找超过千次,有 10 毫秒以上的延迟,所以 Redis 7没有使用这种数据结构。最终使用 rax 树实现了对 replBufBlock 固定区间间隔的索引,每 64 个记录一个索引点。一方面,rax 索引占用的内存较少;另一方面,查询效率也是非常高,理论上查找比较次数不会超过 100,耗时在 1 毫秒以内。

rax 树

Redis 中还有其他地方使用了 Rax 树,比如 streams 这个类型里面的 consumer group(消费者组) 的名称还有和 Redis 集群名称存储。

RAX 叫做基数树(前缀压缩树),就是有相同前缀的字符串,其前缀可以作为一个公共的父节点,什么又叫前缀树?

Trie 树

即字典树,也有的称为前缀树,是一种树形结构。广泛应用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

它的优点是最大限度地减少无谓的字符串比较,查询效率比较高。

Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

先看一下几个场景问题:

1.我们输入 n 个单词,每次查询一个单词,需要回答出这个单词是否在之前输入的 n 单词中出现过。

答:当然是用 map 来实现。

2.我们输入 n 个单词,每次查询一个单词的前缀,需要回答出这个前缀是之前输入的 n 单词中多少个单词的前缀?

答:还是可以用 map 做,把输入 n 个单词中的每一个单词的前缀分别存入map 中,然后计数,这样的话复杂度会非常的高。若有 n 个单词,平均每个单词的长度为 c,那么复杂度就会达到 nc。

因此我们需要更加高效的数据结构,这时候就是 Trie 树的用武之地了。现在我们通过例子来理解什么是 Trie 树。现在我们对 cat、cash、apple、aply、ok 这几个单词建立一颗 Trie 树。

从图中可以看出:

1.每一个节点代表一个字符

2.有相同前缀的单词在树中就有公共的前缀节点。

3.整棵树的根节点是空的。

4.每个节点结束的时候用一个特殊的标记来表示,这里我们用-1 来表示结束,从根节点到-1 所经过的所有的节点对应一个英文单词。

5.查询和插入的时间复杂度为 O(k),k 为字符串长度,当然如果大量字符串没有共同前缀时还是很耗内存的。

所以,总的来说,Trie 树把很多的公共前缀独立出来共享了。这样避免了很多重复的存储。想想字典集的方式,一个个的 key 被单独的存储,即使他们都有公共的前缀也要单独存储。相比字典集的方式,Trie 树显然节省更多的空间。Trie 树其实依然比较浪费空间,比如我们前面所说的“然如果大量字符串没有共同前缀时”。

比如这个字符串列表:"deck", "did", "doe", "dog", "doge" , "dogs"。"deck"这

一个分支,有没有必要一直往下来拆分吗?还是"did",存在着一样的问题。像这

样的不可分叉的单支分支,其实完全可以合并,也就是压缩。

Radix 树:压缩后的 Trie 树

所以 Radix 树就是压缩后的 Trie 树,因此也叫压缩 Trie 树。比如上面的字符串列表完全可以这样存储:

同时在具体存储上,Radix 树的处理是以 bit(或二进制数字)来读取的。一次被对比 r 个 bit。

比如"dog", "doge" , "dogs",按照人类可读的形式,dog 是 dogs 和 doge 的子串。

但是如果按照计算机的二进制比对:

dog: 01100100 01101111 01100111

doge: 01100100 01101111 01100111 01100101

dogs: 01100100 01101111 01100111 01110011

可以发现 dog 和 doge 是在第二十五位的时候不一样的。dogs 和 doge 是在第二十八位不一样的,按照位的比对的结果,doge 是 dogs 二进制子串,这样在存储时可以进一步压缩空间。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL技巧之跨服务器数据查询:基础篇-如何获取查询语句中的参数

MySQL技巧之跨服务器数据查询:基础篇-如何获取查询语句中的参数 上一篇已经描述:借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQL 以…

连续15年霸榜“双11”行业第一,九牧做对了什么?

文 | 螳螂观察(TanglangFin) 作者 | 余一 随着“双十一”的落幕,各类销售榜单再次成为热门话题。 天猫“双11”全周期589个品牌成交额破亿,其中苹果、海尔、美的、小米、九牧等45个品牌成交额突破10亿。 值得注意的是在绝大多…

【网页设计】HTML5 和 CSS3 提高

目标 能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些 1. HTML5 的新特性 注:该部分所有内容可参考菜鸟教程菜鸟教程 - 学的不仅是技术,更是梦想! (runoob.com) HTML5 的新增特性主要是针对于以前的不足&#xf…

Linux手动安装nginx

本次以安装nginx-1.12.2为例 1、首先说明一下,安装nginx之前需要安装如下素材: 2、开始安装 第一步,安装依赖yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel第二步,下载并安装nginx安装包(nginx官网:http://nginx.org/)# 下载 wget http://nginx…

基于springboot+vue实现的大型超市数据处理系统 (源码+L文+ppt)4-015

第4章 系统设计 本章主要讲述的是大型超市数据处理系统的设计开发结构,简单介绍了开发流程与数据库设计的原则以及数据表的关系结构图,并且详细的展示了数据表的内部结构信息与属性。 图4-2 大型超市数据处理系统总体结构图 4.4 数据表信息&#xff…

游戏引擎学习第七天

视频参考:https://www.bilibili.com/video/BV1QFmhYcE69 ERROR_DEVICE_NOT_CONNECTED 是一个错误代码,通常在调用 XInputGetState 或 XInputSetState 函数时返回,表示指定的设备未连接。通常会出现以下几种情况: 未连接控制器:如…

IEC60870-5-104 协议源码架构详细分析

IEC60870-5-104 协议源码架构 前言一、资源三、目录层级一二、目录层级二config/lib60870_config.hdependencies/READMEexamplesCMakeLists.txtcs101_master_balancedcs104_client_asyncmulti_client_servertls_clienttls_server说明 make这些文件的作用是否需要导入这些文件&a…

全面介绍软件安全测试分类,安全测试方法、安全防护技术、安全测试流程

一、软件系统设计开发运行安全 1、注重OpenSource组件安全检查和版本更新(black duck) 现在很多云、云服务器都是由开源的组件去搭成的,对于OpenSource组件应该去做一些安全检查和版本更新,尤其是版本管理,定期对在运…

Cent OS-7的Apache服务配置

WWW是什么? WWW(World Wide Web,万维网)是一个全球性的信息空间,其中的文档和其他资源通过URL标识,并通过HTTP或其他协议访问。万维网是互联网的一个重要组成部分,但它并不是互联网的全部。互联…

遗传算法与深度学习实战(23)——利用遗传算法优化深度学习模型

遗传算法与深度学习实战(23)——利用遗传算法优化深度学习模型 0. 前言1. 神经进化2. 使用遗传算法作为深度学习优化器小结系列链接 0. 前言 神经进化涵盖了所有用于改进深度学习的进化算法。更具体地说,神经进化用来定义应用于深度学习的特…

Kubernetes 核心组件调度器(Scheduler)

文章目录 一,调度约束1.Kubernetes的基本构建块和最小可调度单元pod创建过程(工作机制,重点)1.1list-watch 组件List-Watch 的优点List-Watch 的应用场景List-Watch 的挑战与优化 2.调度过程(重点)2.1调度过程:2.2Kube…

STM32WB55RG开发(3)----生成 BLE 程序连接手机APP

STM32WB55RG开发----3.生成 BLE 程序连接手机APP 概述硬件准备视频教学样品申请源码下载参考程序选择芯片型号配置时钟源配置时钟树RTC时钟配置RF wakeup时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙设置工程信息工程文件设置结果演示 概述 本项目旨…

[C++]内联函数和nullptr

> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 &#x1…

微软OmniParser:一切皆文档,OCR驱动智能操作

前沿科技速递🚀 微软推出的OmniParser是一种创新的框架,旨在将手机和电脑屏幕视为文档,通过OCR技术与多模态大模型实现对用户界面的深度理解和操作。OmniParser能够高效识别和提取界面中的文本信息、位置和语义,助力自动化操作。 …

使用 Web Search 插件扩展 GitHub Copilot 问答

GitHub Copilot 是一个由 GitHub 和 OpenAI 合作开发的人工智能代码提示工具。它可以根据上下文提示代码,还可以回答各种技术相关的问题。但是 Copilot 本身不能回答非技术类型的问题。为了扩展 Copilot 的功能,微软发布了一个名为 Web Search 的插件&am…

Rust语言在系统编程中的应用

💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 Rust语言在系统编程中的应用 Rust语言在系统编程中的应用 Rust语言在系统编程中的应用 引言 Rust 概述 定义与原理 发展历程 Ru…

vue+vite前端项目ci过程中遇到的问题

将项目进行ci流水线构建时,遇到了npm run build 构建完成后命令行不会终止的问题,导致了无法进行下一个步骤。如下图: 排查了好久找到事vite.config.js的配置出了问题,如图所示,将build下的watch改为false即可解决问…

Python 获取PDF的各种页面信息(页数、页面尺寸、旋转角度、页面方向等)

目录 安装所需库 Python获取PDF页数 Python获取PDF页面尺寸 Python获取PDF页面旋转角度 Python获取PDF页面方向 Python获取PDF页面标签 Python获取PDF页面边框信息 了解PDF页面信息对于有效处理、编辑和管理PDF文件至关重要。PDF文件通常包含多个页面,每个页…

企业级RAG(检索增强生成)系统构建研究

— 摘要 检索增强生成(Retrieval-Augmented Generation,RAG)技术已经成为企业在知识管理、信息检索和智能问答等应用中的重要手段。本文将从RAG系统的现状、方法论、实践案例、成本分析、实施挑战及应对策略等方面,探讨企业如何…

前端学习八股资料CSS(二)

更多详情:爱米的前端小笔记,更多前端内容,等你来看!这些都是利用下班时间整理的,整理不易,大家多多👍💛➕🤔哦!你们的支持才是我不断更新的动力!找…