Redis5种基本数据类型和3种特殊数据类型介绍

引言

在当今这个数据驱动的时代,高效的数据存储与访问能力成为衡量系统性能的关键标尺。作为最受欢迎的内存数据库之一,Redis以其惊人的速度和灵活的数据模型,成为了高并发场景下的明星选手。但你是否曾好奇,究竟是什么让Redis能在微秒级别响应海量请求?答案就藏在它精心设计的数据结构之中。

Redis远不止是一个简单的键值存储------它是一个拥有丰富数据类型生态系统的数据结构服务器。从看似简单的字符串到复杂的概率数据结构,每一种数据类型都针对特定场景进行了深度优化。理解这些数据结构的特性、底层原理和适用场景,就像是掌握了Redis这位"瑞士军刀"中的每一把工具,让你能够根据不同的业务需求选择最合适的解决方案。

想象一下这样的场景:你需要统计千万级用户的日活跃量,但又不想消耗数十GB内存;你需要实现实时排行榜,同时支持快速的范围查询;你需要管理地理位置数据,提供"附近的人"这样的功能......这些看似复杂的需求,在Redis中都有优雅的解决方案。

本篇博客将带你深入探索Redis的五种基本数据类型和三种特殊数据类型。我们将从底层实现原理出发,通过实际案例解析,为你揭示:

如何用String实现高性能分布式锁

如何用HyperLogLog仅用12KB统计亿级UV

如何用Geospatial构建实时地理位置服务

以及更多数据结构的实战技巧与性能调优策略

无论你是正在学习Redis的初学者,还是希望优化现有系统性能的资深开发者,这篇文章都将为你提供有价值的见解和实用的解决方案。让我们一同开启这场Redis数据结构探索之旅,解锁高效数据处理的密码。

2. 五种基础数据类型

2.1 String(字符串)

底层数据结构: SDS(Simple Dynamic String)简单动态字符串

自动扩容,减少内存分配次数

二进制安全,可存储任意数据

O(1)获取字符串长度

bash 复制代码
SET key value [EX seconds] [PX milliseconds] [NX|XX]
GET key
INCR key          # 原子递增
DECR key          # 原子递减
APPEND key value  # 追加字符串
STRLEN key        # 获取长度
MSET/MGET         # 批量操作

优点

操作简单高效

支持丰富的数据操作(原子增减等)

可存储任意二进制数据

缺点

大字符串可能占用大量内存

频繁修改可能导致内存碎片

应用场景

缓存数据(用户信息、商品信息)

计数器(浏览量、点赞数)

分布式锁

Session存储

2.2 List(列表)

底层数据结构: quicklist(Redis 3.2+)

双向链表 + ziplist组合

每个节点是一个压缩的ziplist

早期版本:ziplist(元素少时)/ linkedlist(元素多时)

bash 复制代码
LPUSH/RPUSH key element1 [element2...]
LPOP/RPOP key
LRANGE key start stop  # 获取指定范围元素
LINDEX key index       # 按索引获取元素
LLEN key               # 获取长度
BLPOP/BRPOP            # 阻塞式弹出

优点

  • 两端插入删除O(1)
  • 支持分页查询
  • 可实现队列和栈

缺点

  • 随机访问性能差
  • 中间插入删除成本高

应用场景

  • 消息队列
  • 最新消息/文章列表
  • 关注列表
  • 历史记录

2.3 Hash(哈希表)

底层数据结构 ziplist(元素少时)

压缩列表,内存连续

dict(hashtable)(元素多时)

数组+链表实现

渐进式rehash

bash 复制代码
HSET key field value
HGET key field
HGETALL key           # 获取所有字段
HMSET key field1 value1 [field2 value2...]
HDEL key field1 [field2...]
HINCRBY key field increment

优点

适合存储对象

字段可单独操作

内存效率较高(ziplist时)

缺点

大Hash可能rehash阻塞

不适合存储超大规模数据

应用场景

用户信息存储

购物车

对象属性存储

配置信息

2.4 Set(集合)

底层数据结构

  • intset(元素都为整数且数量少时)有序整数数组
  • dict(hashtable)(默认), 只使用键,值为NULL
bash 复制代码
SADD key member1 [member2...]
SMEMBERS key         # 获取所有成员
SISMEMBER key member # 判断是否存在
SINTER key1 key2     # 交集
SUNION key1 key2     # 并集
SDIFF key1 key2      # 差集
SCARD key            # 获取基数

优点

去重能力强

集合运算高效

判断存在性O(1)

缺点

无序存储

大数据集可能占用较大内存

应用场景

标签系统

共同好友/关注

抽奖/随机推荐

数据去重

2.5 ZSet(有序集合)

底层数据结构

  • ziplist(元素少时)
  • skiplist + dict(默认)
    • skiplist:支持范围查询
    • dict:O(1)查找分数
