- 存储类型与操作命令
| 类型(Type key) | 存储编码(OBJECT encoding key) | 实例命令 |
|---|---|---|
| string | int存储8个字节的长整型 (存储范围long,2^63-1) | set num 6379、 incr num、 incrby num 100 、decr num、 decrby num 100 |
| string | embstr格式的SDS,存储小于44个字节的字符串 | set name tom、strlen name、append name ok、set ff 2.2、 incrbyfloat ff 4.4 |
| string | raw格式的SDS,存储大于44个字节的字符串或起始类型为int,通过append命令追加字符串 | set song haskhsdhehhdshnnxcdshhhqqhohdjahlsdahdqohjkljklj set song 1 append song a |
问题:存储编码什么时候转化?会还原吗?
答:存储编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换(不包括重新set)
问题:为什么采取多种存储编码
答:首先是节省内存空间,另外还有内存回收与内存再分配策略相关
存储原理
- SDS(Simple Dynamic String 简单动态字符串)
SDS是Redis开发设计的数据结构,其本质就是扩展了C语言用于存储字符串的字符串数组**(char [] buf)**
struct sdshdr{
uint32_t len;// 记录buf数组内已使用的字节的数量等于SDS保存字符串的长度
uint32_t alloc;// 记录buf数组内未使用的字符数量
unsigned char flag;//低3位用于存储,高5位用于预留
char buf[];// 字节数组,用于保存字符串
}
- SDS与C字符串的区别
- 常数复杂度获取字符串长度
| C字符串数组 | SDS |
|---|---|
| C字符串不会记录自身的长度信息,所以若要获取字符串长度,必须必须遍历整个字符串,这个操作复杂度为O(n) | SDS中的len属性记录了字符串长度,所以通过SDS的len属性立刻知道字符串长度,这个操作复杂度O(1) |
- 杜绝缓冲区溢出
| C字符串数组 | SDS |
|---|---|
| 因为C字符串不记录自身的长度,所以在使用strcat(char *dest ,char *src)这个函数时可能造成缓冲区溢出,举例:内存紧邻的s1与s2,其中s1保存了'Redis',s2保存了'MongoDB',若s1使用strcat(s1,'cluster');由于执行函数前未对s1分足够的空间导致数据溢出到了s2所在的空间中,导致s2保存的内容被意外地修改了 | SDS的空间分配策略完全杜绝了发生缓冲区溢出的情况,当SDS API需要对SDS进行修改时,API会先检查SDS的空间是否满足所需要求**(alloc属性)**,若不满足的话API会自动将SDS的空间扩展至执行修改所需的大小,然后才执行实际的修改操作 |
- 减少修改字符串带来的的内存重分配次数
| C字符串数组 | SDS |
|---|---|
| 因为C字符串不记录自身的长度,所以对于一个包含N个字符串的C字符串来说,这个字符串的底层实现总是一个N+1个字符长的数组**(额外1个字符空间用于保存空字符)**,所以每次增长或者缩短字符串长度需要对字符串数组内存重新分配: 1. 增长字符串操作:拼接(append),执行这个操作之前若未分配空间,造成缓冲区溢出 2.截断字符串操作:截断(trim),执行这个操作若未释放不再使用的空间,造成内存溢出 | SDS通过未使用空间解除了字符串长度和底层数组长度之间的关联:在SDS中,buf数组的长度不一定就是字符串数量加一,数组里面可以包含未使用的字节,而这些字节的数量由SDS的free属性记录 |
- SDS 实现了空间预分配和惰性空间释放两种优化策略
| 空间预分配(SDS字符串增长操作) | 惰性空间释放(SDS字符串缩短操作) |
|---|---|
| 1.若SDS的长度**(即len属性的值)**将小于1MB,那么程序分配和len属性同样大小相同的未使用空间,这个时候SDS len属性与free属性值相同,举例:若对s1进行了修改,其len为13字节,那么程序会分配13字节未使用空间,即 len与free相等,此时s1实际占用空间大小为:13(len)+13(free)+1byte(额外的一个字节保存空字符) = 27字节 2.若对SDS进行了修改其长度大于等于1MB,那么程序会分配1MB未使用空间,举例:若对s1进行了修改,其len为30MB那么程序会分配1MB的未使用空间(free),此时s1实际占用空间大小为:30MB(len)+1MB(free)+1byte(额外的一个字节保存空字符) | 当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用,举例:对SDS所存字符串执行sdstrim操作,对于释放的空间仍然保存在SDS内(free),若SDS需要增加内容即不需要内存重新分配,SDS也提供了相应的API,让我们可以在有需要的时,真正地释放SDS的未使用空间,所以不用担心惰性空间释放策略会造成内存浪费 |
- 二进制安全
| C字符串数组 | SDS |
|---|---|
| C语言判断字符串结束通过空字符串('\0'),若所存字符串包含多个'\0'以至于无法完整读取所存内容 | SDS通过使用len属性的值而不是空字符('\0')来判断字符串是否结束 |
- 兼容部分C字符串函数
- C字符串和SDS之间区别总结
| C字符串数组 | SDS |
|---|---|
| 获取字符串长度时间复杂度O(n) | 获取字符串长度时间复杂度O(1) |
| API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
| 修改字符串N次必然执行N次内存分配 | 修改字符串N次最多执行N次内存分配 |
| 只能保存文本数据 | 可以保存文本或者二进制数据 |
| 可以使用所有<string.h>库中函数 | 可以使用一部分<string.h>库中函数 |
应用场景
- 缓存
- 分布式Session
- 分布式锁 set NX EX
- 分布式全局ID incr
- 计数器 incr
- 限流 incr
- 位操作