Java 项目中 Redis 常用的业务场景

在 Java 项目中,Redis 因其高性能、丰富的数据结构和原子操作,已经成为不可或缺的组件。以下是 Redis 最常用、最经典的业务场景,并结合 Java 代码示例进行说明。


一、缓存(Cache) - 最核心的场景

目的:减轻数据库压力,加速读写,提升系统吞吐量。

  • 原理:将数据库查询结果等耗时计算的结果缓存起来。下次请求时,先查缓存,命中则直接返回;未命中再查数据库,并回填缓存。
  • Java 实现
    • 注解驱动:使用 Spring Cache 抽象,搭配 @Cacheable, @CacheEvict, @CachePut 等注解,无侵入式实现缓存。
    • 手动操作:直接使用 RedisTemplateJedis/Lettuce 客户端进行更灵活的控制。
// 1. 使用 Spring Cache 注解 (最简单)
@Service
public class ProductService {

    @Cacheable(value = "product", key = "#id") // 缓存名为product,key是id
    public Product getProductById(Long id) {
        // 模拟查询数据库
        return productRepository.findById(id).orElse(null);
    }

    @CacheEvict(value = "product", key = "#id") // 更新或删除商品时,清除缓存
    public void updateProduct(Product product) {
        productRepository.save(product);
    }
}

// 2. 手动使用 RedisTemplate (更灵活,例如设置TTL)
@Component
public class ProductServiceWithTemplate {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Product getProductById(Long id) {
        String key = "product:" + id;
        // 1. 先查缓存
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }
        // 2. 缓存未命中,查数据库
        product = productRepository.findById(id).orElse(null);
        if (product != null) {
            // 3. 回填缓存,并设置30分钟过期
            redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30));
        }
        return product;
    }
}

注意事项:缓存穿透、缓存击穿、缓存雪崩、数据一致性。


二、分布式锁(Distributed Lock)

目的:在分布式系统中,确保多个服务实例对共享资源的互斥访问。

  • 原理:利用 Redis 的 SETNX(SET if Not eXists)命令或 SET 命令的 NXPX 选项来争抢一个唯一的 Key。抢到即获得锁。
  • Java 实现
    • 手动实现:使用 setIfAbsent 方法。
    • 框架:使用 Redisson 库,它提供了成熟的分布式锁实现。
// 1. 使用 RedisTemplate 手动实现(简易版)
public boolean tryLock(String lockKey, String requestId, long expireTime) {
    return redisTemplate.opsForValue().setIfAbsent(
        lockKey, 
        requestId, 
        Duration.ofMillis(expireTime)
    );
}

public void unlock(String lockKey, String requestId) {
    // 关键:使用Lua脚本保证判断和删除是原子的
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    RedisScript<Long> script = RedisScript.of(luaScript, Long.class);
    Long result = redisTemplate.execute(script, Collections.singletonList(lockKey), requestId);
    // 根据result判断是否解锁成功
}

// 2. 使用 Redisson(推荐,功能完善)
@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://127.0.0.1:6379");
    return Redisson.create(config);
}

