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 的特点
-
O(1)时间复杂度获取字符串长度
- 传统C字符串需要遍历整个字符串才能获取长度(O(n))
- SDS 直接读取 len 属性即可(O(1))
-
杜绝缓冲区溢出
- SDS API 会先检查空间是否满足需求,不满足会自动扩展
-
减少内存重分配次数
- 空间预分配:当 SDS 需要扩展时,不仅分配必要空间,还会分配额外未使用空间
- 惰性空间释放:缩短字符串时不立即释放多余空间,而是通过 alloc 和 len 的差值保留
-
二进制安全
- 可以存储任意二进制数据,不局限于文本
- 不以空字符'\0'作为字符串结束标识
-
兼容部分 C 字符串函数
- buf 数组仍然以空字符结尾,可以重用部分 C 库函数
SDS 不同类型的内存优化
Redis 3.2 后引入的多种 SDS 结构:
| 类型 | 长度范围 | 头大小 | 适用场景 |
|---|---|---|---|
| sdshdr5 | 0-31字节 | 1字节 | 极小字符串 |
| sdshdr8 | 0-255字节 | 3字节 | 短字符串 |
| sdshdr16 | 0-65,535字节 | 5字节 | 中等长度 |
| sdshdr32 | 0-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:
- 集合元素数量超过
set-max-intset-entries - 尝试添加非整数元素
- 添加的整数过大(超过当前编码范围且升级后超过
set-max-intset-value)
两种实现的对比
| 特性 | intset | hashtable |
|---|---|---|
| 存储类型 | 仅整数 | 任意字符串 |
| 内存效率 | 高 | 较低 |
| 查找复杂度 | O(log n) | O(1) |
| 插入复杂度 | O(n)(可能触发升级) | O(1) |
| 适用场景 | 小整数集合 | 大集合或含非整数元素 |