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 数据结构定义
struct sdshdr {
// 记录 buf 数组中已使用字节的数量
// 等于 SDS 所保存字符串的长度
int len;
// 记录 buf 数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
};
2.2 SDS与C字符串的区别?
2.2.1 获取字符串长度方面
- C语言获取字符串的长度是通过遍历字符串得到,时间复杂度为O(N),每次调用都需要遍历
- Redis 中的SDS使用了变量len保存了字符串的长度,时间复杂度为O(1), 比C提高了效率
2.2.2 防止字符串缓存区溢出
- C 字符串不记录自身长度带来的另一个问题是容易造成缓冲区溢出(buffer overflow)
假设程序里有两个在内存中紧邻着的 C 字符串 s1 和 s2 , 其中 s1 保存了字符串 “Redis” , 而 s2 则保存了字符串 “MongoDB” , 如图 2-7 所示。
此时,需要调用strcat(s1, “ Cluster”),将 s1 的内容修改为 “Redis Cluster” , 但却忘了在执行 strcat 之前为 s1 分配足够的空间, 那么在 strcat 函数执行之后, s1 的数据将溢出到 s2 所在的空间中, 导致 s2 保存的内容被意外地修改, 如图 2-8 所示。
2.2.3 优化内存重分配次数
- C语言字符串内存重分配操作(会频繁进行内存重新分配,N次修改N次分配)
- 增长字符串的操作时,需要先通过内存重分配来扩展底层数组的空间大小 —— 如果忘了这一步就会产生缓冲区溢出。
- 缩短字符串的操作时,需要通过内存重分配来释放字符串不再使用的那部分空间 —— 如果忘了这一步就会产生内存泄漏。
- Redis SDS 为了优化内存重分配次数做了以下操作(N次修改,最多N次分配)
- 空间预分配
空间预分配用于优化 SDS 的字符串增长操作: 当 SDS 的 API 对一个 SDS 进行修改, 并且需要对 SDS 进行空间扩展的时候, 程序不仅会为 SDS 分配修改所必须要的空间, 还会为 SDS 分配额外的未使用空间。
- 惰性空间释放
惰性空间释放用于优化 SDS 的字符串缩短操作: 当 SDS 的 API 需要缩短 SDS 保存的字符串时, 程序并不立即使用内存重分配来回收缩短后多出来的字节, 而是使用 free 属性将这些字节的数量记录起来, 并等待将来使用。当然SDS 也提供了相应的API,真正地释放 SDS 里面的未使用空间,避免造成内存浪费。
2.2.4 二进制安全问题
C 字符串中的字符必须符合某种编码(比如 ASCII), 并且除了字符串的末尾之外, 字符串里面不能包含空字符, 否则最先被程序读入的空字符将被误认为是字符串结尾 —— 这些限制使得 C 字符串只能保存文本数据, 而不能保存像图片、音频、视频、压缩文件这样的二进制数据。
以下例子,在C 字符串中遇到 ‘\0’ 会被误认为是字符串结尾, 最后读取的字符串只有Redis,而忽略之后的 “Cluster” 。
而在Redis的SDS中, 所有API都是二进制安全的,判断字符串结尾是使用字段len 判断的,所以不会出现C字符串中的情况, 可以保存字符串和二进制数据。
总结
C 字符串 | SDS |
---|---|
获取字符串长度的复杂度为 O(N) 。 | 获取字符串长度的复杂度为 O(1) 。 |
API 是不安全的,可能会造成缓冲区溢出。 | API 是安全的,不会造成缓冲区溢出。 |
修改字符串长度 N 次必然需要执行 N 次内存重分配。 | 修改字符串长度 N 次最多需要执行 N 次内存重分配。 |
可以使用所有 |
可以使用一部分 |
三、Redis Strings常用命令了解
# 连接redis
redis-cli -h 127.0.0.1 -p 6379
#验证密码
auth 123456
3.1 set 命令
#添加一个key=test, value = java
127.0.0.1:6379> set test java
OK
3.2 get 命令
# 获取键的值
127.0.0.1:6379> get test
"java"
3.3 APPEND 命令
#命令返回字符串的长度
#key 存在时,追加值到已有字符串的尾部
127.0.0.1:6379> APPEND test tutorial
(integer) 12
127.0.0.1:6379> get test
"javatutorial"
#当key 不存在时,创建key,并设置空字符串,并追加12到尾部
127.0.0.1:6379> APPEND age 12
(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”
<a name="9DWdh"></a>
#### 3.5 GETSET 命令
- 自动将key对应到value并且返回原来key对应的value。
- 如果key存在但是对应的value不是字符串,就返回错误。
- 如果key不存在,则会创建key,并设置value,返回null
```shell
#获取存在的key, 并设置新的值,返回旧的值
127.0.0.1:0>getset num 123
"9"
127.0.0.1:0>getset num 456
"123"
#获取不存在的值
127.0.0.1:0>getset num3 123
null
127.0.0.1:0>getset num3 456
"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”
<a name="AhfqZ"></a>
#### 3.7 SETEX 命令
- 设置key对应字符串value,并且设置key在给定的seconds时间之后超时过期。
```shell
#设置key,给定超时时间30秒,value 为java
127.0.0.1:0>setex expireKey 30 java
"OK"
##使用ttl 命令查看key 的过期时间
127.0.0.1:0>ttl expireKey
"15"
127.0.0.1:0>get expireKey
"java"
127.0.0.1:0>ttl expireKey
"4"
#当过期时间为-2时,证明key已经被删除
127.0.0.1:0>ttl expireKey
"-2"
127.0.0.1:0>get expireKey
null
3.8 SETNX(SET if Not eXists)命令
- 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。
- 返回1, key 已存在,0 key不存在
可以使用该命令实现分布式锁
127.0.0.1:0>SETNX mykey "Hello"
"1"
127.0.0.1:0>SETNX mykey "World"
"0"
127.0.0.1:0>get mykey
"Hello"
参考
- http://www.redis.cn/
- https://www.cnblogs.com/jing99/p/11687308.html
- http://redisbook.com/preview/sds/different_between_sds_and_c_string.html
- 《Redis的设计与实现》