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);

底层实现:

图1

实践建议:

  • 使用SETEX替代SET+EXPIRE组合
  • 大文本考虑压缩后存储
  • 计数器场景注意溢出问题(使用INCRBY范围控制)

2. List:双向链表的力量

List实现了一个双向链表,支持两端的高效操作。

典型场景:

  • 消息队列(LPUSH+BRPOP)
  • 最新消息排行(固定长度列表)
  • 分页查询(LRANGE)
// 消息队列实现
jedis.lpush("news.queue", "message1");
String msg = jedis.brpop(30, "news.queue"); // 阻塞获取

底层结构:

图2

性能注意:

  • 中间元素操作时间复杂度O(N)
  • 大量元素考虑分片(如list:shard1, list:shard2)

3. Hash:对象存储专家

Hash适合存储对象字段,是Redis中使用最频繁的结构之一。

优势对比:

方案存储方式内存占用操作复杂度
Stringuser:1001:name="张三"O(1)
Hashuser: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实现自动排序,是排行榜功能的完美选择。

实现原理:

图3

排行榜示例:

// 游戏排行榜
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);}});

核心概念:

图4

数据结构选型指南

需求场景推荐结构原因
缓存对象Hash字段独立存取
排行榜ZSet自动排序
消息队列Stream/List顺序消费
去重统计Set/HLLSet精确/HLL省空间
位标记Bitmap极致空间利用

高级技巧:

  1. 组合使用数据结构:如用ZSet记录时间戳,用Hash存储详细数据
  2. 利用EXPIRE设置不同过期策略
  3. 大Key拆分:如将1亿用户的Set拆分为100个Key

通过合理选择和使用这些数据结构,您可以在Redis中构建出高效、稳定的应用系统。

添加新评论