一、Redis数据结构概览
Redis不仅仅是一个简单的键值存储,它提供了丰富的数据结构,使其能够适应各种复杂的应用场景。下图展示了Redis数据结构的全貌:
基础数据结构(核心): ├── String(字符串) ├── Hash(哈希) ├── List(列表) ├── Set(集合) └── ZSet(有序集合) 扩展数据结构: ├── Bitmap(位图) ├── HyperLogLog(基数统计) ├── Geo(地理空间) └── Stream(流) 高级模块(需额外安装): ├── JSON ├── Search and Query ├── Time Series └── Bloom Filter等
二、永远的神:Redis帮助系统
在深入学习前,先掌握Redis的内置帮助系统:
# 进入Redis命令行
redis-cli
# 查看所有命令分组
help @<group>
# 查看特定命令帮助
help <command>
# 查看可能的话题
help <tab>
三、核心数据结构详解
3.1 String(字符串)
基础命令
# 基本操作
SET key value # 设置键值
GET key # 获取值
DEL key # 删除键
EXPIRE key seconds # 设置过期时间
# 批量操作
MSET k1 v1 k2 v2 k3 v3 # 批量设置
MGET k1 k2 k3 # 批量获取
# 原子操作
SETNX key value # 不存在才设置(分布式锁基础)
INCR key # 原子递增
DECR key # 原子递减
INCRBY key 5 # 原子加5
DECRBY key 3 # 原子减3
应用场景
1. 对象缓存
# 方式1:JSON格式存储(简单但不易部分更新)
SET user:1 '{"name":"roy","balance":1888}'
# 方式2:拆分存储(推荐,可部分更新)
MSET user:1:name roy user:1:balance 1888
MGET user:1:name user:1:balance
2. 分布式锁
# 获取锁(NX:不存在才设置,EX:10秒过期)
SET lock:order:10001 true EX 10 NX
# 执行业务逻辑...
# 释放锁(Lua脚本保证原子性)
DEL lock:order:10001
3. 计数器
# 文章阅读量统计
INCR article:10001:views
GET article:10001:views
3.2 Hash(哈希表)
基础命令
# 单个操作
HSET user:1 name roy age 25 # 设置字段
HGET user:1 name # 获取字段
HSETNX user:1 email test@qq.com # 字段不存在才设置
# 批量操作
HMSET user:1 name roy balance 1888
HMGET user:1 name balance
# 其他操作
HDEL user:1 age # 删除字段
HLEN user:1 # 字段数量
HGETALL user:1 # 获取所有字段
HINCRBY user:1 balance 100 # 字段值递增
应用场景
1. 购物车实现
# 用户ID为key,商品ID为field,数量为value
HSET cart:1001 10088 1 # 添加商品
HINCRBY cart:1001 10088 1 # 增加数量
HLEN cart:1001 # 商品总数
HDEL cart:1001 10088 # 删除商品
HGETALL cart:1001 # 获取所有商品
2. 用户信息存储
# 比String更节省空间,支持部分更新
HSET user:1001 name "张三" age 30 city "北京"
HGET user:1001 name city # 只获取需要的字段
优缺点
-
✅ 优点:
-
同类数据归类存储,易于管理
-
内存和CPU消耗比String更小
-
节省存储空间
-
-
❌ 缺点:
-
过期时间只能设置在key上,不能针对field
-
集群架构下不适合存储大hash
-
3.3 List(列表)
基础命令
# 两端操作
LPUSH list1 a b c # 左插 → [c, b, a]
RPUSH list1 d e f # 右插 → [c, b, a, d, e, f]
LPOP list1 # 左弹 → c
RPOP list1 # 右弹 → f
# 范围查询
LRANGE list1 0 -1 # 获取所有元素
LRANGE list1 0 2 # 获取前3个
# 阻塞操作
BLPOP list1 10 # 左弹,等待10秒
BRPOP list1 0 # 右弹,无限等待
数据结构实现
-
栈(Stack) :
LPUSH + LPOP(先进后出) -
队列(Queue) :
LPUSH + RPOP(先进先出) -
阻塞队列 :
LPUSH + BRPOP
应用场景
-
消息队列:简单任务队列
-
最新列表:朋友圈动态、新闻推送
-
历史记录:用户浏览记录
注意事项
-
List最多可存储 2³² - 1 个元素(约42亿)
-
底层是双向链表,两端操作O(1),中间操作O(n)
-
注意大Key问题,单个List不宜过大
3.4 Set(集合)
基础命令
# 基本操作
SADD tags java redis spring # 添加元素
SREM tags java # 删除元素
SMEMBERS tags # 获取所有元素
SCARD tags # 元素个数
SISMEMBER tags redis # 是否包含
# 随机操作
SRANDMEMBER tags 2 # 随机取2个(不删除)
SPOP tags 1 # 随机取1个(删除)
# 集合运算
SINTER set1 set2 # 交集
SUNION set1 set2 # 并集
SDIFF set1 set2 # 差集(set1 - set2)
应用场景
1. 抽奖系统
SADD lottery:20240617 user1 user2 user3 # 参与抽奖
SMEMBERS lottery:20240617 # 查看所有参与者
SRANDMEMBER lottery:20240617 3 # 抽取3名(不删除)
SPOP lottery:20240617 3 # 抽取3名(删除)
2. 点赞/收藏系统
SADD like:article:1001 user:10001 # 点赞
SREM like:article:1001 user:10001 # 取消点赞
SISMEMBER like:article:1001 user:10001 # 是否点赞
SCARD like:article:1001 # 点赞总数
SMEMBERS like:article:1001 # 点赞用户列表
3. 社交关系
# 共同关注
SINTER user:1001:follow user:1002:follow
# 可能认识的人(差集)
SDIFF user:1001:follow user:1002:follow
3.5 ZSet(有序集合)
基础命令
# 基本操作
ZADD rank 95 "Alice" 87 "Bob" 92 "Charlie" # 添加带分数元素
ZSCORE rank "Alice" # 获取分数
ZINCRBY rank 5 "Bob" # 增加分数
ZREM rank "Charlie" # 删除元素
ZCARD rank # 元素个数
# 范围查询
ZRANGE rank 0 -1 WITHSCORES # 升序获取全部
ZREVRANGE rank 0 2 WITHSCORES # 降序获取前3
ZRANGEBYSCORE rank 90 100 # 获取90-100分的
# 集合运算
ZUNIONSTORE weekly 7 day1 day2 ... day7 # 周榜合并
应用场景
1. 排行榜系统
# 日榜
ZINCRBY hotNews:20240617 1 "守护香港"
ZREVRANGE hotNews:20240617 0 9 WITHSCORES
# 周榜计算
ZUNIONSTORE hotNews:week 7 hotNews:0611 ... hotNews:0617
ZREVRANGE hotNews:week 0 9 WITHSCORES
2. 延迟队列
# 添加任务(执行时间戳作为分数)
ZADD delay:queue 1741234567 "task:1"
ZADD delay:queue 1741235567 "task:2"
# 获取到期的任务
ZRANGEBYSCORE delay:queue 0 1741234567
3.6 Bitmap(位图)
基础命令
# 位操作
SETBIT sign:user:1001 0 1 # 第0位设为1
GETBIT sign:user:1001 0 # 获取第0位
BITCOUNT sign:user:1001 # 统计1的个数
BITPOS sign:user:1001 1 # 第一个1的位置
# 位运算
BITOP AND dest key1 key2 # 与运算
BITOP OR dest key1 key2 # 或运算
BITOP XOR dest key1 key2 # 异或运算
应用场景
1. 用户签到
# 用户1001每月签到(偏移量代表日期)
SETBIT sign:1001:202406 17 1 # 6月17日签到
BITCOUNT sign:1001:202406 # 本月签到天数
GETBIT sign:1001:202406 17 # 检查某天是否签到
2. 用户活跃度统计
# 每天用户活跃记录
SETBIT active:20240617 1001 1 # 用户1001活跃
BITCOUNT active:20240617 # 当天活跃用户数
BITOP AND daily_active active:0616 active:0617 # 连续活跃用户
3.7 HyperLogLog(基数统计)
基础命令
# 添加与统计
PFADD uv:20240617 user1 user2 user1 user3
PFCOUNT uv:20240617 # 统计UV ≈ 3
# 合并统计
PFADD uv:day1 user1 user2
PFADD uv:day2 user2 user3
PFMERGE uv:total uv:day1 uv:day2
PFCOUNT uv:total # 统计总UV ≈ 3
特点与应用
-
优点:极省内存,12KB可统计2⁶⁴个元素
-
缺点:有约0.81%的标准误差
-
场景:UV统计、独立IP计数
3.8 Geo(地理空间)
基础命令
# 添加位置
GEOADD city:changsha 113.017489 28.200454 "火车站"
# 查询位置
GEOPOS city:changsha "火车站" "橘子洲"
# 计算距离
GEODIST city:changsha "火车站" "橘子洲" KM
# 附近搜索
GEORADIUS city:changsha 113.017489 28.200454 2 km WITHDIST COUNT 5
# 基于成员搜索
GEORADIUSBYMEMBER city:changsha "火车站" 2 km
应用场景
-
附近的人/商家
-
配送距离计算
-
地理围栏
3.9 Stream(流)
基础命令
# 生产消息
XADD mystream * name "order" amount "100.00"
# 消费消息
XREAD COUNT 2 STREAMS mystream 0
# 消费者组
XGROUP CREATE mystream order_group 0
XREADGROUP GROUP order_group consumer1 COUNT 1 STREAMS mystream >
应用场景
-
消息队列(替代RabbitMQ/Kafka的轻量方案)
-
事件溯源
-
日志收集
四、SpringBoot集成Redis
4.1 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
4.2 配置文件
spring:
data:
redis:
host: 192.168.65.214
port: 6379
password: 123qweasd
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
4.3 RedisTemplate使用
@Resource
private RedisTemplate<String, Object> redisTemplate;
// String操作
redisTemplate.opsForValue().set("key", "value");
String value = (String) redisTemplate.opsForValue().get("key");
// Hash操作
redisTemplate.opsForHash().put("user:1", "name", "roy");
// List操作
redisTemplate.opsForList().leftPush("list1", "item1");
// Set操作
redisTemplate.opsForSet().add("tags", "java", "redis");
// ZSet操作
redisTemplate.opsForZSet().add("rank", "Alice", 95.0);
// Geo操作
redisTemplate.opsForGeo().add("city", new Point(113.017489, 28.200454), "火车站");
// HyperLogLog操作
redisTemplate.opsForHyperLogLog().add("uv", "user1", "user2");
// Stream操作
Map<String, String> map = new HashMap<>();
map.put("name", "order");
redisTemplate.opsForStream().add("mystream", map);
// Bit操作
redisTemplate.opsForValue().setBit("bitmap", 0, true);
4.4 解决序列化问题
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 使用String序列化Key
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// 使用Jackson序列化Value
Jackson2JsonRedisSerializer<Object> jsonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}
五、数据结构选择指南
| 数据结构 | 特点 | 适用场景 | 不适用场景 |
|---|---|---|---|
| String | 最简单,支持数值操作 | 缓存、计数器、分布式锁 | 存储复杂对象 |
| Hash | 字段独立,节省内存 | 购物车、用户属性、配置项 | 需要单独过期字段 |
| List | 有序,支持阻塞操作 | 消息队列、最新列表、历史记录 | 随机访问中间元素 |
| Set | 无序,去重,支持集合运算 | 标签、好友关系、抽奖 | 需要有序或重复元素 |
| ZSet | 有序,带权重 | 排行榜、延迟队列、优先级队列 | 不需要排序的场景 |
| Bitmap | 极省空间,位操作 | 签到、用户在线状态、布隆过滤器 | 非布尔值统计 |
| HyperLogLog | 极省内存,基数估计 | UV统计、独立IP计数 | 需要精确计数 |
| Geo | 地理位置存储查询 | 附近的人、位置服务 | 非地理位置数据 |
| Stream | 消息队列,消费组 | 事件流、日志收集 | 简单KV存储 |
六、性能优化建议
6.1 内存优化
-
使用合适的数据结构:Hash存储对象比String更省内存
-
控制Key长度 :使用缩写,如
u:1代替user:1 -
启用压缩 :在
redis.conf中配置hash-max-ziplist-entries等参数
6.2 命令优化
-
批量操作 :使用
MSET、HMGET代替多次单操作 -
管道技术:减少网络往返
-
避免大Key:单个String不超过10KB,集合元素不超过5000
6.3 监控与维护
# 查看内存使用
redis-cli info memory
# 查看慢查询
redis-cli slowlog get 10
# 大Key扫描
redis-cli --bigkeys
七、总结
Redis的强大之处在于其丰富的数据结构,每种结构都有其独特的应用场景:
-
缓存和会话 → String/Hash
-
社交功能 → Set/ZSet
-
消息系统 → List/Stream
-
统计计数 → Bitmap/HyperLogLog
-
位置服务 → Geo
掌握这些数据结构及其应用场景,能够让你在系统设计中游刃有余。记住:没有最好的数据结构,只有最适合场景的数据结构。