图解 Redis String 底层数据结构 SDS 与计数器实战

2023-02-07 0 374

我是 Redis,给合作开发人员提供更多了 String(数组)、Hashes(散条目)、Lists(条目)、Sets(失序子集)、Sorted Sets(可依照覆盖范围查阅的次序子集)、Bitmap(图形)、HyperLogLog、Geospatial (自然地理内部空间)和 Stream(流)等正则表达式。

接下去我要重点项目如是说的是,String 正则表达式的采用基本功和采用情景,和String 正则表达式下层计算机流程基本原理。

正则表达式的采用画法和和五种正则表达式下层同时实现基本原理是你核心理念杨开第必经之地,回去修练。

杨开第牢固,修练绝学,让你的流程更慢还能努力做到无与伦比节约缓存。

2.1.1 String(数组)

1. 是甚么

数组类别的采用最广为,比如说计时器、缓存、分布式系统锁、用作储存登入后的使用者重要信息,key = token,value = Java 第一类字符串化为 JSON 后的数组。

如下表所示命令。

接下去,我宿苞你深入细致介绍 String 类别,下层计算机流程和采用情景。

MySQL:“你都是用 C 词汇合作开发出的,C 词汇本就有数组,威吓谁呢。”

新格局能无法关上一点儿,我并没间接采用 C 词汇的数组,而要他们搞了两个 SDS 内部结构mammalian则表示数组。SDS 的全名是 Simple Dynamic String,英文叫作“单纯静态数组”。

MySQL:“搞 SDS 的目地是啥?”

数组采用最广为,我要确保能全力支持多样和高效能的数组操作表达式,能保存二进制数据,同时还能节约缓存占用。

同时实现了你们领导平时经常对你们提出的既要又要还要的目标。

先看 C 词汇数组数组的内部结构。比如说通过 定义数组变量。

图解 Redis String 底层数据结构 SDS 与计数器实战

图2-1

图 2-1

注意,数组的最后两个字符串是 “\0″,它则表示数组的结束

因为 C 词汇标准库 中的数组有以下几点不足,所以我才设计了 SDS。

C 词汇采用 数组数组来同时实现数组,在创建数组的时候就要需要手动检查和分配数组内部空间。由

无法努力做到“安全的二进制储存”:比如说图片等二进制数据无法保存。无法储存 这种特殊字符是因为 在 C 词汇数组中则表示结尾。

数组的扩容和缩容:char 数组的长度在创建数组的时候就确定下来,如果想要追加数据,要重新申请一块内部空间,把追加后的数组内容拷贝进去,再释放旧的内部空间,十分消耗资源。

2. 修练绝学

MySQL:“说说 SDS 内部结构体吧,你是如何解决这些问题的。”

为了储存数组实际内容,我需要有两个 char 类别数组来储存,采用两个 int 类别的 len字段用作记录 char 数组采用了多少字节。

除此之外,还要有两个 int 类别 的 alloc 字段记录分配的 char 数组总长度, 就等于 char 类别的 buf 数组未采用的字节数(Redis 7.0 已经去掉了则表示未采用字节数 free 字段)。

图2-2

图 2-2

SDS 也遵循 C 数组以空字符“\0”结尾的惯例,保存空字符的大小不计算在 SDS 的 len 属性中。

此外,添加空数组“\0” 到数组末尾等操作,都是由 SDS 表达式自动完成的。

SDS 中 len 保存了数组的长度,同时实现了

你注意到了没,SDS 内部结构有两个 flags 字段,则表示的是 SDS 类别。实际上 SDS 一共设计了 5 种类别,分别是,区别在于数组的 len 长度和分配内部空间长度 alloc。

比如说 sdshdr8。

len、alloc 字段都是 uint8_t 这个类别,在 Java 中 int 就是 32 位,而 C 词汇里面有不同长度的 int 值,uint8_t 就是占 8 位的无符号 int 值,能则表示的最大值就是 2^8-1,那它的 buf 数组,最大长度就是 2^8 -1。

节约缓存

之所以这么设计,就是为了针对不同大小的数组,采用不同的 SDS 类别保存,从而节约缓存占用。

MySQL:“SDS 能储存多大的数组?”

alloc 则表示当前 sds 内部结构允许容纳的最大字符长度, 比如说 的取值覆盖范围是 。理论上 char 数组最大长度为 4294967296,两个 char 字符占用两个字节,可以储存 4 G,更不用说 sdshdr64 了。

