Redis 字符串用起来简单,但是原理可是真不简单(下)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Hello,大家好,我是阿粉~ 无论你现在使用什么编程语言,每天最高频使用的应该就是字符串。可以说字符串对象很基础,也很重要。 那么今天想跟大家聊聊 Redis 字符串相关实现,来看下这个看起来简单的字符串,为什么实现起来确实不简单?

Redis 字符串对象

Redis 字符串对象底层支持三种数据结构,分别是(下面使用 OBJECT ENCODING 输出):

  • int
  • embstr
  • raw

int 代表 long 类型的整数,只要字符串对象中保存的是一个整数值,就将会使用 int

69.jpg

这里需要注意,只有整数才会使用 int,如果是浮点数, Redis 内部其实先将浮点数转化为字符串值,然后再保存。

embstrraw 类型底层的数据结构其实都是 SDS (简单动态字符串),Redis 内部定义 sdshdr 一种结构。

那这两者的区别其实在于,embstr类型将会调用内存分配函数,分配一块连续的内存空间,空间中依次包含 redisObjectsdshdr 两个数据结构。

raw 类型将会调用两次内存分配函数,分配两块内存空间,一块用于包含 redisObject结构,而另一块用于包含 sdshdr 结构。

70.jpg

SDS 简单动态字符串

接下来我们来看下简单动态字符串。

sdshdr

sds 底层的结构包含三个属性:

  • len,用于记录下面 buf[] 数组已使用的长度,即等于 SDS 所保存的字符串长度
  • free,用于记录下面buf[] 数组未使用长度
  • buf[],用户保存字符串

71.jpg

这里我们需要注意了,buf[]数组中最后一个字节将会保存一个空字符 \0,代表字符串结束。sdshdr 结构中的 len 长度是没有包含这个这一个空字符。

这一点设计上与 C 语言的字符串相同。

SDS 扩容

当我们对 SDS 字符串进行增长操作,如果 SDS 空间不足,SDS 将会先进行扩容,然后再执行修改操作。

假设当前 SDS 值如下所示:

72.jpg

现在执行一次字符串拼接指令,

APPEND sds " Cluster"

由于拼接完成之后字符串总长度为 13,SDS 剩余空间不足,所以 SDS 将进行扩容,重新执行一次内存重分配。

由于我们上面的例子,SDS 修改之后的长度(即 len 属性值)小于 1MB,那么程序将会分配和 len 属性同样大小的未使用空间,此时 SDS 中 len 属性与 free 属性值相同。

此时 SDS buf 数组的实际长度为:

13+13+1=27

73.jpg


如果 SDS 修改之后长度大于 1MB,那么 Redis 将会分配 1MB的未使用空间。

假设进行修改之后,SDS len 属性变为 20MB,那么程序将会分配 1 MB 的未使用空间,此时 SDS buf 数组的实际长度为:

20MB+1MB+1byte

接着上面的例子,如果我们再次执行字符串拼接指令:

APPEND sds " Guide"

这次 SDS 未使用空间足够保存拼接字符串,所以这次不需要重新分配内存。


75.jpg


SDS 惰性空间释放

当我们对字符串进行缩减操作, SDS 字符串值将会被缩短,这样 SDS 剩余空间将会变多。

此时程序不会立即就使用内存分配函数回收多余空间,而是使用 free 属性记录多余空间,等待后面使用。

通过这种惰性空间释放策略,SDS 避免字符串缩短所需内存重分配操作,后续 SDS 字符串增长,可以直接使用多余的空间。

当然 SDS 也有相应的 API,可以真正释放 SDS 未使用的空间,所以不用担心惰性释放策略带来的内存浪费。

为什么 Redis 不直接使用 C 语言字符串?

Redis 底层使用 C 语言编程,那么其实 C 语言也有字符串,Redis 完全可以复用 C 语言字符串~

那么为什么 Redis 还要闭门造车,重新设计一个 SDS 数据结构呢?

这是因为 C 原因这种简单的字符串形式,在安全性,效率以及功能方面不满足 Redis 需求。

首先如果我们需要获取 C 语言字符串的长度,我们可以使用以下函数:

strlen(str)

strlen函数实际上使用遍历的方式,对每个字符计数,直到遇到代表字符串结束的空字符。

这个操作时间复杂度 O(N)

