Redis 常见数据类型的使用场景以及底层结构

  • Post author:
  • Post category:其他




前言

Redis 是一种基于内存的高性能的键值存储系统,支持多种数据类型、持久化、高可用集群等。在 Redis 中,每种数据类型都有自己独特的底层实现方式,这些实现方式直接影响着 Redis 的性能。本文将介绍 Redis 各种数据类型的使用场景以及底层结构实现方式。

在介绍类型之前,不妨先了解下 Redis 在 C 语言中的结构长什么样?

redis struct

大概的结构如上图所示(并未列出所有字段,在文末会将具体的 C 语言结构列出),其实 Redis 的所有数据类型都是用同一张

外层哈希表

(ht[2])来保存的,通过 key 可以定位这个数据需要保存在 entry 数组的哪个位置,因为 dictEntry 的值是

viod*

表示任意类型(一般为 redisObject),所以不伦是 string、int、ziplist、linkedlist、intset、hashtable、skiplist 等都能支持。

为什么需要定义为 dictht dt[2] 呢?

这里的 ht[2] 数组,一个用来存储正常的数据,另一个是扩容时渐进式 rehash 时使用。由于 redis 扩容需要 rehash,如果一次性把所有的数据都进行 rehash 操作的话会导致线程阻塞,严重影响性能,因此在每次扩容时先把一部分数据先迁移到新的 ht 中,后续再渐进式的迁移,直到全部完成时释放掉旧的 ht。需要注意的是,在访问数据的时候,两个 ht 都需要去查找,新加的数据会被放到新的 ht 中。




数据类型构成及使用场景



String类型


String

类型的值最大长度为

512MB

,它是 Redis 最简单的数据类型,也是我们用的最多的一种,类似我们平常使用的

HashMap

都是键值对,底层结构实现方式也是最简单的。在 Redis 中,字符串类型的值被实现为一个简单的动态字符串,即 RedisObject 结构体。

这里的 String 并不是所有的都保存为 String,如果保存整数、浮点数,Redis 也只会保存整数、浮点数,并不一定为 String。



使用场景
  • 用来做计数器
  • 分布式锁
  • 当分布式的 HashMap 使用



Hash 类型


Hash

是一个键值对集合,类似于关联数组。哈希表类型的值是一个由多个键值对组成的无序集合。可以快速地查找某个键对应的值,还可以对键值对进行批量操作。

当数量不多时使用

ziplist

实现,键值对也是按照在

ziplist

中的对应顺序去取,如

hmset hkey a 1 b 2

,存在

ziplist

的顺序为[a, 1, b, 2] ;当

ziplist

中的元素数量超过 hash-max-ziplist-entries(默认为 512)时,Redis 会将其转换为 hashtable。此外,当哈希表中的某个元素的大小超过 hash-max-ziplist-value(默认为 64 字节)时,Redis 也会将其转换为 hashtable。



使用场景
  • 存储对象属性



List 类型


List

数据类型是一种双向链表的结构,支持在链表头部或尾部进行元素的添加、删除和查找,因此它可以用来表示队列或者栈等数据结构。List 的每个节点都存储了一个字符串类型的值,这些节点之间通过指针进行链接,从而形成了一个链表。

当数据量不多时,使用

ziplist

实现,达到一定数量时(list-max-ziplist-entries=512),为了减少更新操作对

ziplist

的影响,会将这些

ziplist

分段,并用

linkedlist

连接。



使用场景
  • 消息队列
  • 简单的发布订阅



Set 类型


Set

是一种无序的不重复的数据类型,类似于

Java

中的 Set 接口,它能进行稽核的交并差集计算。

如果保存的是

整型

数据且数据较少(server.set_max_intset_entries=512)时,则使用

intset

来保存,

intset

其实就是一个数组,添加时需要判重;当不是整数或者超过数据限制最大值时将使用

Hashtable

实现。



使用场景
  • 记录 websocket 连接数
  • 记录网站的 IP 白名单、IP 黑名单等
  • 社交关系:某个用户的粉丝列表、关注列表、好友列表
  • 点赞收藏:存储某个微博的点赞用户 ID、收藏用户 ID



ZSet 类型


ZSet

作用于

Set

类似,只不过它是有序的,可以通过

score

进行排序,可以按照分数去做排名操作。

它的底层在数据量少的时候同样的使用的是

ziplist

达到一定数量后才转为

skiplist



使用场景
  • 通过将 score 设置为时间戳来实现延时队列
  • 某网红直播间的排行榜,可以清楚的知道榜一大哥



其他

文章开头说的,Redis 在 C 语言中的结构代码(为了代码顺序好看,不考虑报错):

typedef struct redisServer {
    redisDb *db;    // 数据库数组
    int dbnum;    // 数据库数量
    dict *commands;    // Redis 命令表
    dict *lua_scripts;    // Lua 脚本缓存
    // ...
} redisServer;

typedef struct redisDb {
    dict *dict;    // 数据库的键值对存储在这里
    dict *expires;    // 存储所有键的过期时间
    dict *blocking_keys;    // 存储被阻塞的键
    dict *ready_keys;    // 存储已经就绪的键
    dict *watched_keys;    // 存储被 WATCH 命令监视的键
    int id;    // 数据库编号
} redisDb;

typedef struct dict {
    dictType *type;             // 类型特定函数
    void *privdata;             // 私有数据
    dictht ht[2];               // 两个哈希表
    long rehashidx;             // 重哈希进度标记
    unsigned long iterators;    // 正在迭代哈希表的迭代器数量
} dict;

typedef struct dictht {
    dictEntry **table;  // 哈希表数组
    unsigned long size; // 哈希表大小
    unsigned long sizemask; // 哈希表大小掩码,用于计算索引值
    unsigned long used; // 已经使用的节点数量
} dictht;

typedef struct DictEntry {
    void *key;    // 指向键的指针
    union {
        void *val;    // 指向值的指针
        uint64_t u64;    // 64 位无符号整数
        int64_t s64;    // 64 位有符号整数
        double d;    // 双精度浮点数
    } v;
    struct DictEntry *next;    // 哈希冲突时使用,指向下一个哈希表节点的指针
} dictEntry;

还有最重要的

redisObject

 typedef struct redisObject {
    unsigned type:4;       // 对象类型,字符串类型,type 值为 0、列表类型,type 值为 1、哈希表类型,type 值为 2、集合类型,type 值为 3
    unsigned encoding:4;   // 对象编码
    // 引用计数
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;             // 指向实际值的指针
} robj;



总结

文章一开头就先介绍了 Redis 其实都是通过 key 来共用一张哈希表,不同的数据类型只不过是 val 对应的类型有所区别;并介绍了各种数据类型需要什么数据结构以及在什么使用用哪种,但没有提到很多细节,只是简单的总结;最后给出 c 的结构体源码,这样能够更加清晰的了解他们的存储,但对于类似

ziplist



skiplist

等数据结构这里就不作介绍了,不是本文的重点。



版权声明:本文为qq_39363204原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。