Redis以及如何在springboot中使用

redis 是高性能基于键值对的内存数据库,专门用来存数据,做缓存,做分布式锁,做消息队列,支持多种数据类型,如 String, List, Map, Set, ZSet有序集合

解决序列化问题

将对象变成二进制字节/字符串的方式叫做 "序列化"

首先准备一个类用作解决 redis 的序列化问题

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
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;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class RedisConfig {

    // 定义时间格式化器(统一格式)
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 1. 配置 Jackson 序列化器(处理 LocalDateTime)
        ObjectMapper objectMapper = new ObjectMapper();
        // 注册 Java 8 时间模块
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        // 自定义 LocalDateTime 序列化/反序列化格式
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
        objectMapper.registerModule(javaTimeModule);

        // 2. 序列化器实例
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);

        // 3. 配置序列化器
        template.setKeySerializer(stringSerializer);        // key 用字符串序列化
        template.setValueSerializer(jsonSerializer);       // value 用 JSON 序列化
        template.setHashKeySerializer(stringSerializer);   // Hash key 序列化
        template.setHashValueSerializer(jsonSerializer);   // Hash value 序列化

        template.afterPropertiesSet();
        return template;
    }
}

然后准备一个 redis 工具类(这是一个针对于String的工具类)

java 复制代码
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 通用操作工具类(基于 StringRedisTemplate)
 */
@Component // 交给 Spring 容器管理
public class RedisUtils {

