redis string类型的底层实现

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: redis string类型的底层实现:简单动态字符串(SDS)

Redis没有直接使用c语言传统的字符串标识(以空字符串结尾的字符数组),而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型,并将SDS用作Redis的默认字符串表示。


1 SDS的定义

image.png

上图展示了SDS的结构:

  • free表示未使用空间,上图的属性值为5,表示这个SDS分配的未使用空间为5字节长
  • len表示字符串的长度,注意不包含尾部SDS默认的空字符串,上图表示该字符串的长度为5字节
  • buf属性是一个char类型的数组,数组的前五个字节分别保存了R、e、d、i、s五个字符串,而最后一个字符串则保存了空字符串\0.

SDS遵循c字符串以空字符串结尾的惯例,这一好处是SDS可以直接重用一部分c字符串函数库里面的函数


2.SDS与c字符串的区别

image.png

上图展示了一个简单的c语言字符串。c语言使用的这种简单的字符串表示方式,并不能满足Redis对字符串在安全性、效率以及功能方面的要求,下面将对比c字符串和SDS之间的区别,并说明SDS比c字符串更适用于Redis的原因。


2.1常数复杂度获取字符串长度

image.png

c字符串并不记录自身的长度信息,所以为了获取一个c字符串的长度,程序必须要遍历整个字符串,对遇到的每个字符进行计数,直到遇到代表字符串结尾的空字符串为止,整个操作的复杂度为O(N)。上图获取字符串长度的遍历次数为5

image.png

对于SDS来说,由于其结构存储了len属性,所以只需要通过len即可直到Redis字符串的长度,每次的时间复杂度都为O(1)

总结:通过使用SDS,Redis将获取字符串长度的复杂度控制在O(1),这确保了获取字符串长度的工作不会成为Redis的性能瓶颈。


2.2杜绝缓冲区溢出

除了获取字符串长度的复杂度高之外,c字符串不记录自身长度带来的另外一个问题是会带来缓冲区溢出

image.png

举个例子:程序里有两个在内存中紧邻的c字符串s1和s2,如图2-7所示

如果一个程序员决定通过执行strcat(s1, "Cluster")将s1的内容修改为"Redis Cluster",但他粗心地忘了在执行前为s1分配足够的空间,那么执行之后,s1的数据将溢出到s2所在的空间,导致s2保存的内容被意外地修改,如图2-8所示。

image.png

与c不同,SDS的内存分配策略完全杜绝了发生缓冲区溢出的可能性:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需的要求,如果不满足的话,API会自动将SDS的空间拓展至执行修改锁需的大小,然后再执行实际的修改操作。

image.png

SDS也需要做上面的操作。如图2-9,执行strcat之前先检查len,发现内存空间不足以拼接"Cluster",SDS会拓展内存空间,再执行后面的strcat操作,执行之后的SDS如图2-10所示

image.png

注意:执行之后SDS的free变成了13,这并非bug,后面会讲到


2.3减少修改字符串时带来的内存重分配次数

由于c字符串未记录自身的长度,所以对于一个包含N个字符的c字符串来说,这个c字符串的底层实现总是一个N+1的字符数组。每次增长或者缩短一个c字符串,程序都总要保存这个c字符串的数组进行一次内存重分配操作:

  • 如果增长字符串,那么在执行这个操作之前,程序需要先通过内存重分配来拓展底层数组的空间大小 -------- 如果忘了这一操作,将会产生缓冲区溢出。
  • 如果缩短操作,比如截断操作,程序需要通过内存重分配来释放字符串不再使用的那部分空间 --------- 如果忘了这一操作,将会产生内存泄露。


为了避免c字符串的这种缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度的关联:在SDS中,buf数组的长度不一定就是字符数量加一,数组里面可以包含未使用的字节,而这些字节的数量就又SDS的free属性记录。

通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略。


2.3.1空间预分配

空间预分配用户优化SDS的字符串操作:当SDS的API对一个SDS进行修改,并且需要对SDS空间进行拓展时,程序不仅会对SDS分配修改所需要的空间,还会为SDS分配额外的未使用空间。其中额外的未使用空间数量由一下公式决定:

  • 如果修改之后的长度小于1MB,那么程序分配和len一样的未使用空间。比如修改之后的SDS实际长度为13,那么也会分配13字节的未使用空间,SDS的buf数组的实际长度会变成13+13+1=27字节
  • 如果修改时候大于1MB,那么程序会额外为SDS分配1MB的未使用空间


2.3.2惰性空间释放

惰性空间释放用于优化SDS的字符串缩短操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用



3.二进制安全

c字符串中的字符必须符合某种编码,并且除了字符串的末尾之外,字符串里面不能包含空字符串,否则最先被程序读入的空字符串会被误认为是字符串结尾,这限制使得c字符串只能保存文本数据,不能保存想图片、音频、视频这样的二进制数据。比如保存"Redis Cluster",c字符串会认为Redis后面的空字符串是结尾,使得Cluster被截掉。

image.png


而SDS有len属性,使得SDS并不需要判断空字符串来确认是否为末尾。这样的结构保证了SDS的二进制是安全的,可以保存任意结构的二进制数据。


4、兼容部分c字符串函数

由于SDS默认会为buf分配一字节的空间来保存空字符串结尾,这使得SDS可以重用部分的c字符串函数,避免了不必要的代码重复

相关实践学习
基于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
相关文章
|
22天前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
30天前
|
NoSQL Redis
Redis 字符串(String)
10月更文挑战第16天
37 4
|
1月前
|
消息中间件 存储 监控
redis 的List类型 实现 排行榜
【10月更文挑战第8天】
37 2
|
1月前
|
存储 NoSQL Redis
redis-set类型
【10月更文挑战第6天】
41 1
|
1月前
|
数据可视化 Java
让星星月亮告诉你,通过反射创建类的实例对象,并通过Unsafe theUnsafe来修改实例对象的私有的String类型的成员属性的值
本文介绍了如何使用 Unsafe 类通过反射机制修改对象的私有属性值。主要包括: 1. 获取 Unsafe 的 theUnsafe 属性:通过反射获取 Unsafe类的私有静态属性theUnsafe,并放开其访问权限,以便后续操作 2. 利用反射创建 User 类的实例对象:通过反射创建User类的实例对象,并定义预期值 3. 利用反射获取实例对象的name属性并修改:通过反射获取 User类实例对象的私有属性name,使用 Unsafe`的compareAndSwapObject方法直接在内存地址上修改属性值 核心代码展示了详细的步骤和逻辑,确保了对私有属性的修改不受 JVM 访问权限的限制
52 4
|
1月前
|
存储 分布式计算 NoSQL
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
大数据-40 Redis 类型集合 string list set sorted hash 指令列表 执行结果 附截图
27 3
|
1月前
|
消息中间件 分布式计算 NoSQL
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
大数据-41 Redis 类型集合(2) bitmap位操作 geohash空间计算 stream持久化消息队列 Z阶曲线 Base32编码
27 2
|
22天前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
2月前
|
存储 JSON NoSQL
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
这篇文章是关于Redis基本数据结构的学习笔记,包括了String、Hash、Set、List和SortedSet的介绍和常用命令。文章解释了每种数据结构的特点和使用场景,并通过命令示例演示了如何在Redis中操作这些数据结构。此外,还提供了一些练习示例,帮助读者更好地理解和应用这些数据结构。
redis基本数据结构(String,Hash,Set,List,SortedSet)【学习笔记】
|
1月前
|
消息中间件 NoSQL Kafka
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
133 0