java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot集成Redis

SpringBoot 集成 Redis实现缓存与分布式锁的全过程(提升系统性能与并发能力)

作者:小北方城市网

本文介绍了SpringBoot与Redis的实战应用,涵盖了环境搭建、缓存注解使用、手动缓存操作、分布式锁实现以及常见缓存问题解决方案,通过实际代码示例,帮助读者快速掌握Redis的核心应用,提升系统性能和并发处理能力,感兴趣的朋友跟随小编一起看看吧

在高并发、高流量后端系统中,数据库往往是性能瓶颈 —— 频繁的数据库查询会导致响应延迟、数据库压力过大。Redis 作为高性能键值对缓存数据库,支持内存存储、持久化、分布式部署,可通过缓存热点数据、实现分布式锁等方式,大幅提升系统响应速度、降低数据库压力,同时解决分布式环境下的并发安全问题,是企业级系统性能优化的核心工具。

本文聚焦 SpringBoot 与 Redis 的实战落地,从环境搭建、缓存注解使用、手动缓存操作,到分布式锁实现、缓存问题解决方案,全程嵌入 Java 代码教学,帮你快速掌握 Redis 核心应用,打造高性能、高并发的后端系统。

一、核心认知:Redis 核心价值与适用场景

1. 核心优势

2. 核心适用场景

3. Redis 核心概念

二、核心实战一:环境搭建(Docker 快速部署)

1. Docker 部署 Redis(单节点,开发测试场景)

# 1. 拉取 Redis 镜像(最新稳定版)
docker pull redis:latest
# 2. 启动 Redis 容器(配置密码、持久化、端口映射)
docker run -d --name redis -p 6379:6379 \
  -v redis-data:/data \ # 挂载数据卷,持久化数据
  -e REDIS_PASSWORD=redis123 \ # 设置访问密码
  redis:latest \
  redis-server --requirepass redis123 \ # 开启密码验证
  --appendonly yes # 开启 AOF 持久化(数据更可靠)

三、核心实战二:SpringBoot 集成 Redis 基础配置

1. 引入依赖(Maven)

<!-- Spring Data Redis 依赖(简化 Redis 操作) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖(提升 Redis 连接效率) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- Web 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2. 配置文件(application.yml)

# Redis 配置
spring:
  redis:
    host: localhost # Redis 服务地址
    port: 6379 # 端口
    password: redis123 # 访问密码
    database: 0 # 数据库索引(默认 0,Redis 支持 16 个数据库)
    timeout: 10000 # 连接超时时间(毫秒)
    lettuce: # 连接池配置(Lettuce 是 SpringBoot 2.x 默认 Redis 客户端)
      pool:
        max-active: 16 # 最大连接数
        max-idle: 8 # 最大空闲连接数
        min-idle: 4 # 最小空闲连接数
        max-wait: -1 # 最大等待时间(-1 表示无限制)
# 服务端口
server:
  port: 8085

3. Redis 配置类(自定义序列化方式)

默认序列化方式会导致 Redis 中存储的数据可读性差,需自定义序列化(JSON 格式),同时配置缓存管理器。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
public class RedisConfig {
    // 自定义 RedisTemplate(JSON 序列化,支持复杂对象存储)
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // 字符串序列化器(Key 用字符串序列化)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        // JSON 序列化器(Value 用 JSON 序列化,保留对象结构)
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
        // 配置 Key、Value 序列化方式
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jsonSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jsonSerializer);
        // 初始化 RedisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    // 配置缓存管理器(支持缓存注解)
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 缓存配置(设置默认过期时间 30 分钟,JSON 序列化)
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30)) // 默认缓存过期时间
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues(); // 禁止缓存 null 值(避免缓存穿透)
        // 创建缓存管理器
        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(cacheConfig)
                .build();
    }
}

四、核心实战三:Redis 缓存操作(注解式 + 手动式)

1. 注解式缓存(简化开发,适用于简单场景)

通过 @Cacheable@CachePut@CacheEvict 等注解,无需手动编写缓存逻辑,自动实现数据缓存与更新。

