Skip to content

Redis 数据类型

Redis 存储架构层级

Redis 整个结构抽象:

数据库(redisDb)字典(dict)哈希表(dictht)哈希桶(dictEntry数组)值对象(redisObject)

1. 数据库层 redisDb

Redis 单实例默认划分为 16 个独立的数据库,以实现逻辑上的环境隔离,避免键名冲突

c
typedef struct redisDb {
    dict *dict;     // 键空间字典 存放所有键值对的入口
    dict *expires;  // 过期字典 记录每条 key 的存活时间
    int id;         // 数据库ID(0~15)
    // ... 其他字段(如阻塞keys、watched keys)
} redisDb;

2.字典 dict

每个数据库有一个字典结构,字典有两个哈希表(ht[0]ht[1]),实现渐进式 Rehash

c
typedef struct dict {
    dictht ht[2];           // 两个哈希表(ht[0]主表,ht[1]Rehash临时表)
    long rehashidx;         // Rehash进度(-1表示未启动,≥0表示当前迁移到第几个桶)
    unsigned long iterators;// 当前运行的迭代器数量(安全迭代用)
    // ...
} dict;

渐进式 Rehash 触发条件

  • 扩容:当 ht[0].used / ht[0].size > load_factor(默认负载因子为1)
  • 缩容:当 ht[0].used < ht[0].size / 10

渐进式 Rehash 过程

  1. 分配新哈希表 ht[1] 空间
  2. 设置 rehashidx = 0 ,表示 Rehash 开始
  3. 每次操作迁移一个桶
  4. 若用户进行查询操作,先去 ht[0] 查找,若未找到,则去 ht[1] 查找
  5. 若用户进行新增操作,则直接写入 ht[1]
  6. rehashidx 逐步增加,直到所有桶迁移完成

3.哈希表 dictht

哈希表是承载哈希桶数组的实际容器,管理内存中的键值对分布

c
typedef struct dictht {
    dictEntry **table;       // 哈希桶数组(每个元素是链表的头节点指针)
    unsigned long size;      // 桶的总数量(数组长度,总为 2^n)
    unsigned long sizemask;  // 掩码 = size - 1(快速计算索引:hash & sizemask)
    unsigned long used;      // 已存储键值对的数量
} dictht;

4.哈希桶 dictEntry

c
typedef struct dictEntry {
    void *key;               // 键(指向SDS字符串)
    union {
        void *val;           // 值(指向redisObject)
        uint64_t u64;
        int64_t s64;
        // ... 其他数值类型
    } v;
    struct dictEntry *next;  // 下一个Entry指针(链地址法解决冲突)
} dictEntry;

5.值对象 redisObject

统一封装 Redis 所有的值数据类型,携带 类型编码内存管理LRU 信息

c
typedef struct redisObject {
    unsigned type:4;       // 数据类型(4位,如 OBJ_STRING、OBJ_LIST)
    unsigned encoding:4;   // 编码方式(4位,决定底层结构,如 REDIS_ENCODING_INT)
    unsigned lru:24;       // LRU时间戳或LFU计数(内存淘汰策略使用)
    int refcount;          // 引用计数(用于内存回收)
    void *ptr;             // 指向实际数据的指针(如指向sds、quicklist结构体等)
} robj;

全链路示例:读取一个 Key 的过程

  1. 用户发起请求Get user:1001
  2. 数据库定位:根据当前 SELECT 的数据库ID,找到对应的 redisDb
  3. 哈希表选择
    • 若未在 Rehash ,则直接在 ht[0] 中查找
    • 若在 Rehash ,则先在 ht[0] 中查找,若未找到,则去 ht[1] 中查找
  4. 哈希计算与桶定位
    • 对 Key user:1001 执行哈希函数,找到桶索引 index
    • 遍历链表(dictEntry->next),比较每个节点的 Key 是否匹配
  5. 获取值对象
    • 找到 Key 对应的 dictEntry,获得 Val 指针 ➡ redisObject.
    • 根据 redisObjecttypeencoding 字段,决定如何解析数据
    • type=OBJ_STRINGencoding=OBJ_ENCODING_EMBSTR ,直接读取嵌入的字符数据
  6. 返回结果:将值反序列化客户端协议格式并返回

