Redis数据结构

20 阅读5分钟

1 概述

1.1 理解redis的数据结构和数据存储

redis暴露给用户的常见的数据类型包含5种(string, list, hash, set, zset),但是它们存储的内容和场景不同,Redis会在内存空间和性能两因素的作用下做出一个权衡,用到底层数据结构也不一样,以string为例,如果string是数字,底层存储用的就是一个整型值来存储的,而如果string是字符串,那么底层是用SDS结构体来存储的,但无论是数字还是字符串,暴露给用户的时候是一样的,都是以string这个类型暴露给用户的。完整的对应情况可以参看下图。

redis常用的5种数据类型定义(源码)

#define REDIS_STRING 0 // 字符串
#define REDIS_LIST 1 // 列表
#define REDIS_SET 2 // 集合
#define REDIS_ZSET 3 // 有序集
#define REDIS_HASH 4 // 哈希表

redis常用的10种底层数据结构(源码)

#define REDIS_ENCODING_RAW 0 // 编码为字符串
#define REDIS_ENCODING_INT 1 // 编码为整数
#define REDIS_ENCODING_HT 2 // 编码为哈希表
#define REDIS_ENCODING_ZIPMAP 3 // 编码为 zipmap
#define REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表
#define REDIS_ENCODING_ZIPLIST 5 // 编码为压缩列表
#define REDIS_ENCODING_INTSET 6 // 编码为整数集合
#define REDIS_ENCODING_SKIPLIST 7 // 编码为跳跃表

1.2 redis的通用结构体

redis的数据结构体都是用k-v键值对的形式存储的,其中k都是字符串类型(string),v的类型不确定,如前所述目前官方提供的常见的v的类型包含5种(string, list, hash, set, zset)。因为v的类型不确定,所以又用了redisObject这个结构体来封装了一层,来为5种不同的对象类型提供统一的表示形式。

redis最新版其实提供了10种数据类型

到redis最新的版本(7.0)为止,redis一共提供了10种数据类型

  • string(字符串)
  • hash(哈希)
  • list(列表)
  • set(集合)
  • zset(有序集合)
  • stream(流)
  • geospatial(地理)
  • bitmap(位图)
  • bitfield(位域)
  • hyperloglog(基数统计)
typedef struct redisObject {
    unsigned type:4; //对象的数据类型,占4bits,共5种类型         
    unsigned encoding:4;  //对象的编码类型,占4bits,共10种类型
    unsigned lru:LRU_BITS; //实用LRU算法计算相对server.lruclock的LRU时间
    int refcount; // 引用计数
    void *ptr; //指向底层数据实现的指针
} robj;

分别来解释这里面的字段

  • type:表示这个object是什么数据类型
  • encoding:表示这个object要被编码成什么底层数据结构
  • lru:记录RedisObject访问时间信息,在Redis设置了内存上限限制之后会根据该属性来回收redisObject从而达到释放内存的效果
  • refcount:引用计数,在Redis中同一个RedisObject可能被多个地方共用
  • *ptr:指向底层实现数据结构的指针

在详细介绍一下lru这个字段:Redis数据库是基于内存的,如果Redis内存使用量超过了机器物理内存,可能会导致Redis崩溃,为了应对这种场景,Redis允许用于设置数据库内存使用的上限,当内存使用量达到用户设置的上限,Redis便会采取一些特定的算法策略(目前支持LRU, LFU,Random等回收策略)对数据库中某些符合要求的数据进行回收, 而lru属性便会记录数据库键的空转时长或者一些访问频率信息供回收算法参考。

需要注意,redisObject中type=0时encoding不可能为0和1以外的数值,因为这是不合法的,其他数据类型同理,而且redis每个版本也可能会导致type对应的encoding的范围的变化,例如OBJ_ENCODING_LINKEDLIST在高版本中已经废弃不适用了。

2 常见的redis的底层数据结构

2.1 SDS和string

前面说过,redis对外暴露string数据类型,如果string是数字,底层存储用的就是一个整型值来存储的,而如果string是字符串,那么底层是用SDS结构体来存储的。

SDS实际上是一个改良版的字符串(char*)。最原始的C语言的字符串有两个棘手的业务痛点它并不能高效地支持长度计算(strlen)和追加(append)这两种操作:

  • 每次计算字符串长度(strlen(s))的复杂度为 O(N) 。
  • 对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。

SDS基于其做改良

struct sdshdr {
    int len; // buf 已占用长度
    int free; // buf 剩余可用长度
    char buf[]; // 实际保存字符串数据的地方
};


struct sdshdr {
    len = 11;
    free = 0;
    buf = "hello world\0";  // buf 的实际长度为 len + 1,因为\0是一定存在的,表示字符串的结束
};

这样对于

STRLEN操作:直接读取len参数

APPEND操作:如果free字段不是0,不引起buf的内存重分配,这样可以有效减少buf的内存分配的次数

分配策略:在目前版本的 Redis 中, SDS_MAX_PREALLOC的值为1024 * 1024 , 也就是说, 当大小小于1MB的字符串执行追加操作时, sdsMakeRoomFor 就为它们分配多于所需大小一倍的空间; 当字符串的大小大于 1MB , 那么 sdsMakeRoomFor 就为它们额外多分配 1MB 的空间。

3 参考链接

redis.io/docs/latest…

cloud.tencent.cn/developer/a…

axlgrep.github.io/tech/redis-…

<redisbook.readthedocs.io/en/latest/i…>