这些都是理论值,实际上 Redis 内部会限制最大的数组长度是 512M。

编码格式

我还对 String 类别的数据采用了三种编码格式来储存,分别是 int、embstr、raw,你可采用 来查值第一类所采用的编码类别。

编码选择流程如图 2-3 所示。

图解 Redis String 底层数据结构 SDS 与计数器实战

图 2-3

图 2-3

int 编码,8 个字节的长整型,值是数字类别且数字的长度小于 20

embstr,小于等于 44 字节的数组。

大于 44 字节的字符串。

MySQL:“是甚么玩意?”

这是我采用了专门的编译优化手段来节约缓存内部空间作用就是告诉编译器,不要采用字节对齐的方式,而要采用紧凑的方式分配缓存。

默认情况下,编译器会按照 8 字节对齐的方式分配缓存,即使这个变量的大小不到 8 字节。

采用了 定义内部结构体,编译器会按照实际占用来分配缓存内部空间。

二进制安全

SDS 不仅可以储存 String 类别数据,还能储存二进制数据。SDS 并不是通过“\0” 来判断数组结束,用的是 len 标志结束,所以可以间接将二进制数据储存。

内部空间预分配

在需要对 SDS 的内部空间进行扩容时,不仅仅分配所需的内部空间,还会分配额外的未采用内部空间。

通过预分配策略,减少了执行字符串增长所需的缓存重新分配次数,降低由于数组增加操作方式的性能损耗。

惰性内部空间释放

当对 SDS 进行缩短操作方式时,流程并不会回收多余的缓存内部空间,如果后面需要 append 追加操作方式,则间接采用 buf 数组 中未采用的内部空间。

通过惰性内部空间释放策略,避免了减小数组所需的缓存重新分配操作方式,为未来增长操作方式提供更多了优化。

3. 出招两栖作战:分布式系统 ID 生成器

我相信你会经常遇到要生成唯一 ID 的情景,比如说标识每次请求、生成两个订单编号、创建使用者需要创建两个使用者 ID。

分布式系统 ID 生成器需要满足以下特性。

有序性之单调递增,想要分而治之、二分法查找就必须同时实现。另外,MySQL 是你们用的最多的数据库,B+ 树为了维护 ID 的有序性,就会频繁的在索引的中间位置插入而挪动后面节点的位置,甚至导致频繁的页分裂,这对于性能的影响是极大的。

全局唯一性,ID 不唯一就会出现主键冲突。

高效能,生成 ID 是高频操作方式,如果性能缓慢,系统的整体性能都会受到限制。

高可用,也就是在给定的时间间隔内,两个系统总的可用时间占的比例。

储存内部空间小,用 MySQL 的 InnoDB B+树来说,普通索引(非聚集索引)会储存主键值,主键越大,每个 Page 页可以储存的数据就越少,访问磁盘 I/O 的次数就会增加。

Redis 集群能确保高可用和高效能,为了节约缓存,ID 可以采用数字的形式,并且通过递增的方式来创建新的 ID。

防止重启数据丢失,你还需要把 Redis AOF 持久化开启。

MySQL:“开启 AOF 持久,为了性能设置成 everysec 策略还是有可能丢失一秒的数据,所以你还可以采用两个异步机制将生成的最大 ID 持久化到两个 MySQL。”

好主意,在生成 ID 之后发送一条消息到 MQ 消息队列中,把值持久化到 MySQL 中。

我提供更多了 命令,它能把 key 中储存的数字加 1 并返回客户端。如果 key 不存在,那么 key 的 value 先被初始化为 0,再执行加 1 操作方式并返回给客户端。

该命令的值限制在 64 位有符号数字之内。

设计思路

假设订单 ID 生成器的 key 是“counter:order”,当应用服务启动的时候先从数据库中查阅出最大值 M。执行 判断是否存在 key。

Redis 中不存在 key “counter:order”,执行 将 M 值作写入 Redis。

Redis 中存在 key “counter:order”,值为 K,那么就比较 M 和 K 的值,执行 将最大值写入 Redis,相等的话就不操作方式。

应用服务启动完成后,每次需要生成 ID 的时候,应用流程就向 Redis 服务器发送 命令。

图解 Redis String 底层数据结构 SDS 与计数器实战

图 2-4

图 2-4

String 类别的两栖作战和下层储存基本原理就到这里了,接下去我会继续如是说其他正则表达式的下层储存基本原理和两栖作战。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务