redis从入门到熟练 (一) String,Set类型详解

113 阅读6分钟

1.介绍

本篇文章主要介绍了什么是redis,以及redis的几种常见的数据类型以及底层结构

2.什么是redis

Redis是一个由C语言开发的nosql数据库,以k-v键值对的方式存储数据,存储在内存中,因此读写速度很快,一般用作缓存。

redis由5种基本数据类型(String,Set,List,Zset,Hash)和3种特殊数据类型(bitmap,hyperloglog,geospecial)组成,对每种数据类型从介绍是什么有哪些功能,应用场景,底层的数据结构是什么三个维度去分析

前面提到redis以k-v的方式存储字符串,其中的v就是我们的数据类型了。

3.String 字符串

1.是什么,有哪些功能

String,就是个字符串,意味着你可以通过k值来取出字符串的内容,redis的string是二进制安全的,可以存放字符串,数字,地址,图片的二进制文件

2.应用场景

1.缓存常规数据
缓存session,token,字典数据,商品信息缓存,对象存储(不频繁改变对象内容,如果频繁改变对象内容需要用hash)

2.计数的场景
文章访问量,点赞数,限流器

3.分布式锁
用setnx或者是redssion实现分布式锁

3.底层数据结构

Redis 没有直接使用 C 语言的传统字符串(以空字符'\0'结尾的字符数组),而是自定义了 SDS 作为字符串的底层实现,这使得 Redis 字符串具有更高的效率和安全性。

struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len;        // 已使用的长度
    uint32_t alloc;      // 分配的总容量(不包括头和空字符)
    unsigned char flags; // 低3位表示类型,高5位未使用
    char buf[];          // 实际存储的字符数组
};

SDS 的特点

  1. O(1)时间复杂度获取字符串长度

    • 传统C字符串需要遍历整个字符串才能获取长度(O(n))
    • SDS 直接读取 len 属性即可(O(1))
  2. 杜绝缓冲区溢出

    • SDS API 会先检查空间是否满足需求,不满足会自动扩展
  3. 减少内存重分配次数

    • 空间预分配:当 SDS 需要扩展时,不仅分配必要空间,还会分配额外未使用空间
    • 惰性空间释放:缩短字符串时不立即释放多余空间,而是通过 alloc 和 len 的差值保留
  4. 二进制安全

    • 可以存储任意二进制数据,不局限于文本
    • 不以空字符'\0'作为字符串结束标识
  5. 兼容部分 C 字符串函数

    • buf 数组仍然以空字符结尾,可以重用部分 C 库函数

SDS 不同类型的内存优化

Redis 3.2 后引入的多种 SDS 结构:

类型长度范围头大小适用场景
sdshdr50-31字节1字节极小字符串
sdshdr80-255字节3字节短字符串
sdshdr160-65,535字节5字节中等长度
sdshdr320-4,294,967,295字节9字节长字符串
sdshdr64超大字符串17字节极少使用

SDS 与 C 字符串对比

特性C 字符串SDS
长度获取O(n)O(1)
缓冲区安全不安全安全
内存分配策略每次修改都需要预分配+惰性释放
存储内容仅文本任意二进制数据
兼容C函数完全兼容部分兼容

4.set 无序集合

1.是什么,有哪些功能

Redis Set(集合)是一个无序的、元素唯一的字符串集合。它是基于哈希表实现的,因此添加、删除和查找操作的时间复杂度都是 O(1)。

2.应用场景

1.唯一数据存储
IP黑名单/白名单,商品唯一编码集合

2.需要获取多个数据源交集、并集和差集的场景
共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集)等等。

3.抽奖,随机推荐

3.底层数据结构

Redis Set(集合)的底层实现采用了两种主要数据结构,会根据集合元素的数量和特性自动选择最合适的

1. intset(整数集合)

适用场景:当集合中的所有元素都是整数,并且元素数量较少时(默认配置下元素数量小于512个)

typedef struct intset {
    uint32_t encoding;  // 编码方式(INTSET_ENC_INT16/32/64)
    uint32_t length;    // 元素个数
    int8_t contents[];  // 元素数组
} intset;

特点

  • 内存紧凑,连续存储整数
  • 自动升级机制:当新加入的整数无法用当前编码存储时,会自动升级整个集合的编码(从16位→32位→64位)
  • 有序存储:虽然Redis Set对外表现是无序的,但intset内部元素是按从小到大排序的
  • 二分查找:查找时间复杂度为O(log n)

示例

SADD numbers 1 2 3 4 5  # 小整数集合会使用intset

2. hashtable(哈希表)

适用场景

  • 集合中包含非整数元素
  • 元素数量超过配置的阈值(默认512个)
  • 单个元素过大(默认64字节以上)

哈希表实现特点:

  • 使用与Redis字典相同的哈希表实现
  • 只有key没有value(所有value都为NULL)
  • 自动扩容/缩容机制

结构

struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];     // 两个哈希表,用于渐进式rehash
    long rehashidx;
    unsigned long iterators;
};

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

在传统哈希表实现中,扩容/缩容时需要一次性将所有元素从旧表迁移到新表,这会导致单次操作耗时较长,可能阻塞 Redis 的响应。Redis 通过双哈希表机制将 rehash 过程分摊到多个操作中逐步完成。

在rehash的时候每次对字典的增删改查操作都会顺带迁移该区域位置整个桶(bucket)到另一个区域

自动转换规则

Redis 会根据以下配置参数自动决定使用哪种实现(可在redis.conf中配置):

set-max-intset-entries 512  # intset最大元素数
set-max-intset-value 64     # 单个元素最大字节数

当以下任一条件满足时,Redis会将intset转换为hashtable:

  1. 集合元素数量超过set-max-intset-entries
  2. 尝试添加非整数元素
  3. 添加的整数过大(超过当前编码范围且升级后超过set-max-intset-value

两种实现的对比

特性intsethashtable
存储类型仅整数任意字符串
内存效率较低
查找复杂度O(log n)O(1)
插入复杂度O(n)(可能触发升级)O(1)
适用场景小整数集合大集合或含非整数元素