一个简单的字符串,为什么 Redis 要设计的如此特别 (3)

Redis 当中每种数据类型都是经过特别设计的,相信大家看完这个系列也会体会到 Redis 设计的精妙之处。字符串在我们眼里是非常简单的一种数据结构了,但是 Redis 却把它优化到了极致,为了节省空间,其通过编码的方式定义了三种不同的存储方式:

编码属性 描述 object encoding命令返回值
OBJ_ENCODING_INT   使用整数的字符串对象   int  
OBJ_ENCODING_EMBSTR   使用 embstr 编码实现的字符串对象   embstr  
OBJ_ENCODING_RAW   使用 raw 编码实现的字符串对象   raw  

int 编码
当我们用字符串对象存储的是整型,且能用 8 个字节的 long 类型进行表示(即 2 的 63 次方减 1),则 Redis 会选择使用 int 编码来存储,此时 redisObject 对象中的 ptr 指针直接替换为 long 类型。我们想想 8 个字节如果用字符串来存储只能存 8 位,也就是千万级别的数字,远远达不到 2 的 63 次方减 1 这个级别,所以如果都是数字,用 long 类型会更节省空间。

embstr 编码
当字符串对象中存储的是字符串,且长度小于 44 (Redis 3.2 版本之前是 39)时,Redis 会选择使用 embstr 编码来存储。

raw 编码
当字符串对象中存储的是字符串,且长度大于 44 时,Redis 会选择使用 raw 编码来存储。

讲了半天理论,接下来让我们一起来验证下这些结论,依次输入 set name lonely_wolf,type name,object encoding name 命令:

一个简单的字符串,为什么 Redis 要设计的如此特别

可以发现当前的数据类型就是 string,普通字符串因为长度小于 44,所以采用的是 embstr 编码。

再依次输入:set num 1111111111,set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(长度 44),set address aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(长度 45),分别查看类型和编码:

一个简单的字符串,为什么 Redis 要设计的如此特别

可以发现,当输入纯数字的时候,采用的是 int 编码,而字符串小于等于 44 则为 embstr,大于 44 则为 raw 编码。

字符串对象中除了上面提到的纯整数和字符串,还可以存储浮点型类型,所以字符串对象可以存储以下三种类型:

字符串

整数

浮点数

而当我们的 value 为整数时,还可以使用原子自增命令来实现 value 的自增,这个命令在实际开发过程中非常实用。

incr:自增 1。

incrby:自增指定数值。

一个简单的字符串,为什么 Redis 要设计的如此特别

不过这两个命令只能用在 value 为整数的场景,当 value 不是整数时则会报错。

embstr 编码为什么从 39 位修改为 44 位

embstr 编码中,redisObject 和 sds 是连续的一块内存空间,这块内存空间 Redis 限制为了 64 个字节,而redisObject 固定占了16字节(上面定义中有标注),Redis 3.2 版本之前的 sds 占了 8 个字节,再加上字符串末尾 \0 占用了 1 个字节,所以:64-16-8-1=39 字节。

Redis 3.2 版本之后 sds 做了优化,对于 embstr 编码会采用 sdshdr8 来存储,而 sdshdr8 占用的空间只有 24 位:3 字节(len+alloc+flag)+ \0 字符(1字节),所以最后就剩下了:64-16-3-1=44 字节。

embstr 编码和 raw 编码的区别

embstr 编码是一种优化的存储方式,其在申请空间的时候因为 redisObject 和 sds 两个对象是一个连续空间,所以只需要申请 1 次空间(同样的,释放内存也只需要 1 次),而 raw 编码因为 redisObject 和 sds 两个对象的空间是不连续的,所以使用的时候需要申请 2 次空间(同样的,释放内存也需要 2 次)。但是使用 embstr 编码时,假如需要修改字符串,那么因为 redisObject 和 sds 是在一起的,所以两个对象都需要重新申请空间,为了避免这种情况发生,embstr 编码的字符串是只读的,不允许修改

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssxdx.html