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

redis的八种使用场景(redis常见使用场景)

mhr18 2025-03-23 21:19 26 浏览 0 评论

前言:

redis是我们工作开发中,经常要打交道的,下面对redis的使用场景做总结介绍也是对redis举报的功能做梳理。

缓存

Redis最常见的用途是作为缓存,用于加速应用程序的响应速度。

把频繁访问的数据放在内存中,可以减少对后端数据库的访问压力。如热点数据缓存,对象缓存、全页缓存、可以提升热点数据的访问速度。

java实现redis缓存:

添加依赖:



    
        org.springframework.boot
        spring-boot-starter-data-redis
    
    
        redis.clients
        jedis
    

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 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 redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(
            redisScript,
            Collections.singletonList(lockKey),
            requestId
        );
        return result != null && result == 1;
    }
}

方案三:Redisson实现(生产级方案)


    org.redisson
    redisson-spring-boot-starter
    3.23.4
@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 executeWithLock(String lockKey, long timeout, Callable 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> getTopPlayers(int topN) {
        Set 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 batchGetViews(List articleIds) {
        List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (String id : articleIds) {
                connection.stringCommands().get(("article:views:" + id).getBytes());
            }
            return null;
        });

        Map 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(
            "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 getTopLikedPosts() {
        Set<Map.Entry> 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 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 messages = jedis.brpop(timeoutSeconds, QUEUE_KEY);
            return messages != null ? messages.get(1) : null;
        }
    }

    // 批量消费
    public List 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 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 message) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, message);
        }
    }

    // 消费消息(支持ACK)
    public List<Map> consume(int batchSize) {
        try (Jedis jedis = jedisPool.getResource()) {
            // 读取未ACK的消息
            Map.Entry streamQ = 
                new AbstractMap.SimpleEntry<>(STREAM_KEY, StreamEntryID.UNRECEIVED_ENTRY);
            
            List<Entry<String, List>> responses = 
                jedis.xreadGroup(CONSUMER_GROUP, CONSUMER_NAME, batchSize, 1000, false, streamQ);

            List<Map> 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 itemScores) {
        String key = "recommendations:" + userId;
        Map scoreMap = new HashMap<>();
        
        for (Map.Entry entry : itemScores.entrySet()) {
            scoreMap.put(entry.getKey(), entry.getValue());
        }
        jedis.zadd(key, scoreMap);
    }

    // 获取TopN推荐(按分数从高到低)
    public List getTopRecommendations(String userId, int topN) {
        String key = "recommendations:" + userId;
        // 使用 ZREVRANGE 获取高分在前的结果
        Set 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 redisTemplate;

    // 批量添加推荐项
    public void addRecommendations(String userId, Map itemScores) {
        String key = "recommendations:" + userId;
        redisTemplate.opsForZSet().add(key, itemScores);
    }

    // 获取带权重的推荐列表
    public Map getRecommendationsWithScores(String userId, int count) {
        String key = "recommendations:" + userId;
        Set<ZSetOperations.TypedTuple> tuples = 
            redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, count - 1);
        
        return tuples.stream()
            .collect(Collectors.toMap(
                ZSetOperations.TypedTuple::getValue,
                ZSetOperations.TypedTuple::getScore
            ));
    }
}

总结

上述介绍redis的8种使用场景,以及怎样实现对应的方案。

相关推荐

【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...

Pure Storage推出统一数据管理云平台及新闪存阵列

PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...

对Java学习的10条建议(对java课程的建议)

不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...

SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!

官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...

JDK21有没有什么稳定、简单又强势的特性?

佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...

「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了

在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...

Java面试题及答案最全总结(2025版)

大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...

数据库日常运维工作内容(数据库日常运维 工作内容)

#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...

分布式之系统底层原理(上)(底层分布式技术)

作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...

oracle 死锁了怎么办?kill 进程 直接上干货

1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...

SpringBoot 各种分页查询方式详解(全网最全)

一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...

《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略

《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...

LoadRunner(loadrunner录制不到脚本)

一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...

Redis数据类型介绍(redis 数据类型)

介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...

RMAN备份监控及优化总结(rman备份原理)

今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...

取消回复欢迎 发表评论: