SDS(simple dynamid string)

一、Redis的String的基本概念

1.1 二进制安全

Redis String 是二进制安全的。
二进制安全是指,在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译等,如果被攻击,能够及时检测出来。

1.2 Redis String值的最大长度为?

字符串值的最大长度为512 MB

(8 1024 1024 * 512) -1 = 2^32 - 1

二、SDS的定义

2.1 数据结构定义

  1. struct sdshdr {
  2. // 记录 buf 数组中已使用字节的数量
  3. // 等于 SDS 所保存字符串的长度
  4. int len;
  5. // 记录 buf 数组中未使用字节的数量
  6. int free;
  7. // 字节数组,用于保存字符串
  8. char buf[];
  9. };

Redis学习笔记之动态字符串(SDS) - 图1

2.2 SDS与C字符串的区别?

2.2.1 获取字符串长度方面
  • C语言获取字符串的长度是通过遍历字符串得到,时间复杂度为O(N),每次调用都需要遍历

Redis学习笔记之动态字符串(SDS) - 图2

  • Redis 中的SDS使用了变量len保存了字符串的长度,时间复杂度为O(1), 比C提高了效率

Redis学习笔记之动态字符串(SDS) - 图3

2.2.2 防止字符串缓存区溢出
  • C 字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)

假设程序里有两个在内存中紧邻着的 C 字符串 s1 和 s2 , 其中 s1 保存了字符串 “Redis” , 而 s2 则保存了字符串 “MongoDB” , 如图 2-7 所示。
Redis学习笔记之动态字符串(SDS) - 图4
此时,需要调用strcat(s1, “ Cluster”),将 s1 的内容修改为 “Redis Cluster” , 但却忘了在执行 strcat 之前为 s1 分配足够的空间, 那么在 strcat 函数执行之后, s1 的数据将溢出到 s2 所在的空间中, 导致 s2 保存的内容被意外地修改, 如图 2-8 所示。

Redis学习笔记之动态字符串(SDS) - 图5

2.2.3 优化内存重分配次数
  • C语言字符串内存重分配操作(会频繁进行内存重新分配,N次修改N次分配)
    • 增长字符串的操作时,需要先通过内存重分配来扩展底层数组的空间大小 —— 如果忘了这一步就会产生缓冲区溢出。
    • 缩短字符串的操作时,需要通过内存重分配来释放字符串不再使用的那部分空间 —— 如果忘了这一步就会产生内存泄漏。
  • Redis SDS 为了优化内存重分配次数做了以下操作(N次修改,最多N次分配)
    • 空间预分配

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

  • 惰性空间释放

惰性空间释放用于优化 SDS 的字符串缩短操作: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。当然SDS 也提供了相应的API,真正地释放 SDS 里面的未使用空间,避免造成内存浪费。
image.png

2.2.4 二进制安全问题

C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
以下例子,在C 字符串中遇到 ‘\0’ 会被误认为是字符串结尾, 最后读取的字符串只有Redis,而忽略之后的 “Cluster” 。

Redis学习笔记之动态字符串(SDS) - 图8

而在Redis的SDS中, 所有API都是二进制安全的,判断字符串结尾是使用字段len 判断的,所以不会出现C字符串中的情况, 可以保存字符串和二进制数据。
Redis学习笔记之动态字符串(SDS) - 图9

总结

C 字符串 SDS
获取字符串长度的复杂度为 O(N) 。 获取字符串长度的复杂度为 O(1) 。
API 是不安全的,可能会造成缓冲区溢出。 API 是安全的,不会造成缓冲区溢出。
修改字符串长度 N 次必然需要执行 N 次内存重分配。 修改字符串长度 N 次最多需要执行 N 次内存重分配。
可以使用所有 库中的函数。 可以使用一部分 库中的函数。

三、Redis Strings常用命令了解

  1. # 连接redis
  2. redis-cli -h 127.0.0.1 -p 6379
  3. #验证密码
  4. auth 123456

3.1 set 命令

  1. #添加一个key=test, value = java
  2. 127.0.0.1:6379> set test java
  3. OK

3.2 get 命令

  1. # 获取键的值
  2. 127.0.0.1:6379> get test
  3. "java"

3.3 APPEND 命令

  1. #命令返回字符串的长度
  2. #key 存在时,追加值到已有字符串的尾部
  3. 127.0.0.1:6379> APPEND test tutorial
  4. (integer) 12
  5. 127.0.0.1:6379> get test
  6. "javatutorial"
  7. #当key 不存在时,创建key,并设置空字符串,并追加12到尾部
  8. 127.0.0.1:6379> APPEND age 12
  9. (integer) 2

3.4 DECR

  • 对存储的key值进行减一操作
  • 如果操作的key 不存在,则会先初始化key,并设置key的值为0,再减一
  • 如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。
  • 这个操作最多支持64位有符号的正型数字。 ```shell

    对已有key 操作

    127.0.0.1:0>set num 10 “OK” 127.0.0.1:0>decr num “9”

对不存在的key 操作

127.0.0.1:0>decr num1 “-1”

对于不是整型的key 操作

127.0.0.1:0>set name Java95 “OK” 127.0.0.1:0>decr name “ERR value is not an integer or out of range”

对于超出64位的值操作

127.0.0.1:0>set num2 9999999999999999999999 “OK” 127.0.0.1:0>decr num2 “ERR value is not an integer or out of range”

  1. <a name="9DWdh"></a>
  2. #### 3.5 GETSET 命令
  3. - 自动将key对应到value并且返回原来key对应的value。
  4. - 如果key存在但是对应的value不是字符串,就返回错误。
  5. - 如果key不存在,则会创建key,并设置value,返回null
  6. ```shell
  7. #获取存在的key, 并设置新的值,返回旧的值
  8. 127.0.0.1:0>getset num 123
  9. "9"
  10. 127.0.0.1:0>getset num 456
  11. "123"
  12. #获取不存在的值
  13. 127.0.0.1:0>getset num3 123
  14. null
  15. 127.0.0.1:0>getset num3 456
  16. "123"

3.6 INCR 命令

  • 对存储在指定key的数值执行原子的加1操作
  • 如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0
  • 如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。
  • 这个操作仅限于64位的有符号整型数据。 ```shell

    存在的key

    127.0.0.1:0>get num “456” 127.0.0.1:0>incr num “457”

不存在的key

127.0.0.1:0>incr num4 “1” 127.0.0.1:0>incr num4 “2”

  1. <a name="AhfqZ"></a>
  2. #### 3.7 SETEX 命令
  3. - 设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期。
  4. ```shell
  5. #设置key,给定超时时间30秒,value 为java
  6. 127.0.0.1:0>setex expireKey 30 java
  7. "OK"
  8. ##使用ttl 命令查看key 的过期时间
  9. 127.0.0.1:0>ttl expireKey
  10. "15"
  11. 127.0.0.1:0>get expireKey
  12. "java"
  13. 127.0.0.1:0>ttl expireKey
  14. "4"
  15. #当过期时间为-2时,证明key已经被删除
  16. 127.0.0.1:0>ttl expireKey
  17. "-2"
  18. 127.0.0.1:0>get expireKey
  19. null

3.8 SETNX(SET if Not eXists)命令