String 字符串

String 类型是最基本的数据类型,一个 key 对应一个 value

String 类型是二进制安全的,Redis 的 String 可以包含任何数据,如数字,字符串,jpg图片或序列化的对象

编码方式

  • int:保存有符号整数值,使用 8 个字节,支持 64 位整数
  • embstr:保存长度小于 44 字节的简单动态字符串,SDS(Simple Dynamic String)
  • raw:保存长度大于 44 字节的字符串;如果 embstr 发生修改,则直接转换为 raw,无论长度是否大于 44 字节。

SDS 简单动态字符串

Redis 没有复用 C 语言的字符串,而是新建了字节的字符串结构 SDS ,Redis 中的所有键都是 SDS 结构

C
// SDS 类型(根据长度自动选择)
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len;    // 字符串实际长度(已用字节)
    uint8_t alloc;  // 总分配空间(不含头部和末尾的\0)
    unsigned char flags; // SDS 类型标识(低 3 位)
    char buf[];     // 字符数组(兼容 C 字符串)
};

常用命令

类别命令说明示例
读写SET key value [NX/XX]存值(NX:不存在才存;XX:存在才覆盖SET user:1001 "Alice" NX
GET key取值GET user:1001"Alice"
数值增减INCR key整数值 +1INCR counter1
INCRBY key increment增加指定整数值INCRBY counter 56
DECR key整数值 -1DECR stock99
批量操作MSET key1 val1 key2 val2批量存值MSET a 1 b 2
MGET key1 key2批量取值MGET a b["1", "2"]
过期控制SETEX key sec value存值并设置秒级 TTLSETEX otp:1001 60 "38493"
TTL key查看剩余存活时间(秒)TTL otp:100158

Hash 散列

Hash 是 Redis 中的 K-V 键值对结构,特别适合用来存储对象

编码方式

  • zipList:压缩列表,当哈希对象键值对数量小于 512 个;所有键值对的键和值的字符串长度都小于等于 64 byte 时采用此数据结构
  • hashTable:当超过上述限制时,从 zipList 升级为 hashTable,但不支持降级。
  • listPack:Redis 7 使用 listPack 替代了 zipList

压缩列表 ZipList

ZipList.png

ZipList 为了节约内存而开发的,它是由连续内存块组成的顺序型数据结构,有点类似于数组

ZipList 是一种经过特殊编码的双向链表它不存储指向前一个链表节点的 prev 和指向下一个链表节点的指针 next,而是存储上一个节点长度和当前节点长度

通过牺牲部分读写性能,来换取高效的内存空间利用率,节约内存,只用在字段个数少,字段值小的场景里面

为什么有链表了,还要使用压缩列表

因为压缩列表不存储指针,有时候指针的存储空间甚至比值本身还大,大大节省了内存空间

ZipList 连锁更新问题

  • 比如,初始所有节点的 prevlen 长度都为 1 个字节 (前节点长度均 < 254)
  • 此时在头部插入一个长度 >= 254 的大节点,导致第二个节点的 prevlen 扩展为 5 字节 ➡ 后续所有节点都需要更新 prevlen

因此 Redis 7 使用 listPack 替代了 zipListlistPack 最大的区别是保存的是本节点的长度,而不是前节点长度

常用命令

类别命令说明示例
读写字段HSET key field value设置字段值HSET user:1001 name "Bob"
HGET key field获取字段值HGET user:1001 name"Bob"
批量操作HMSET key field1 val1 ...批量设置字段(推荐使用 HSETHSET user:1001 age 30 ...
HMGET key field1 field2批量获取字段HMGET user:1001 name age
全量操作HGETALL key获取所有字段和值HGETALL user:1001 → 完整哈希
原子增减HINCRBY key field increment增减数值(支持负数)HINCRBY user:1001 age 131

List 列表

Redis 的 List 是一种有序的序列数据结构,支持在头尾两端高效地插入和删除元素

编码方式

  • 3.2 版本之前,采用 ZipList (小数据)+ LinkedList (大数据) 实现
  • 3.2 版本之后,采用 QuickList 实现

快表 QuickList

QuickList.png

QuickList 是整体结构是链表,每个节点又是压缩列表,结合了 ZipListLinkedList ,平衡了内存占用和读写性能

常用命令

类别命令说明示例
插入元素LPUSH key elem1 elem2左插入元素LPUSH tasks "task1" "task2"
RPUSH key elem1 elem2右插入元素RPUSH log:1001 "error1" "..."
弹出元素LPOP key [count]左弹出元素(Redis 6.2+ 支持弹出多个)LPOP tasks 2 → 弹出两个
RPOP key [count]右弹出元素RPOP log:1001
范围查询LRANGE key start end范围查询(0 -1 表示全部)LRANGE queue 0 9
阻塞弹出BLPOP key1 key2 timeout阻塞左弹出(秒级等待)BLPOP orders 10

Set 集合

Set 是 Redis 中的无序且唯一集合数据结构,支持高效的集合运算(交集、并集、差集等)

编码方式

  • intset:集合元素都是整数,且元素个数 <= set-max-intset-entries(默认 512)
  • hashTable:反之使用 hashTable

常用命令

类别命令说明示例
元素管理SADD key member1 ...添加元素SADD tags:news "AI"
SREM key member删除元素SREM tags:news "Blockchain"
集合运算SINTER key1 key2交集SINTER user:a:tags user:b:tags
SUNION key1 key2并集SUNION group1 group2
SDIFF key1 key2差集(key1 - key2SDIFF all_users vip_users
元素检查SISMEMBER key member检查是否存在SISMEMBER tags:news "AI"0

ZSet 集合

ZSet 是 Redis 中的有序且唯一的数据结构,每个元素关联一个分数 score ,通过分数进行排序

编码方式

  • zipList:元素数量 <= set-max-intset-entries(默认 128); 所有元素的长度 <= 64 byte
  • skipList:反之使用 skipList 跳表

SkipList 跳表

跳表.png

跳表是多层链表结构

  • 最底层:是完整的双向链表,包含所有元素,按 score 排序
  • 上层链表:指数减少节点数量(概率随机生成层数),用于加快查找速度

查找路径

  • 从高层链表开始,右向查找(若下一个节点值 <= 目标 继续,否则下探一层)
  • 重复直到最底层找到目标节点或确定不存在

双向链表的时间复杂度为 O(N) , 跳表的时间复杂度最低为 O(logN)

常用命令

类别命令说明示例
元素管理ZADD key score member [...]添加/更新元素(分数可重复,值唯一)ZADD leaderboard 100 "user1" 90 "user2"
排名查询ZRANGE key start end [WITHSCORES]升序查询(WITHSCORES 返回分数)ZRANGE leaderboard 0 2 WITHSCORES
ZREVRANGE ...降序查询ZREVRANGE leaderboard 0 0
分数范围ZRANGEBYSCORE key min max查询分数介于 [min, max] 的元素ZRANGEBYSCORE grades 80 100
排名计算ZRANK key member获取升序排名(从 0 开始)ZRANK leaderboard "user1"0
Redis7新增命令ZMPOP numkeys key [MIN/MAX] [COUNT]原子弹出指定数量的最高/最低分成员ZMPOP 1 leaderboard MAX COUNT 2

Stream 流

Redis 提供的消息队列,实际应用有限(实际生产采用 MQ 中间件)

HyperLogLog 基数统计

超低内存消耗的基数统计,误差率为 0.81% ,适合做高并发基数统计,比如 UV/DAU 统计

GEO 地理位置

基于 ZSet 实现的地理位置存储与计算,适合寻找附近的商家、配送范围计算等

Bitmaps 位图

二进制位标记与统计,非常适合签到打卡、在线用户状态场景,非常节省内存空间