bash 复制代码
ZADD key score1 member1 [score2 member2...]
ZRANGE key start stop [WITHSCORES]  # 按排名范围查询
ZREVRANGE key start stop            # 倒序查询
ZRANGEBYSCORE key min max           # 按分数范围查询
ZSCORE key member                   # 获取分数
ZRANK key member                    # 获取排名
ZINCRBY key increment member        # 增加分数

优点

有序且支持范围查询

元素唯一性

性能优秀(查询、插入O(logN))

缺点

内存占用相对较大

比普通集合复杂

应用场景

排行榜

延时队列

带权重的队列

时间轴(按时间排序)

实际案例

String案例:电商用户会话管理

实际场景:用户登录后,存储会话信息,记录用户状态,实现简单的分布式锁控制。

bash 复制代码
# 存储用户会话信息(30分钟过期)
SET session:user123 '{"userId":123,"username":"张三","loginTime":"2024-02-15 10:00","cartCount":3}' EX 1800

# 获取会话信息
GET session:user123

# 用户添加商品到购物车,更新购物车数量
INCRBY user:123:cart:count 1

# 记录用户最后活跃时间
SET user:123:last_active "2024-02-15 10:30:00"

# 分布式锁:秒杀商品库存
SET product:1001:lock "locked" NX PX 10000  # 10秒锁
# 业务处理...
DEL product:1001:lock

List案例:微博消息时间线

实际场景:社交媒体平台的消息流,最新消息在列表前端,支持分页查看。

bash 复制代码
# 用户发表新微博
LPUSH user:123:timeline "今天天气真好!#阳光#"
LPUSH global:timeline "user123:今天天气真好!#阳光#"

# 获取用户最近20条微博
LRANGE user:123:timeline 0 19

# 关注系统:当用户关注的人发微博时
# 用户123关注了用户456,当456发微博时:
LPUSH user:123:feed "user456:发布了新照片"

# 实现简单的消息队列(任务队列)
LPUSH task:queue '{"type":"email","to":"user@example.com","content":"..."}'
BRPOP task:queue 30  # 工作进程阻塞获取任务

Hash案例:电商商品详情页

实际场景:商品信息缓存,购物车实现,可以单独更新某个字段而不用读取整个对象。

bash 复制代码
# 存储商品信息
HSET product:1001 "name" "iPhone 15 Pro"
HSET product:1001 "price" "8999"
HSET product:1001 "stock" "100"
HSET product:1001 "description" "最新款苹果手机"
HSET product:1001 "sales" "1500"

# 获取商品所有信息
HGETALL product:1001

# 用户购物车(用户ID: 123)
HSET cart:user:123 "product:1001" 2  # 商品1001,数量2
HSET cart:user:123 "product:2002" 1  # 商品2002,数量1

# 更新商品库存(原子操作)
HINCRBY product:1001 "stock" -1

# 用户下单后获取购物车所有商品
HGETALL cart:user:123

Set案例:社交网络好友关系

实际场景:社交网络的好友关系、共同兴趣推荐、标签系统、抽奖活动。

bash 复制代码
# 用户添加好友
SADD user:123:friends "456" "789" "101"
SADD user:456:friends "123" "789"

# 判断是否是好友
SISMEMBER user:123:friends "456"  # 返回1,是好友

# 共同好友计算
SINTER user:123:friends user:456:friends  # 返回789,共同好友

# 可能认识的人(差集)
SDIFF user:456:friends user:123:friends  # 用户456的好友中,用户123没有的

# 标签系统
SADD product:1001:tags "手机" "苹果" "高端"
SADD product:1002:tags "手机" "安卓" "性价比"

# 查找包含"手机"标签的商品
SINTER product:1001:tags product:1002:tags  # 共同标签"手机"

# 抽奖活动参与用户
SADD lottery:202402 "user1" "user2" "user3"
SCARD lottery:202402  # 统计参与人数
# 随机抽取3名获奖者
SRANDMEMBER lottery:202402 3

ZSet案例:游戏排行榜系统

实际场景:游戏积分排行榜、热点新闻排序、延时任务调度、时间轴功能。

bash 复制代码
# 玩家得分更新
ZADD game:leaderboard 1500 "player1"
ZADD game:leaderboard 2200 "player2"
ZADD game:leaderboard 1800 "player3"

# 玩家得分增加
ZINCRBY game:leaderboard 100 "player1"  # player1得分增加100

# 获取排行榜前10名
ZREVRANGE game:leaderboard 0 9 WITHSCORES
# 返回:
# 1) "player2"
# 2) "2200"
# 3) "player3"
# 4) "1800"
# ...

# 获取玩家排名
ZREVRANK game:leaderboard "player1"  # 返回排名(从0开始)

# 获取分数在1700-2000之间的玩家
ZRANGEBYSCORE game:leaderboard 1700 2000 WITHSCORES

