结合PHP 看Redis 字符串 浅析

一 实现原理

1.1 c语言字符串

以空字符串结尾的字符数组,比如hello在C语言中,经过一系列算法分配内存后,再产生出图中结构代表字符串'hello!~'。

使用长度为N+1的字符数组来表示字符串,最后总加一个'\0'代表结尾。

    

1.2 php字符串

底层为C,结构如代码所示:

  

1.3 redis字符串

和php的字符串有类似之处,redis作者封装了一个名为SDS的结构体来表示字符串,如图所示:

 

 那它为啥要这样做呢,我们在下面小结探讨~,先说一下结构中各个属性的作用:

  1. len:

    标识该字符串长度( 结合PHP的字符串结构体想想有什么好处~ )

  2. free:

    标识该redis字符串未使用空间大小,它有着自己的分配策略(下面简单介绍)

  3. buf:

    类似一个C语言的字符串,以'\0'结尾,方便重用C语言的一些字符串函数(说白了就是当做C语言的字符串用,但是\0后面会有长度free的空间,对于C函数来说不影响)

  

二 对比

2.1 查询复杂度 

2.1.1 c语言

由于C语言的字符串结构使然,它只能通过遍历加计数来获取字符串长度,很简单,O(N)

2.1.2 PHP、Redis

这两个一起说,是因为它们都在自己的字符串结构体中内置了长度的属性,直接O(1)获取该属性的值即可得到字符串长度

2.2 抠门C之申请与释放

C作为可操作内存的一门语言,它是比较抠门的,不会动态为我们分配内存。我们需要手动去申请内存储存数据,在我们不需要用到某些内存的时候,也要去手动将其释放(垃圾回收、GC)。

2.2.1 缓冲区溢出 忘记申请

redis为什么要这么设计呢,我们先简单了解一下C语言中的 缓冲区溢出 问题

 

我们想在hello!~后追加 得到 hello!~ world~! 但是str2意外被修改 

2.2.2 内存泄漏 忘记释放

标题我们说忘记释放,和申请分配相对应,如果我们在C中想要做到缩短字符串的操作,比如我们把str1改为hello之后,没有去释放~!所占用的空间,就会造成 内存泄漏 ,产生空占内存。

2.2.3 Redis、PHP如何做

PHP作为一门“高级语言”,有自己的动态分配和GC,Redis也一样。上面我们了解到,C语言的字符串操作相对比较麻烦,那么它的优点呢?比较明显的是节省内存,我们也从图片中得知,PHP和Redis的字符串结构相对于C来说有其他附加的属性,它们都会占用一定的空间。反之,我占用这么多空间,图啥?一句话:空间换时间。

Redis作为一种高性能的缓存服务,它需要对客户端的请求做到快速响应。字符串的更改操作对于Redis来说也比较频繁,如果像C那样每次都需要申请内存和GC,对性能来说影响是比较大的,更不用说像strlen之类的操作了,直接将O(N)变成了O(1),典型的实现空间换时间。

那么它具体是怎么做的呢?

2.3 空间换时间

2.3.1 预分配

当我们对某个string类型的key执行set后,如果字符串的长度增长,Redis不仅对字符串分配相应长度的空间,还会进行预分配给一定的未使用空间,该未使用空间的长度由free属性标识。机制如下:

  1. 如果修改之后len长度小于1MB,就分配和len长度一样的空间。

  2. 如果修改之后len长度大于1MB,就分配1MB空间。

举例:如果str修改之后字符串大小为30MB,那么实际分配长度为30MB(len)+1MB(free)+1byte(\0),假如我们继续修改str 长度为30MB+500KB,那Redis就不需要重新申请内存空间,直接利用free的1MB空间即可。嗯~ 空间换来了时间

2.3.2 懒释放

当我们对某个string类型的key执行set后,如果字符串的长度减少,Redis将字符串直接放进当前key对应的空间中,多出来的空间不会被立即释放,多出来的空间由free属性标识,以便即将到来的字符串增长操作。举个例子:

set str helloworld;

set str hello;

set str helloredis;

执行第二、三个命令发生了什么呢

helloworld\0 占用11个字符空间,此时free为0

hello\0 占用6个字符空间,此时free为5

hellophp\0 占用9个空间,php直接利用free的空间,不需要重新申请内存,此时free为2

2.4 二进制安全

讲这点肯定是和C有区别滴,C语言的字符串结尾用\0 空字符来判定。假设有个文本 redis\0!,在C中!是会被忽略的。所以C中的字符串并不能保存比较特殊的数据。得益于Redis的字符串结构体,我们用len属性来判断字符串是否结束,所以数据是存的什么样,取出来还是原来的样子。

三 总结

C字符串 Redis字符串
计算长度需要遍历 直接取len的值
手动申请释放内存 len和free配合Redis机制不需要考虑C中此类问题
二进制不安全 二进制安全
频繁操作慢,每次内存分配 频繁操作快,不需要每次分配内存

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章