redis string类型的底层实现

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 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
相关文章
|
29天前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
24天前
|
NoSQL 安全 Java
Redis6入门到实战------ 三、常用五大数据类型(字符串 String)
这篇文章深入探讨了Redis中的String数据类型,包括键操作的命令、String类型的命令使用,以及String在Redis中的内部数据结构实现。
Redis6入门到实战------ 三、常用五大数据类型(字符串 String)
|
24天前
|
SQL 分布式计算 DataWorks
DataWorks产品使用合集之如何将STRING类型转换为DATETIME类型
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
11天前
|
C# 开发者 UED
WPF开发者必备秘籍:深度解析文件对话框使用技巧,打开与保存文件原来如此简单!
【8月更文挑战第31天】在WPF应用开发中,文件操作是常见需求。本文详细介绍了如何利用`Microsoft.Win32`命名空间下的`OpenFileDialog`和`SaveFileDialog`类来正确实现文件打开与保存功能。通过示例代码展示了如何设置文件过滤器、初始目录等属性,并使用对话框进行文件读写操作。正确使用文件对话框能显著提升用户体验,使应用更友好易用。
23 0
|
17天前
|
存储 NoSQL 索引
MPP架构数据仓库使用问题之在ORC文件中,String类型字段是怎么进行编码的
MPP架构数据仓库使用问题之在ORC文件中,String类型字段是怎么进行编码的
|
18天前
|
开发工具 数据安全/隐私保护
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
【Azure Developer】使用MSAL4J 与 ADAL4J 的SDK时候,遇见了类型冲突问题 "java.util.Collections$SingletonList cannot be cast to java.lang.String"
|
18天前
|
存储 JSON NoSQL
揭秘Redis字符串String的隐藏技能!从基础到进阶,让你的数据存储操作秒变高大上!
【8月更文挑战第24天】Redis中的字符串类型作为其基石,不仅能够存储从简单文本到复杂格式如JSON的各种数据,还能通过多样化的命令实现包括但不限于自增自减、设置过期时间等高级功能,极大提升了其实用性和灵活性。例如,使用`SET`命令可添加或更新键值对,`GET`获取值,`DEL`删除键;同时,`INCR`和`DECR`支持对整数值的原子性增减操作,非常适合实现计数器等功能;通过`EXPIRE`命令设置过期时间,则适用于需要限时存储的应用场景。尽管名为“字符串”,但实际上还可存储图片、音频文件的Base64编码等形式的数据,为开发者提供了强大而灵活的工具。
27 0
|
22天前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
29 0
|
3月前
|
Java UED
Java中String强转int:一种常见的错误和解决方法
在Java中将非数字字符串转换为整数会导致`NumberFormatException`。要解决这个问题,可以使用`try-catch`捕获异常,正则表达式验证数字格式,或利用异常信息提供错误提示。例如,`Integer.parseInt()`会因遇到非数字字符如`"123abc"`而抛出异常,但通过异常处理或正则`\\d+`可确保安全转换。记得在编程时避免直接强转,以防止程序异常中断。
|
27天前
|
前端开发 Java
成功解决:java.lang.String cannot be cast to java.lang.Integer
这篇文章记录了作者在使用Axios二次封装时遇到的一个Java类型转换问题,即前端传递的字符串参数不能直接转换为Integer类型,文章提供了正确的转换方法来解决这个问题。
成功解决:java.lang.String cannot be cast to java.lang.Integer