Redis设计与实现之String

Redis设计与实现之String

Redis简介

Redis(Remote Dictionary Server ),即远程字典服务,是一个完全开源(遵守BSD协议)免费的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

在这里插入图片描述

Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

  • Redis支持数据的备份,即master-slave模式的数据备份。

SDS

SDS全称是简单动态字符串(simple dynamic string)。

  • Redis只有在无需对字符串值进行修改的地方才会使用C语言原生的字符串,比如日志打印。

  • 对于需要对字符串的值可能会修改的地方,则使用SDS。所以Redis将SDS作为默认的字符串表示。比如存储字符串的时候就是使用SDS表示。

在这里插入图片描述

  • 键值对的健是一个字符串对象,对象的底层实现是一个保存着字符串“msg”的SDS。

  • 键值对的值也是一个字符串对象,对象的底层实现是一个保存着字符串“hello world”的SDS。

在这里插入图片描述

  • 键值对的健是一个字符串对象,对象的底层实现是一个保存着字符串“fruits”的SDS。

  • 键值对的值是一个列表对象,列表对象包含了三个字符串对象,这三个字符串对象分别是由三个SDS对象实现:

    • 第一个SDS保存字符串“apple”,

    • 第二个SDS保存字符串“banana”,

    • 第三个SDS保存字符串“cherry”

SDS的定义

struct sdshdr {

	// 记录buf数组中已使用的字节数量
	// 等于SDS所保存字符串的长度
	int len;
	
	// 记录buf数组中未使用的字节数量
	int free;
	
	// 字节数组,用于保存字符串
	char buf[];
}

在这里插入图片描述

  • free属性的值为0,表示这个SDS没有分配任何未使用空间。

  • len属性的值为5,表示这个SDS保存了一个5字节长度的字符串。

  • buf属性是一个char类型的数组,数组的前五个字节分别保存了‘R’、‘e’、‘d’、‘i’、‘s’五个字符。

  • 最后一个字节则保存了空字符串’\0’(这是遵循C字符串以空字符结尾的惯例,这个空字符不计算在SDS的len属性中。)。

SDS的优势

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

在这里插入图片描述

C字符串本身不记录字符串长度,每次获取字符串长度需要遍历一次数组。这个操作的复杂度为O(N)。

在这里插入图片描述
SDS的len属性记录了本身的长度,所以获取一个SDS长度的复杂度为O(1)。

杜绝缓冲区溢出

C字符串本身不记录字符串长度,导致的另外一个问题是容易造成缓冲区溢出。

在这里插入图片描述
假设程序里有2个内存中紧邻的C字符串s1和s2,其中s1保存了字符串“Redis”,而s2则保存了字符串“MongoDB”。

如果有人直接执行:strcat(s1, “ Cluster”);将s1的内容修改为“Redis Cluster”。由于没有提前给s1分配足够的空间,那么在strcat函数执行后,s1数据将溢出到s2所在的空间,导致s2保存的内容被意外修改。

在这里插入图片描述

与C字符串不同,SDS空间分配策略完全可以杜绝缓冲区溢出的问题:当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足修改所需要的要求,如果不满足的话,API会自动将SDS空间扩展至执行修改所需要的大小,然后才执行实际的修改操作,所以使用SDS即不需要手动修改SDS空间大小,有不会前面所说的缓冲区溢出问题。

在这里插入图片描述

如上图执行strcat(s1, “ Cluster”)操作,那么空间不够拼接,会先扩展空间然后执行拼接“ Cluster”操作,拼接的结果如下图所示。

在这里插入图片描述

减少修改字符串时的内存分配次数

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

  • 如果程序执行的是增长字符串操作,比如拼接操作(append)那么在执行这个操作之前,程序需要先通过内存重新分配来扩展底层数组的空间大小,如果忘记了就会导致缓冲区溢出。

  • 如果程序执行的是缩短字符串操作,比如截断操作(trim)那么在执行这个操作之后,程序需要通过内存重分配来释放不再使用的那部分空间,如果忘记了就会导致内存泄露。

为了避免C字符串的频繁分配内存的缺陷,SDS通过未使用空间解除了字符串长度和底层数组长度的关联:在SDS中,buf数组的长度不一定就是数量+1(如下图),数组里面可以包含未使用字节,而这些字节的数量由SDS的free属性记录。

在这里插入图片描述

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

空间预分配

空间预分配用于优化SDS的字符串增长操作:当SDS的API对一个SDS进行修改,并且需要对SDS进行空间扩展的时候,程序不仅会为SDS分配修改所必须的空间,还会为SDS分配额外的未使用空间。
分配未使用空间数量的公式如下:

  • 如果对SDS进行修改之后,SDS的长度(也就是len属性的值)将小于1MB,那么程序分配的len属性同样大小的未使用空间,这时SDS的len属性和free属性的值相同。

例子:如果修改后SDS的len将变成13字节,那么程序会分配13字节的未使用空间,SDS的buf数组的实际长度将变成13+13+1(保存空字符)=27字节。

在这里插入图片描述
在这里插入图片描述

  • 如果对SDS进行修改后,SDS的长度将大于等于1MB,那么程序会分配1MB的未使用空间。

例子:如果字符串修改后,SDS的len将变成30MB,那么程序会分配1MB的未使用空间,SDS的buf数组的实际长度为30MB+1MB+1byte

在扩展SDS空间之前,SDS API会先检查未使用空间是否足够,如果足够的话,API就会使用未使用空间,而无须执行内存重分配。

  • 优点:通过这种预分配策略,SDS将连续增长N次字符串所需要的内存重新分配次数从必定需要N次降低为最多N次。

惰性空间释放

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

在这里插入图片描述

例子:如果左图的SDS字符串s执行了sdstrim(s, “XY”);移除SDS中所有的X和Y。函数执行后,并没有释放多出来的8个字节空间,而是将这8个字节空间作为未使用空间保留在SDS里面,使用free属性记录。如果有增长操作可以直接使用,而不用重新分配内存空间。

在这里插入图片描述

字符串对象编码

字符串对象编码可以是int、raw或者embstr。

在这里插入图片描述

如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示。那么这个字符串对象的编码设置为int。

在这里插入图片描述

如果字符串对象保存的是一个字符串值,并且这个字符串的长度大于32字节,那么字符串对象使用一个简单动态字符串(SDS)来保存这个值,并且将对象的编码设置为raw。

在这里插入图片描述

如果字符串对象保存的是一个字符串值,并且这个字符串的长度小于等于32字节,那么字符串对象使用一个简单动态字符串(SDS)来保存这个值,并且将对象的编码设置为embstr。

embstr编码是专门用于保存短字符串的一种编码优化方式。

embstr和raw的区别

raw编码会调用两次内存分配函数分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来创建一块连续的内存空间,空间中依次包含了redisObject和sdshdr两个结构。

扩展

对象 编码(数据结构)
列表对象 ziplist(压缩列表)
linkedlist(双端列表)
哈希对象 ziplist(压缩列表)
hashtable
集合对象 intset(整数集合)
hashtable
有序集合对象 ziplist(压缩列表)
skiplist(跳跃表)
展开阅读全文

Redis 服务器管理(集群主从复制及高可用)

08-30
Redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。     本课程主要讲解以下内容: 1. Redis的基本使用 2. Redis数据库的数据类型 3. Redis数据库数据管理 4. Redis的主从复制 5. Redis数据库的持久性 6. Redis的高可靠性和集群 7. Redis的优化和性能测试 8. Redis服务器的维护和管理 9. Redis服务器的常见问题排错  
©️2020 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值