🎓博主介绍:Java、Python、js全栈开发 “多面手”,精通多种编程语言和技术,痴迷于人工智能领域。秉持着对技术的热爱与执着,持续探索创新,愿在此分享交流和学习,与大家共进步。
📖DeepSeek-行业融合之万象视界(附实战案例详解100+)
📖全栈开发环境搭建运行攻略:多语言一站式指南(环境搭建+运行+调试+发布+保姆级详解)
👉感兴趣的可以先收藏起来,希望帮助更多的人
在这里插入图片描述

SpringBoot整合Redis实战:分布式锁与缓存穿透解决方案

一、引言

在当今的分布式系统开发中,Spring Boot 作为一款轻量级的开发框架,凭借其快速开发和便捷配置的特性,受到了广大开发者的青睐。而 Redis 作为高性能的内存数据库,在缓存、分布式锁等场景中发挥着重要作用。本文将详细介绍如何在 Spring Boot 项目中整合 Redis,并深入探讨分布式锁和缓存穿透的解决方案。

二、Spring Boot 整合 Redis 基础配置

2.1 添加依赖

首先,在 pom.xml 文件中添加 Spring Boot 整合 Redis 的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 配置 Redis 连接信息

在 application.properties 或 application.yml 中配置 Redis 的连接信息,以 application.yml 为例:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: yourpassword

2.3 创建 RedisTemplate Bean

为了方便操作 Redis,我们可以创建一个 RedisTemplate Bean:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }
}

三、分布式锁的实现与应用

3.1 分布式锁的概念

在分布式系统中,多个服务可能会同时访问共享资源,为了避免数据不一致等问题,需要使用分布式锁来保证同一时间只有一个服务能够访问该资源。

3.2 Redis 实现分布式锁的原理

Redis 可以使用 SETNX(SET if Not eXists)命令来实现分布式锁。当一个客户端尝试使用 SETNX 命令设置一个键值对时,如果该键不存在,则设置成功并返回 1,表示获取到锁;如果该键已经存在,则设置失败并返回 0,表示锁已经被其他客户端持有。

3.3 Spring Boot 中使用 Redis 实现分布式锁

以下是一个简单的分布式锁实现示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public boolean tryLock(String key, String value, long expireTime, TimeUnit timeUnit) {
        Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, timeUnit);
        return result != null && result;
    }

    public void unlock(String key) {
        redisTemplate.delete(key);
    }
}

3.4 分布式锁的应用场景

在高并发场景下,多个客户端同时对某个资源进行写操作时,可以使用分布式锁来保证数据的一致性。例如,在商品库存扣减的场景中:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    @Autowired
    private RedisDistributedLock redisDistributedLock;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void deductStock(String productId, int quantity) {
        String lockKey = "product:lock:" + productId;
        String lockValue = String.valueOf(System.currentTimeMillis());
        boolean isLocked = redisDistributedLock.tryLock(lockKey, lockValue, 10, TimeUnit.SECONDS);
        if (isLocked) {
            try {
                // 从 Redis 中获取商品库存
                Integer stock = (Integer) redisTemplate.opsForValue().get("product:stock:" + productId);
                if (stock != null && stock >= quantity) {
                    // 扣减库存
                    redisTemplate.opsForValue().set("product:stock:" + productId, stock - quantity);
                    System.out.println("扣减库存成功");
                } else {
                    System.out.println("库存不足");
                }
            } finally {
                // 释放锁
                redisDistributedLock.unlock(lockKey);
            }
        } else {
            System.out.println("获取锁失败,稍后重试");
        }
    }
}

四、缓存穿透问题及解决方案

4.1 缓存穿透的概念

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样每次请求都会穿透缓存直接访问数据库,导致数据库压力过大。

4.2 缓存穿透的原因

  • 恶意攻击:攻击者可能会故意请求不存在的数据,导致大量请求穿透缓存。
  • 业务数据错误:业务系统中可能存在数据错误,导致请求的数据不存在。

4.3 解决方案一:布隆过滤器

布隆过滤器是一种空间效率极高的概率型数据结构,用于判断一个元素是否存在于一个集合中。在缓存之前使用布隆过滤器可以过滤掉不存在的数据请求,避免这些请求直接访问数据库。

以下是使用 Google Guava 实现布隆过滤器的示例:

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;

@Component
public class BloomFilterUtil {

    private BloomFilter<String> bloomFilter;

    @PostConstruct
    public void init() {
        // 预计插入元素数量
        long expectedInsertions = 1000000;
        // 误判率
        double fpp = 0.001;
        bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8), expectedInsertions, fpp);
        // 初始化布隆过滤器,将存在的数据添加到布隆过滤器中
        // 这里省略具体的添加逻辑
    }

    public boolean mightContain(String key) {
        return bloomFilter.mightContain(key);
    }
}

在业务代码中使用布隆过滤器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @Autowired
    private BloomFilterUtil bloomFilterUtil;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Object getData(String key) {
        if (!bloomFilterUtil.mightContain(key)) {
            return null;
        }
        // 从 Redis 中获取数据
        Object data = redisTemplate.opsForValue().get(key);
        if (data == null) {
            // 从数据库中获取数据
            // 这里省略具体的数据库查询逻辑
            data = "database data";
            if (data != null) {
                // 将数据存入 Redis
                redisTemplate.opsForValue().set(key, data);
            }
        }
        return data;
    }
}

4.4 解决方案二:缓存空对象

当请求的数据在数据库中不存在时,将一个空对象(如 null 或一个特定的空对象标识)存入缓存,这样下次相同的请求就会直接从缓存中获取空对象,避免再次访问数据库。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CacheServiceWithNullValue {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Object getData(String key) {
        // 从 Redis 中获取数据
        Object data = redisTemplate.opsForValue().get(key);
        if (data == null) {
            // 从数据库中获取数据
            // 这里省略具体的数据库查询逻辑
            data = null;
            // 将空对象存入 Redis
            redisTemplate.opsForValue().set(key, data, 60, TimeUnit.SECONDS);
        }
        return data;
    }
}

五、总结

本文详细介绍了 Spring Boot 整合 Redis 的基础配置,以及如何使用 Redis 实现分布式锁和解决缓存穿透问题。分布式锁可以保证在分布式系统中对共享资源的安全访问,而缓存穿透的解决方案可以有效减轻数据库的压力。通过合理运用这些技术,可以提高系统的性能和稳定性。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