作为一个在 Java 堆里摸爬滚打了八年的老油条,我对 Redis 的感情早就不是 "缓存工具" 这么简单了。这玩意儿就像我工位上的瑞士军刀 ------ 看起来是个开快递的,实际上能修电脑、撬瓶盖、甚至当镜子照(夸张了啊)。今天咱就掰开揉碎了聊聊,这些年我用 Redis 搞过的那些 "不务正业" 的操作,全是踩过坑的实战经验,代码示例都是能直接抄的那种。
一、分布式锁:高并发场景下的排队神器
当年踩过的坑
记得刚做电商秒杀那会,多个服务器跟打了鸡血似的抢库存,数据库行锁被压得嗷嗷叫,自己写的锁又总担心 "死锁"------ 就像一群人挤地铁,没个排队规则全乱套。直到用了 Redis 的分布式锁,才算是给线程们套上了 "秩序绳"。
推荐玩法:Redisson 真香警告
scss
// 引入依赖时记得选最新版,别问我怎么知道的(踩过旧版bug的痛)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.20.0</version>
</dependency>
// 初始化就像开罐头,拧开盖子就能用
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 加锁逻辑:就像去银行取号,先等叫号再办事
RLock lock = redisson.getLock("stock:1001");
try {
// 最多等30秒,锁自动续期30秒(比排队买奶茶还省心)
boolean locked = lock.tryLock(30, 30, TimeUnit.SECONDS);
if (locked) {
// 扣库存这事儿,必须拿到锁才能干
updateStockInDB();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 被打断了就乖乖退出
} finally {
// 自己的锁自己解,别误伤别人的
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
避坑指南(划重点!)
- 新手别用
setnx + expire
分段写!当年我就因为网络卡了一下,锁没设置过期时间,差点让系统 "卡死" 一晚上(老板至今不知道这事)。 - 集群模式直接上 RedLock,主节点挂了也不怕,就像老大罢工了马上有替补顶上。
二、计数器:高并发场景下的性能救火员
真实案例:直播在线人数的血与泪
做直播项目时,用数据库UPDATE
统计在线人数,QPS 刚到 200 就卡死了。换成 Redis 的INCR
后,直接扛住 2 万并发 ------ 这感觉就像把小电驴换成了跑车,油门随便踩。
接口限流代码(直接抄作业)
ini
// 初始化Jedis就像开冰箱门,啪嗒一下就打开了
Jedis jedis = new Jedis("redis://127.0.0.1:6379");
String key = "rate_limit:user:" + userId + ":minute";
long count = jedis.incr(key); // 每来一次请求,计数器+1
if (count == 1) {
jedis.expire(key, 60); // 第一次来才设过期时间,省点力气
}
if (count > 100) { // 一分钟最多100次,再挤就踢出去
throw new RuntimeException("你点太快了,喝口茶歇会吧!");
}
进阶操作(老码农私藏)
- 生成订单号:用
INCRBY order_id 1
比数据库自增快 10 倍,再也不怕订单号重复。 - 库存预热:秒杀前用
SET stock:1001 1000 NX
直接把库存塞进 Redis,比现查数据库快到飞起。
三、排行榜:ZSET 的凡尔赛时刻
当年的神优化:从 3 小时到 5 分钟的逆袭
做 "年度博主" 排名时,数据库跑批要 3 小时,换成 ZSET 后,凌晨 3 点跑批 5 分钟搞定 ------ 就像把手动算盘换成了计算器,效率起飞。
点赞排行榜代码(Spring Data Redis 版)
javascript
// 点赞时直接给用户加分,就像老师给学生打勾
redisTemplate.opsForZSet().incrementScore("blog:like:rank", "user:1001", 1);
// 取Top10就像查考试排名,倒着查前10名
Set<ZSetOperations.TypedTuple<String>> top10 = redisTemplate.opsForZSet()
.reverseRangeWithScores("blog:like:rank", 0, 9);
// 查用户排名就像查自己考了多少名,reverseRank就是倒着数
Long rank = redisTemplate.opsForZSet().reverseRank("blog:like:rank", "user:1001");
优化技巧(划重点)
- 热榜(前 100 名)用 ZSET 实时算,冷榜(100 名之后)每天凌晨归档到数据库,别让 Redis 太累。
- 近期点赞加时间权重,比如 "昨天点赞算 10 分,上个月点赞算 1 分",让排行榜更 "新鲜"。
四、消息队列:轻量级的异步快递员
微服务里的快递站:订单异步处理
做订单系统时,用 Redis 的 List 当队列,生产者把订单扔进去,消费者慢慢处理,就像快递站先收快递再分拣,再也不怕高峰期爆仓。
两种模式对比(人话版)
模式 | 像什么 | 优点 | 缺点 | 适合场景 |
---|---|---|---|---|
队列模式 | 快递仓库 | 快递丢不了(持久化) | 得有人一直盯着仓库 | 订单处理、日志收集 |
发布订阅 | 班级群通知 | 消息秒发 | 没看消息就错过 | 实时通知、聊天 |
可靠消费代码(带重试的那种)
javascript
// 生产者:订单一创建就扔到队列里,像扔快递到仓库
jedis.lpush("order_queue", JSON.toJSONString(order));
// 消费者:蹲在仓库门口等快递,没快递就等着(阻塞读取)
while (true) {
List<String> message = jedis.brpop(0, "order_queue"); // 0表示一直等
if (message != null && message.size() > 1) {
String orderJson = message.get(1);
try {
processOrder(orderJson); // 处理订单,就像拆快递
} catch (Exception e) {
// 处理失败就放回重试队列,再给一次机会
jedis.lpush("order_retry_queue", orderJson);
}
}
}
五、会话共享:分布式系统的登录管家
当年的崩溃时刻:用户登录后突然 "失忆"
做分布式电商时,用户在 A 服务器登录,请求转到 B 服务器就登出了 ------ 就像刚存完包,换个柜子取包就没了。用 Redis 存 Session 后,不管去哪台服务器都能 "记住" 用户。
Spring Session 集成(一行代码搞定)
typescript
// 加个依赖就像装了个插件,自动把Session存到Redis
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
// 配置类就像设置保险箱,指定Redis地址就行
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 30分钟过期
public class SessionConfig {
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1", 6379));
}
}
安全小技巧
- SessionID 加随机盐值,就像给保险箱加密码,防止被人猜中。
- 每次登录生成新 SessionID,就像每次存包都换个柜子,防君子也防小人。
六、地理位置:"附近的人" 背后的摸鱼神器
O2O 项目的救命稻草:附近的门店查询
用 Redis 的 GEO 查附近 5 公里的门店,比数据库算距离快 5 倍 ------ 就像用导航 APP 搜附近餐厅,秒出结果。
代码示例(Lettuce 客户端版)
less
// 存门店坐标就像在地图上钉图钉
redisTemplate.geoOps().add("shop:locations",
new Point(116.4074, 39.9042), "shop:1001");
// 查附近5公里的门店,就像用放大镜找图钉
GeoResults<RedisGeoCommands.GeoLocation<String>> results =
redisTemplate.geoOps().radius("shop:locations",
new Circle(new Point(116.4074, 39.9042), Metrics.KILOMETERS.toMeters(5)),
RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().sortAscending());
// 解析结果就像看地图标注
results.getContent().forEach(geoResult -> {
String shopId = geoResult.getContent().getName();
double distance = geoResult.getDistance().getValue();
System.out.println(shopId + "离你还有" + distance + "米,赶紧去!");
});
注意事项(别踩坑)
- 存的地点别超过 10 万个,不然 Redis 查起来像在图书馆找书,慢得要命。
- 经纬度小数点后留 6 位就行,精确到 1 米够用了,别存一堆没用的零。
七、其他野路子玩法(老码农私货)
1. 分布式限速:滑动窗口防刷
用 ZSET 记请求时间,每分钟最多 50 次,就像地铁限流,超了就不让进:
ini
String key = "rate_limit:user:" + userId;
long now = System.currentTimeMillis();
// 删掉一分钟前的记录,就像清理过期垃圾
redisTemplate.opsForZSet().removeRangeByScore(key, 0, now - 60000);
// 记当前时间,像盖个时间戳
redisTemplate.opsForZSet().add(key, now, now);
// 数人数,超了就踢人
long count = redisTemplate.opsForZSet().zCard(key);
if (count > 50) throw new Exception("你刷得太猛了,歇会吧!");
2. 分布式事务:秒杀防超卖
用WATCH + MULTI + EXEC
就像买东西先锁起来,别人改不了:
ini
jedis.watch("stock:1001"); // 盯着库存,别让别人偷偷改
String stock = jedis.get("stock:1001");
if (Integer.parseInt(stock) > 0) {
jedis.multi(); // 开始办正事
jedis.decr("stock:1001"); // 库存-1
List<Object> results = jedis.exec(); // 提交,要是库存被改了就返回null
if (results == null) {
// 重试,就像没买到票再排队一次
}
}
3. 实时在线用户:SET 集合去重神器
用 SET 存用户 ID,上线加进去,下线删出来,比数据库DISTINCT
快 10 倍 ------ 就像班级点名,来了的打勾,走了的擦掉。
八、老码农的肺腑之言
-
别死磕工具,先想数据结构:Redis 的 List、Set、ZSET 就像不同的工具钳,用对了能省 80% 的力气。
-
过期时间是个宝:用完就删,别让 Redis 变成垃圾场,尤其是计数器和排行榜,不然内存爆了够你加班的。
-
集群分片要当心热点 :比如 "秒杀商品" 的 key 可能全挤在一个分片,加个随机后缀(如
user:1001:seckill:goods
)分散一下。 -
监控比写代码重要 :用 Prometheus 盯着
redis_hit_rate
,内存碎片率超过 1.5 就该优化了,别等系统挂了才知道。
说白了就是:Redis牛逼,但得会用。别光拿它存缓存,多想想还能干啥骚操作,指不定就帮你把难题给解决了!