Redis中的数据结构
1.五种常见的数据结构类型
1. 字符串(String)
特点
- 字符串是 Redis 最基本的数据类型,可以存储字符串、整数或浮点数。
- 一个字符串最多可以存储 512 MB 的数据。
使用场景
- 缓存简单数据:比如存储用户的登录状态、会话信息或配置信息。
- 计数器:字符串支持自增和自减,可以方便地用来实现计数器,如网站访问次数统计。
- 分布式锁:通过
SETNX
命令可以实现分布式锁。
2. 列表(List)
特点
- 列表是一个有序的字符串序列,可以从两端添加或移除元素。
- 支持常见的栈和队列操作(如 LIFO 和 FIFO)。
使用场景
- 消息队列:列表支持从头部或尾部推入和弹出元素,因此常用于简单的消息队列。
- 任务列表:可以用来存储一系列需要按顺序处理的任务。
- 时间轴:社交媒体的时间轴可以用列表来实现,记录用户的操作历史等。
3. 集合(Set)
特点
- 集合是无序且唯一的字符串集合,元素不重复。
- 支持交集、并集和差集等集合运算。
使用场景
- 标签和关注关系:如用于存储用户的关注列表、标签等,避免重复。
- 抽奖系统:用户抽奖时,可以使用集合确保每位用户只参与一次。
- 共同好友推荐:通过交集运算,找出两个用户共同关注的好友。
4. 有序集合(Sorted Set)
特点
- 有序集合中的每个元素都有一个分数(score),Redis 会根据分数对元素进行排序。
- 支持按分数范围查询元素,也可以按分数排名来访问元素。
使用场景
- 排行榜:常用于实现游戏或应用的排行榜,按得分高低排序。
- 任务调度:可以根据任务的优先级来安排执行顺序。
- 带权重的数据:例如评分系统,可以根据评分来展示内容的排名。
5. 哈希(Hash)
特点
- 哈希是一个键值对集合,适合存储对象或数据结构。
- 可以把多个字段及其值存储在一个键下,通过字段名称快速访问字段的值。
使用场景
- 用户信息存储:可以用来存储用户信息(如用户名、年龄、地址等),并快速访问特定字段。
- 缓存对象数据:适合存储一些需要快速访问的对象或数据结构。
- 配置项管理:适合存储分模块的配置信息。
类型对比
数据类型 | 是否有序 | 是否唯一 | 适用场景 |
---|---|---|---|
字符串 | 否 | 否 | 简单数据、计数器、锁 |
列表 | 是 | 否 | 消息队列、任务列表 |
集合 | 否 | 是 | 标签、抽奖系统 |
有序集合 | 是 | 是 | 排行榜、带权重数据 |
哈希 | 否 | 否 | 用户信息、配置项 |
2.后续引入的四种数据类型
1. BitMap
版本
- Redis 2.2 版引入。
特点
- BitMap 并不是 Redis 独立的数据类型,而是一种基于字符串类型的位操作方法。
- BitMap 允许我们将一个大的字符串值视为一系列的位(bit),并可以对每一位进行单独操作。
- BitMap 中的每一位可以是 0 或 1,能高效地进行位操作,例如设置某一位的值、获取某一位的值,或者统计多个位中的 1 的个数。
使用场景
- 用户签到:可以用一个 BitMap 表示每个用户在一年中的签到情况。假设有 365 天,用 365 位表示即可。
- 活跃用户统计:在每一位表示一个用户的活跃状态,0 表示不活跃,1 表示活跃。通过位操作快速统计活跃用户。
- 二进制标记:用于对大量用户的某些特定标记进行标识和统计。
示例
假设我们想记录一个用户在一个月中的签到情况,可以用 BitMap 操作设置某天为签到:
# 设置用户在第 5 天签到
SETBIT user:1001:checkin 5 1
# 获取用户第 5 天的签到状态
GETBIT user:1001:checkin 5 # 返回 1
# 统计这个用户的签到天数
BITCOUNT user:1001:checkin
2. HyperLogLog
版本
- Redis 2.8 版引入。
特点
- HyperLogLog 是一种基数估计算法,可以高效地计算大规模数据的基数(独特元素的数量)。
- HyperLogLog 的存储空间是固定的,只占用 12 KB 内存,即使是十亿级的去重数据,也不会增加内存占用。
- HyperLogLog 通过概率算法近似计算基数,因此有误差(标准误差约 0.81%),但通常可接受。
使用场景
- UV(独立访问用户)统计:在大数据量的情况下,用 HyperLogLog 统计页面的独立访问用户数量。
- 去重计数:如在电商网站中统计特定时间内访问商品详情页的独立用户数,HyperLogLog 非常适合这种场景。
示例
使用 HyperLogLog 统计每天访问某网站的用户数量:
# 将用户 ID 添加到 HyperLogLog
PFADD page:1001:user_set user1 user2 user3
# 获取 HyperLogLog 基数
PFCOUNT page:1001:user_set # 返回基数估算值
# 合并多个 HyperLogLog 的基数
PFMERGE combined_users page:1001:user_set page:1002:user_set
3. GEO
版本
- Redis 3.2 版引入。
特点
- GEO 是 Redis 的地理位置数据类型,基于有序集合实现,能够存储地理位置信息(经纬度)并支持地理操作。
- Redis 提供了一系列 GEO 命令,如添加位置、计算两地距离、查询指定范围内的位置等。
使用场景
- 附近的人:在社交或打车应用中,可以快速查找某个范围内的用户或服务提供者。
- POI(兴趣点)查询:例如餐馆、加油站等可以存储在 Redis 中,用户可以根据当前地点查询附近的 POI。
示例
假设我们要在 Redis 中存储和查询城市的地理位置:
# 添加地理位置(城市名称和经纬度)
GEOADD cities 13.361389 38.115556 "Palermo"
GEOADD cities 15.087269 37.502669 "Catania"# 查询两个城市之间的距离
GEODIST cities "Palermo" "Catania" km # 返回距离,单位为公里# 获取指定地点附近的城市
GEORADIUS cities 15 37 200 km # 返回 200 公里范围内的地点
4. Stream
版本
- Redis 5.0 版引入。
特点
- Stream 是一种可无限增长的日志结构数据类型,支持队列和发布-订阅模式,适合处理实时数据流。
- Stream 提供了对消息的追加、读取、分组消费等操作,并可以按 ID 或时间戳查询数据。
- Stream 支持消费者分组,允许不同消费者组读取相同的数据流。
使用场景
- 日志和事件存储:可以用于存储系统日志、用户行为数据等,可以在需要时实时读取。
- 消息队列:Stream 的消费者分组特性使其适合构建简单的消息队列系统。
- 实时数据处理:如 IoT 设备传回的数据流处理,Redis Stream 可以充当数据缓冲区。
示例
使用 Stream 存储和读取事件:
# 添加一条新事件(Stream 会自动生成 ID)
XADD mystream * temperature 22.5 humidity 60# 读取最新的事件
XRANGE mystream - + # 返回按时间排序的所有事件# 创建消费者组
XGROUP CREATE mystream mygroup $ # $ 表示从最新消息开始消费# 消费者组读取消息
XREADGROUP GROUP mygroup consumer1 COUNT 2 STREAMS mystream >
类型对比
数据类型 | 引入版本 | 特点 | 使用场景 |
---|---|---|---|
BitMap | 2.2 | 位操作,高效标记和统计 | 用户签到、活跃用户统计 |
HyperLogLog | 2.8 | 基数估计,固定存储 | UV统计、大数据去重计数 |
GEO | 3.2 | 地理位置存储和查询 | 附近的人、POI查询 |
Stream | 5.0 | 实时数据流、消费者分组 | 消息队列、实时数据处理 |
3.五种常见数据类型的实现
1. 字符串(String)
- 实现方式:Redis 使用 简单动态字符串(SDS) 实现字符串类型。
- 结构:
- len:记录字符串的实际长度,避免每次操作都需要重新计算长度。
- alloc:记录已分配的空间大小,方便动态扩展。
- buf:用于存储字符串数据的实际内容。
- 特点:
- 预分配空间:SDS 扩展空间时会预留一些额外空间,减少扩展操作的频率。
- 惰性空间释放:在缩短字符串时,直接更新
len
,而不立即释放多余内存,减少频繁内存重分配操作。
- 适用场景:适合存储单个字符串数据、整数、浮点数等简单数据,支持 512 MB 以内的内容。
2. 列表(List)
- 实现方式:Redis 使用 双向链表(quicklist) 或 压缩列表(ziplist) 来实现列表类型。
- 结构:
- Quicklist:一种特殊的双向链表结构,结合了链表和压缩列表。链表节点存储压缩列表,支持快速的头部和尾部插入、删除操作。
- Ziplist:连续内存存储的小型双向链表,适用于少量短字符串的列表,减少内存开销。
- 特点:
- 高效插入和删除:Quicklist 支持快速的头部和尾部操作,适合频繁的增删操作。
- 内存优化:当数据量小且节点较少时,压缩列表减少内存消耗。
- 适用场景:适合存储有序的字符串序列,通常用于消息队列或任务队列等需要顺序访问的场景。
3. 集合(Set)
- 实现方式:Redis 使用 整数集合(intset) 或 哈希表(hashtable) 来实现集合类型。
- 结构:
- Intset:连续存储的整数集合,适用于只包含整数且元素数量较少的集合。
- Hashtable:包含更多元素或非整数元素时使用哈希表,以保证插入和查找的效率。
- 特点:
- 高效查找和去重:哈希表结构能够快速判断元素是否存在。
- 内存优化:小型整数集合使用 intset 节省内存。
- 适用场景:适合需要快速查重和集合操作的场景,如标签、用户权限管理等。
4. 有序集合(Sorted Set)
- 实现方式:Redis 使用 压缩列表(ziplist) 或 跳表(skiplist) 来实现有序集合。
- 结构:
- Ziplist:小型集合时使用压缩列表,减少内存开销。
- Skiplist:当元素数量或元素大小较多时使用跳表,保证数据按分数排序和高效的范围查询。
- Hashtable:跳表配合哈希表存储键值对,保证查找效率。
- 特点:
- 有序性和高效查询:跳表能快速进行范围查询和按分数排序。
- 节省内存:小集合使用压缩列表存储,减少开销。
- 适用场景:适合按优先级或分数排序的场景,如排行榜、任务调度等。
5. 哈希(Hash)
- 实现方式:Redis 使用 压缩列表(ziplist) 或 哈希表(hashtable) 来实现哈希类型。
- 结构:
- Ziplist:当哈希键包含少量字段时使用压缩列表,节省内存。
- Hashtable:字段多或较大时使用哈希表,保证高效的读写操作。
- 特点:
- 高效存储和读取:小型哈希对象用压缩列表减少内存开销。
- 高效扩展:当哈希结构变大时自动转为哈希表,提升操作速度。
- 适用场景:适合存储对象数据,如用户信息等,需要存储多个字段的情况。
总结
数据类型 | 实现方式 | 结构描述 | 特点 | 适用场景 |
---|---|---|---|---|
字符串 | 简单动态字符串(SDS) | - len :记录实际长度- alloc :已分配空间大小- buf :存储字符串数据的内容 | - 预分配空间减少扩展操作 - 惰性释放优化内存分配 | 存储单个字符串、整数、浮点数等简单数据 |
列表 | 双向链表(quicklist)或压缩列表(ziplist) | - Quicklist:链表节点存储压缩列表,适合频繁增删 - Ziplist:连续存储短字符串,减少内存占用 | - 高效插入、删除操作 - 小数据时内存优化 | 有序字符串序列,消息或任务队列 |
集合 | 整数集合(intset)或哈希表(hashtable) | - Intset:适合小型整数集合 - Hashtable:适合更多元素或非整数集合,快速插入查找 | - 高效查找去重 - 小型集合使用 intset 优化内存 | 需要快速查重和集合操作的场景,如标签或权限管理 |
有序集合 | 压缩列表(ziplist)或跳表(skiplist) | - Ziplist:小型集合减少内存开销 - Skiplist:支持按分数排序、范围查询 - Hashtable:与跳表结合,确保高效查找 | - 支持有序性和快速范围查询 - 小数据时节省内存 | 排行榜、优先级队列等按分数排序的场景 |
哈希 | 压缩列表(ziplist)或哈希表(hashtable) | - Ziplist:小型哈希节省内存 - Hashtable:字段多时使用,保证读写效率 | - 高效读写 - 小型哈希使用 ziplist 优化内存 | 存储对象数据,如用户信息等需要多个字段的情况 |
Redis 的这种“结构适应性”设计,让它可以根据数据特点自动调整底层实现,在保证数据存储灵活性的同时,保持高效的查询和操作速度。