05.Redis 数据结构与BitMap的使用

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 碰撞,将会采用头插法,形成链表,链表内存储指针,指向节点。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值