Redis数据结构和应用场景
Redis 之所以强大和受欢迎,很大程度上是因为它提供了丰富的数据结构,而不仅仅是一个简单的键值存储。这些数据结构让开发者可以直接在存储层解决许多问题,而无需在应用层进行复杂的处理。
Redis 的数据结构可以分为两大类:
- 基本数据结构:String(字符串)、List(列表)、Hash(哈希)、Set(集合)、Sorted Set(有序集合)。
- 特殊和高级数据结构: Bitmaps(位图)、HyperLogLogs(基数统计)、Geospatial Indexes(地理空间索引)、Streams(流)。这些通常是在基本数据结构之上实现的,但提供了独特的 API 来解决特定问题。
一、基本数据结构
1. String(字符串)
描述:最基本的数据类型,一个 Key 对应一个 Value。它是二进制安全的,意味着可以存储任何数据,比如图片、序列化对象等。Value 最大不能超过 512MB。
常用命令:SET, GET, MSET, MGET, INCR, DECR, INCRBY, SETEX(设置过期时间)等。
典型应用场景:
- 缓存(Cache):这是最经典的场景。将数据库查询结果、热点数据、会话信息(Session)等序列化成字符串后存入 Redis,加速访问。
- 计数器(Counter):利用
INCR,DECR命令实现点赞数、浏览数、库存扣减等。这些操作是原子性的,无需担心并发问题。 - 分布式锁(Distributed Lock):使用
SET key value NX EX seconds命令实现简单的分布式锁(NX 表示只有 key 不存在时才设置,EX 设置过期时间)。 - Session 共享:在分布式集群中,将用户的 Session 信息统一存储在 Redis 里,实现多台服务器间的状态共享。
2. List(列表)
描述:一个简单的字符串列表,按插入顺序排序。你可以在头部(left)或尾部(right)添加元素。底层实现是双向链表,所以操作头尾元素的速度极快。
常用命令:LPUSH, RPUSH, LPOP, RPOP, LRANGE, BLPOP(阻塞式弹出)等。
典型应用场景:
- 消息队列(Message Queue):使用
LPUSH+BRPOP组合实现一个简单的 FIFO(先进先出)队列。生产者LPUSH消息,消费者用BRPOP阻塞地获取消息。 - 最新列表(Timeline):例如朋友圈的最新动态、新闻网站的最新文章列表。使用
LPUSH将新内容插入列表头部,再用LRANGE 0 9获取最新的 10 条。 - 排行榜(简单版):如果需要实时更新的排行榜,但逻辑不复杂,可以用 List 存储。
3. Hash(哈希)
描述:是一个键值对集合,特别适合用于存储对象。类似于编程语言中的 Map 或 dict 类型。
常用命令:HSET, HGET, HMSET, HMGET, HGETALL, HINCRBY 等。
典型应用场景:
- 存储对象信息:这是最自然的应用。例如存储用户信息(
user:1)、商品信息(product:101)。可以单独获取或修改对象的某个字段(如HGET user:1 name),而不用像 String 那样需要序列化/反序列化整个对象。 - 购物车:以用户ID为 Key,商品ID为 Field,商品数量为 Value。可以轻松地添加商品、增加数量、获取所有商品。
4. Set(集合)
描述:是 String 类型的无序集合,通过哈希表实现,保证了元素的唯一性(自动去重)。支持高效的集合操作,如交集、并集、差集。
常用命令:SADD, SMEMBERS, SISMEMBER, SINTER(交集), SUNION(并集), SDIFF(差集), SPOP(随机弹出)等。
典型应用场景:
- 标签(Tag):例如给用户打标签(
SADD user:1:tags tag1 tag2),给文章打标签。可以很方便地找到有相同标签的人或文章(求交集)。 - 共同关注/好友(Social Graph):
SINTER user:A:follows user:B:follows可以快速计算出 A 和 B 的共同关注。 - 抽奖/随机事件:
SADD添加所有参与者,SMEMBERS查看所有参与者,SPOP随机移除并返回一个元素(用于抽奖)。 - 数据排重:例如对爬取的 URL 进行去重。
5. Sorted Set(有序集合 / ZSet)
描述:与 Set 类似,也是 String 类型元素的集合,且不允许重复。但每个元素都会关联一个 double 类型的分数(score)。Redis 正是通过这个分数来为集合中的成员进行从小到大排序。元素是唯一的,但分数可以重复。
常用命令:ZADD, ZRANGE(按分数正序), ZREVRANGE(按分数倒序), ZRANK(获取排名), ZRANGEBYSCORE 等。
典型应用场景:
- 排行榜(Leaderboard):这是最完美的场景。例如游戏玩家积分排行榜、视频热度排名。用
ZADD更新分数,ZREVRANGE来获取前十名,ZRANK来查看某用户的排名。 - 带权重的队列:分数可以作为优先级,实现优先级队列。
- 范围查找(Range Query):例如处理延时任务,将任务执行时间作为 score,用
ZRANGEBYSCORE查询到期的任务。或者统计一段时间内的操作记录。
二、特殊和高级数据结构
1. Bitmaps(位图)
描述:本质上不是一种独立的数据结构,它其实是基于 String 类型的一套面向位的操作方法。可以将 String value 看作是一个由比特位组成的数组,并能对其中任意一位进行置位(set)、清零(get)、计数等操作。
常用命令:SETBIT, GETBIT, BITCOUNT, BITOP(位操作)等。
典型应用场景:
- 大量布尔值的存储与统计:例如用户的每日签到记录(1 表示签到,0 表示未签到),一年只需 365 bit ≈ 46 字节。
- 用户活跃度统计:使用
SETBIT记录用户是否活跃,BITCOUNT可以快速统计某段时间内活跃用户数,BITOP AND/OR可以计算共同活跃天数等。
2. HyperLogLogs
描述:用于做基数统计的算法。它的优点是,在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定且很小的。每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。注意:它只会根据输入元素来计算基数,而不会储存输入元素本身。
常用命令:PFADD, PFCOUNT, PFMERGE。
典型应用场景:
- 大规模数据去重统计:例如统计一个网站的 UV(独立访客数)、搜索关键词的不重复数量。它提供的是近似值,有小于 1% 的误差,但对于大数据量且不要求绝对精确的场景非常高效。
3. Geospatial Indexes(地理空间索引)
描述:在 Redis 3.2 版本中推出。用于存储和操作地理位置信息,如经纬度。底层基于 Sorted Set 实现。
常用命令:GEOADD, GEOPOS(获取位置), GEODIST(计算距离), GEORADIUS(查找指定半径内的元素)等。
典型应用场景:
- 附近的人/地点(Nearby):例如美团/饿了么查找附近的餐馆,滴滴/ Uber 查找附近的车辆。
- 计算两地距离:
GEODIST可以轻松计算出两个地理位置之间的直线距离。
4. Streams(流)
描述:在 Redis 5.0 中引入的新的强大数据结构。它提供了一个完整的、支持多消费者的消息队列功能,弥补了之前用 List 做消息队列时功能上的不足(如没有消息回溯、消费者组等功能)。
常用命令:XADD(添加消息), XREAD(读取消息), XGROUP(创建消费者组)等。
典型应用场景:
- 可靠的消息队列(Message Queue):类似于 Kafka 的日志流,可用于事件溯源、实时消息传输、监控数据采集等场景。它支持消费者组(Consumer Group),确保每条消息能被多个组消费,且在同一个组内只能被一个消费者消费。
总结与选择建议
| 数据结构 | 特性 | 典型场景 |
|---|---|---|
| String | 最简单的键值对 | 缓存、计数器、分布式锁 |
| List | 有序、可重复、双向链表 | 消息队列、最新列表 |
| Hash | 存储对象、可操作单个字段 | 用户信息、购物车 |
| Set | 无序、唯一、支持集合运算 | 标签、共同好友、抽奖 |
| Sorted Set | 有序、唯一、按分数排序 | 排行榜、延时任务、范围查找 |
| Bitmaps | 位操作、极其节省空间 | 签到统计、用户画像 |
| HyperLogLog | 超大基数去重统计、有误差 | UV 统计 |
| Geo | 地理位置计算 | 附近的人、计算距离 |
| Streams | 持久化的消息流、消费者组 | 可靠的消息队列 |
选择哪种数据结构,取决于你的具体业务需求:
- 需要存一个简单的值或做计数器?用 String。
- 需要存一个对象并单独修改其字段?用 Hash。
- 需要保证消息顺序或实现简单队列?用 List。
- 需要去重或求交集并集?用 Set。
- 需要排序和排行榜?用 Sorted Set。
- 需要统计大量的是/否状态?用 Bitmaps。
- 需要估算巨大集合的不重复元素数?用 HyperLogLog。
- 需要处理地理位置?用 Geo。
- 需要构建一个可靠的消息系统?用 Streams。