(1)Service 层使用示例

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.example.redis.entity.User;
import com.example.redis.mapper.UserMapper;
import javax.annotation.Resource;
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;
    // ✅ 缓存查询结果:key 为 "user:info:" + id,缓存过期时间 30 分钟
    @Cacheable(value = "user", key = "'info:' + #id", unless = "#result == null")
    public User getUserById(Long id) {
        // 缓存不存在时,查询数据库(仅执行一次)
        System.out.println("查询数据库,用户ID:" + id);
        return userMapper.selectById(id);
    }
    // ✅ 更新缓存:更新数据库后,同步更新缓存(避免缓存与数据库不一致)
    @CachePut(value = "user", key = "'info:' + #user.id", unless = "#user == null")
    public User updateUser(User user) {
        userMapper.updateById(user);
        return user;
    }
    // ✅ 清除缓存:删除数据库数据后,删除对应缓存
    @CacheEvict(value = "user", key = "'info:' + #id")
    public void deleteUser(Long id) {
        userMapper.deleteById(id);
    }
    // ✅ 清除所有缓存(如批量更新时)
    @CacheEvict(value = "user", allEntries = true)
    public void clearUserCache() {
        // 仅清除缓存,无业务逻辑
    }
}

(2)Controller 层接口

import org.springframework.web.bind.annotation.*;
import com.example.redis.entity.User;
import com.example.redis.result.Result;
import com.example.redis.service.UserService;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @GetMapping("/{id}")
    public Result<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return Result.success(user);
    }
    @PutMapping
    public Result<User> updateUser(@RequestBody User user) {
        User updatedUser = userService.updateUser(user);
        return Result.success(updatedUser);
    }
    @DeleteMapping("/{id}")
    public Result<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success();
    }
}

2. 手动式缓存(灵活控制,适用于复杂场景)

通过 RedisTemplate 手动操作 Redis,支持多种数据结构,适配复杂业务场景(如 Hash、List 操作)。

(1)Redis 工具类封装

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    // ✅ 存储 String 类型数据(设置过期时间)
    public void setString(String key, Object value, Long expireTime, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, expireTime, timeUnit);
    }
    // ✅ 获取 String 类型数据
    public Object getString(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    // ✅ 删除数据
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }
    // ✅ 判断键是否存在
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    // ✅ 设置键过期时间
    public Boolean expire(String key, Long expireTime, TimeUnit timeUnit) {
        return redisTemplate.expire(key, expireTime, timeUnit);
    }
    // ✅ 存储 Hash 类型数据(示例:存储用户详情,字段拆分)
    public void setHash(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }
    // ✅ 获取 Hash 类型数据
    public Object getHash(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }
}

(2)工具类使用示例(热点商品缓存)

import org.springframework.stereotype.Service;
import com.example.redis.entity.Goods;
import com.example.redis.mapper.GoodsMapper;
import com.example.redis.utils.RedisUtils;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class GoodsService {
    @Resource
    private GoodsMapper goodsMapper;
    @Resource
    private RedisUtils redisUtils;
    // 热点商品缓存(手动控制缓存逻辑)
    public Goods getHotGoodsById(Long id) {
        String key = "goods:hot:" + id;
        // 1. 先查缓存
        Goods goods = (Goods) redisUtils.getString(key);
        if (goods != null) {
            return goods;
        }
        // 2. 缓存不存在,查数据库
        goods = goodsMapper.selectById(id);
        if (goods != null) {
            // 3. 存入缓存,设置过期时间 10 分钟(热点数据可缩短过期时间)
            redisUtils.setString(key, goods, 10L, TimeUnit.MINUTES);
        }
        return goods;
    }
}

五、核心实战四:分布式锁实现(解决并发安全问题)

分布式环境下,多个服务实例同时操作同一资源(如库存扣减)会导致数据不一致,通过 Redis 分布式锁可确保同一时间只有一个线程操作资源。