# 延时任务队列(时间戳作为score)
ZADD delay:queue 1707962400 "task:send_email:123"  # 2024-02-15 10:00:00
ZADD delay:queue 1707962460 "task:clean_cache"

# 获取当前时间之前的所有任务
ZRANGEBYSCORE delay:queue 0 1707962410 WITHSCORES  # 获取10:00:10之前的所有任务

综合案例:电商系统完整实现

bash 复制代码
# 1. 用户登录(String)
SETEX session:${userId} 1800 '{"userId":123,"username":"张三"}'

# 2. 浏览商品历史(List)
LPUSH user:123:history "product:1001"
LTRIM user:123:history 0 49  # 只保留最近50条

# 3. 商品详情(Hash)
HMSET product:1001 name "iPhone" price "8999" stock "100"

# 4. 商品标签(Set)
SADD product:1001:tags "手机" "苹果" "高端"

# 5. 商品销量排行榜(ZSet)
ZINCRBY product:sales:daily 1 "product:1001"

# 6. 用户购物车(Hash)
HSET cart:user:123 "product:1001" 2

# 7. 限时秒杀(String + 过期时间)
SET product:1001:seckill:stock 100 EX 3600  # 1小时秒杀
DECR product:1001:seckill:stock  # 原子减库存

# 8. 用户订单队列(List作为队列)
LPUSH order:queue '{"orderId":"ORD123","userId":123,"products":[{"id":1001,"qty":2}]}'
BRPOP order:queue 30  # 订单处理服务消费

各数据类型选择技巧总结

String:简单KV,需要原子操作的计数器

List:需要按时间顺序排列的数据,队列/栈

Hash:对象存储,需要单独操作字段

Set:需要去重,集合运算(交集/并集)

ZSet:需要排序和范围查询

性能优化提示

bash 复制代码
# 批量操作减少网络开销
MGET session:user1 session:user2 session:user3

# Pipeline批量执行
# 客户端代码示例(伪代码):
pipeline = redis.pipeline()
pipeline.incr("counter")
pipeline.set("key", "value")
pipeline.execute()

# 合理设置过期时间避免内存泄漏
SETEX key 3600 value  # 1小时后过期
EXPIRE key 3600       # 设置已有key的过期时间

小结

类型 时间复杂度 内存效率 适用场景
String O(1) 简单KV缓存、计数器
List O(1)两端操作 队列、栈、时间线
Hash O(1)单字段 对象存储、购物车
Set O(1)存在性 标签、去重、社交关系
ZSet O(logN)操作 排行榜、优先级队列

3. 三种特殊数据类型

3.1 HyperLogLog(基数统计)

HyperLogLog是一种用于估计集合中不重复元素数量的概率数据结构。

底层原理

使用16384个6KB的寄存器(共约12KB内存)

基于概率算法,不是精确计数

标准错误率约为0.81%

使用调和平均数来减少异常值影响

bash 复制代码
PFADD key element [element...]  # 添加元素
PFCOUNT key [key...]            # 计算基数估计值
PFMERGE destkey sourcekey [sourcekey...]  # 合并多个HLL

优点

内存占用极小:无论集合多大,都只需要约12KB

合并成本低:可以快速合并多个HyperLogLog

添加元素快速:O(1)时间复杂度

缺点

不精确:存在约0.81%的标准误差

无法获取具体元素:只能估算数量,不能获取元素内容

无法删除元素:只能整体重置

应用场景

网站UV统计(每日/每月独立访客数)

大数据去重估算(如:估算某关键词搜索用户数)

社交网络共同好友数估算

API调用去重统计

bash 复制代码
# 统计某文章页面的UV
PFADD article:123:uv user1 user2 user3
PFCOUNT article:123:uv

# 合并三天的UV数据
PFADD day1 user1 user2 user3
PFADD day2 user2 user3 user4
PFMERGE total_uv day1 day2
PFCOUNT total_uv  # 估算总UV

3.2 Bitmap(位图)

概述

Bitmap本质上是一个String类型,但可以当作位数组操作,每个bit只能存储0或1。

底层原理

基于String类型实现

使用SDS(简单动态字符串)存储二进制位

自动扩容,按需分配内存

支持位操作(AND、OR、NOT、XOR)

bash 复制代码
SETBIT key offset value    # 设置指定偏移量的位值
GETBIT key offset          # 获取指定偏移量的位值
BITCOUNT key [start end]   # 统计值为1的位数
BITOP operation destkey key [key...]  # 位运算
BITPOS key bit [start] [end]  # 查找第一个为0/1的位

优点

内存效率极高:每个用户仅需1bit

操作速度快:支持大规模位运算

支持复杂查询:支持范围统计和位运算

持久化方便:可以导出为二进制文件

缺点

稀疏数据浪费内存:稀疏分布时效率低

