一、redis 在 spring 项目中的常见使用场景
在企业开发中,redis 不只是一个缓存工具,而是一个高性能分布式中间件。
它既能加速数据访问,也能解决很多分布式问题。
| 场景类别 | 场景说明 | 使用目的 |
|---|---|---|
| 1. 缓存(cache) | 将数据库查询结果或计算结果暂存到 redis | 提高访问速度、减轻数据库压力 |
| 2. 分布式 session 管理 | 在多台服务器间共享登录状态 | 解决 session 不一致问题 |
| 3. 分布式锁(distributed lock) | 控制分布式环境下的资源竞争 | 保证接口幂等性、避免超卖 |
| 4. 消息队列(message queue) | 使用 redis 的 list 或 stream 实现简单消息队列 | 异步处理任务、削峰填谷 |
| 5. 排行榜 / 计数系统 | 使用 zset、incr 等结构实现排行榜、点赞、热度统计 | 实时性强、性能高 |
| 6. 限流(rate limiting) | 基于 redis 的计数器算法实现请求限流 | 防止接口被恶意频繁调用 |
| 7. 数据共享 / 配置中心缓存 | 缓存配置、公共数据字典、系统参数 | 提高系统访问效率 |
| 8. 地理位置功能(geo) | 使用 redis 的 geo 类型 | 实现附近的人 / 店铺功能 |
| 9. 延时任务 / 定时任务 | 利用 zset 的分数排序实现延迟调度 | 替代消息队列中的延迟功能 |
| 10. 热点数据保护 | 对热点 key 设置过期策略或锁 | 避免缓存击穿、穿透、雪崩问题 |
二、环境准备
在开始编码前,我们需要在 spring boot 项目中引入 redis 依赖。
maven 依赖:
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>yaml 配置:
spring:
data:
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 2000ms这就完成了基础配置,spring boot 会自动帮我们创建 redistemplate 对象。
三、redistemplate 的作用
在 spring 中,redistemplate 是操作 redis 的核心对象,
相当于 redis 的 jdbc 模板。所有读写都靠它完成。
| 方法 | redis 类型 | 常见业务场景 | 示例 |
|---|---|---|---|
opsforvalue() | string | 缓存对象、验证码、token | set("user:1001:name", "tom") |
opsforhash() | hash | 存储对象属性,如用户、商品 | put("user:1001", "age", "25") |
opsforlist() | list | 评论列表、任务队列 | leftpush("comment:101", "很好!") |
opsforset() | set | 点赞去重、好友推荐 | add("like:post:100", "user1") |
opsforzset() | zset | 排行榜、积分榜 | add("rank:score", "tom", 100) |
一句话理解:
redistemplate 是万能 钥匙,不管你想存什么类型的数据,它都能帮你搞定。
四、应用场景实战
4.1 缓存(cache)
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 缓存数据库查询结果 | 缓存商品详情、用户信息 | 提高访问速度 | string / hash | user:1001 → {…} |
| 缓存热点数据 | 首页推荐、排行榜 | 提升并发性能 | string / zset | rank:hot → [id, score] |
| 缓存计算结果 | 缓存统计分析结果 | 降低重复计算 | string | report:2025-11 → value |
| 本地 + 分布式缓存 | spring cache + redis | 提高系统扩展性 | hash | @cacheable(value="user") |
业务背景
在高并发项目中(比如商城、题库、短视频平台),数据库访问往往成为性能瓶颈。
而 redis 的读写性能是数据库的上千倍,因此我们常常将热点数据缓存起来,
以“读缓存 → 缓存失效 → 回源数据库”的模式运行。
示例:缓存用户详情数据
@service
public class userservice {
@resource
private redistemplate<string, object> redistemplate;
@resource
private usermapper usermapper;
public user getuserbyid(long id) {
string key = "user:" + id;
// step 1: 尝试从缓存中读取
user user = (user) redistemplate.opsforvalue().get(key);
if (user != null) {
system.out.println("【命中缓存】userid = " + id);
return user;
}
// step 2: 缓存未命中 → 查询数据库
user = usermapper.selectbyid(id);
if (user == null) {
return null;
}
// step 3: 写入缓存,设置过期时间 10 分钟
redistemplate.opsforvalue().set(key, user, 10, timeunit.minutes);
system.out.println("【写入缓存】userid = " + id);
return user;
}
}亮点:
- 每次查询会优先走 redis;
- 避免同一数据被频繁访问数据库;
- 可控制缓存时间,防止脏数据积累。
4.2 分布式 session 管理
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 分布式 session 管理 | 多台服务器共享登录状态 | 解决 session 不一致 | string / hash | 登录共享、sso、token 验证 |
业务背景
假设一个网站部署在三台服务器上:
- 用户第一次请求被分配到 a 服务器并登录;
- 第二次请求被负载均衡到 b;
- b 并不知道这个用户的登录状态。
这就是典型的 “session 不一致” 问题。
方案一:使用 spring session(推荐)
依赖:
<dependency>
<groupid>org.springframework.session</groupid>
<artifactid>spring-session-data-redis</artifactid>
</dependency>配置:
spring:
session:
store-type: redis
redis:
host: localhost
port: 6379实现效果:
- 自动拦截所有
httpsession; - 自动序列化到 redis;
- 多台服务器访问同一 redis,即可共享登录状态。
方案二:自定义 token(前后端分离项目常用)
登录接口:
@postmapping("/login")
public responseentity<?> login(@requestbody loginrequest request) {
user user = usermapper.findbyusername(request.getusername());
if (user == null || !user.getpassword().equals(request.getpassword())) {
return responseentity.status(httpstatus.unauthorized).body("用户名或密码错误");
}
// 生成 token
string token = uuid.randomuuid().tostring();
redistemplate.opsforvalue().set("login:token:" + token, user, 30, timeunit.minutes);
return responseentity.ok(map.of("token", token));
}请求拦截器:
@override
public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) {
string token = request.getheader("authorization");
if (token == null) return false;
user user = (user) redistemplate.opsforvalue().get("login:token:" + token);
if (user == null) {
response.setstatus(401);
return false;
}
request.setattribute("user", user);
return true;
}效果:
- token 与用户信息缓存在 redis;
- 任意节点可通过 token 验证身份;
- 登录状态共享、可过期控制。
4.3 分布式锁(distributed lock)
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 分布式锁 | 控制资源竞争 | 保证幂等、防止超卖 | string | 秒杀、库存控制 |
业务背景
电商场景中,秒杀活动高并发下会导致:
- 多人同时下单同一商品;
- 超卖、库存负数。
此时可以用 redis 分布式锁,确保一个商品同一时刻只能被一个线程修改。
代码实现:
@service
public class orderservice {
@resource
private redistemplate<string, object> redistemplate;
public void placeorder(long productid) {
string lockkey = "lock:product:" + productid;
string clientid = uuid.randomuuid().tostring();
try {
boolean locked = redistemplate.opsforvalue()
.setifabsent(lockkey, clientid, 10, timeunit.seconds);
if (boolean.false.equals(locked)) {
throw new runtimeexception("系统繁忙,请稍后再试");
}
// 模拟扣库存逻辑
integer stock = getstock(productid);
if (stock > 0) {
updatestock(productid, stock - 1);
system.out.println("下单成功,库存剩余:" + (stock - 1));
} else {
system.out.println("库存不足");
}
} finally {
// 释放锁(防止误删)
string value = (string) redistemplate.opsforvalue().get(lockkey);
if (clientid.equals(value)) {
redistemplate.delete(lockkey);
}
}
}
private integer getstock(long id) { return 5; } // 模拟
private void updatestock(long id, integer newstock) {}
}要点说明:
setifabsent()= redis 实现的原子加锁;- 过期时间避免死锁;
- 校验
clientid防止误删他人锁。
4.4 消息队列(message queue)
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 消息队列 | 使用 list / stream 实现异步任务 | 异步处理任务、削峰填谷 | list / stream | 下单通知、邮件发送 |
业务背景
假设用户下单成功后,需要:
- 发送邮件;
- 推送短信;
- 更新统计数据。
这些都可以异步执行,不应阻塞主线程。
方案一:基于 list 的简单队列
// 生产者:发送任务
public void sendmailtask(string mailjson) {
redistemplate.opsforlist().leftpush("queue:mail", mailjson);
}
// 消费者:异步线程处理任务
@scheduled(fixeddelay = 2000)
public void consumemailtask() {
string task = (string) redistemplate.opsforlist().rightpop("queue:mail");
if (task != null) {
system.out.println("发送邮件任务:" + task);
// 执行邮件发送逻辑
}
}方案二:使用 stream(支持消费组)
xadd queue:order * orderid 1001 userid 2002 xgroup create queue:order group1 0 mkstream
消费者代码:
@scheduled(fixeddelay = 3000)
public void consumeorder() {
list<maprecord<string, object, object>> messages = redistemplate.opsforstream()
.read(consumer.from("group1", "consumera"),
streamreadoptions.empty().count(1),
streamoffset.create("queue:order", readoffset.lastconsumed()));
if (messages != null) {
for (maprecord<string, object, object> msg : messages) {
system.out.println("消费订单消息:" + msg.getvalue());
redistemplate.opsforstream().acknowledge("queue:order", "group1", msg.getid());
}
}
}优势:
- 支持多消费者组;
- 消息可持久化;
- 可追溯未消费记录。
4.5 排行榜 / 计数系统
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 排行榜 / 计数 | 实现积分榜、热度榜、点赞统计 | 实时计算 + 高性能排序 | zset / string | 热门文章榜、游戏积分榜 |
业务背景
在很多应用中(例如:短视频热榜、游戏积分榜、帖子点赞排行),我们都需要根据“分数”动态排序。
数据库的排序操作成本高,而 redis 的 zset 类型天然支持「按分数排序」,
非常适合做排行榜类功能。
代码示例:用户积分排行榜
@service
public class rankservice {
@resource
private redistemplate<string, object> redistemplate;
private static final string rank_key = "rank:user:score";
// 增加用户积分
public void addscore(string userid, double score) {
redistemplate.opsforzset().incrementscore(rank_key, userid, score);
}
// 获取前 n 名用户
public set<string> gettopusers(int limit) {
return redistemplate.opsforzset().reverserange(rank_key, 0, limit - 1);
}
// 查询某个用户的排名
public long getrank(string userid) {
return redistemplate.opsforzset().reverserank(rank_key, userid);
}
// 查询某个用户的分数
public double getuserscore(string userid) {
return redistemplate.opsforzset().score(rank_key, userid);
}
}使用效果:
rankservice.addscore("tom", 10);
rankservice.addscore("jerry", 20);
rankservice.addscore("alice", 15);
system.out.println(rankservice.gettopusers(3)); // [jerry, alice, tom]扩展应用
- 文章热度排行榜(根据阅读量 / 点赞数累计)
- 商品销量榜单
- 实时在线活跃用户榜
- 视频播放量榜单
4.6 限流(rate limiting)
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 接口限流 | 限制单位时间内访问次数 | 防止接口被刷、控制 qps | string | 登录防暴力 破解、api 限速 |
业务背景
在接口开放或秒杀活动中,往往会出现某些接口被频繁请求,
轻则浪费资源,重则打垮系统。
此时我们可以利用 redis 做“计数限流”:
以 ip / 用户 id 为维度,统计在某个时间窗口内的访问次数。
代码示例:基于固定窗口的限流实现
@service
public class ratelimitservice {
@resource
private redistemplate<string, object> redistemplate;
// 每分钟最多请求 60 次
private static final int limit_count = 60;
private static final int expire_time = 60;
public void checkratelimit(string ip) {
string key = "limit:ip:" + ip;
long count = redistemplate.opsforvalue().increment(key);
if (count == 1) {
redistemplate.expire(key, expire_time, timeunit.seconds);
}
if (count > limit_count) {
throw new runtimeexception("请求过于频繁,请稍后再试!");
}
}
}示例使用:
@getmapping("/api/test")
public string test(httpservletrequest request) {
string ip = request.getremoteaddr();
ratelimitservice.checkratelimit(ip);
return "正常访问成功";
}
升级方案:滑动窗口限流
使用 redis 的 zset 来记录时间戳,实现更精确的滑动窗口限流:
public boolean allowrequest(string key, int limit, int seconds) {
long now = system.currenttimemillis();
long windowstart = now - seconds * 1000l;
redistemplate.opsforzset().removerangebyscore(key, 0, windowstart);
redistemplate.opsforzset().add(key, string.valueof(now), now);
long count = redistemplate.opsforzset().zcard(key);
redistemplate.expire(key, seconds, timeunit.seconds);
return count <= limit;
}4.7 延时任务 / 定时任务
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 延时任务 / 定时任务 | 基于 zset 时间戳实现延迟执行 | 实现订单超时取消、消息延迟发送 | zset | 延迟队列、定时提醒 |
业务背景
在很多业务中存在“延时触发”的逻辑,例如:
- 订单 30 分钟未支付自动取消;
- 延迟发送消息;
- 提前 10 分钟推送会议提醒。
使用 redis 的 zset 可以轻松实现。
代码示例:订单超时取消任务
@service
public class delaytaskservice {
@resource
private redistemplate<string, object> redistemplate;
private static final string delay_key = "delay:order";
// 添加延时任务
public void adddelaytask(long orderid, long delayms) {
long executetime = system.currenttimemillis() + delayms;
redistemplate.opsforzset().add(delay_key, orderid, executetime);
system.out.println("添加延迟任务: orderid=" + orderid + ", 执行时间=" + executetime);
}
// 定时扫描执行任务
@scheduled(fixeddelay = 5000)
public void executetasks() {
long now = system.currenttimemillis();
set<object> tasks = redistemplate.opsforzset().rangebyscore(delay_key, 0, now);
if (tasks != null) {
for (object task : tasks) {
system.out.println("执行超时任务: orderid=" + task);
// 执行业务逻辑:关闭订单
redistemplate.opsforzset().remove(delay_key, task);
}
}
}
}测试示例:
delaytaskservice.adddelaytask(1001l, 60000); // 1分钟后取消订单
4.8 热点数据保护
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 热点数据保护 | 防止缓存击穿、穿透、雪崩 | 保证系统稳定性 | string / hash | 商品详情缓存、热点新闻 |
业务背景
在高并发系统中常见三种缓存问题:
| 问题类型 | 含义 | 解决方案 |
|---|---|---|
| 缓存穿透 | 查询不存在的 key 导致每次都打数据库 | 设置空值缓存、布隆过滤器 |
| 缓存击穿 | 热点 key 失效瞬间被大量请求打爆 | 加互斥锁、延迟失效 |
| 缓存雪崩 | 大量 key 同时过期 | 设置随机过期时间、预热机制 |
代码示例:缓存击穿防护
@service
public class productservice {
@resource
private redistemplate<string, object> redistemplate;
@resource
private productmapper productmapper;
public product getproduct(long id) {
string key = "product:" + id;
// 1. 先查缓存
product product = (product) redistemplate.opsforvalue().get(key);
if (product != null) {
return product;
}
// 2. 缓存为空,加锁防止击穿
synchronized (this) {
product = (product) redistemplate.opsforvalue().get(key);
if (product == null) {
product = productmapper.selectbyid(id);
if (product == null) {
// 防止穿透:缓存空对象
redistemplate.opsforvalue().set(key, new product(), 2, timeunit.minutes);
} else {
// 防止雪崩:过期时间随机
int expiretime = 10 + new random().nextint(5);
redistemplate.opsforvalue().set(key, product, expiretime, timeunit.minutes);
}
}
}
return product;
}
}4.9 地理位置(geo)
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 地理位置功能 | 使用 geo 存储经纬度信息 | 实现“附近的人 / 店铺”功能 | geo | 外卖、打车、社交定位 |
示例:查找附近商铺
@service
public class geoservice {
@resource
private redistemplate<string, object> redistemplate;
private static final string geo_key = "shop:geo";
// 添加商铺坐标
public void addshop(string shopname, double lon, double lat) {
redistemplate.opsforgeo().add(geo_key, new point(lon, lat), shopname);
}
// 查找附近商铺
public void findnearby(double lon, double lat, double distancekm) {
circle area = new circle(new point(lon, lat), new distance(distancekm, metrics.kilometers));
georesults<redisgeocommands.geolocation<object>> results =
redistemplate.opsforgeo().radius(geo_key, area);
results.foreach(r -> system.out.println("附近店铺:" + r.getcontent().getname()));
}
}4.10 数据共享 / 配置中心缓存
| 分类 | 场景说明 | 使用目的 | redis 类型 | 示例 |
|---|---|---|---|---|
| 数据共享 / 配置缓存 | 缓存公共配置与数据字典 | 减少数据库压力 | hash / string | 系统参数缓存 |
代码示例:系统配置缓存
@service
public class configservice {
@resource
private redistemplate<string, object> redistemplate;
public string getconfig(string key) {
string value = (string) redistemplate.opsforhash().get("config:system", key);
if (value == null) {
// 假设从数据库加载配置
value = loadfromdb(key);
redistemplate.opsforhash().put("config:system", key, value);
}
return value;
}
private string loadfromdb(string key) {
// 模拟数据库
if ("maxloginretry".equals(key)) return "5";
return "default";
}
}到此这篇关于redis 在 spring 项目中的使用(包看包会)的文章就介绍到这了,更多相关redis spring 项目使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论