Redis 基本特性
- 非关系型的键值对数据库,可以根据键以O(1) 的时间复杂度取出或插入关联值
- Redis 的数据是存在内存中的
- 键值对中键的类型可以是字符串,整型,浮点型等,且键是唯一的
- 键值对中的值类型可以是string,hash,list,set,sorted set 等
- Redis 内置了复制,磁盘持久化,LUA脚本,事务,SSL, ACLs,客户端缓存,客户端代理(6.0新特性)等功能
- 可以搭建 Redis 哨兵架构和 Redis Cluster 模式提供高可用性
Redis的数据结构
Redis 是使用 C 语言编写的,但其底层数据结构不是简单的 C 语言字符串,而是定义的一种新的数据类型 SDS(Simple Dynamic String,简单动态字符串),它是一种二进制安全的数据结构,且兼容了 C 语言的函数库。在不同版本的 Redis 中 SDS 的数据结构又相差较大,但是其核心思想是不变的。
二进制安全: C语言中,使用 ‘\0’ 表示字符串的结束,如果字符串本身包含有 ‘\0’ 字符,字符串就会被截断,即非二进制安全,SDS 通过某种机制,保证了读写字符串时发生截断,保证了二进制安全。
SDS 的最大长度: 512M,因为 len 是使用 int 修饰的,故 buf 最大长度就是 232 - 1,即 512M。
Redis3.2 以前底层的数据结构0
struct sdshdr {
// 记录buf []中已使用字节的数量,等于SDS所保存字符串的长度
unsigned int len;
// 记录buf []中未使用字节的数量
unsigned int free;
// char数组,用于保存字符串,真正存储的内容
char buf[];
};
扩容机制:如果你修改了一个数据的长度,Redis 会认为你之后还可能继续修改,将进行内存预分配机制。如果剩余空间大于新增长度,则无需扩容,如果剩余空间不足,则 new len = (len + addlen) x 2,addlen 是增加的长度,如果长度达到 1024 x 1024 (1M)后,每次扩容长度增加 1M。需要注意的是,Redis 的SDS扩容是不可逆的。
为什么不直接使用 buf [ ] 数组呢?
- C 语言字符串会自动在尾部追加 ‘\0’ 字符串结尾,Redis 需要兼容其他语言,如果直接使用,会造成误结束的问题
- buf 尾部会自动追加一个 ‘\0’ 字符,但并不会计算在 SDS 的 len 中以兼容 C 语言,使得 SDS 可以直接使用一部分string.h库中的函数,如strlen
该数据结构为什么会在 Redis3.2以后被放弃呢?
- 不同长度的字符串占用的头部(len + free)是相同的,但是字符串很短,头部空间却更大,造成了空间的浪费
Redis3.2后的数据结构
在 Redis3.2 以后,对 SDS 的结构进行了优化,为了避免头部浪费空间,Redis 将其数据结构又分成了三种级别、五种类型的SDS(sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64)。
- 短字符串(长度小于32),len 和 free 的长度用1字节即可
- 长字符串,用 2 字节或者4字节
- 超长字符串,用 8 字节
Redis3.2 后的 SDS 底层源码
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 低3位存储类型, 高5位存储长度 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* 已使用 */
uint8_t alloc; /* 总长度,用1字节存储 */
unsigned char flags; /* 低3位存储类型, 高5位预留 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* 已使用 */
uint16_t alloc; /* 总长度,用2字节存储 */
unsigned char flags; /* 低3位存储类型, 高5位预留 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* 已使用 */
uint32_t alloc; /* 总长度,用4字节存储 */
unsigned char flags; /* 低3位存储类型, 高5位预留 */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* 已使用 */
uint64_t alloc; /* 总长度,用8字节存储 */
unsigned char flags; /* 低3位存储类型, 高5位预留 */
char buf[];
};
我们可以发现,此时的 SDS 减少了 free 字段,增加了 alloc 和 flag 字段,并且不同的 SDS 类型使用了不同长度的类型定义 header。之前的 free 也可以通过 alloc - len 得出。同时还增加了对其填充(struct attribute ((packed)))。比如短字符串,1个字节的flag中,低三位存储类型,高5位存储长度,最多能标识的长度为32,所以短字符串的长度必定小于32,所有不需要其他标识
sdshdr5与sdshdr8的数据结构
Redis 的 K-V 结构
Redis 支持海量的数据存储,使用数组 + 链表的方式存储这些键,当我们插入一个键的时候,这个将会对这个 key 进行 Hash 随机散列成一个自然数,这个自然数肯会比较大,底层会对其进行求模运算,将这个 key 散列到数组上,如果发生 Hash 碰撞,将会采用头插法,形成链表,链表内存储指针,指向节点。