偏移量限制:最大偏移量为2^32-1

需要预分配内存:可能产生内存碎片

应用场景

用户签到系统

用户活跃度统计

布隆过滤器实现

用户标签系统

实时用户在线状态

bash 复制代码
# 用户签到系统
SETBIT user:sign:202402:1001 15 1  # 用户1001在2月16日签到
GETBIT user:sign:202402:1001 15    # 查看是否签到
BITCOUNT user:sign:202402:1001     # 统计本月签到次数

# 用户在线状态
SETBIT online:users 1001 1         # 用户1001上线
SETBIT online:users 1001 0         # 用户1001下线
BITCOUNT online:users              # 统计在线人数

3.3 Geospatial(地理空间)

Geospatial用于存储地理位置信息,支持半径查询、距离计算等地理空间操作。

底层原理

基于ZSet(有序集合)实现

使用GeoHash算法将经纬度编码为52位整数

使用Haversine公式计算球面距离

数据存储在ZSet中,分数为GeoHash值

bash 复制代码
GEOADD key longitude latitude member [longitude latitude member...]
GEOPOS key member [member...]       # 获取位置
GEODIST key member1 member2 [unit]  # 计算距离
GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]
GEORADIUSBYMEMBER key member radius unit [options...]  # 按成员查询附近
GEOHASH key member [member...]      # 获取GeoHash字符串

优点

查询效率高:基于ZSet的跳表实现,范围查询快

功能丰富:支持多种地理查询

集成度高:原生支持,无需外部依赖

精度可控:可通过GeoHash精度控制

缺点

精度有限:存在边缘效应问题

无法处理复杂多边形

距离计算为直线距离(不考虑地形)

删除操作需要特殊处理

应用场景

附近的人/商家

共享单车/车辆定位

配送范围计算

地理位置围栏

疫情风险区域标注

bash 复制代码
# 添加地理位置
GEOADD restaurants 116.397128 39.916527 "全聚德" 116.407526 39.904030 "海底捞"

# 查找附近5km的餐厅
GEORADIUS restaurants 116.400000 39.900000 5 km WITHDIST WITHCOORD COUNT 10

# 计算两个地点距离
GEODIST restaurants "全聚德" "海底捞" km

# 获取所有餐厅的位置
GEOPOS restaurants "全聚德" "海底捞"

小结

数据类型 内存占用 查询复杂度 精度 适用场景
HyperLogLog 固定12KB O(1) 99.19% 大数据去重统计
Bitmap 随偏移量变化 O(1) 100% 布尔标记、状态记录
Geospatial 与ZSet相同 O(logN+N) 地理位置服务

HyperLogLog

适用于海量数据去重统计

不关心具体元素内容时使用

注意误差范围,不适合精确计数场景

可以合并多个数据集的统计结果

Bitmap

适合用户ID连续或范围集中的场景

稀疏数据考虑使用压缩算法

定期清理过期数据

配合SETBIT和BITCOUNT实现复杂统计

Geospatial

合理选择GeoHash精度

考虑使用辅助索引提升查询性能

定期更新位置数据

结合其他数据类型(如Hash)存储额外信息

实际案例

案例1:网站UV统计系统

bash 复制代码
# 使用HyperLogLog统计每日UV
PFADD uv:20240215 user1 user2 user3
PFCOUNT uv:20240215

# 月度UV统计
PFMERGE uv:202402 uv:20240201 uv:20240202 ... uv:20240228

案例2:用户签到与活跃度分析

bash 复制代码
# 用户签到(Bitmap)
SETBIT sign:202402:1001 15 1

# 活跃用户统计(Bitmap位运算)
# 统计连续7天都签到的用户
BITOP AND active_users sign:day1 sign:day2 ... sign:day7
BITCOUNT active_users

案例3:附近商家推荐系统

bash 复制代码
# 商家地理位置存储
GEOADD merchants 116.397128 39.916527 "商家A"

# 查找用户附近3km的商家
GEORADIUS merchants 116.400000 39.900000 3 km WITHDIST WITHCOORD COUNT 20
相关推荐
q***76562 小时前
ubuntu 安装 Redis
linux·redis·ubuntu
青衫码上行2 小时前
Redis新数据类型 - Bitmap、HyperLogLog、Geospatial
数据库·redis·缓存
2401_848009724 小时前
Redis零基础入门学习
数据库·redis·学习
Tangcan-4 小时前
在Ubuntu 22.04上安装redis
linux·redis·ubuntu
zsyf19876 小时前
Linux部署Redis集群
linux·运维·redis
小跌—7 小时前
Redis的string数据类型
数据库·redis·缓存
bepeater12348 小时前
Linux安装Redis以及Redis三种启动方式
linux·redis·bootstrap
Re.不晚10 小时前
Redis——分布式锁
数据库·redis·分布式