news 2026/5/21 9:22:52

Redis高级数据结构实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Redis高级数据结构实战

Redis高级数据结构实战

引言

Redis作为高性能的内存数据存储,不仅支持String字符串类型,还提供了丰富的高级数据结构:Hash字典、List列表、Set集合、ZSet有序集合、Bitmap位图、HyperLogLog基数统计、Geospatial地理位置等。深入理解和正确使用这些数据结构,可以解决很多实际业务问题,如排行榜、计数器、分布式锁、消息队列、实时统计等。本文将详细介绍Redis各数据结构的特性、适用场景和最佳实践。

一、String类型

1.1 基础操作

String是Redis最基本的数据类型,可存储字符串、整数或浮点数。

@Service public class RedisStringService { private final StringRedisTemplate redisTemplate; public void basicOperations() { // 设置值 redisTemplate.opsForValue().set("user:100", "Alice"); // 设置值并指定过期时间 redisTemplate.opsForValue().set("captcha:13800138000", "123456", Duration.ofMinutes(5)); // 获取值 String value = redisTemplate.opsForValue().get("user:100"); // 批量设置 Map<String, String> userMap = new HashMap<>(); userMap.put("user:101", "Bob"); userMap.put("user:102", "Charlie"); redisTemplate.opsForMultiValue().multiSet(userMap); // 原子递增/递减 redisTemplate.opsForValue().increment("counter"); redisTemplate.opsForValue().increment("counter", 10); redisTemplate.opsForValue().decrement("counter"); } }

1.2 分布式锁实现

@Service public class DistributedLockService { private final StringRedisTemplate redisTemplate; private static final String LOCK_PREFIX = "lock:"; private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then " + " return redis.call('del', KEYS[1]) " + "else " + " return 0 " + "end"; public boolean tryLock(String key, String value, Duration timeout) { String lockKey = LOCK_PREFIX + key; Boolean result = redisTemplate.opsForValue() .setIfAbsent(lockKey, value, timeout); return Boolean.TRUE.equals(result); } public void unlock(String key, String value) { String lockKey = LOCK_PREFIX + key; redisTemplate.execute( new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class), Collections.singletonList(lockKey), value ); } // 防重入锁 public boolean tryReentrantLock(String key, String value, int maxRetry) { String lockKey = LOCK_PREFIX + key; String currentValue = redisTemplate.opsForValue().get(lockKey); if (currentValue != null && currentValue.equals(value)) { redisTemplate.opsForValue().increment(lockKey + ":count"); return true; } if (tryLock(key, value, Duration.ofSeconds(30))) { redisTemplate.opsForValue().set(lockKey + ":count", "1"); return true; } return false; } }

二、Hash类型

1.1 基础操作

Hash适合存储对象类型的数据,比String类型更节省内存。

@Service public class RedisHashService { private final RedisTemplate<String, Object> redisTemplate; public void hashOperations() { String userKey = "user:100"; // 设置Hash字段 redisTemplate.opsForHash().put(userKey, "name", "Alice"); redisTemplate.opsForHash().put(userKey, "email", "alice@example.com"); redisTemplate.opsForHash().put(userKey, "age", "25"); // 批量设置 Map<String, Object> userMap = new HashMap<>(); userMap.put("city", "Beijing"); userMap.put("phone", "13800138000"); redisTemplate.opsForHash().putAll(userKey, userMap); // 获取单个字段 Object name = redisTemplate.opsForHash().get(userKey, "name"); // 获取所有字段和值 Map<Object, Object> allFields = redisTemplate.opsForHash().entries(userKey); // 判断字段是否存在 Boolean hasEmail = redisTemplate.opsForHash() .hasKey(userKey, "email"); // 删除字段 redisTemplate.opsForHash().delete(userKey, "phone"); // 字段值递增 redisTemplate.opsForHash().increment(userKey, "age", 1); } }

1.2 购物车实现

@Service public class CartService { private final StringRedisTemplate redisTemplate; public void addToCart(String userId, String productId, int quantity) { String cartKey = "cart:" + userId; if (quantity <= 0) { redisTemplate.opsForHash().delete(cartKey, productId); return; } redisTemplate.opsForHash().put(cartKey, productId, String.valueOf(quantity)); } public Map<String, Integer> getCart(String userId) { String cartKey = "cart:" + userId; Map<Object, Object> cart = redisTemplate.opsForHash().entries(cartKey); Map<String, Integer> result = new HashMap<>(); cart.forEach((k, v) -> result.put( k.toString(), Integer.parseInt(v.toString()))); return result; } public void removeFromCart(String userId, String productId) { String cartKey = "cart:" + userId; redisTemplate.opsForHash().delete(cartKey, productId); } public void clearCart(String userId) { String cartKey = "cart:" + userId; redisTemplate.delete(cartKey); } }

三、List类型

1.1 基础操作

List是双向链表,支持从两端插入和删除元素。

@Service public class RedisListService { private final RedisTemplate<String, String> redisTemplate; public void listOperations() { String listKey = "tasks"; // 从左边添加 redisTemplate.opsForList().leftPush(listKey, "task3"); redisTemplate.opsForList().leftPushAll(listKey, "task2", "task1"); // 从右边添加 redisTemplate.opsForList().rightPush(listKey, "task4"); // 获取列表长度 Long size = redisTemplate.opsForList().size(listKey); // 获取范围内元素 List<String> tasks = redisTemplate.opsForList() .range(listKey, 0, -1); // 获取所有 // 获取单个元素 String task = redisTemplate.opsForList().index(listKey, 0); // 从左边弹出 String popped = redisTemplate.opsForList().leftPop(listKey); // 阻塞弹出 String blocked = redisTemplate.opsForList() .leftPop(listKey, Duration.ofSeconds(10)); } }

1.2 消息队列实现

@Service public class MessageQueueService { private final RedisTemplate<String, String> redisTemplate; public void sendMessage(String queueName, String message) { redisTemplate.opsForList().rightPush("queue:" + queueName, message); } public String receiveMessage(String queueName) { return redisTemplate.opsForList().leftPop("queue:" + queueName); } public String receiveMessageWithBlock(String queueName, Duration timeout) { return redisTemplate.opsForList() .leftPop("queue:" + queueName, timeout); } // 延时队列实现 public void sendDelayMessage(String queueName, String message, Duration delay) { String delayQueueKey = "delay:" + queueName; String msgWithScore = System.currentTimeMillis() + delay.toMillis() + ":" + message; redisTemplate.opsForZSet().add(delayQueueKey, msgWithScore, System.currentTimeMillis() + delay.toMillis()); } public String receiveDelayMessage(String queueName) { String delayQueueKey = "delay:" + queueName; Set<String> messages = redisTemplate.opsForZSet() .rangeByScore(delayQueueKey, 0, System.currentTimeMillis()); if (messages.isEmpty()) { return null; } String message = messages.iterator().next(); Long removed = redisTemplate.opsForZSet() .remove(delayQueueKey, message); if (removed > 0) { return message.substring(message.indexOf(":") + 1); } return null; } }

四、Set类型

1.1 基础操作

Set是无序不重复集合,适合标签、好友关系等场景。

@Service public class RedisSetService { private final RedisTemplate<String, String> redisTemplate; public void setOperations() { String tagKey = "article:100:tags"; // 添加标签 redisTemplate.opsForSet().add(tagKey, "Java", "Spring", "Redis", "Backend"); // 获取所有标签 Set<String> tags = redisTemplate.opsForSet().members(tagKey); // 判断是否包含 Boolean hasTag = redisTemplate.opsForSet() .isMember(tagKey, "Java"); // 获取集合大小 Long size = redisTemplate.opsForSet().size(tagKey); // 删除标签 redisTemplate.opsForSet().remove(tagKey, "Redis"); // 随机获取并移除 String randomTag = redisTemplate.opsForSet().pop(tagKey); } }

1.2 抽奖系统

@Service public class LotteryService { private final RedisTemplate<String, String> redisTemplate; public void addParticipant(String activityId, String userId) { String key = "lottery:" + activityId; redisTemplate.opsForSet().add(key, userId); } public boolean hasParticipated(String activityId, String userId) { String key = "lottery:" + activityId; return Boolean.TRUE.equals( redisTemplate.opsForSet().isMember(key, userId)); } public String drawLottery(String activityId) { String key = "lottery:" + activityId; return redisTemplate.opsForSet().pop(key); } public List<String> drawMultiple(String activityId, int count) { String key = "lottery:" + activityId; List<String> winners = new ArrayList<>(); for (int i = 0; i < count; i++) { String winner = redisTemplate.opsForSet().pop(key); if (winner != null) { winners.add(winner); } } return winners; } public Set<String> getParticipants(String activityId) { String key = "lottery:" + activityId; return redisTemplate.opsForSet().members(key); } }

五、ZSet有序集合

5.1 基础操作

ZSet是带分数的有序集合,元素按分数排序。

@Service public class RedisZSetService { private final RedisTemplate<String, String> redisTemplate; public void zsetOperations() { String leaderboardKey = "game:leaderboard"; // 添加玩家分数 redisTemplate.opsForZSet().add(leaderboardKey, "player1", 1000); redisTemplate.opsForZSet().add(leaderboardKey, "player2", 2500); redisTemplate.opsForZSet().add(leaderboardKey, "player3", 1800); // 批量添加 Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>(); tuples.add(ZSetOperations.TypedTuple.of("player4", 2200.0)); tuples.add(ZSetOperations.TypedTuple.of("player5", 3000.0)); redisTemplate.opsForZSet().add(leaderboardKey, tuples); // 获取排名(从小到大,0开始) Long rank = redisTemplate.opsForZSet() .rank(leaderboardKey, "player3"); // player3排名第2 // 获取逆序排名(从大到小) Long reverseRank = redisTemplate.opsForZSet() .reverseRank(leaderboardKey, "player3"); // player3排名第2 // 获取分数 Double score = redisTemplate.opsForZSet() .score(leaderboardKey, "player3"); // 分数递增 redisTemplate.opsForZSet().incrementScore( leaderboardKey, "player3", 500); // 获取top N Set<String> topPlayers = redisTemplate.opsForZSet() .reverseRange(leaderboardKey, 0, 9); // 获取分数范围内的玩家 Set<String> midPlayers = redisTemplate.opsForZSet() .rangeByScore(leaderboardKey, 1500, 2500); } }

5.2 排行榜实现

@Service public class LeaderboardService { private final RedisTemplate<String, String> redisTemplate; public void updateScore(String gameId, String playerId, int score) { String key = "game:" + gameId + ":leaderboard"; redisTemplate.opsForZSet().add(key, playerId, score); } public Long getRank(String gameId, String playerId) { String key = "game:" + gameId + ":leaderboard"; Long rank = redisTemplate.opsForZSet().reverseRank(key, playerId); return rank != null ? rank + 1 : null; } public List<RankInfo> getTopN(String gameId, int n) { String key = "game:" + gameId + ":leaderboard"; Set<ZSetOperations.TypedTuple<String>> topSet = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n - 1); List<RankInfo> result = new ArrayList<>(); int rank = 1; for (ZSetOperations.TypedTuple<String> tuple : topSet) { result.add(new RankInfo( rank++, tuple.getValue(), tuple.getScore().intValue() )); } return result; } public List<RankInfo> getRankRange(String gameId, long start, long end) { String key = "game:" + gameId + ":leaderboard"; Set<ZSetOperations.TypedTuple<String>> rangeSet = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); List<RankInfo> result = new ArrayList<>(); long rank = start + 1; for (ZSetOperations.TypedTuple<String> tuple : rangeSet) { result.add(new RankInfo( rank++, tuple.getValue(), tuple.getScore().intValue() )); } return result; } public List<RankInfo> getRankAroundMe(String gameId, String playerId) { String key = "game:" + gameId + ":leaderboard"; Long myRank = redisTemplate.opsForZSet() .reverseRank(key, playerId); if (myRank == null) { return Collections.emptyList(); } long start = Math.max(0, myRank - 2); long end = myRank + 2; Set<ZSetOperations.TypedTuple<String>> rangeSet = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end); List<RankInfo> result = new ArrayList<>(); long rank = start + 1; for (ZSetOperations.TypedTuple<String> tuple : rangeSet) { result.add(new RankInfo( rank++, tuple.getValue(), tuple.getScore().intValue() )); } return result; } }

六、Bitmap位图

6.1 基础操作

Bitmap使用位来存储数据,适合签到、在线状态等场景。

@Service public class RedisBitmapService { private final RedisTemplate<String, String> redisTemplate; public void bitmapOperations() { String signKey = "user:100:sign"; // 设置某天签到(2024年1月1日是第1天偏移量) long offset = calculateOffset("2024-01-15"); redisTemplate.opsForValue().setBit(signKey, offset, true); // 检查是否签到 Boolean signed = redisTemplate.opsForValue() .getBit(signKey, offset); // 统计签到天数 Long signDays = redisTemplate.opsForBitCount(signKey); // 统计某月签到天数 Long monthSignDays = redisTemplate.opsForBitCount(signKey, calculateOffset("2024-01-01"), calculateOffset("2024-01-31")); } private long calculateOffset(String date) { LocalDate targetDate = LocalDate.parse(date); LocalDate baseDate = LocalDate.of(2024, 1, 1); return ChronoUnit.DAYS.between(baseDate, targetDate); } }

6.2 签到系统实现

@Service public class SignService { private final RedisTemplate<String, String> redisTemplate; public boolean sign(String userId, LocalDate date) { String key = getSignKey(userId, date.getYear()); long offset = ChronoUnit.DAYS.between( date.withDayOfYear(1), date); Boolean result = redisTemplate.opsForValue().setBit(key, offset, true); return !Boolean.TRUE.equals(result); // true表示今天已签到 } public boolean hasSigned(String userId, LocalDate date) { String key = getSignKey(userId, date.getYear()); long offset = ChronoUnit.DAYS.between( date.withDayOfYear(1), date); return Boolean.TRUE.equals( redisTemplate.opsForValue().getBit(key, offset)); } public int getSignCount(String userId, LocalDate startDate, LocalDate endDate) { String key = getSignKey(userId, startDate.getYear()); long startOffset = ChronoUnit.DAYS.between( startDate.withDayOfYear(1), startDate); long endOffset = ChronoUnit.DAYS.between( startDate.withDayOfYear(1), endDate); Long count = redisTemplate.opsForBitCount(key, startOffset, endOffset); return count != null ? count.intValue() : 0; } public List<Boolean> getMonthSignRecord(String userId, int year, int month) { String key = getSignKey(userId, year); LocalDate monthStart = LocalDate.of(year, month, 1); LocalDate monthEnd = monthStart.withDayOfMonth( monthStart.lengthOfMonth()); List<Boolean> result = new ArrayList<>(); for (int day = 1; day <= monthEnd.getDayOfMonth(); day++) { LocalDate date = LocalDate.of(year, month, day); long offset = ChronoUnit.DAYS.between(monthStart, date); Boolean bit = redisTemplate.opsForValue().getBit(key, offset); result.add(Boolean.TRUE.equals(bit)); } return result; } private String getSignKey(String userId, int year) { return String.format("sign:%s:%d", userId, year); } }

七、HyperLogLog

7.1 UV统计实现

@Service public class UVStatisticsService { private final RedisTemplate<String, String> redisTemplate; public void addUV(String date) { String key = "uv:" + date; String userId = generateUserId(); // 生成访客ID redisTemplate.opsForHyperLogLog().add(key, userId); } public long getUV(String date) { String key = "uv:" + date; Long size = redisTemplate.opsForHyperLogLog().size(key); return size != null ? size : 0; } public long getUVRange(String startDate, String endDate) { List<String> keys = new ArrayList<>(); LocalDate start = LocalDate.parse(startDate); LocalDate end = LocalDate.parse(endDate); while (!start.isAfter(end)) { keys.add("uv:" + start.toString()); start = start.plusDays(1); } Long size = redisTemplate.opsForHyperLogLog().size(keys.toArray( new String[0])); return size != null ? size : 0; } private String generateUserId() { return UUID.randomUUID().toString(); } }

八、GEO地理位置

8.1 附近的人实现

@Service public class GeoService { private final RedisTemplate<String, String> redisTemplate; public void addLocation(String userId, double longitude, double latitude) { redisTemplate.opsForGeo().add("user:locations", new Point(longitude, latitude), userId); } public List<NearbyUser> getNearbyUsers(String userId, double radiusKm) { // 获取用户位置 List<Point> positions = redisTemplate.opsForGeo() .position("user:locations", userId); if (positions == null || positions.isEmpty() || positions.get(0) == null) { return Collections.emptyList(); } Point myPosition = positions.get(0); // 搜索附近用户 GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius("user:locations", new Circle(myPosition, new Distance(radiusKm, Metrics.KILOMETERS)), RedisGeoCommands.GeoRadiusCommandArgs .newGeoRadiusArgs() .includeDistance() .includeCoordinates() .sortAscending() .limit(100)); List<NearbyUser> nearbyUsers = new ArrayList<>(); if (results != null) { for (GeoResult<RedisGeoCommands.GeoLocation<String>> result : results.getContent()) { nearbyUsers.add(new NearbyUser( result.getContent().getName(), result.getDistance().getValue(), result.getContent().getPoint().getX(), result.getContent().getPoint().getY() )); } } return nearbyUsers; } public double calculateDistance(String userId1, String userId2) { Distance distance = redisTemplate.opsForGeo() .distance("user:locations", userId1, userId2, Metrics.KILOMETERS); return distance != null ? distance.getValue() : 0; } }

九、Pipeline管道

9.1 批量操作优化

@Service public class RedisPipelineService { private final RedisTemplate<String, String> redisTemplate; public void batchOperations() { long startTime = System.currentTimeMillis(); // 使用Pipeline批量执行 List<Object> results = redisTemplate.executePipelined( (RedisCallback<Object>) connection -> { for (int i = 0; i < 1000; i++) { connection.stringCommands() .set(("key:" + i).getBytes(), ("value:" + i).getBytes()); } return null; }); long endTime = System.currentTimeMillis(); System.out.println("Pipeline执行1000次操作耗时: " + (endTime - startTime) + "ms"); } public Map<String, String> batchGet(List<String> keys) { List<Object> results = redisTemplate.executePipelined( (RedisCallback<Object>) connection -> { for (String key : keys) { connection.stringCommands().get(key.getBytes()); } return null; }); Map<String, String> map = new HashMap<>(); for (int i = 0; i < keys.size(); i++) { String value = (String) results.get(i); if (value != null) { map.put(keys.get(i), value); } } return map; } }

十、最佳实践

10.1 键命名规范

// 推荐命名格式:业务:实体:属性 String key = "order:100:status"; // 订单状态 String key = "user:100:profile"; // 用户资料 String key = "session:abc123:data"; // 会话数据 String key = "cache:product:100"; // 产品缓存 // 避免的命名 String key = "data"; // 太模糊 String key = "myvalue"; // 无意义 String key = "123"; // 无描述性

10.2 内存优化

@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate( RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用StringRedisSerializer作为key的序列化器 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // value使用JdkSerializationRedisSerializer template.setValueSerializer( new JdkSerializationRedisSerializer()); template.setHashValueSerializer( new JdkSerializationRedisSerializer()); template.afterPropertiesSet(); return template; } }

总结

Redis提供了丰富的数据结构,每种结构都有其独特的适用场景。String适合简单的键值存储和计数器;Hash适合存储对象和字典类型数据;List适合消息队列和任务队列;Set适合去重和集合运算;ZSet适合排行榜和有序数据;Bitmap适合签到和状态监控;HyperLogLog适合UV统计;GEO适合附近的人和地理位置相关应用。在实际开发中,需要根据业务特点选择合适的数据结构,并注意键命名规范、内存优化和性能监控。正确使用Redis数据结构,可以构建高性能、易扩展的应用系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/18 11:21:34

Spring Boot进阶篇:底层原理最佳实践

Spring Boot不用多说&#xff0c;是咱们Java程序员必须熟练掌握的基本技能。工作上它让配置、代码编写、部署和监控都更简单&#xff0c;面试时互联网企业招聘对于Spring Boot这个系统开发的首选框架也是考察的比较严苛&#xff0c;如果你不是刚入行&#xff0c;只是停留在会用…

作者头像 李华
网站建设 2026/5/18 11:21:23

JavaScript二进制数据处理:从ArrayBuffer到Typed Array实战解析

1. 从“模糊”到“清晰”&#xff1a;JavaScript二进制处理的前世今生作为一名在Web前端和Node.js领域摸爬滚打了十多年的老码农&#xff0c;我亲眼见证了JavaScript从一个只能处理字符串和简单数字的“玩具语言”&#xff0c;一步步成长为如今能驾驭复杂二进制数据的“全能选手…

作者头像 李华
网站建设 2026/5/18 11:21:18

Steam游戏数据一键获取神器:GetDataFromSteam-SteamDB完整指南

Steam游戏数据一键获取神器&#xff1a;GetDataFromSteam-SteamDB完整指南 【免费下载链接】GetDataFromSteam-SteamDB 项目地址: https://gitcode.com/gh_mirrors/ge/GetDataFromSteam-SteamDB 想要轻松获取Steam游戏的详细数据吗&#xff1f;GetDataFromSteam-SteamD…

作者头像 李华
网站建设 2026/5/18 11:21:15

188.YOLOv8 三大任务全流程代码,含 CSPDarknet/CIoU 公式,零错误运行(CUDA11.8 适配)

摘要 YOLO(You Only Look Once)系列目标检测算法自2016年问世以来,凭借其端到端、单阶段、高速度的特性,已成为工业界最广泛使用的目标检测框架。本文从数学原理出发,系统讲解YOLOv8的核心架构,包含CSPDarknet骨干网络、PAN-FPN特征融合、解耦检测头及损失函数设计。提供…

作者头像 李华
网站建设 2026/5/18 11:21:12

彻底告别卡顿!CLion在Ubuntu上优化VM参数与内存配置的实战心得

彻底告别卡顿&#xff01;CLion在Ubuntu上优化VM参数与内存配置的实战心得 当你在Ubuntu上使用CLion处理稍大项目时&#xff0c;是否经历过界面卡顿、响应迟缓的困扰&#xff1f;这种体验不仅影响开发效率&#xff0c;还可能打断编程思路。作为一款功能强大的C/C IDE&#xff0…

作者头像 李华