1. 分布式锁工具类

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    // 锁前缀(避免键冲突)
    private static final String LOCK_PREFIX = "lock:";
    // 锁过期时间(避免死锁,默认 30 秒)
    private static final Long LOCK_EXPIRE = 30L;
    /**
     * 获取分布式锁
     * @param lockKey 锁标识(如 "stock:1001")
     * @param requestId 唯一标识(如 UUID,确保释放锁时是自己的锁)
     * @return 是否获取成功
     */
    public Boolean tryLock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        // SETNX 命令:当键不存在时设置值,原子操作(确保并发安全)
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(key, requestId, LOCK_EXPIRE, TimeUnit.SECONDS);
        return locked != null && locked;
    }
    /**
     * 释放分布式锁
     * @param lockKey 锁标识
     * @param requestId 唯一标识
     */
    public void unlock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        try {
            String value = (String) redisTemplate.opsForValue().get(key);
            // 仅释放自己的锁(避免释放其他线程的锁)
            if (requestId.equals(value)) {
                redisTemplate.delete(key);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2. 分布式锁使用示例(库存扣减)

import org.springframework.stereotype.Service;
import com.example.redis.mapper.StockMapper;
import com.example.redis.utils.RedisDistributedLock;
import javax.annotation.Resource;
import java.util.UUID;
@Service
public class StockService {
    @Resource
    private StockMapper stockMapper;
    @Resource
    private RedisDistributedLock distributedLock;
    // 库存扣减(分布式环境下安全操作)
    public Boolean deductStock(Long goodsId, Integer num) {
        String lockKey = "stock:" + goodsId;
        String requestId = UUID.randomUUID().toString();
        try {
            // 1. 获取分布式锁(最多等待 5 秒,每隔 1 秒重试)
            Boolean locked = false;
            int count = 0;
            while (!locked && count < 5) {
                locked = distributedLock.tryLock(lockKey, requestId);
                if (!locked) {
                    Thread.sleep(1000);
                    count++;
                }
            }
            if (!locked) {
                // 获取锁失败(限流)
                return false;
            }
            // 2. 扣减库存(业务逻辑)
            int row = stockMapper.deductStock(goodsId, num);
            return row > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            // 3. 释放锁(确保无论是否异常,都释放锁)
            distributedLock.unlock(lockKey, requestId);
        }
    }
}

六、进阶:缓存常见问题解决方案

1. 缓存穿透(查询不存在的数据,缓存无效,直击数据库)

// 缓存 null 值解决方案(修改查询方法)
public Goods getGoodsById(Long id) {
    String key = "goods:info:" + id;
    Goods goods = (Goods) redisUtils.getString(key);
    if (goods != null) {
        return goods;
    }
    // 数据库查询也不存在,缓存 null 值(过期时间 5 分钟)
    goods = goodsMapper.selectById(id);
    if (goods == null) {
        redisUtils.setString(key, null, 5L, TimeUnit.MINUTES);
        return null;
    }
    redisUtils.setString(key, goods, 30L, TimeUnit.MINUTES);
    return goods;
}

2. 缓存击穿(热点数据缓存过期,大量请求同时直击数据库)

3. 缓存雪崩(大量缓存同时过期,或 Redis 服务宕机,所有请求直击数据库)

七、避坑指南

坑点 1:缓存与数据库数据不一致

表现:更新数据库后,缓存未同步更新,导致查询到旧数据;✅ 解决方案:采用「更新数据库 + 同步更新缓存」或「更新数据库 + 删除缓存」策略,避免单独操作数据库或缓存。

坑点 2:Redis 序列化后数据可读性差

表现:Redis 中存储的数据为乱码,无法直接查看;✅ 解决方案:自定义序列化方式(如 JSON 序列化),避免使用默认的 JdkSerializationRedisSerializer。

坑点 3:分布式锁死锁

表现:获取锁后线程异常退出,未释放锁,导致其他线程无法获取锁;✅ 解决方案:设置锁过期时间,确保锁自动释放;使用 try-finally 确保锁一定会被释放。

坑点 4:Redis 连接池耗尽

表现:系统报错「Could not get a resource from the pool」,无法获取 Redis 连接;✅ 解决方案:合理配置连接池参数(max-active、max-idle),避免频繁创建销毁连接,排查是否有连接未释放的情况。

八、终极总结:Redis 实战的核心是「缓存高效 + 并发安全」

Redis 实战的核心价值,是通过缓存热点数据提升系统响应速度,通过分布式锁解决分布式并发问题,同时兼顾数据可靠性与系统稳定性。企业级开发中,需根据业务场景选择合适的缓存策略与数据结构,规避常见缓存问题,平衡性能与一致性。

核心原则总结:

  1. 缓存策略适配业务:简单场景用注解式缓存,复杂场景用手动式缓存,热点数据缩短过期时间;
  2. 并发安全优先:分布式环境下操作共享资源,必须使用分布式锁,避免数据不一致;
  3. 高可用不可少:生产环境必用 Redis 集群(主从 + 哨兵),避免单点故障导致缓存雪崩;
  4. 问题提前预防:针对缓存穿透、击穿、雪崩,提前部署解决方案,而非事后补救。

到此这篇关于SpringBoot 集成 Redis实现缓存与分布式锁的全过程(提升系统性能与并发能力)的文章就介绍到这了,更多相关SpringBoot集成Redis 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文