Redis中的数据结构

1. 五大基本数据类型和底层结构

  1. String(字符串)—— Redis 最基础的 “万能型” 结构

    • 底层实现(3 种编码自动切换):
      • int:存储 64 位整数(如 123、456),直接以整数形式存储,而非字符串,节省内存 + 支持原子增减;
      • embstr:存储短字符串(默认≤44 字节),将「Redis 对象头 + 字符串内容」连续存储在一块内存,减少内存碎片;
      • raw:存储长字符串(>44 字节),对象头和字符串内容分开存储,避免大字符串占用连续内存。
    • 核心特点:
      • Key 和 Value 本质都是字符串,Value 可存储文本、数字、二进制(如图片、序列化数据),最大 512MB;
      • 数字操作(INCR/DECR)是原子操作(单线程执行,无并发问题),这是计数器场景的核心优势。
    • 常用命令:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 基础操作
    SET key value [EX 过期时间] [NX/XX] # NX=不存在才设,XX=存在才设
    GET key # 获取值
    DEL key # 删除

    # 数字原子操作
    INCR key # 整数+1
    INCRBY key 10 # 整数+10
    DECR key # 整数-1

    # 字符串操作
    APPEND key "abc" # 追加字符串
    STRLEN key # 获取字符串长度
    SUBSTR key 0 2 # 截取子串(索引0到2)
    • 典型场景:
      • 计数器:文章阅读量、接口限流计数(INCR 原子性);
      • 缓存:简单的键值缓存(如用户昵称、接口返回数据);
      • 分布式锁:SET lock:order 1 NX EX 30(NX 保证互斥,EX 避免死锁)。
    • 关键补充:
      • int 编码的特殊情况:
        • 当 Value 是可转成 64 位整数的字符串(如 “123456”),Redis 会自动转 int 编码,直接用对象头的 ptr 存数值,完全不用 SDS 的 buf 数组,内存利用率拉满;
        • 只有数值超出 64 位范围,才会转 embstr/raw 编码。
      • embstr 和 raw 的核心区别:
        • embstr:一次性分配连续内存(对象头 + SDS + 字符串),销毁时一次性释放,效率高;但因为内存连续,无法扩容(扩容会触发转 raw);
      • raw:分两块内存存储,SDS 可自由扩容(按 len*2 预留空间),适合长字符串。
    • 核心总结(补全之前的知识点)
      • SDS 是 Redis 字符串的 “底层载体”:所有 String 类型(除 int 编码的整数)最终都由 SDS 存储,解决了 C 字符串的性能 / 功能缺陷;
      • int/embstr/raw 是 SDS 的 “编码优化”:Redis 根据字符串内容(整数 / 长短)选择不同编码,平衡内存和性能;
      • 核心逻辑:String 是 Redis 对外暴露的 “数据结构名”,SDS 是底层实现,int/embstr/raw 是基于 SDS 的编码策略 —— 三者的关系是「对外接口 → 编码优化 → 底层实现」。
  2. Hash(哈希)—— 适合存储 “结构化对象”

    • 底层实现(2 种编码自动切换):
      • ziplist(压缩列表):Hash 元素少(默认≤512 个)且字段 / 值都短(默认≤64 字节)时使用;
        • 底层是连续内存的数组,按「字段 + 值 + 字段 + 值」顺序存储,内存利用率极高;
        • 缺点:查找元素需遍历,元素多了性能下降。
      • hashtable(哈希表):Hash 元素多或字段 / 值长时自动切换;
    • 底层是数组 + 链表(Redis 6.0+ 引入哈希表扩容时的渐进式 rehash,避免阻塞);
      • 优点:查找 / 插入 / 删除 O (1),适合大哈希表;
      • 缺点:内存占用比 ziplist 高(有链表指针开销)。
    • 核心特点:
      • 一个 Key 对应多个「字段 (Field)- 值 (Value)」对,类似 Java 的 HashMap;
      • 可单独操作某个 Field,无需修改整个对象(比 String 存储 JSON 更灵活,节省带宽)。
    • 常用命令:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 基础操作
    HSET key field value # 设置单个字段
    HGET key field # 获取单个字段
    HGETALL key # 获取所有字段和值
    HDEL key field # 删除字段
    HLEN key # 获取字段数量

    # 原子操作
    HINCRBY key field 5 # 字段值(数字)+5
    • 典型场景:
      • 存储对象:用户信息(user:1001 → name: 张三,age:25, phone:138xxxx);
      • 购物车:cart:user:1001 → 商品 ID: 数量(如 10086:2, 10087:1)。
  3. List(列表)—— 有序可重复的 “双向链表”

    • 底层实现(Redis 3.2+ 统一为 quicklist):
      • 早期 Redis:短列表用 ziplist,长列表用 linkedlist(双向链表);
      • quicklist(快速列表):Redis 3.2+ 唯一实现,是「ziplist + linkedlist」的结合体;
        • 底层是双向链表,每个链表节点是一个 ziplist;
        • 既保留了 linkedlist 两端操作高效的特点,又通过 ziplist 减少内存碎片,平衡性能和内存。
    • 核心特点:
      • 有序、可重复,支持按索引访问;
      • 两端(头 / 尾)操作(LPUSH/RPUSH/LPOP/RPOP)效率 O (1),中间插入 / 删除 O (n)(需遍历);
      • 支持阻塞弹出(BRPOP/BLPOP),适合做简单的消息队列。
    • 常用命令:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 插入操作
    LPUSH key val1 val2 # 从左侧(头)插入
    RPUSH key val3 val4 # 从右侧(尾)插入

    # 弹出操作
    LPOP key # 左侧弹出(非阻塞)
    RPOP key # 右侧弹出(非阻塞)
    BRPOP key 0 # 右侧阻塞弹出(0=永久阻塞,直到有数据)

    # 查询/修改
    LRANGE key 0 -1 # 获取所有元素(0=-1表示全量)
    LLEN key # 获取列表长度
    LSET key 0 "newval" # 修改指定索引的元素
    • 典型场景:
      • 消息队列:简单异步任务(如订单通知、日志收集);
      • 最新榜单:朋友圈动态、评论列表(LPUSH 新增,LRANGE 取最新 N 条);
      • 栈 / 队列:LPUSH+LPOP = 栈,LPUSH+RPOP = 队列。
  4. Set(集合)—— 无序不可重复的 “哈希表 / 整数集”

    • 底层实现(2 种编码自动切换):
      • intset(整数集合):Set 中所有元素都是整数且数量少(默认≤512 个)时使用;
        • 底层是有序整数数组,内存利用率极高,查找用二分查找 O (logn);
        • 缺点:插入需移动数组元素,数量多了性能下降。
      • hashtable(哈希表):Set 包含非整数元素,或整数数量超过阈值时切换;
        • 底层是哈希表,Value 固定为 null(只利用 Key 去重);
        • 优点:查找 / 插入 / 删除 O (1),支持海量元素。
    • 核心特点:
      • 元素唯一(自动去重)、无序;
      • 支持高效的集合运算:交集(SINTER)、并集(SUNION)、差集(SDIFF)。
    • 常用命令:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 基础操作
    SADD key val1 val2 # 添加元素(自动去重)
    SMEMBERS key # 获取所有元素
    SISMEMBER key val1 # 判断元素是否存在(O(1))
    SREM key val1 # 删除元素
    SCARD key # 获取元素数量

    # 集合运算
    SINTER key1 key2 # 求交集(共同元素)
    SUNION key1 key2 # 求并集(所有元素)
    SDIFF key1 key2 # 求差集(key1有、key2无)

    # 随机操作
    SRANDMEMBER key # 随机获取一个元素(不删除)
    SPOP key # 随机弹出一个元素(删除)
    • 典型场景:
      • 去重:点赞、收藏、签到(避免重复操作);
      • 社交场景:共同好友、共同关注(SINTER);
      • 抽奖:随机抽取中奖用户(SRANDMEMBER/SPOP)。
  5. Sorted Set(ZSet)—— 带分数的 “有序集合”

    • 底层实现(2 种编码自动切换):
      • ziplist(压缩列表):ZSet 元素少(默认≤128 个)且成员 / 分数都短(默认≤64 字节)时使用;
        • 按「成员 + 分数」顺序存储,且按分数排序,内存利用率极高;
        • 缺点:查找 / 排序需遍历,元素多了性能下降。
      • skiplist + dict(跳表 + 哈希表):元素多或成员 / 分数长时切换(Redis 核心实现);
        • dict:存储「成员→分数」的映射,O (1) 查找成员的分数;
        • skiplist(跳表):存储「分数→成员」的有序结构,按分数排序,支持范围查询 O (logn);
        • 为什么不用红黑树?跳表实现更简单,范围查询更高效(红黑树范围查询需中序遍历)。
    • 核心特点:
      • 元素唯一,每个元素绑定一个浮点型 “分数(score)”,按分数有序排列;
      • 支持按分数范围、排名范围查询,排序效率极高。
    • 常用命令:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 基础操作
    ZADD key 95 user1 88 user2 98 user3 # 添加元素(score+成员)
    ZSCORE key user1 # 获取成员的分数
    ZREM key user2 # 删除成员

    # 按排名查询(分数从低到高)
    ZRANGE key 0 -1 WITHSCORES # 获取所有元素+分数
    ZRANGE key 0 1 WITHSCORES # 获取前2名(排名从0开始)

    # 按分数查询
    ZRANGEBYSCORE key 80 95 WITHSCORES # 分数80-95的元素
    ZCOUNT key 80 95 # 分数80-95的元素数量

    # 分数增减
    ZINCRBY key 2 user2 # user2分数+2(变为90)
    • 典型场景:
      • 排行榜:游戏积分、商品销量、文章点赞数(ZREVRANGE 取前 N 名);
      • 延时队列:score 设为过期时间戳,轮询 ZRANGEBYSCORE 获取到期任务;
      • 权重排序:按优先级处理任务(score 越高,优先级越高)。

关键底层设计总结(新手必记)

  • 编码自动切换:Redis 对所有复杂结构都做了 “小数据用紧凑编码(ziplist/intset)、大数据用高效编码(hashtable/skiplist)” 的优化,平衡内存和性能;
  • 单线程 + 高效结构:Redis 单线程执行命令,底层用哈希表、跳表、双向链表等高效结构,保证核心操作 O (1)/O (logn),性能极高;
  • 内存友好:紧凑编码(ziplist/intset)无指针开销,内存利用率比纯链表 / 哈希表高一个量级,适合小数据场景。

核心场景速记

数据结构 核心优势 最佳场景
String 简单、原子数字操作 缓存、计数器、分布式锁
Hash 结构化存储、按需修改 用户信息、购物车、对象缓存
List 有序、两端操作高效 消息队列、最新榜单、栈 / 队列
Set 去重、集合运算 点赞、共同好友、抽奖
ZSet 有序、分数排序 排行榜、延时队列、权重任务