1.SDS
- 没有使用C语言传统的字符串表示,而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS) 的抽象类型,并将SDS用作Redis的默认字符串表示。在Redis里,包含字符串值得键值对在底层都是由DS来实现的。
- SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输入缓冲区,都是由SDS实现的。
SDS定义:
struct sdshdr{
// 记录buf数组已使用字节数量
int len;
// 记录buf数组中未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
SDS相比较于c字符串的好处
- 取字符长度,SDS直接读取len属性。
- 杜绝缓冲区溢出。C字符串拼接时时假定已经为拼接的字符串预留了足够多的内存,如果这个假定不成立,那么就会产生缓冲区溢出。而SDS是这样做的:SDS的API会会先检查SDS的空间是否满足所需的要求,如果不满足,API自动将空间扩展至所需大小。
- 减少修改字符串长度时所需的内存重分配次数。
- 二进制安全。
- 兼容部分C字符串函数。
SDS空间分配策略:
- 通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略。
- 空间预分配: 用于优化SDS的字符增长操作:程序不仅会为SDS分配修改所必须要的空间,还会为SDS分配额外的未使用空间。(**具体:**1. len(SDS)< 1mB时,分配len(free)=len,2.如果len(SDS) >1mb,分配len(free)=1mb)
- 惰性空间释放: 用于优化SDS字符缩短操作:当缩短字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性将这些字节的数量记录起来,并等待将来使用。
2. 对象系统
- Redis基于以上数据结构创建了一个对象系统,每种对象都用到了至少一种之前介绍的数据结构。
- 每次当我们在Redis中新创建一个键值对时,我们至少会创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。
使用对象系统的好处:
(1)在执行命令时,根据对象类型判断一个对象是否可以执行给定的命令
(2)针对不同的使用场景,为对象设置多种不同的数据结构,从而优化对象在不同场景下的使用效率;
(3)基于对象引用计数技术实现内存的回收;
(4)通过引用计数技术实现对象的共享;
在Redis中每个对象都由一个redisObject结构表示,包含了type,encoding,ptr属性:
/*
* Redis 对象
*/
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
类型:
其中, 键总是一个字符串对象,而值可以是其上任意一种。
Type命令的实现方式: Type(键名) : 返回的结果为数据库键对应的值对象的类型。
编码和底层实现:
ptr:指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。
encoding:记录了对象所使用的编码,也就是说这个对象使用了什么数据结构作为对象的底层实现,有如下:
每种类型的对象至少使用了两种不同的编码,如下表所示对应关系:
使用OBJECT ENCODING命令可以查看一个数据库键的值对象的编码。
- 使用encoding属性来设定对象所使用的编码,极大的提升了Redis的灵活性和效率。
3. 各对象对应的编码
- String对象的编码可以是int、embstr、raw。
- 列表对象的编码可以是ziplist或者linkedlist。
- 哈希对象的编码可以是ziplist或者是hashtable。
- 集合对象的编码可以是intset或者hashtable。
- 有序集合的编码可以是ziplist或者skiplist。
参考:《Redis设计与实现》第二版—黄健宏