// 在业务代码中
public void doSomethingWithLock() {
    RLock lock = redissonClient.getLock("myLock");
    try {
        // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
        if (lock.tryLock(100, 30, TimeUnit.SECONDS)) {
            try {
                // 执行业务代码
            } finally {
                lock.unlock(); // 确保最终解锁
            }
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

三、会话存储(Session Store)

目的:实现分布式会话,使服务端无状态化,便于水平扩展。

  • 原理:将 Tomcat 等 Web 容器默认的内存 Session 存储,替换为集中式的 Redis 存储。所有服务实例都从 Redis 读写 Session 数据。
  • Java 实现:使用 spring-session-data-redis 库,配置简单,几乎零代码入侵。
<!-- pom.xml 添加依赖 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# application.yml
spring:
  session:
    store-type: redis
  redis:
    host: localhost
    port: 6379
// 之后,使用 HttpSession 就和原来完全一样,但数据存在Redis中
@RestController
public class SessionController {
    @PostMapping("/login")
    public String login(HttpSession session, @RequestBody User user) {
        //  session.setAttribute 会自动存入Redis
        session.setAttribute("user", user);
        return "success";
    }

    @GetMapping("/user")
    public User getUser(HttpSession session) {
        //  session.getAttribute 会自动从Redis读取
        return (User) session.getAttribute("user");
    }
}

四、计数器(Counter)与限流(Rate Limiting)

目的:实现点赞、浏览数统计、接口访问频率限制等功能。

  • 原理:利用 Redis 的 INCRINCRBY 命令实现原子性计数。利用 EXPIRE 命令实现时间窗口。
  • Java 实现
@Service
public class CounterService {

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate; // 可使用StringRedisTemplate

    // 文章点赞数增加
    public Long incrementArticleLike(Long articleId) {
        String key = "article:like:" + articleId;
        return redisTemplate.opsForValue().increment(key);
    }

    // 获取文章点赞数
    public Integer getArticleLikeCount(Long articleId) {
        String key = "article:like:" + articleId;
        return redisTemplate.opsForValue().get(key);
    }

    // 简单限流:1分钟内同一个ip只能访问10次
    public boolean isAllowed(String ip) {
        String key = "rate:limit:" + ip;
        Long count = redisTemplate.opsForValue().increment(key);
        if (count != null && count == 1) {
            // 如果是第一次访问,设置key的过期时间为1分钟
            redisTemplate.expire(key, Duration.ofMinutes(1));
        }
        return count != null && count <= 10;
    }
}

五、排行榜(Leaderboard)

目的:实现实时更新的排行榜,如游戏积分榜、销量榜。

  • 原理:使用 Redis 的 ZSet(有序集合)数据结构。member 是用户ID,score 是分数。ZSet 会自动按分数排序。
  • Java 实现
@Service
public class RankingService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate; // 通常用StringRedisTemplate

    private static final String RANKING_KEY = "global:ranking";

    // 更新用户分数
    public void addScore(String userId, double score) {
        redisTemplate.opsForZSet().add(RANKING_KEY, userId, score);
    }

    // 获取前10名
    public Set<ZSetOperations.TypedTuple<String>> getTop10() {
        return redisTemplate.opsForZSet().reverseRangeWithScores(RANKING_KEY, 0, 9);
    }

    // 获取用户排名
    public Long getUserRank(String userId) {
        // 排名从0开始,所以+1
        return redisTemplate.opsForZSet().reverseRank(RANKING_KEY, userId) + 1;
    }
}

六、消息队列(Pub/Sub)与延迟队列

目的:实现简单的服务间通知、事件发布/订阅、延迟任务。

  • 原理
    • Pub/Sub:使用 Redis 的 publish/subscribe 命令。发布者向频道发送消息,所有订阅该频道的消费者都会收到。注意:消息是即时的,无持久化
    • 延迟队列:使用 ZSet,将任务作为 member,执行时间戳作为 score。后台线程轮询 ZSet 中 score 小于当前时间的任务来执行。
// 1. Pub/Sub 发布者
@Component
public class MessagePublisher {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void publish(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

// 2. Pub/Sub 订阅者(监听器)
@Component
public class MessageSubscriber implements MessageListener {
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String channel = new String(message.getChannel());
        String body = new String(message.getBody());
        System.out.println("Received message: " + body + " from channel: " + channel);
        // 处理业务逻辑
    }
}

// 需要配置监听容器(在@Configuration类中)
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listenerAdapter, new PatternTopic("news.*")); // 订阅news开头的频道
    return container;
}

总结

场景核心数据结构关键命令/特性Java 常用库
缓存String, HashSET, GET, SETEXSpring Cache, RedisTemplate
分布式锁StringSET NX PXRedisTemplate, Redisson
会话存储HashHSET, HGETspring-session-data-redis
计数器/限流StringINCR, EXPIRERedisTemplate
排行榜ZSetZADD, ZREVRANGERedisTemplate
消息队列-PUBLISH, SUBSCRIBERedisTemplate

选择建议:对于简单场景,优先使用 Spring Data Redis 的 RedisTemplate。对于复杂的分布式锁、分布式集合等需求,强烈推荐使用 Redisson,它封装了更多分布式功能,更加可靠和易用。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值