百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

redis的八种使用场景

wptr33 2025-08-06 23:27 85 浏览

前言:

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=2

RedisCacheService服务类:

@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字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...