Redis数据结构全解析:String到Stream深度指南
Redis数据结构全解析:从String到Stream的深度指南
Redis之所以强大,很大程度上源于其丰富的数据结构。本文将深入解析Redis的9种核心数据结构,包括使用场景、底层实现和最佳实践。
1. String:最简单的强大
String是Redis最基本的数据类型,但功能远不止存储字符串这么简单。
核心特性:
- 二进制安全,可存储任何数据(最大512MB)
- 支持数值的原子性操作(INCR/DECR)
- 支持位操作(BITCOUNT/BITFIELD)
// Jedis操作示例
jedis.set("user:1001:name", "张三");
jedis.incr("article:1002:views");
jedis.setbit("user:1001:active", dayOfYear, true);
底层实现:
实践建议:
- 使用
SETEX
替代SET+EXPIRE
组合 - 大文本考虑压缩后存储
- 计数器场景注意溢出问题(使用INCRBY范围控制)
2. List:双向链表的力量
List实现了一个双向链表,支持两端的高效操作。
典型场景:
- 消息队列(LPUSH+BRPOP)
- 最新消息排行(固定长度列表)
- 分页查询(LRANGE)
// 消息队列实现
jedis.lpush("news.queue", "message1");
String msg = jedis.brpop(30, "news.queue"); // 阻塞获取
底层结构:
性能注意:
- 中间元素操作时间复杂度O(N)
- 大量元素考虑分片(如list:shard1, list:shard2)
3. Hash:对象存储专家
Hash适合存储对象字段,是Redis中使用最频繁的结构之一。
优势对比:
方案 | 存储方式 | 内存占用 | 操作复杂度 |
---|---|---|---|
String | user:1001:name="张三" | 高 | O(1) |
Hash | user:1001 {name:"张三"} | 低 | O(1) |
// 用户对象存储
jedis.hset("user:1001", "name", "张三");
jedis.hincrBy("user:1001", "age", 1);
Map<String,String> user = jedis.hgetAll("user:1001");
内存优化:
- 小Hash使用ziplist编码(默认hash-max-ziplist-entries 512)
- 字段名尽量简短(如用"nm"替代"name")
4. Set:去重与集合运算
Set提供去重功能和丰富的集合运算能力。
典型应用:
- 标签系统(SADD/SMEMBERS)
- 共同好友(SINTER)
- 随机抽奖(SRANDMEMBER)
// 标签系统示例
jedis.sadd("article:1002:tags", "科技", "Redis", "数据库");
Set<String> tags = jedis.smembers("article:1002:tags");
性能警告:
- SMEMBERS操作会返回全部元素,大集合慎用
- 大数据集考虑SSCAN迭代
5. Sorted Set:排行榜神器
ZSet通过score实现自动排序,是排行榜功能的完美选择。
实现原理:
排行榜示例:
// 游戏排行榜
jedis.zadd("game:rank", 3500, "player1");
jedis.zadd("game:rank", 4000, "player2");
// 获取TOP10
Set<Tuple> top10 = jedis.zrevrangeWithScores("game:rank", 0, 9);
优化技巧:
- 相同score的成员按字典序排序
- 超大排行榜考虑分片(如按日期分片)
6. Bitmaps:极致节省的二进制
Bitmaps通过位操作提供超省空间的布尔统计。
使用场景:
- 用户在线状态
- 每日活跃用户统计
- 特征标记
// 用户签到系统
int dayOfYear = LocalDate.now().getDayOfYear();
jedis.setbit("user:1001:sign", dayOfYear, true);
// 计算本月签到次数
jedis.bitcount("user:1001:sign", startOffset, endOffset);
内存计算:
- 1亿用户每日登录状态只需约12MB(100000000/8/1024/1024)
7. HyperLogLog:海量基数统计
HLL用极小空间实现独立用户数统计,误差率仅0.81%。
// UV统计
jedis.pfadd("article:1002:uv", "user1", "user2");
long uv = jedis.pfcount("article:1002:uv");
注意事项:
- 不是精确计算,适合近似统计
- 合并时使用PFMERGE
8. Geospatial:地理位置服务
基于ZSet实现的地理位置功能,支持半径查询。
// 添加地理位置
jedis.geoadd("stores:location", 116.404, 39.915, "王府井店");
// 查询5公里内的店铺
List<GeoRadiusResponse> results = jedis.georadius(
"stores:location", 116.404, 39.915, 5, GeoUnit.KM);
底层原理:
- 使用GeoHash编码将二维坐标转换为一维ZSet score
9. Stream:完善的消息流
Redis 5.0引入的Stream提供了完善的消息队列功能。
// 消息生产者
Map<String,String> message = new HashMap<>();
message.put("event", "login");
message.put("user", "1001");
jedis.xadd("user:events", StreamEntryID.NEW_ENTRY, message);
// 消费者组
jedis.xgroupCreate("user:events", "analytics-group", null, true);
List<StreamEntry> messages = jedis.xreadgroup(
"analytics-group", "consumer1", 1, 0, false,
new HashMap<String, StreamEntryID>(){{put("user:events", StreamEntryID.UNRECEIVED_ENTRY);}});
核心概念:
数据结构选型指南
需求场景 | 推荐结构 | 原因 |
---|---|---|
缓存对象 | Hash | 字段独立存取 |
排行榜 | ZSet | 自动排序 |
消息队列 | Stream/List | 顺序消费 |
去重统计 | Set/HLL | Set精确/HLL省空间 |
位标记 | Bitmap | 极致空间利用 |
高级技巧:
- 组合使用数据结构:如用ZSet记录时间戳,用Hash存储详细数据
- 利用EXPIRE设置不同过期策略
- 大Key拆分:如将1亿用户的Set拆分为100个Key
通过合理选择和使用这些数据结构,您可以在Redis中构建出高效、稳定的应用系统。