Redis认知-String类型编码(二进制安全)

2024-08-07 16:44:41 浏览数 (2)

Redis 是一个K-V NOSQL,使用键值对存储数据,其中的值(对象)包括 5 种类型,即字符串、哈希、列表、集合、有序集合。

这 5 种类型是 Redis 对外提供的,实际上,在 Redis 内部,每种类型可能有 2 种或更多的内部编码实现。

本文着重介绍字符串编码。

二进制安全

Redis进程与外界交互时,客户端从socket里获取的是字节流。Redis-Server服务端存储二进制字节,即二进制安全。

二进制安全,换句话说只要未来的双方客户端有统一的编码、解码,数据就不会被破坏。

在计算机里ASCII码是字符集标准,其他是扩展字符集。它有个规律0xxxxxxx,第一个字节肯定是0,后面可以从全0到全1逐个变化,代表不同的东西。而GBK、UTF-8是一个编码,不是字符集。

示例:

终端控制台设置采用GBK编码

终端控制台设置采用GBK编码-效果展示终端控制台设置采用GBK编码-效果展示

终端控制台设置采用UTF-8编码

终端控制台设置采用UTF-8编码-效果展示终端控制台设置采用UTF-8编码-效果展示

补充:命令【strlen $key】会返回$key的长度

在GBK情况下,redis-server是不知道客户端外围什么编码。redis-client发送写请求时先变成字节数组,redis-server底层按字节预存。

从这点上看,redis-server服务端与客户端侧的编码无直接关系。这就是二进制安全。

字符串编码-Int和Raw

Redis中的String编码是指Redis中存储字符串时所使用的数据结构。Redis中的字符串最大长度为512MB。

String类型直接存储了字符串值,但 Redis 内部为了优化存储效率和访问速度,会根据存储的内容自动选择不同的编码方式。Redis 中 String 类型的编码主要有两种:int(或称为 embstr,嵌入式字符串)和 raw(或称为 sdshdr,动态字符串)。

  • int 编码(或 embstr)

适用条件:当存储的字符串是整数且长度小于 Redis 源码中定义的一个阈值(通常是 32 位或 64 位整数的大小限制,但具体会受 Redis 配置和版本的影响)时,Redis 会使用 int 编码来存储这个字符串。

存储结构:在这种编码下,Redis 直接使用整数的原始二进制形式来存储数据,不需要额外的内存来存储字符串的长度或其他元数据。这使得访问速度非常快,且内存占用非常低。

  • raw 编码(或 sdshdr)

适用条件:当存储的字符串不是整数,或者字符串的长度超过了 int 编码的阈值时,Redis 会使用 raw 编码。

存储结构:raw 编码使用 Redis 自定义的动态字符串(SDS, Simple Dynamic String)来存储数据。SDS 不仅存储了字符串的内容,还存储了字符串的长度、空闲空间等信息。这种设计使得 Redis 在进行字符串操作时(如追加、修改等)更加高效,并且能够自动管理内存,减少内存碎片的产生。

编码转换

Redis 在存储 String 类型的数据时,会根据数据的实际情况自动选择合适的编码方式。在数据更新过程中,如果数据的内容或大小发生了变化,Redis 也会自动调整编码方式,以确保存储效率和访问速度的最优化。

示例:

127.0.0.1:6379> set key 9

OK

127.0.0.1:6379> object encoding key // 查看key的编码

"int"

127.0.0.1:6379> append key 1 // 追加字符”1”

(integer) 2

127.0.0.1:6379> get key

"91"

127.0.0.1:6379> strlen key

(integer) 2

127.0.0.1:6379> object encoding key // 再次查看key的编码

"raw"

127.0.0.1:6379>

上述,写入key存储是用int编码,但经过append操作后,变更为raw。

有关字符串数值计算

先来看个例子:

janesong@192 ~ % redis-cli

127.0.0.1:6379> set key 9

OK

127.0.0.1:6379> append key 1

(integer) 2

127.0.0.1:6379> object encoding key

"raw"

127.0.0.1:6379> incr key // value 1作为新value存入key

(integer) 92

127.0.0.1:6379> get key

"92"

127.0.0.1:6379> object encoding key

"int"

127.0.0.1:6379>

从上述例子可以看出,编码格式为raw也可以参与计算,只要是数字型的。

redis-server其实是要把这个key的value字节在内存里面拿出来,先转换为数值,然后它会更新key上的encoding编码。这是为了方便调一次encoding,因为key可能开始是raw,也能是字符串。

当转换数字没有报错没有报异常,而且参与计算也成功了,encoding就更新为int类型,下次做incr、decr等就可以直接参与计算了。如果发现不是int类型,就可以避归报错的问题。这样做就是为了提速,直接触发计算,无排错过程

从这点上说,Redis自身的编码么有影响数据存储。

数值计算溢出

既然Redis的字符串是字节方式,那么在参与计算时,是否没有”计算溢出”一说。答案显然不是。

举个例子:

127.0.0.1:6379> set key 9999999999999999999

OK

127.0.0.1:6379> incr key

(error) ERR value is not an integer or out of range // 服务端溢出无法计算

127.0.0.1:6379> get key

"9999999999999999999"

127.0.0.1:6379>

补充:本处只是考虑redis-server在对数字类字符串溢出问题,实际上对于客户端也会有语言偏差方面的溢出考虑处理。比如:redis-server返回一个比较大的value[数字字符型],而jedis在用变量去接并转int,需要考虑溢出。

所以,我们在使用Redis时,对于计算数值溢出,不仅仅要考虑redis-server,还要考虑redis-client的处理。

0 人点赞