redis的八种使用场景
wptr33 2025-08-06 23:27 86 浏览
前言:
redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。
缓存
Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。
把频繁访问的数据放在内存中,可以减少对后端数据库的访问压力。如热点数据缓存,对象缓存、全页缓存、可以提升热点数据的访问速度。
java实现redis缓存:
添加依赖:
<!-- 添加依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>application.properties配置文件:
# Redis 配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=2RedisCacheService服务类:
@Service
public class RedisCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 基础缓存操作
public void setCache(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public Object getCache(String key) {
return redisTemplate.opsForValue().get(key);
}
// 带数据库回源的缓存查询
public Object getWithCache(String key, long timeout, TimeUnit unit, Supplier<Object> supplier) {
Object value = redisTemplate.opsForValue().get(key);
if (value != null) {
return value;
}
value = supplier.get(); // 执行数据库查询
if (value != null) {
setCache(key, value, timeout, unit);
}
return value;
}
}使用 Spring Cache 注解方式(推荐):UserService类使用redis缓存
@Service
public class UserService {
@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public User getUserById(String userId) {
// 数据库查询逻辑
return userRepository.findById(userId);
}
@CacheEvict(value = "users", key = "#userId")
public void updateUser(User user) {
userRepository.update(user);
}
}分布式锁
日常开发中,我们经常会使用Redis做为分布式锁。可以在分布式系统中协调多节点对共享资源的访问,确保操作的原子性。
从三个方面讲解实现分布式锁:
方案一:原生 Jedis实现(基础版)
public class RedisDistributedLock {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
private Jedis jedis;
public RedisDistributedLock(Jedis jedis) {
this.jedis = jedis;
}
/**
* 获取分布式锁
* @param lockKey 锁
* @param requestId 请求标识(需保证唯一)
* @param expireTime 超期时间(毫秒)
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
/**
* 释放分布式锁(Lua脚本保证原子性)
*/
public boolean unlock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
}方案二:Spring DataRedis 实现(推荐)
@Service
public class DistributedLockService {
@Autowired
private StringRedisTemplate redisTemplate;
//获取锁
public boolean tryLock(String lockKey, String requestId, Duration expireTime) {
return redisTemplate.opsForValue().setIfAbsent(
lockKey,
requestId,
expireTime
);
}
//释放锁
public boolean unlock(String lockKey, String requestId) {
// Lua 脚本保证原子性
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(
redisScript,
Collections.singletonList(lockKey),
requestId
);
return result != null && result == 1;
}
}方案三:Redisson实现(生产级方案)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.4</version>
</dependency>@Service
public class RedissonLockService {
@Autowired
private RedissonClient redissonClient;
public void doWithLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待100秒,上锁后30秒自动解锁
boolean isLock = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (isLock) {
// 执行业务逻辑方法
executeBusiness();
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}使用redisson实现分布式锁,也是通用模板:
public <T> T executeWithLock(String lockKey, long timeout, Callable<T> action) {
RLock lock = redissonClient.getLock(lockKey);
boolean isLock = false;
try {
// 尝试获取锁:最多等待1秒,锁自动释放时间为 timeout 秒
isLock = lock.tryLock(1, timeout, TimeUnit.SECONDS);
if (isLock) {
try {
// 执行业务逻辑
return action.call();
} finally {
// 确保只有持有锁的线程能释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} else {
System.out.println("未能获取锁,稍后重试");
return null; // 实际建议抛出 DistributedLockException
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("获取锁时被中断");
return null;
} catch (Exception e) {
System.out.println("执行操作时发生异常: " + e.getMessage());
return null;
}
}排行榜
redis 经常用来做排行榜,比如游戏积分实时排名、直播送礼排名等等。
可以基于Sorted Set来实现:实时排名更新:
ZADD game_leaderboard 1000 "player_1" # 插入分数player_1玩家标识 1000
ZINCRBY game_leaderboard 50 "player_1" # 增加分数 1050
ZREVRANGE game_leaderboard 0 9 WITHSCORES # 获取Top10 获取分数从高到低的前 10 名玩家
#返回结果
1) "player_3" # 第一名
2) "1200"
3) "player_1" # 第二名
4) "1050"
5) "player_2" # 第三名
6) "800"
...使用:
public class LeaderboardService {
private Jedis jedis;
public LeaderboardService(String host, int port) {
this.jedis = new Jedis(host, port);
}
// 更新玩家分数(新增或累加)
public void updateScore(String playerId, double score) {
jedis.zadd("game_leaderboard", score, playerId);
}
// 获取玩家排名(从1开始)
public Long getPlayerRank(String playerId) {
// ZREVRANK 返回的是从0开始的排名,+1 转换为实际名次
return jedis.zrevrank("game_leaderboard", playerId) + 1;
}
// 获取前N名玩家(带分数)
public List<Map.Entry<String, Double>> getTopPlayers(int topN) {
Set<Tuple> tuples = jedis.zrevrangeWithScores("game_leaderboard", 0, topN-1);
return tuples.stream()
.map(t -> new AbstractMap.SimpleEntry<>(
t.getElement(),
t.getScore()))
.collect(Collectors.toList());
}
// 使用示例
public static void main(String[] args) {
LeaderboardService service = new LeaderboardService("localhost", 6379);
// 更新分数
service.updateScore("player_1", 1000);
service.updateScore("player_2", 800);
service.updateScore("player_3", 1200);
// 获取当前排名
System.out.println("player_1 排名: " + service.getPlayerRank("player_1")); // 输出 2
// 获取Top3
service.getTopPlayers(3).forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue()));
}
}计数器
redis 也经常应用作为计数器,如文章的阅读量、微博点赞数等等。
方案一:基础计数器(文章阅读量)
@Service
public class ArticleService {
@Autowired
private StringRedisTemplate redisTemplate;
// 文章阅读量+1
public Long incrementViewCount(String articleId) {
String key = "article:views:" + articleId;
return redisTemplate.opsForValue().increment(key);
}
// 获取阅读量
public Long getViewCount(String articleId) {
String key = "article:views:" + articleId;
String count = redisTemplate.opsForValue().get(key);
return count != null ? Long.parseLong(count) : 0L;
}
// 批量获取阅读量(使用Pipeline优化)
public Map<String, Long> batchGetViews(List<String> articleIds) {
List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
for (String id : articleIds) {
connection.stringCommands().get(("article:views:" + id).getBytes());
}
return null;
});
Map<String, Long> viewMap = new HashMap<>();
for (int i = 0; i < articleIds.size(); i++) {
String count = (String) results.get(i);
viewMap.put(articleIds.get(i), count != null ? Long.parseLong(count) : 0L);
}
return viewMap;
}
}方案二:哈希表计数器(用户点赞数)
@Service
public class LikeService {
private static final String LIKE_KEY = "post:likes";
@Autowired
private StringRedisTemplate redisTemplate;
// 用户点赞(返回最新点赞数)
public Long likePost(String postId, String userId) {
return redisTemplate.opsForHash().increment(LIKE_KEY, postId, 1L);
}
// 取消点赞(防止负数)
public Long unlikePost(String postId, String userId) {
return redisTemplate.execute(new DefaultRedisScript<Long>(
"if redis.call('HGET', KEYS[1], ARGV[1]) > '0' then " +
" return redis.call('HINCRBY', KEYS[1], ARGV[1], -1) " +
"else " +
" return 0 " +
"end",
Long.class
), Collections.singletonList(LIKE_KEY), postId);
}
// 获取点赞数前10的帖子
public List<PostLikeDTO> getTopLikedPosts() {
Set<Map.Entry<Object, Object>> entries = redisTemplate.opsForHash().entries(LIKE_KEY);
return entries.stream()
.map(e -> new PostLikeDTO((String)e.getKey(), Long.parseLong((String)e.getValue())))
.sorted((a, b) -> Long.compare(b.getLikes(), a.getLikes()))
.limit(10)
.collect(Collectors.toList());
}
}方案三:分布式ID生成器(全局唯一计数)
@Service
public class IdGeneratorService {
private static final String ORDER_ID_KEY = "counter:order:id";
@Autowired
private StringRedisTemplate redisTemplate;
// 获取分布式订单ID
public Long generateOrderId() {
return redisTemplate.opsForValue().increment(ORDER_ID_KEY);
}
// 带步长的ID生成(提升性能)
public List<Long> batchGenerateIds(String bizType, int batchSize) {
Long end = redisTemplate.opsForValue().increment("counter:" + bizType, batchSize);
return LongStream.rangeClosed(end - batchSize + 1, end)
.boxed()
.collect(Collectors.toList());
}
}
消息队列
方案一:基础List实现(生产-消费模型)
@Service
public class RedisQueueService {
private static final String QUEUE_KEY = "app:queue:orders";
@Autowired
private JedisPool jedisPool;
// 生产者:左推消息
public void produce(String message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.lpush(QUEUE_KEY, message);
}
}
// 消费者:阻塞右取(支持多队列)
public String consume(int timeoutSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
List<String> messages = jedis.brpop(timeoutSeconds, QUEUE_KEY);
return messages != null ? messages.get(1) : null;
}
}
// 批量消费
public List<String> batchConsume(int batchSize) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.lrange(QUEUE_KEY, 0, batchSize - 1);
}
}
}方案二:发布/订阅模式(实时通知)
@Service
public class RedisPubSubService {
private final JedisPool jedisPool;
private Thread subscriberThread;
public RedisPubSubService(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 发布消息
public void publish(String channel, String message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.publish(channel, message);
}
}
// 订阅频道(异步)
public void subscribe(String channel, Consumer<String> messageHandler) {
subscriberThread = new Thread(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
messageHandler.accept(message);
}
}, channel);
}
});
subscriberThread.start();
}
// 取消订阅
public void unsubscribe() {
if (subscriberThread != null) {
subscriberThread.interrupt();
}
}
}方案三:Stream实现(支持消费者组)
@Service
public class RedisStreamService {
private static final String STREAM_KEY = "app:stream:orders";
private static final String CONSUMER_GROUP = "order_processor";
private static final String CONSUMER_NAME = "consumer_1";
@Autowired
private JedisPool jedisPool;
// 初始化消费者组
@PostConstruct
public void initGroup() {
try (Jedis jedis = jedisPool.getResource()) {
try {
jedis.xgroupCreate(STREAM_KEY, CONSUMER_GROUP, new StreamEntryID(), true);
} catch (Exception e) {
// 消费者组已存在时的正常情况
}
}
}
// 生产消息
public void produce(Map<String, String> message) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, message);
}
}
// 消费消息(支持ACK)
public List<Map<String, String>> consume(int batchSize) {
try (Jedis jedis = jedisPool.getResource()) {
// 读取未ACK的消息
Map.Entry<String, StreamEntryID> streamQ =
new AbstractMap.SimpleEntry<>(STREAM_KEY, StreamEntryID.UNRECEIVED_ENTRY);
List<Entry<String, List<StreamEntry>>> responses =
jedis.xreadGroup(CONSUMER_GROUP, CONSUMER_NAME, batchSize, 1000, false, streamQ);
List<Map<String, String>> messages = new ArrayList<>();
for (StreamEntry entry : responses.get(0).getValue()) {
messages.add(entry.getFields());
// 实际业务处理完成后需要ACK
jedis.xack(STREAM_KEY, CONSUMER_GROUP, entry.getID());
}
return messages;
}
}
}会话管理
Redis 非常适合用作 会话管理,尤其是在分布式应用中。
- 分布式会话:解决多服务器间 Session 共享问题。
- 快速失效:通过 EXPIRE 实现自动会话清理。
HSET session:abBitmapc123 user_id 1001 last_active 1690000000
EXPIRE session:abc123 1800 # 30分钟过期地理位置服务
Redis 可以作为地理位置服务(Geolocation Service)的存储和查询引擎。Redis 提供了 GEO 数据结构,专门用于存储和查询地理位置信息。
# 添加餐厅地理位置
GEOADD restaurants 13.361389 38.115556 "餐厅A"
GEOADD restaurants 15.087269 37.502669 "餐厅B"
GEOADD restaurants 9.191383 45.464211 "餐厅C"
# 用户当前位置:经纬度 (14, 37)
# 查找附近 100 公里内的餐厅
GEORADIUS restaurants 14 37 100 km
# 返回:餐厅A 餐厅B推荐模型
使用 Redis 有序集合 (Sorted Set) 实现推荐系统的典型操作。
基于用户行为推荐商品。
基于Sorted Set:
ZADD recommendations:user1001 0.9 "product_1" 0.8 "product_2"
ZRANGE recommendations:user1001 0 9 WITHSCORES功能实现:
public class RecommendService {
private Jedis jedis;
public RecommendService(String host, int port) {
this.jedis = new Jedis(host, port);
}
// 添加/更新推荐项
public void addRecommendation(String userId, Map<String, Double> itemScores) {
String key = "recommendations:" + userId;
Map<String, Double> scoreMap = new HashMap<>();
for (Map.Entry<String, Double> entry : itemScores.entrySet()) {
scoreMap.put(entry.getKey(), entry.getValue());
}
jedis.zadd(key, scoreMap);
}
// 获取TopN推荐(按分数从高到低)
public List<RecommendItem> getTopRecommendations(String userId, int topN) {
String key = "recommendations:" + userId;
// 使用 ZREVRANGE 获取高分在前的结果
Set<Tuple> tuples = jedis.zrevrangeWithScores(key, 0, topN - 1);
return tuples.stream()
.map(t -> new RecommendItem(t.getElement(), t.getScore()))
.collect(Collectors.toList());
}
// 推荐项DTO
public static class RecommendItem {
private String itemId;
private double score;
// 构造方法/getter/setter
}
}@Service
public class RedisRecommendService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 批量添加推荐项
public void addRecommendations(String userId, Map<String, Double> itemScores) {
String key = "recommendations:" + userId;
redisTemplate.opsForZSet().add(key, itemScores);
}
// 获取带权重的推荐列表
public Map<String, Double> getRecommendationsWithScores(String userId, int count) {
String key = "recommendations:" + userId;
Set<ZSetOperations.TypedTuple<String>> tuples =
redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, count - 1);
return tuples.stream()
.collect(Collectors.toMap(
ZSetOperations.TypedTuple::getValue,
ZSetOperations.TypedTuple::getScore
));
}
}总结
上述介绍redis的8种使用场景,以及怎样实现对应的方案。
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
-
- oracle数据导入导出_oracle数据导入导出工具
- 继续学习Python中的while true/break语句
- python continue和break的区别_python中break语句和continue语句的区别
- 简单学Python——关键字6——break和continue
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
- Python 中 break 和 continue 傻傻分不清
- python中的流程控制语句:continue、break 和 return使用方法
- L017:continue和break - 教程文案
- 作为前端开发者,你都经历过怎样的面试?
- 面试被问 const 是否不可变?这样回答才显功底
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
