前言
本篇读者收益
- 理解 Redis 五大数据结构的特性和适用场景
- 掌握在 Java 中高效操作各种 Redis 数据结构的方法
- 学会根据业务需求选择合适的数据结构
- 了解数据结构层面的性能优化技巧
先修要求
- 已完成第一篇环境搭建
- 了解 Redis 基本命令
- 熟悉 Java 集合框架
- 具备面向对象编程基础
关键要点
- 字符串不仅是文本,更是计数器、缓存和分布式锁的基础
- 哈希适合存储对象,减少键数量,优化内存使用
- 列表实现队列、栈和时间线,支持阻塞操作
- 集合处理唯一性、标签系统和社交关系
- 有序集合构建排行榜、延迟队列和范围查询
背景简述
Redis 之所以能够提供极高的性能,很大程度上得益于其精心设计的数据结构体系。与传统的键值存储不同,Redis 的值可以是多种数据结构,每种结构都针对特定的使用场景进行了优化。
Redis 数据结构体系:
理解这些数据结构的特性和适用场景,是高效使用 Redis 的关键。不同的数据结构在内存使用、操作复杂度和适用场景上都有显著差异。
环境准备与快速上手
在开始深入学习数据结构之前,需要已经建立了 Redis 连接。我们基于第一篇的优化连接池继续构建:
Java
public class RedisDataStructureBase {
protected JedisPool jedisPool;
public RedisDataStructureBase() {
this.jedisPool = OptimizedJedisPool.getJedisPool();
}
// 统一的资源清理方法
public void cleanup() {
try (Jedis jedis = jedisPool.getResource()) {
// 清理测试数据
jedis.del("test:*");
}
}
}
核心用法与代码示例
字符串(String):不仅仅是文本
字符串是 Redis 最基本的数据类型,但它的应用远不止存储文本那么简单。
内存结构与特性:
- 最大 512MB 容量
- 二进制安全,可存储任何数据
- 支持数值操作和位操作
- 常用于缓存、计数器、分布式锁
Java 操作实战:
Java
public class StringOperations extends RedisDataStructureBase {
/**
* 基础字符串操作 - 缓存场景
*/
public void basicStringOperations() {
try (Jedis jedis = jedisPool.getResource()) {
// 设置键值 - 普通缓存
jedis.set("user:1001:name", "张三");
jedis.set("user:1001:email", "zhangsan@example.com");
// 带过期时间的缓存 - 会话数据
jedis.setex("session:abc123", 3600, "session_data_json");
// 只有键不存在时设置 - 分布式锁基础
boolean success = jedis.setnx("resource:lock", "locked") == 1;
System.out.println("获取锁结果: " + success);
// 批量操作 - 提升性能
jedis.mset("config:timeout", "30", "config:retries", "3", "config:theme", "dark");
}
}
/**
* 计数器应用 - 阅读量统计
*/
public void counterApplications() {
try (Jedis jedis = jedisPool.getResource()) {
// 文章阅读计数
Long views = jedis.incr("article:1001:views");
System.out.println("文章阅读量: " + views);
// 带步长的计数
jedis.incrBy("user:1001:points", 10);
// 递减操作
jedis.decr("product:1001:stock");
// 获取并设置 - 原子操作
String oldValue = jedis.getSet("config:version", "2.0");
System.out.println("旧版本: " + oldValue);
}
}
/**
* 位操作 - 用户标签系统
*/
public void bitOperations() {
try (Jedis jedis = jedisPool.getResource()) {
String userKey = "user:2001:tags";
// 设置位 - 每个位代表一个标签
jedis.setbit(userKey, 0, true); // 标签1: VIP用户
jedis.setbit(userKey, 1, true); // 标签2: 活跃用户
jedis.setbit(userKey, 2, false); // 标签3: 新用户(未设置)
// 检查标签
boolean isVip = jedis.getbit(userKey, 0);
System.out.println("是否是VIP: " + isVip);
// 统计设置的位数 - 用户标签数量
long tagCount = jedis.bitcount(userKey);
System.out.println("用户标签数量: " + tagCount);
}
}
}
应用场景分析:
- 缓存系统:存储序列化的对象、HTML 片段
- 计数器:网站访问量、用户积分、商品库存
- 分布式锁:基于 SETNX 实现互斥访问
- 会话存储:用户登录状态、临时配置
- 位图统计:用户标签、活跃度统计
哈希(Hash):对象存储的最佳选择
哈希类型适合存储对象,可以将多个字段组合在一个键中,减少键的数量,优化内存使用。
内存优化原理:
- 小哈希使用 ziplist 编码,内存紧凑
- 字段数量少时,比多个字符串键更节省内存
- 适合存储结构化数据
Java 操作实战:
Java
public class HashOperations extends RedisDataStructureBase {
/**
* 用户对象存储示例
*/
public void userObjectStorage() {
try (Jedis jedis = jedisPool.getResource()) {
String userKey = "user:3001";
// 单个字段设置
jedis.hset(userKey, "name", "李四");
jedis.hset(userKey, "age", "28");
jedis.hset(userKey, "email", "lisi@example.com");
// 批量设置字段 - 性能更优
Map<String, String> userFields = new HashMap<>();
userFields.put("department", "技术部");
userFields.put("position", "高级工程师");
userFields.put("salary", "15000");
jedis.hmset(userKey, userFields);
// 获取单个字段
String userName = jedis.hget(userKey, "name");
System.out.println("用户姓名: " + userName);
// 获取多个字段
List<String> userInfo = jedis.hmget(userKey, "name", "age", "department");
System.out.println("用户信息: " + userInfo);
// 获取所有字段 - 小心大对象
Map<String, String> allFields = jedis.hgetAll(userKey);
System.out.println("完整用户信息: " + allFields);
// 字段递增 - 用户积分
Long newPoints = jedis.hincrBy(userKey, "points", 5);
System.out.println("新积分: " + newPoints);
}
}
/**
* 购物车实现
*/
public void shoppingCartExample() {
try (Jedis jedis = jedisPool.getResource()) {
String cartKey = "cart:user:4001";
// 添加商品到购物车
jedis.hset(cartKey, "product:1001", "2"); // 商品ID:1001, 数量:2
jedis.hset(cartKey, "product:1002", "1");
jedis.hset(cartKey, "product:1003", "3");
// 更新商品数量
jedis.hincrBy(cartKey, "product:1001", 1); // 增加1个
// 移除商品
jedis.hdel(cartKey, "product:1002");
// 获取购物车商品数量
long itemCount = jedis.hlen(cartKey);
System.out.println("购物车商品种类: " + itemCount);
// 获取购物车所有商品
Map<String, String> cartItems = jedis.hgetAll(cartKey);
System.out.println("购物车内容: " + cartItems);
}
}
/**
* 对象序列化工具方法
*/
public <T> void storeObject(String key, T obj, Class<T> clazz) {
try (Jedis jedis = jedisPool.getResource()) {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> fieldMap = mapper.convertValue(obj,
mapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
jedis.hmset(key, fieldMap);
} catch (Exception e) {
throw new RuntimeException("对象存储失败", e);
}
}
public <T> T getObject(String key, Class<T> clazz) {
try (Jedis jedis = jedisPool.getResource()) {
Map<String, String> fieldMap = jedis.hgetAll(key);
if (fieldMap.isEmpty()) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(fieldMap, clazz);
} catch (Exception e) {
throw new RuntimeException("对象获取失败", e);
}
}
}
应用场景分析:
- 用户信息存储:用户属性字段动态更新
- 购物车系统:商品 ID 和数量的映射
- 配置信息:应用的动态配置项
- 对象缓存:结构化数据的序列化存储
- 计数器组:多个相关计数器的集合
列表(List):队列与时间线的实现
Redis 列表基于双向链表实现,支持从两端快速插入和删除,是实现队列、栈和时间线的理想选择。
数据结构特性:
- 最大元素数:2³² - 1 个
- 两端操作时间复杂度 O(1)
- 支持阻塞操作,适合消息队列
- 索引操作时间复杂度 O(n)
Java 操作实战:
Java
public class ListOperations extends RedisDataStructureBase {
/**
* 消息队列实现
*/
public void messageQueueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String queueKey = "queue:notifications";
// 生产者:从左侧推入消息
jedis.lpush(queueKey, "消息1: 用户注册成功");
jedis.lpush(queueKey, "消息2: 订单创建完成");
jedis.lpush(queueKey, "消息3: 支付成功");
// 消费者:从右侧弹出消息
String message = jedis.rpop(queueKey);
while (message != null) {
System.out.println("处理消息: " + message);
message = jedis.rpop(queueKey);
}
}
}
/**
* 阻塞队列 - 实时消息处理
*/
public void blockingQueueExample() {
String queueKey = "queue:real-time";
// 生产者线程
Thread producer = new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
for (int i = 0; i < 5; i++) {
jedis.lpush(queueKey, "实时消息_" + i);
System.out.println("生产消息: 实时消息_" + i);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
});
// 消费者线程 - 阻塞等待
Thread consumer = new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
for (int i = 0; i < 5; i++) {
// 阻塞式弹出,最多等待10秒
List<String> messages = jedis.blpop(10, queueKey);
if (messages != null) {
System.out.println("消费消息: " + messages.get(1));
}
}
}
});
producer.start();
consumer.start();
}
/**
* 最新消息时间线
*/
public void timelineExample() {
try (Jedis jedis = jedisPool.getResource()) {
String timelineKey = "timeline:user:5001";
// 添加动态到时间线
jedis.lpush(timelineKey,
"{\"type\": \"post\", \"content\": \"发布了新文章\", \"time\": \"2024-01-15 10:00:00\"}",
"{\"type\": \"like\", \"content\": \"点赞了文章\", \"time\": \"2024-01-15 09:30:00\"}",
"{\"type\": \"comment\", \"content\": \"发表了评论\", \"time\": \"2024-01-15 09:00:00\"}"
);
// 限制时间线长度,防止无限增长
jedis.ltrim(timelineKey, 0, 99); // 只保留最新100条
// 分页获取时间线
List<String> recentActivities = jedis.lrange(timelineKey, 0, 9);
System.out.println("最近10条动态: " + recentActivities);
// 获取时间线长度
long timelineLength = jedis.llen(timelineKey);
System.out.println("时间线长度: " + timelineLength);
}
}
/**
* 栈(Stack)实现 - 后进先出
*/
public void stackExample() {
try (Jedis jedis = jedisPool.getResource()) {
String stackKey = "stack:operations";
// 压栈操作
jedis.lpush(stackKey, "操作1", "操作2", "操作3");
// 弹栈操作
String operation = jedis.lpop(stackKey);
while (operation != null) {
System.out.println("执行操作: " + operation);
operation = jedis.lpop(stackKey);
}
}
}
}
应用场景分析:
- 消息队列:系统间异步通信
- 时间线:用户动态、新闻流
- 操作日志:用户操作记录
- 任务队列:后台任务处理
- 最新列表:最新文章、最新评论
集合(Set):唯一性与关系运算
Redis 集合存储不重复的字符串元素,支持交集、并集、差集等关系运算,是处理唯一性和集合关系的利器。
性能特点:
- 添加、删除、查找时间复杂度 O(1)
- 支持集合间运算
- 最大元素数:2³² - 1 个
- 内部实现:整数集合或哈希表
Java 操作实战:
Java
public class SetOperations extends RedisDataStructureBase {
/**
* 标签系统实现
*/
public void tagSystemExample() {
try (Jedis jedis = jedisPool.getResource()) {
// 文章标签
String articleTagsKey = "article:6001:tags";
jedis.sadd(articleTagsKey, "技术", "Redis", "数据库", "高性能");
// 用户兴趣标签
String userInterestsKey = "user:6001:interests";
jedis.sadd(userInterestsKey, "技术", "编程", "Redis", "Java");
// 检查文章是否包含某个标签
boolean hasTechTag = jedis.sismember(articleTagsKey, "技术");
System.out.println("文章是否包含技术标签: " + hasTechTag);
// 获取共同兴趣 - 集合交集
Set<String> commonTags = jedis.sinter(articleTagsKey, userInterestsKey);
System.out.println("共同标签: " + commonTags);
// 获取所有标签 - 集合并集
Set<String> allTags = jedis.sunion(articleTagsKey, userInterestsKey);
System.out.println("所有标签: " + allTags);
// 获取文章特有标签 - 集合差集
Set<String> articleOnlyTags = jedis.sdiff(articleTagsKey, userInterestsKey);
System.out.println("文章特有标签: " + articleOnlyTags);
}
}
/**
* 好友关系与社交网络
*/
public void socialNetworkExample() {
try (Jedis jedis = jedisPool.getResource()) {
String userAFriends = "user:7001:friends";
String userBFriends = "user:7002:friends";
// 添加好友
jedis.sadd(userAFriends, "7002", "7003", "7004");
jedis.sadd(userBFriends, "7001", "7005", "7006");
// 共同好友
Set<String> mutualFriends = jedis.sinter(userAFriends, userBFriends);
System.out.println("共同好友: " + mutualFriends);
// 可能认识的人 - 好友的好友
Set<String> potentialFriends = jedis.sdiff(userBFriends, userAFriends);
potentialFriends.remove("7001"); // 排除自己
System.out.println("可能认识的人: " + potentialFriends);
// 统计好友数量
long friendCount = jedis.scard(userAFriends);
System.out.println("用户A好友数量: " + friendCount);
// 随机推荐好友
String randomFriend = jedis.srandmember(userAFriends);
System.out.println("随机好友推荐: " + randomFriend);
}
}
/**
* 唯一值处理 - 用户投票系统
*/
public void uniqueValueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String votersKey = "poll:8001:voters";
// 用户投票 - 自动去重
long result1 = jedis.sadd(votersKey, "user:9001");
long result2 = jedis.sadd(votersKey, "user:9002");
long result3 = jedis.sadd(votersKey, "user:9001"); // 重复投票
System.out.println("第一次投票结果: " + (result1 == 1 ? "成功" : "失败"));
System.out.println("第二次投票结果: " + (result2 == 1 ? "成功" : "失败"));
System.out.println("第三次投票结果: " + (result3 == 1 ? "成功" : "失败"));
// 获取所有投票用户
Set<String> allVoters = jedis.smembers(votersKey);
System.out.println("所有投票用户: " + allVoters);
// 统计投票人数
long voterCount = jedis.scard(votersKey);
System.out.println("总投票人数: " + voterCount);
}
}
/**
* 抽奖系统 - 随机抽取
*/
public void lotterySystemExample() {
try (Jedis jedis = jedisPool.getResource()) {
String participantsKey = "lottery:9001:participants";
// 添加参与者
jedis.sadd(participantsKey,
"user:10001", "user:10002", "user:10003",
"user:10004", "user:10005", "user:10006"
);
// 随机抽取3名获奖者(不重复)
List<String> winners = jedis.srandmember(participantsKey, 3);
System.out.println("获奖用户: " + winners);
// 随机抽取并移除(抽奖后移除)
String grandPrizeWinner = jedis.spop(participantsKey);
System.out.println("特等奖获得者: " + grandPrizeWinner);
// 剩余参与者数量
long remaining = jedis.scard(participantsKey);
System.out.println("剩余参与者: " + remaining);
}
}
}
应用场景分析:
- 标签系统:文章标签、用户兴趣
- 社交关系:好友列表、关注关系
- 唯一值处理:用户投票、访问 IP 记录
- 数据过滤:已读文章、已处理任务
- 随机抽样:抽奖系统、AB 测试
有序集合(ZSet):排行榜与范围查询
有序集合为每个元素关联一个分数(score),支持按分数排序和范围查询,是实现排行榜、延迟队列的理想选择。
排序原理:
- 跳跃表(skiplist)实现,查询效率 O(logN)
- 元素按分数从小到大排序
- 分数可重复,元素唯一
- 支持按分数范围、按排名范围查询
Java 操作实战:
Java
public class SortedSetOperations extends RedisDataStructureBase {
/**
* 游戏排行榜实现
*/
public void leaderboardExample() {
try (Jedis jedis = jedisPool.getResource()) {
String leaderboardKey = "leaderboard:game:10001";
// 添加玩家分数
jedis.zadd(leaderboardKey, 1500.0, "player:1001");
jedis.zadd(leaderboardKey, 1800.5, "player:1002");
jedis.zadd(leaderboardKey, 2200.0, "player:1003");
jedis.zadd(leaderboardKey, 1900.0, "player:1004");
jedis.zadd(leaderboardKey, 2100.0, "player:1005");
// 更新玩家分数
jedis.zincrby(leaderboardKey, 100.0, "player:1001"); // 增加100分
// 获取top3玩家
List<String> topPlayers = jedis.zrevrange(leaderboardKey, 0, 2);
System.out.println("排行榜前三名: " + topPlayers);
// 获取玩家排名(从0开始,分数从高到低)
Long playerRank = jedis.zrevrank(leaderboardKey, "player:1001");
System.out.println("玩家1001的排名: " + (playerRank != null ? playerRank + 1 : "未上榜"));
// 获取玩家分数
Double playerScore = jedis.zscore(leaderboardKey, "player:1001");
System.out.println("玩家1001的分数: " + playerScore);
// 获取分数区间内的玩家(1800-2100分)
Set<String> rangePlayers = jedis.zrangeByScore(leaderboardKey, 1800, 2100);
System.out.println("1800-2100分数段玩家: " + rangePlayers);
}
}
/**
* 延迟队列实现
*/
public void delayedQueueExample() {
try (Jedis jedis = jedisPool.getResource()) {
String delayedQueueKey = "queue:delayed:tasks";
// 当前时间戳
long currentTime = System.currentTimeMillis();
// 添加延迟任务(分数为执行时间戳)
jedis.zadd(delayedQueueKey, currentTime + 5000, "task:process_order:1001"); // 5秒后执行
jedis.zadd(delayedQueueKey, currentTime + 10000, "task:send_email:1001"); // 10秒后执行
jedis.zadd(delayedQueueKey, currentTime + 15000, "task:cleanup_cache:1001"); // 15秒后执行
// 处理到期任务的工作线程
Thread worker = new Thread(() -> {
try (Jedis workerJedis = jedisPool.getResource()) {
while (!Thread.currentThread().isInterrupted()) {
long now = System.currentTimeMillis();
// 获取所有到期的任务(分数小于等于当前时间)
Set<String> readyTasks = workerJedis.zrangeByScore(delayedQueueKey, 0, now);
for (String task : readyTasks) {
// 处理任务
System.out.println("执行任务: " + task + " at " + new Date(now));
// 从队列中移除已处理任务
workerJedis.zrem(delayedQueueKey, task);
}
if (readyTasks.isEmpty()) {
// 没有任务,休眠1秒
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
});
worker.start();
// 运行10秒后停止
try {
Thread.sleep(10000);
worker.interrupt();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* 时间序列数据 - 股票价格记录
*/
public void timeSeriesExample() {
try (Jedis jedis = jedisPool.getResource()) {
String stockPriceKey = "stock:AAPL:prices";
// 记录不同时间点的股价(时间戳作为分数)
long baseTime = System.currentTimeMillis();
jedis.zadd(stockPriceKey, baseTime, "150.25");
jedis.zadd(stockPriceKey, baseTime + 60000, "151.10"); // 1分钟后
jedis.zadd(stockPriceKey, baseTime + 120000, "150.75"); // 2分钟后
jedis.zadd(stockPriceKey, baseTime + 180000, "152.30"); // 3分钟后
jedis.zadd(stockPriceKey, baseTime + 240000, "153.15"); // 4分钟后
// 查询最近3分钟的股价数据
long threeMinutesAgo = baseTime + 180000; // 从开始时间算3分钟后
Set<String> recentPrices = jedis.zrangeByScore(stockPriceKey, threeMinutesAgo, baseTime + 240000);
System.out.println("最近股价: " + recentPrices);
// 获取股价范围统计
long priceCount = jedis.zcount(stockPriceKey, 150.0, 152.0);
System.out.println("150-152价格区间的数据点数量: " + priceCount);
}
}
/**
* 带权重的标签系统
*/
public void weightedTagsExample() {
try (Jedis jedis = jedisPool.getResource()) {
String articleWeightedTags = "article:11001:weighted_tags";
// 添加标签及权重(权重代表相关性强度)
jedis.zadd(articleWeightedTags, 0.9, "Redis");
jedis.zadd(articleWeightedTags, 0.7, "数据库");
jedis.zadd(articleWeightedTags, 0.5, "缓存");
jedis.zadd(articleWeightedTags, 0.3, "NoSQL");
jedis.zadd(articleWeightedTags, 0.1, "开源");
// 获取相关性最高的3个标签
Set<String> topTags = jedis.zrevrange(articleWeightedTags, 0, 2);
System.out.println("最相关标签: " + topTags);
// 按权重范围查询标签
Set<String> strongTags = jedis.zrangeByScore(articleWeightedTags, 0.7, 1.0);
System.out.println("强相关标签: " + strongTags);
// 增加标签权重
jedis.zincrby(articleWeightedTags, 0.1, "Redis");
// 获取标签权重
Double redisWeight = jedis.zscore(articleWeightedTags, "Redis");
System.out.println("Redis标签权重: " + redisWeight);
}
}
}
应用场景分析:
- 排行榜系统:游戏分数、商品销量、内容热度
- 延迟队列:定时任务、消息延迟投递
- 时间序列:监控数据、股票价格、传感器数据
- 带权重标签:内容相关性、用户兴趣强度
- 范围查询:地理位置、价格区间、时间范围
性能优化
数据结构选择策略
| 业务需求 | 推荐数据结构 | 理由 |
|---|---|---|
| 简单缓存 | 字符串 | 直接、高效 |
| 对象存储 | 哈希 | 内存优化、字段操作 |
| 消息队列 | 列表 | 顺序性、阻塞操作 |
| 唯一集合 | 集合 | 去重、集合运算 |
| 排序需求 | 有序集合 | 自动排序、范围查询 |
内存优化技巧
Java
public class MemoryOptimizationTips {
/**
* 小对象存储优化
*/
public void smallObjectOptimization() {
try (Jedis jedis = jedisPool.getResource()) {
// 错误方式:大量小字符串键
// jedis.set("user:1001:name", "张三");
// jedis.set("user:1001:age", "25");
// jedis.set("user:1001:email", "zhangsan@example.com");
// 正确方式:使用哈希存储小对象
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "张三");
userInfo.put("age", "25");
userInfo.put("email", "zhangsan@example.com");
jedis.hmset("user:1001", userInfo);
}
}
/**
* 大集合分片策略
*/
public void largeSetSharding() {
try (Jedis jedis = jedisPool.getResource()) {
String largeSetKey = "large:set";
int shardCount = 10;
// 添加元素时分配到不同的分片
for (int i = 0; i < 10000; i++) {
String element = "element_" + i;
int shardIndex = Math.abs(element.hashCode()) % shardCount;
String shardKey = largeSetKey + ":" + shardIndex;
jedis.sadd(shardKey, element);
}
// 查询时检查所有分片
String targetElement = "element_5000";
int targetShard = Math.abs(targetElement.hashCode()) % shardCount;
boolean exists = jedis.sismember(largeSetKey + ":" + targetShard, targetElement);
System.out.println("元素是否存在: " + exists);
}
}
}
案例:电商平台数据模型设计
Java
public class ECommerceDataModel {
private JedisPool jedisPool;
public ECommerceDataModel(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 完整的电商数据模型示例
*/
public void completeEcommerceExample() {
try (Jedis jedis = jedisPool.getResource()) {
// 1. 用户信息 - 使用哈希
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "王五");
userInfo.put("level", "VIP");
userInfo.put("points", "1500");
jedis.hmset("user:12001", userInfo);
// 2. 商品信息 - 使用哈希
Map<String, String> productInfo = new HashMap<>();
productInfo.put("name", "iPhone 15");
productInfo.put("price", "5999");
productInfo.put("stock", "100");
productInfo.put("category", "electronics");
jedis.hmset("product:2001", productInfo);
// 3. 购物车 - 使用哈希(用户ID -> 商品ID:数量)
jedis.hset("cart:12001", "product:2001", "2");
jedis.hset("cart:12001", "product:2002", "1");
// 4. 商品分类索引 - 使用集合
jedis.sadd("category:electronics:products", "product:2001", "product:2002");
jedis.sadd("category:books:products", "product:3001", "product:3002");
// 5. 商品销量排行榜 - 使用有序集合
jedis.zadd("leaderboard:products:sales", 150, "product:2001");
jedis.zadd("leaderboard:products:sales", 89, "product:2002");
jedis.zadd("leaderboard:products:sales", 203, "product:3001");
// 6. 用户浏览历史 - 使用列表(最近浏览)
jedis.lpush("user:12001:history", "product:2001", "product:3002");
jedis.ltrim("user:12001:history", 0, 49); // 只保留最近50条
// 7. 用户收藏夹 - 使用集合
jedis.sadd("user:12001:favorites", "product:2001", "product:3001");
// 8. 库存计数器 - 使用字符串
jedis.set("product:2001:stock", "100");
System.out.println("电商数据模型初始化完成");
}
}
/**
* 复杂的业务操作:用户下单
*/
public boolean placeOrder(String userId, String productId, int quantity) {
try (Jedis jedis = jedisPool.getResource()) {
// 使用事务保证原子性
jedis.watch("product:" + productId + ":stock", "user:" + userId + ":points");
// 检查库存
String stockStr = jedis.hget("product:" + productId, "stock");
if (stockStr == null || Integer.parseInt(stockStr) < quantity) {
jedis.unwatch();
return false; // 库存不足
}
// 检查用户积分
String pointsStr = jedis.hget("user:" + userId, "points");
int userPoints = pointsStr != null ? Integer.parseInt(pointsStr) : 0;
int requiredPoints = quantity * 10; // 假设每件商品需要10积分
if (userPoints < requiredPoints) {
jedis.unwatch();
return false; // 积分不足
}
// 开启事务
Transaction transaction = jedis.multi();
try {
// 扣减库存
transaction.hincrBy("product:" + productId, "stock", -quantity);
// 扣减积分
transaction.hincrBy("user:" + userId, "points", -requiredPoints);
// 增加销量
transaction.zincrby("leaderboard:products:sales", quantity, productId);
// 生成订单记录
String orderId = "order:" + System.currentTimeMillis();
Map<String, String> orderInfo = new HashMap<>();
orderInfo.put("userId", userId);
orderInfo.put("productId", productId);
orderInfo.put("quantity", String.valueOf(quantity));
orderInfo.put("status", "created");
transaction.hmset(orderId, orderInfo);
// 添加到用户订单列表
transaction.lpush("user:" + userId + ":orders", orderId);
// 提交事务
List<Object> results = transaction.exec();
return results != null; // null表示事务失败
} catch (Exception e) {
transaction.discard();
throw e;
}
}
}
}
小结
| 业务场景 | 推荐数据结构 | 关键优势 |
|---|---|---|
| 缓存数据 | 字符串 | 简单高效,支持过期 |
| 用户信息 | 哈希 | 字段操作,内存优化 |
| 消息队列 | 列表 | 顺序性,阻塞操作 |
| 社交关系 | 集合 | 去重,集合运算 |
| 排行榜 | 有序集合 | 自动排序,范围查询 |