    // 注入 StringRedisTemplate(Spring Boot 自动配置,无需手动创建)
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // ====================== 字符串(最常用) ======================
    /**
     * 存储字符串(无过期时间)
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * 存储字符串(带过期时间)
     * @param timeout 过期时间(如 5)
     * @param unit 时间单位(如 TimeUnit.MINUTES 分钟)
     */
    public void set(String key, String value, long timeout, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 获取字符串
     */
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    // ====================== 哈希(适合存储对象) ======================
    /**
     * 存储哈希字段
     */
    public void hSet(String key, String hashKey, String value) {
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
    }

    /**
     * 获取哈希字段
     */
    public String hGet(String key, String hashKey) {
        return (String) stringRedisTemplate.opsForHash().get(key, hashKey);
    }

    // ====================== 通用操作 ======================
    /**
     * 删除指定 key
     */
    public Boolean delete(String key) {
        return stringRedisTemplate.delete(key);
    }

    /**
     * 判断 key 是否存在
     */
    public Boolean hasKey(String key) {
        return stringRedisTemplate.hasKey(key);
    }

    /**
     * 设置 key 过期时间
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return stringRedisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取所有匹配的 key(如模糊查询:user:*)
     */
    public Set<String> keys(String pattern) {
        return stringRedisTemplate.keys(pattern);
    }
}

对于List的工具类

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class RedisListUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // ==================== 基础操作 ====================
    /**
     * 往 List 尾部添加元素(RPUSH)
     * @param key 缓存key
     * @param values 要添加的元素(可多个)
     * @return 添加后 List 长度
     */
    public Long rPush(String key, Object... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 往 List 头部添加元素(LPUSH)
     * @param key 缓存key
     * @param values 要添加的元素(可多个)
     * @return 添加后 List 长度
     */
    public Long lPush(String key, Object... values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    /**
     * 获取 List 全部元素
     * @param key 缓存key
     * @return 完整 List
     */
    public List<Object> lRangeAll(String key) {
        // 0 = 第一个元素,-1 = 最后一个元素
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 分页获取 List 元素(核心!适合大 List)
     * @param key 缓存key
     * @param start 起始索引(从0开始)
     * @param end 结束索引(如取10条:start=0, end=9)
     * @return 分页结果
     */
    public List<Object> lRangePage(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 获取 List 长度
     * @param key 缓存key
     * @return List 元素个数
     */
    public Long lSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 根据索引获取元素
     * @param key 缓存key
     * @param index 索引(0=第一个,-1=最后一个)
     * @return 对应元素
     */
    public Object lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 删除 List 中的元素
     * @param key 缓存key
     * @param count 删除次数(count=0:删除所有匹配元素;count>0:从头部删count个;count<0:从尾部删)
     * @param value 要删除的元素
     * @return 删除的个数
     */
    public Long lRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }

    /**
     * 设置 List 过期时间(必须!避免内存泄漏)
     * @param key 缓存key
     * @param timeout 过期时间
     * @param unit 时间单位
     * @return 是否设置成功
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
    }

    /**
     * 删除整个 List
     * @param key 缓存key
     */
    public void delList(String key) {
        redisTemplate.delete(key);
    }
}

redis在springboot中的有以下几个使用场景

接口数据缓存

对于接口缓存而言,存入 redis 一般先需要一个Key

java 复制代码
private static String key = "product:info";

然后使用 工具类中的方法

java 复制代码
@GetMapping("/getAllArticle")
    public CommonRespose getAllArticle() {
        String cacheKey = article_redis_key + "allArticle";

        Long s = redisListUtils.lSize(cacheKey);
        if( s != null && s > 0)
        {
            System.out.println("缓存命中============查询到所有的文章=============");
            return CommonRespose.success(redisListUtils.lRangeAll(cacheKey));
        }

        List<Article> res = articleService.getAllArticle();
        if(res.size() > 0)
        {
            redisListUtils.rPush(cacheKey,res.toArray());
            redisListUtils.expire(cacheKey,10, TimeUnit.HOURS);
        }


        return CommonRespose.success(res);
    }

分布式锁

什么是分布式锁?

分布式锁就是,在同一时间内,多台服务器同时竞争同一把锁,确保在同一时间内只有一台服务器执行到关键代码,避免在分布式系统中因竞争同一资源导致数据混乱

秒杀库存扣减为例:

  1. 用户点击秒杀
  2. 服务器尝试获取分布式锁
  3. 获取成功 → 执行扣库存、创建订单
  4. 执行完成 → 释放锁
  5. 获取失败 → 提示 "抢购太火爆"

案例:

先封装redis分布式锁工具类(服务器向运行redis的服务器申请锁,操纵锁)

java 复制代码
@Component
public class RedisDistributedLockUtils {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 锁的默认过期时间(5秒,防止死锁)
    private static final long DEFAULT_EXPIRE_TIME = 5;
    // 锁的重试间隔(100毫秒,避免频繁请求Redis)
    private static final long RETRY_INTERVAL = 100;
    // 最大重试次数(3次,避免无限等待)
    private static final int MAX_RETRY = 3;

    /**
     * 获取分布式锁
     * @param lockKey 锁的Key(如:lock:seckill:1001)
     * @return 锁的唯一标识(释放锁时需要验证)
     */
    public String tryLock(String lockKey) {
        return tryLock(lockKey, DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
    }

    /**
     * 带过期时间的锁
     * @param lockKey 锁Key
     * @param expireTime 过期时间
     * @param timeUnit 时间单位
     * @return 唯一标识(UUID),null表示获取失败
     */
    public String tryLock(String lockKey, long expireTime, TimeUnit timeUnit) {
        // 生成唯一标识(防止误删别人的锁)
        String requestId = UUID.randomUUID().toString();
        int retryCount = 0;

        // 重试获取锁
        while (retryCount < MAX_RETRY) {
            // Redis核心命令:SET key value NX EX time(原子操作)
            Boolean success = stringRedisTemplate.opsForValue()
                    .setIfAbsent(lockKey, requestId, expireTime, timeUnit);

            if (Boolean.TRUE.equals(success)) {
                return requestId; // 获取锁成功,返回唯一标识
            }

            // 获取失败,重试
            retryCount++;
            try {
                Thread.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        return null; // 重试多次仍失败
    }

    /**
     * 释放分布式锁(必须验证requestId,防止误删)
     * @param lockKey 锁Key
     * @param requestId 唯一标识(获取锁时返回的UUID)
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        if (requestId == null) {
            return false;
        }

        // 先查锁的当前值,再删除(保证原子性,避免并发误删)
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = stringRedisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Collections.singletonList(lockKey),
                requestId
        );

        return result != null && result > 0;
    }
}

业务层使用分布式锁

java 复制代码
@Service
public class SeckillDistributedService {
    @Resource
    private ProductStockService stockService;
    @Resource
    private RedisDistributedLockUtils lockUtils;

    // 锁Key前缀(规范命名)
    private static final String LOCK_PREFIX = "lock:seckill:";

    public String seckill(Long productId) {
        // 1. 拼接锁Key(商品维度,只锁当前商品)
        String lockKey = LOCK_PREFIX + productId;

        // 2. 获取分布式锁
        String requestId = lockUtils.tryLock(lockKey);
        if (requestId == null) {
            return "商品" + productId + "秒杀太火爆,请稍后再试!";
        }

        // 3. 核心逻辑:扣库存(加锁成功后执行)
        try {
            Integer stock = stockService.getStock(productId);
            if (stock <= 0) {
                return "商品" + productId + "库存不足!";
            }
            boolean success = stockService.deductStock(productId);
            if (success) {
                return "商品" + productId + "秒杀成功!";
            } else {
                return "商品" + productId + "秒杀失败!";
            }
        } finally {
            // 4. 释放锁(必须放finally,无论成功失败都要释放)
            lockUtils.releaseLock(lockKey, requestId);
        }
    }
}

接口限流

限流就是指限制一个ip,用户, 或者接口 在一段时间内的访问次数

核心原理:redis 计数器 + 过期时间

一个请求发出 计数器 + 1

当计数器达到上限,就将接口锁住,不能访问

当过期时间达到在放开接口

首先封装 redis 限流工具类

java 复制代码
@Component
public class RedisRateLimitUtils {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 接口限流核心方法
     * @param keyPrefix 限流Key前缀(如:limit:login:)
     * @param identifier 唯一标识(如:用户ID/IP)
     * @param limit 时间窗口内的最大请求数(如:5次)
     * @param timeout 时间窗口(如:1分钟)
     * @param timeUnit 时间单位
     * @return true=允许访问,false=拒绝访问
     */
    public boolean rateLimit(String keyPrefix, String identifier, int limit, long timeout, TimeUnit timeUnit) {
        // 1. 拼接限流Key:前缀 + 唯一标识(如:limit:login:1001)
        String limitKey = keyPrefix + identifier;

        // 2. Redis计数器自增(原子操作,避免并发问题)
        Long count = stringRedisTemplate.opsForValue().increment(limitKey, 1);

        // 3. 首次计数时,设置过期时间(只设置一次,避免重复覆盖)
        if (count != null && count == 1) {
            stringRedisTemplate.expire(limitKey, timeout, timeUnit);
        }

        // 4. 判断是否超过限流阈值
        return count != null && count <= limit;
    }
}

并在业务层使用

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private RedisRateLimitUtils rateLimitUtils;

    // 限流Key前缀(规范命名)
    private static final String LOGIN_LIMIT_PREFIX = "limit:login:";

    /**
     * 用户登录接口(限流:1分钟最多5次)
     */
    @PostMapping("/login")
    public CommonResponse login(@RequestParam Long userId, @RequestParam String password) {
        // 1. 先执行限流判断
        boolean allow = rateLimitUtils.rateLimit(
                LOGIN_LIMIT_PREFIX,    // 限流前缀
                userId.toString(),     // 唯一标识(用户ID)
                5,                     // 1分钟最多5次
                1,                     // 时间窗口:1分钟
                TimeUnit.MINUTES       // 时间单位
        );

        // 2. 超过限流阈值,直接拒绝
        if (!allow) {
            return CommonResponse.fail("请求过于频繁,请1分钟后再试!");
        }

        // 3. 未超过阈值,执行登录逻辑
        if ("123456".equals(password)) {
            return CommonResponse.success("登录成功!");
        } else {
            return CommonResponse.fail("密码错误!");
        }
    }
}

用户登录态存储(Session 共享)

Session 是客户端 存储在服务器(内存/redis) 的身份凭证,表示这个用户的登录,等等信息

把用户的登录状态(Session)从 "单台服务器的内存" 搬到 "所有服务器都能访问的 Redis" 里,让多台服务器都能查到同一个用户的登录信息

什么是消息队列?

消息队列本质是一个异步的操作,一个任务发出,比如说一个电商订单的发布(下单),然后要经过很多步骤按顺序完成,但是消息队列的优势是,下单之后,将任务放在队列中交给其他任务去执行,然后直接返回下单成功即可

为什么 redis 比较适合实现消息队列?

因为 redis 的 List 结构是先进先出的,可重复,有序的数据结构,非常适合用作消息队列,且是阻塞读取,支持多消费者

阻塞读取(Blocking Read) :消费者去队列拿消息时,如果队列里没有消息 ,就 "原地等待"(阻塞),直到队列里有新消息进来,再立刻取走;如果队列里有消息,就直接取走。

支持多消费者 :Redis 的消息队列可以同时让多个消费者(线程 / 服务器) 监听同一个队列,队列里的消息会被均匀分配给不同的消费者处理(一个消息只会被一个消费者拿走),实现 "多人分工干活"。

相关推荐
编程饭碗2 小时前
【Mysql日期字段】
数据库·mysql
Thomas.Sir2 小时前
精通 MySQL 面试题
数据结构·数据库·mysql
上海云盾-小余2 小时前
应用层漏洞实战防护:SQL 注入、XSS、文件上传漏洞一站式加固方案
数据库·sql·xss
鸽芷咕2 小时前
从语法兼容到语义一致:深度解析金仓如何“无感”承接MySQL复杂业务
数据库·mysql
新缸中之脑2 小时前
AI智能体评估指南
数据库·人工智能·oracle
add45a2 小时前
Python类型提示(Type Hints)详解
jvm·数据库·python
曾阿伦2 小时前
SQL 用法详解:从基础操作到进阶实战的全场景指南
数据库·sql
NCU_wander2 小时前
操作系统/数据库和业务应用/中间件/硬件之间的关系
数据库·中间件
Navicat中国2 小时前
如何从0到1完成函数设计 | Navicat 教程
数据库·函数·navicat