而 SDS 由于 len 记录当前字符串的长度,所以直接读取即可,时间复杂度仅为 O(1)

这就确保获取字符串长度的操作不会成为 Redis 性能瓶颈。

第二点每次增长或缩短 C 语言的字符串将会进行内存的重分配,否则可能导致缓冲区溢出,也有可能导致内存泄漏。

而 SDS 由于使用空间预分配的策略,如果 SDS 连续增长 N 次,内存重分配的次数从必定 N 次降低为最多 N 次。

第三点,C 语言字符串不能包含空字符,否则第一个空字符将会被误认为字符串结尾,这就大大限制使用的场景。

而 SDS 中由于可以使用 len 属性的值判断字符串是否结束,所以没有这种困扰。

所以 SDS 字符串是二进制安全的。

字符串对象底层数据结构转换

SDS 结构由于需要额外的属性记录长度以及未使用长度,虽然这样减少系统的复杂度,提高了性能,但是还是付出相应的代价,即存在一定内存空间浪费。

所以 Redis 字符串对象底层结构并不都是采用了 SDS。

如果字符串对象保存的是整数值,那么这个整数值将会直接保存在字符串对象结构的 ptr 属性。

76.jpg

如果保存不是整数,但是字符串长度小于等于 39 字节,那么字符串将会使用 embstr编码方式。最后上述两个都不满足才会使用 raw 编码方式。

另外,int 编码与 embstr,在一定条件也将会转换为 raw 编码格式。

如果对底层整数执行追加操作,使其不再是一个整数,则字符串对象的编码就会从 int 变为 raw

对于 embstr 编码,实际其实只读的,这是因为 Redis 底层并没有提供任何程序修改 embstr 编码对象。

所以一旦我们对embstr 编码字符串进行修改,字符串对象的编码就会从 embstr 变为 raw

总结

Redis 字符串是我们平常操作最频繁的数据结构,了解底层字符串对象,对于我们了解 Redis 非常重要的。

Redis 字符串对象底层结构可以分为整数与 SDS,当我们在操作 set 命令保存整数时,就将会直接使用整数。

而 SDS 是 Redis 内部定义另一种结构,相比于 C 语言字符串,它可以快速计算字符串长度,不需要频繁的分配的内存,最终要一点 SDS 是一种二进制安全的字符串。

SDS 设计虽然带来的很多优势,但是这种结构相比于 C 语言,至少多占用 4(len)+4(free)=8 字节内存,如果 buf []数组还存在没有使用的空间,空间浪费更加严重。

所以 Redis 字符串不是万金油,某些场景下可能会占用大量内存。但是如果换一种数据结构,可能内存占用就会变少。

所以我们一定要基于合适场景使用合适的数据机构。

站在巨人的肩膀

  1. Redis 设计与实现


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
4月前
|
存储 缓存 NoSQL
redis数据结构-字符串
redis数据结构-字符串
39 1
|
4月前
|
NoSQL Redis
Redis 执行 Lua保证原子性原理
Redis 执行 Lua 保证原子性原理
383 1
|
4月前
|
监控 NoSQL Redis
看完这篇就能弄懂Redis的集群的原理了
看完这篇就能弄懂Redis的集群的原理了
146 0
|
2月前
|
NoSQL Redis
Redis 字符串(String)
10月更文挑战第16天
46 4
|
3月前
|
缓存 NoSQL Linux
redis的原理(三)
redis的原理(三)
redis的原理(三)
|
2月前
|
设计模式 NoSQL 网络协议
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
40 2
|
2月前
|
存储 缓存 NoSQL
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
大数据-46 Redis 持久化 RDB AOF 配置参数 混合模式 具体原理 触发方式 优点与缺点
67 1
|
2月前
|
NoSQL 关系型数据库 MySQL
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
本文全面阐述了Redis事务的特性、原理、具体命令操作,指出Redis事务具有原子性但不保证一致性、持久性和隔离性,并解释了Redis事务的适用场景和WATCH命令的乐观锁机制。
319 0
Redis 事务特性、原理、具体命令操作全方位诠释 —— 零基础可学习
|
3月前
|
存储 缓存 NoSQL
3)深度解密 Redis 的字符串
3)深度解密 Redis 的字符串
36 1
|
3月前
|
存储 缓存 NoSQL
redis的原理(四)
redis的原理(四)