“小王,昨晚促销又崩了!用户投诉说卡成PPT,订单都丢了好几个!赶紧查!” —— 凌晨3点,老板的电话像催命符。
你揉着惺忪睡眼,盯着监控面板上飙升的数据库CPU曲线和满屏的慢查询日志,心里一万头羊驼奔腾而过。数据库,又双叒叕成了瓶颈! 高并发、瞬时流量、热点数据... 这些词光是想想就让人头皮发麻。
别慌!是时候请出我们应对高并发、提升性能的“瑞士军刀”—— Redis 了!作为Java开发者,用好Redis,不仅能让你从数据库泥潭中脱身,更能让你的系统性能原地起飞!今天,我们就来深扒Java里玩转Redis的核心姿势,让你告别“凌晨三点惊魂夜”!
一、 Redis:不止是缓存,更是Java性能的“涡轮增压”
先快速过一下Redis的核心价值:
- 内存存储,速度炸裂: 数据存在内存,读写操作快到飞起(微秒级),秒杀传统磁盘数据库(毫秒级)。
- 丰富数据结构: String, Hash, List, Set, Sorted Set, Bitmaps... 应对不同场景游刃有余,比单纯KV强大N倍。
- 持久化可选: RDB快照、AOF日志,按需保障数据安全,不用担心掉电全丢。
- 高可用 & 分布式: 主从复制、Sentinel哨兵、Redis Cluster集群,轻松构建健壮架构。
- 功能丰富: 发布订阅、Lua脚本、事务、管道、过期策略... 满足各种骚操作需求。
对于Java应用来说,Redis简直是解决以下痛点的“特效药”:
- 缓存穿透/雪崩/击穿: 用缓存扛住海量读请求,保护脆弱数据库。
- 会话管理 (Session): 分布式场景下用户状态共享,告别粘性Session。
- 排行榜/计数器:
Sorted Set和INCR命令轻松搞定实时排行、点赞计数。 - 分布式锁: 用
SETNX或Redisson实现跨JVM的互斥访问。 - 消息队列 (轻量级):
List的LPUSH/RPOP或BRPOP实现简单队列。 - 实时数据聚合: 利用内存速度做高速计算中间结果存储。
二、 Java连接Redis:选对“驾驶舱”很重要
想在Java里“驾驶”Redis,你需要一个可靠的客户端。主流选择有俩:
-
Jedis: 老牌经典,同步阻塞IO,简单直接,API贴近Redis命令。适合大多数常规场景。
// 简单示例 Jedis jedis = new Jedis("localhost", 6379); jedis.set("myKey", "掘金技术干货YYDS!"); String value = jedis.get("myKey"); System.out.println(value); // 输出:掘金技术干货YYDS! jedis.close(); // 记得关闭! -
Lettuce: 基于Netty的异步非阻塞IO,性能更高(尤其在高并发下),支持响应式编程,连接可共享(
StatefulRedisConnection)。Spring Boot 2.x+ 默认推荐!RedisClient client = RedisClient.create("redis://localhost:6379"); StatefulRedisConnection<String, String> connection = client.connect(); RedisCommands<String, String> commands = connection.sync(); commands.set("lettuceKey", "异步就是快!"); String asyncValue = commands.get("lettuceKey"); System.out.println(asyncValue); // 输出:异步就是快! connection.close(); // 关闭连接 client.shutdown(); // 关闭客户端
怎么选?
- 追求简单、稳定、项目不大?Jedis够用。
- 追求极致性能、高并发、响应式、或用新版本Spring Boot?无脑Lettuce!
三、 Spring Data Redis:官方“外挂”,解放生产力
如果你在用Spring Boot,那spring-boot-starter-data-redis绝对是你的福音!它封装了客户端(默认Lettuce),提供了超好用的RedisTemplate和StringRedisTemplate,大大简化操作。
@Autowired
private StringRedisTemplate stringRedisTemplate; // 操作字符串最方便
public void useTemplate() {
// 存String
stringRedisTemplate.opsForValue().set("templateKey", "Spring大法好!");
// 取String
String templateValue = stringRedisTemplate.opsForValue().get("templateKey");
// 操作Hash
stringRedisTemplate.opsForHash().put("user:1001", "name", "码哥");
stringRedisTemplate.opsForHash().put("user:1001", "age", "30");
String name = (String) stringRedisTemplate.opsForHash().get("user:1001", "name");
// 操作List
stringRedisTemplate.opsForList().leftPush("msgQueue", "订单消息1");
String msg = stringRedisTemplate.opsForList().rightPop("msgQueue");
// 更多 opsForSet(), opsForZSet()...
}
优点:
- 自动序列化/反序列化: 省去手动转换字节的麻烦(注意序列化策略,推荐String或JSON)。
- 统一API: 不同数据结构的操作API风格统一。
- 连接管理: Spring帮你管理连接池,配置方便。
- Repository支持 (可选): 可以像操作JPA一样操作Redis(特定场景)。
四、 实战“神技”:Java + Redis 经典场景代码秀
神技1:缓存查询结果 (防数据库被锤爆)
public Product getProductById(Long id) {
String key = "product:" + id;
// 1. 先查缓存
String productJson = stringRedisTemplate.opsForValue().get(key);
if (productJson != null) {
return JSON.parseObject(productJson, Product.class); // 假设用Fastjson
}
// 2. 缓存没有,查数据库 (模拟)
Product product = productRepository.findById(id).orElse(null);
if (product != null) {
// 3. 写入缓存,设置过期时间 (防永久缓存、防雪崩)
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
}
return product;
}
神技2:分布式锁 (防止超卖/重复提交)
简单版 (SETNX + 过期时间):
public boolean tryLock(String lockKey, String requestId, long expireSeconds) {
// SET lockKey requestId NX EX expireSeconds
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
lockKey, requestId, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
public void unlock(String lockKey, String requestId) {
// Lua脚本保证原子性:只有加锁的客户端才能解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
stringRedisTemplate.execute(script, Collections.singletonList(lockKey), requestId);
}
生产级推荐: 直接用 Redisson 库,它封装了完善的 RLock,支持可重入锁、锁续期、红锁等高级特性,更可靠!
神技3:排行榜 (Sorted Set 大显身手)
// 用户得分增加
public void addUserScore(String rankKey, String userId, double score) {
stringRedisTemplate.opsForZSet().incrementScore(rankKey, userId, score);
}
// 获取Top N
public List<UserRank> getTopN(String rankKey, long n) {
Set<ZSetOperations.TypedTuple<String>> topSet = stringRedisTemplate.opsForZSet().reverseRangeWithScores(rankKey, 0, n - 1);
List<UserRank> topList = new ArrayList<>();
if (topSet != null) {
long rank = 1;
for (ZSetOperations.TypedTuple<String> tuple : topSet) {
topList.add(new UserRank(rank++, tuple.getValue(), tuple.getScore()));
}
}
return topList;
}
神技4:计数器 (INCR 原子操作)
// 文章点赞数+1
public long incrementArticleLike(Long articleId) {
String key = "article:like:" + articleId;
return stringRedisTemplate.opsForValue().increment(key); // 原子操作,返回最新值
}
// 获取点赞数
public long getArticleLikeCount(Long articleId) {
String key = "article:like:" + articleId;
String countStr = stringRedisTemplate.opsForValue().get(key);
return countStr == null ? 0 : Long.parseLong(countStr);
}
神技5:管道 (Pipeline) - 批量操作,性能翻倍!
当你需要执行大量Redis命令时,管道可以将多个命令打包一次发送,减少网络往返时间(RTT),极大提升性能。
// 使用Lettuce Pipeline
List<Object> results = connection.sync().pipelined(commands -> {
for (int i = 0; i < 1000; i++) {
commands.set("pipelineKey" + i, "value" + i);
commands.get("pipelineKey" + i);
}
});
// results 包含所有命令的返回结果
// 使用RedisTemplate executePipelined
List<Object> results = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (int i = 0; i < 1000; i++) {
connection.stringCommands().set(("pipeKey" + i).getBytes(), ("val" + i).getBytes());
connection.stringCommands().get(("pipeKey" + i).getBytes());
}
return null;
});
五、 避坑指南:Java用Redis的“雷区”
- 序列化选择不当: 默认的
JdkSerializationRedisSerializer序列化结果不可读、兼容性差。强烈推荐使用StringRedisSerializer(操作字符串)或GenericJackson2JsonRedisSerializer(操作对象,注意类型信息)。 - 大Key问题: 单个String值过大(>10KB)或Hash/List/Set元素过多(>5000)会阻塞Redis,影响性能。拆Key、用SCAN/HSCAN分批处理。
- 热Key问题: 某个Key访问量极高(如明星八卦缓存),可能打爆单节点。用本地缓存、多级缓存、或读写分离分散压力。
- 缓存穿透: 大量请求查询数据库中根本不存在的数据(如不合法ID)。解决方案: 缓存空值(设置短过期时间)、布隆过滤器(Bloom Filter)拦截。
- 缓存雪崩: 大量缓存在同一时间失效,请求瞬间涌向数据库。解决方案: 设置缓存过期时间时加随机值、热点数据永不过期(后台异步更新)、保证数据库高可用。
- 缓存击穿: 某个热点Key失效瞬间,大量并发请求直接打到数据库。解决方案: 使用互斥锁(如分布式锁)只让一个线程去重建缓存,其他线程等待。
- 连接泄露: 忘记关闭
Jedis或Lettuce Connection。务必使用try-with-resources或在finally块中关闭! Spring Data Redis的RedisTemplate帮你管理了。 - 过度依赖Redis事务: Redis事务不是关系型数据库事务(ACID),它是将命令打包顺序执行,中间不会被其他客户端打断,但不保证原子性(部分失败不会回滚)。复杂原子操作优先考虑Lua脚本。
六、 总结:用好Redis,Java工程师的“必修课”
Redis早已不是简单的缓存工具,它已成为现代高并发Java架构的基石之一。掌握其核心数据结构、熟练使用Java客户端(特别是Lettuce和Spring Data Redis)、并能在实际场景(缓存、锁、计数、队列、排行)中灵活运用,是你提升系统性能和开发效率的关键一步。
记住核心公式:
Java + Redis + 正确的姿势 = 高性能 + 高可用 + 快乐的程序员 (不用再凌晨三点救火!)
最后抛个问题: 你在项目中用Redis遇到过最坑爹的问题是什么?或者最得意的Redis优化案例是啥?欢迎在评论区分享交流,一起避坑,一起飞升!🚀