Spring Data Redis + Redisson 学习笔记

Spring Data Redis + Redisson 学习笔记


目录

  1. 背景与动机
  2. 核心概念与技术栈
  3. [Spring Data Redis 详解](#Spring Data Redis 详解)
  4. [Redisson 详解](#Redisson 详解)
  5. 两者对比与选择
  6. 实战案例
  7. 常见问题与最佳实践
  8. 进阶主题
  9. 扩展阅读

1. 背景与动机

1.1 为什么需要 Redis?

Redis (Remote Dictionary Server)是一个开源的内存数据结构存储系统,可用作:

  • 缓存(Cache): 减轻数据库压力,提升响应速度
  • 消息队列(Message Queue): 异步处理、削峰填谷
  • 分布式锁(Distributed Lock): 解决分布式系统并发问题
  • 会话存储(Session Store): 分布式 Session 共享
  • 计数器/排行榜: 高性能计数与排序

1.2 为什么需要 Spring Data Redis?

原生 Jedis/Lettuce 客户端虽然功能强大,但:

  • API 较底层,需要手动管理连接
  • 缺少序列化/反序列化支持
  • 与 Spring 生态集成不便

Spring Data Redis 提供:

  • 统一的 RedisTemplate 抽象
  • 自动连接池管理
  • 内置序列化方案(JSON、JDK、Protobuf 等)
  • 声明式缓存(@Cacheable)支持
  • 与 Spring Boot 无缝集成

1.3 为什么需要 Redisson?

Spring Data Redis 主要面向基础数据操作,但分布式场景需要:

  • 分布式锁(避免超卖、重复执行)
  • 分布式集合(跨 JVM 共享的 Map、Set)
  • 限流器(RateLimiter)
  • 消息队列/发布订阅

Redisson 是一个基于 Redis 的 Java 分布式工具库,提供:

  • 丰富的分布式对象(RMap、RSet、RLock 等)
  • 开箱即用的分布式锁(支持看门狗、公平锁)
  • 消息队列、Topic、Stream 支持
  • 限流、布隆过滤器等高级功能

2. 核心概念与技术栈

2.1 技术栈概览

复制代码
┌─────────────────────────────────────────────┐
│          Spring Boot Application            │
├─────────────────────────────────────────────┤
│  Spring Data Redis    │    Redisson         │
│  (基础操作 + 缓存)      │   (分布式工具)        │
├─────────────────────────────────────────────┤
│         Lettuce / Jedis 客户端               │
├─────────────────────────────────────────────┤
│              Redis Server                   │
│   (单机/哨兵/集群)                             │
└─────────────────────────────────────────────┘

2.2 核心依赖

xml 复制代码
<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Redisson(可选,按需引入) -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.24.3</version>
</dependency>

<!-- 连接池(推荐) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

3. Spring Data Redis 详解

3.1 核心组件

组件 说明
RedisTemplate 通用 Redis 操作模板,支持各种数据类型
StringRedisTemplate 针对 String 类型优化(Key/Value 都是 String)
ValueOperations String 类型操作(get/set)
HashOperations Hash 类型操作(hget/hset)
ListOperations List 类型操作(lpush/rpop)
SetOperations Set 类型操作(sadd/smembers)
ZSetOperations Sorted Set 操作(zadd/zrange)

3.2 配置示例

3.2.1 application.yml 配置
yaml 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: your_password  # 如果有密码
      database: 0              # 默认数据库编号
      timeout: 3000            # 连接超时(毫秒)
      
      # 连接池配置(Lettuce)
      lettuce:
        pool:
          max-active: 8        # 最大连接数
          max-idle: 8          # 最大空闲连接
          min-idle: 0          # 最小空闲连接
          max-wait: -1ms       # 连接池最大阻塞等待时间

      # 如果使用 Jedis
      # jedis:
      #   pool:
      #     max-active: 8
      #     max-idle: 8
      #     min-idle: 0
      #     max-wait: -1ms
3.2.2 Redis 配置类(推荐)
java 复制代码
@Configuration
public class RedisConfig {

    /**
     * 自定义 RedisTemplate(解决序列化问题)
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 使用 Jackson2JsonRedisSerializer 序列化 Value
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(objectMapper);

        // String 类型的 Key 使用 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        // Value 使用 JSON 序列化
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }

    /**
     * StringRedisTemplate(Key/Value 都是 String)
     */
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        return new StringRedisTemplate(factory);
    }
}

3.3 基础操作示例

3.3.1 String 操作
java 复制代码
@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // ========== String 操作 ==========
    
    /**
     * 设置键值对
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 设置键值对(带过期时间)
     */
    public void setWithExpire(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 获取值
     */
    public Object get(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 timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取剩余过期时间
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 自增操作
     */
    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }
}
3.3.2 Hash 操作
java 复制代码
/**
 * Hash 操作(适合存储对象)
 */
public void hashOperations() {
    String key = "user:1001";
    
    // 单个字段设置
    redisTemplate.opsForHash().put(key, "name", "张三");
    redisTemplate.opsForHash().put(key, "age", 25);
    
    // 批量设置
    Map<String, Object> userMap = new HashMap<>();
    userMap.put("name", "李四");
    userMap.put("age", 30);
    userMap.put("email", "lisi@example.com");
    redisTemplate.opsForHash().putAll(key, userMap);
    
    // 获取单个字段
    Object name = redisTemplate.opsForHash().get(key, "name");
    
    // 获取所有字段
    Map<Object, Object> allFields = redisTemplate.opsForHash().entries(key);
    
    // 删除字段
    redisTemplate.opsForHash().delete(key, "email");
    
    // 判断字段是否存在
    Boolean exists = redisTemplate.opsForHash().hasKey(key, "name");
}
3.3.3 List 操作
java 复制代码
/**
 * List 操作(适合消息队列、排行榜)
 */
public void listOperations() {
    String key = "message:queue";
    
    // 左侧插入(队头)
    redisTemplate.opsForList().leftPush(key, "消息1");
    redisTemplate.opsForList().leftPushAll(key, "消息2", "消息3");
    
    // 右侧插入(队尾)
    redisTemplate.opsForList().rightPush(key, "消息4");
    
    // 左侧弹出(消费消息)
    Object message = redisTemplate.opsForList().leftPop(key);
    
    // 阻塞式弹出(等待消息)
    Object blockMessage = redisTemplate.opsForList().leftPop(key, 10, TimeUnit.SECONDS);
    
    // 获取列表范围
    List<Object> messages = redisTemplate.opsForList().range(key, 0, -1);
    
    // 获取列表长度
    Long size = redisTemplate.opsForList().size(key);
}
3.3.4 Set 操作
java 复制代码
/**
 * Set 操作(适合去重、交集/并集/差集)
 */
public void setOperations() {
    String key1 = "user:tags:1001";
    String key2 = "user:tags:1002";
    
    // 添加元素
    redisTemplate.opsForSet().add(key1, "Java", "Redis", "MySQL");
    redisTemplate.opsForSet().add(key2, "Python", "Redis", "MongoDB");
    
    // 获取所有元素
    Set<Object> members = redisTemplate.opsForSet().members(key1);
    
    // 判断元素是否存在
    Boolean isMember = redisTemplate.opsForSet().isMember(key1, "Java");
    
    // 交集
    Set<Object> intersect = redisTemplate.opsForSet().intersect(key1, key2); // [Redis]
    
    // 并集
    Set<Object> union = redisTemplate.opsForSet().union(key1, key2);
    
    // 差集
    Set<Object> diff = redisTemplate.opsForSet().difference(key1, key2); // [Java, MySQL]
    
    // 移除元素
    redisTemplate.opsForSet().remove(key1, "MySQL");
}
3.3.5 ZSet(Sorted Set)操作
java 复制代码
/**
 * ZSet 操作(适合排行榜、延时队列)
 */
public void zSetOperations() {
    String key = "game:rank";
    
    // 添加元素(score 用于排序)
    redisTemplate.opsForZSet().add(key, "玩家A", 100);
    redisTemplate.opsForZSet().add(key, "玩家B", 200);
    redisTemplate.opsForZSet().add(key, "玩家C", 150);
    
    // 增加分数
    redisTemplate.opsForZSet().incrementScore(key, "玩家A", 50); // 100 + 50 = 150
    
    // 获取排名(从小到大,0-based)
    Long rank = redisTemplate.opsForZSet().rank(key, "玩家A");
    
    // 获取排名(从大到小)
    Long reverseRank = redisTemplate.opsForZSet().reverseRank(key, "玩家A");
    
    // 获取分数
    Double score = redisTemplate.opsForZSet().score(key, "玩家A");
    
    // 获取排名范围(Top 10)
    Set<Object> top10 = redisTemplate.opsForZSet().reverseRange(key, 0, 9);
    
    // 获取分数范围内的元素
    Set<Object> range = redisTemplate.opsForZSet().rangeByScore(key, 100, 200);
    
    // 删除元素
    redisTemplate.opsForZSet().remove(key, "玩家C");
}

3.4 声明式缓存(@Cacheable)

3.4.1 启用缓存
java 复制代码
@SpringBootApplication
@EnableCaching  // 启用缓存支持
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
3.4.2 配置 CacheManager
java 复制代码
@Configuration
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 配置序列化
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);

        // 配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))  // 缓存过期时间 1 小时
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
            .disableCachingNullValues();  // 不缓存 null 值

        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}
3.4.3 使用缓存注解
java 复制代码
@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    /**
     * @Cacheable: 查询时先检查缓存,不存在则执行方法并缓存结果
     * value: 缓存名称
     * key: 缓存键(支持 SpEL 表达式)
     * unless: 条件不缓存
     */
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public User getUserById(Long id) {
        System.out.println("从数据库查询用户: " + id);
        return userRepository.findById(id).orElse(null);
    }

    /**
     * @CachePut: 更新缓存(总是执行方法,并更新缓存)
     */
    @CachePut(value = "user", key = "#user.id")
    public User updateUser(User user) {
        System.out.println("更新用户: " + user.getId());
        return userRepository.save(user);
    }

    /**
     * @CacheEvict: 删除缓存
     * allEntries = true: 清空整个缓存空间
     */
    @CacheEvict(value = "user", key = "#id")
    public void deleteUser(Long id) {
        System.out.println("删除用户: " + id);
        userRepository.deleteById(id);
    }

    /**
     * 清空所有用户缓存
     */
    @CacheEvict(value = "user", allEntries = true)
    public void clearAllCache() {
        System.out.println("清空所有用户缓存");
    }
}

4. Redisson 详解

4.1 核心概念

Redisson 是一个基于 Redis 的 Java 分布式工具库,提供:

  • 分布式对象: RMap、RSet、RList、RQueue 等
  • 分布式锁: RLock、ReadWriteLock、Semaphore 等
  • 分布式集合: RMultimap、RSetCache 等
  • 消息队列: RTopic、RStream、RDelayedQueue 等
  • 限流器: RRateLimiter
  • 布隆过滤器: RBloomFilter

4.2 配置示例

4.2.1 application.yml 配置(单机模式)
yaml 复制代码
spring:
  redis:
    redisson:
      # 配置文件路径(可选)
      config: classpath:redisson.yaml

# 或直接配置
redisson:
  single-server-config:
    address: redis://localhost:6379
    password: your_password
    database: 0
    connection-pool-size: 64
    connection-minimum-idle-size: 10
    idle-connection-timeout: 10000
    timeout: 3000
4.2.2 Java 配置(推荐)
java 复制代码
@Configuration
public class RedissonConfig {

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // 单机模式
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setPassword("your_password")
            .setDatabase(0)
            .setConnectionPoolSize(64)
            .setConnectionMinimumIdleSize(10)
            .setIdleConnectionTimeout(10000)
            .setTimeout(3000);
        
        // 集群模式
        // config.useClusterServers()
        //     .addNodeAddress("redis://127.0.0.1:7000")
        //     .addNodeAddress("redis://127.0.0.1:7001")
        //     .addNodeAddress("redis://127.0.0.1:7002");
        
        // 哨兵模式
        // config.useSentinelServers()
        //     .setMasterName("mymaster")
        //     .addSentinelAddress("redis://127.0.0.1:26379", "redis://127.0.0.1:26380");
        
        return Redisson.create(config);
    }
}

4.3 分布式对象

4.3.1 RMap(分布式 Map)
java 复制代码
@Service
public class RedissonMapService {

    @Autowired
    private RedissonClient redissonClient;

    public void rMapDemo() {
        // 获取 RMap
        RMap<String, User> userMap = redissonClient.getMap("user:map");
        
        // 基本操作(与 Java Map 一致)
        userMap.put("1001", new User("张三", 25));
        userMap.put("1002", new User("李四", 30));
        
        User user = userMap.get("1001");
        
        userMap.remove("1002");
        
        // 批量操作
        Map<String, User> batch = new HashMap<>();
        batch.put("1003", new User("王五", 28));
        batch.put("1004", new User("赵六", 32));
        userMap.putAll(batch);
        
        // 异步操作
        RFuture<User> future = userMap.getAsync("1001");
        future.whenComplete((u, ex) -> {
            if (ex == null) {
                System.out.println("异步获取用户: " + u.getName());
            }
        });
        
        // 过期时间
        userMap.expire(Duration.ofHours(1));
    }
}
4.3.2 RSet(分布式 Set)
java 复制代码
public void rSetDemo() {
    RSet<String> tags = redissonClient.getSet("user:tags");
    
    tags.add("Java");
    tags.add("Redis");
    tags.add("MySQL");
    
    boolean contains = tags.contains("Java");
    
    tags.remove("MySQL");
    
    // 集合操作
    RSet<String> tags2 = redissonClient.getSet("user:tags:2");
    tags2.add("Python");
    tags2.add("Redis");
    
    Set<String> intersection = tags.readIntersection("user:tags:2");
}
4.3.3 RList(分布式 List)
java 复制代码
public void rListDemo() {
    RList<String> messageList = redissonClient.getList("message:list");
    
    messageList.add("消息1");
    messageList.add("消息2");
    messageList.add(0, "消息0");  // 指定位置插入
    
    String first = messageList.get(0);
    
    messageList.remove(1);
    
    // 排序
    messageList.sort(Comparator.naturalOrder());
}

4.4 分布式锁(重点)

4.4.1 基础分布式锁
java 复制代码
@Service
public class DistributedLockService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 基础分布式锁
     */
    public void basicLock() {
        RLock lock = redissonClient.getLock("my:lock");
        
        try {
            // 加锁(阻塞等待)
            lock.lock();
            
            // 业务逻辑
            System.out.println("执行业务逻辑...");
            Thread.sleep(1000);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 带超时的分布式锁
     */
    public void lockWithTimeout() {
        RLock lock = redissonClient.getLock("my:lock");
        
        try {
            // 尝试加锁(等待 10 秒,锁定 30 秒后自动释放)
            boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);
            
            if (acquired) {
                try {
                    // 业务逻辑
                    System.out.println("获取锁成功,执行业务...");
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("获取锁失败");
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 看门狗机制(自动续期)
     * 默认锁定 30 秒,每 10 秒自动续期
     */
    public void lockWithWatchdog() {
        RLock lock = redissonClient.getLock("my:lock");
        
        try {
            // 不指定 leaseTime,使用看门狗机制
            lock.lock();
            
            // 长时间业务逻辑(超过 30 秒也不会释放)
            Thread.sleep(60000);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
4.4.2 公平锁
java 复制代码
/**
 * 公平锁(按请求顺序获取锁)
 */
public void fairLock() {
    RLock fairLock = redissonClient.getFairLock("fair:lock");
    
    try {
        fairLock.lock();
        // 业务逻辑
    } finally {
        fairLock.unlock();
    }
}
4.4.3 读写锁
java 复制代码
/**
 * 读写锁(读读不互斥,读写互斥,写写互斥)
 */
public void readWriteLock() {
    RReadWriteLock rwLock = redissonClient.getReadWriteLock("rw:lock");
    
    // 读锁
    RLock readLock = rwLock.readLock();
    readLock.lock();
    try {
        // 读操作
        System.out.println("读取数据...");
    } finally {
        readLock.unlock();
    }
    
    // 写锁
    RLock writeLock = rwLock.writeLock();
    writeLock.lock();
    try {
        // 写操作
        System.out.println("写入数据...");
    } finally {
        writeLock.unlock();
    }
}
4.4.4 联锁(MultiLock)
java 复制代码
/**
 * 联锁(同时锁定多个资源)
 */
public void multiLock() {
    RLock lock1 = redissonClient.getLock("lock1");
    RLock lock2 = redissonClient.getLock("lock2");
    RLock lock3 = redissonClient.getLock("lock3");
    
    // 同时锁定多个锁
    RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
    
    try {
        multiLock.lock();
        // 业务逻辑
    } finally {
        multiLock.unlock();
    }
}
4.4.5 红锁(RedLock)
java 复制代码
/**
 * 红锁(适用于 Redis 集群,更高的可靠性)
 */
public void redLock() {
    RLock lock1 = redissonClient.getLock("lock1");
    RLock lock2 = redissonClient.getLock("lock2");
    RLock lock3 = redissonClient.getLock("lock3");
    
    // 红锁(需要多数节点成功才算获取锁)
    RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3);
    
    try {
        redLock.lock();
        // 业务逻辑
    } finally {
        redLock.unlock();
    }
}

4.5 信号量(Semaphore)

java 复制代码
/**
 * 信号量(限制并发数)
 */
public void semaphoreDemo() {
    RSemaphore semaphore = redissonClient.getSemaphore("my:semaphore");
    
    try {
        // 设置许可数量
        semaphore.trySetPermits(3);
        
        // 获取许可
        semaphore.acquire();
        
        // 业务逻辑(最多 3 个线程同时执行)
        System.out.println("执行任务...");
        Thread.sleep(2000);
        
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        // 释放许可
        semaphore.release();
    }
}

4.6 限流器(RateLimiter)

java 复制代码
/**
 * 限流器(控制访问频率)
 */
@Service
public class RateLimiterService {

    @Autowired
    private RedissonClient redissonClient;

    public void rateLimiterDemo() {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:limit");
        
        // 设置速率: 每 5 秒最多 10 次请求
        rateLimiter.trySetRate(RateType.OVERALL, 10, 5, RateIntervalUnit.SECONDS);
        
        // 尝试获取令牌
        boolean acquired = rateLimiter.tryAcquire();
        
        if (acquired) {
            System.out.println("请求通过");
            // 处理业务
        } else {
            System.out.println("请求被限流");
        }
    }

    /**
     * API 限流注解(自定义)
     */
    public void apiWithRateLimit() {
        RRateLimiter rateLimiter = redissonClient.getRateLimiter("api:user:query");
        rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES);
        
        if (!rateLimiter.tryAcquire()) {
            throw new RuntimeException("请求过于频繁,请稍后再试");
        }
        
        // 执行业务逻辑
    }
}

4.7 布隆过滤器(BloomFilter)

java 复制代码
/**
 * 布隆过滤器(快速判断元素是否存在,允许一定误判率)
 */
public void bloomFilterDemo() {
    RBloomFilter<String> bloomFilter = redissonClient.getBloomFilter("user:exists");
    
    // 初始化(预计元素数量 100000,误判率 0.01)
    bloomFilter.tryInit(100000, 0.01);
    
    // 添加元素
    bloomFilter.add("user:1001");
    bloomFilter.add("user:1002");
    
    // 判断元素是否存在
    boolean exists1 = bloomFilter.contains("user:1001");  // true
    boolean exists2 = bloomFilter.contains("user:9999");  // false(或小概率误判为 true)
    
    System.out.println("user:1001 存在: " + exists1);
    System.out.println("user:9999 存在: " + exists2);
}

4.8 消息队列

4.8.1 普通队列(RQueue)
java 复制代码
/**
 * 普通队列(FIFO)
 */
public void queueDemo() {
    RQueue<String> queue = redissonClient.getQueue("message:queue");
    
    // 生产者
    queue.offer("消息1");
    queue.offer("消息2");
    
    // 消费者
    String message = queue.poll();
    System.out.println("消费消息: " + message);
    
    // 阻塞式消费
    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("message:blocking");
    try {
        String msg = blockingQueue.poll(10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
4.8.2 延时队列(RDelayedQueue)
java 复制代码
/**
 * 延时队列(订单超时取消、定时任务等)
 */
public void delayedQueueDemo() {
    RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:queue");
    RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
    
    // 生产者: 30 分钟后执行
    delayedQueue.offer("订单:123456", 30, TimeUnit.MINUTES);
    
    // 消费者(监听延时队列)
    new Thread(() -> {
        while (true) {
            try {
                String orderId = blockingQueue.take();
                System.out.println("处理超时订单: " + orderId);
                // 取消订单逻辑
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}
4.8.3 优先级队列(RPriorityQueue)
java 复制代码
/**
 * 优先级队列(按优先级消费)
 */
public void priorityQueueDemo() {
    RPriorityQueue<Task> queue = redissonClient.getPriorityQueue("task:priority");
    
    // 添加任务(按优先级排序)
    queue.add(new Task("任务1", 3));
    queue.add(new Task("任务2", 1));  // 优先级最高
    queue.add(new Task("任务3", 2));
    
    // 按优先级消费
    Task task = queue.poll();  // 先取出优先级 1 的任务
}
4.8.4 发布订阅(RTopic)
java 复制代码
/**
 * 发布订阅模式
 */
@Service
public class PubSubService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 订阅消息
     */
    public void subscribe() {
        RTopic topic = redissonClient.getTopic("news:channel");
        
        // 添加监听器
        topic.addListener(String.class, (channel, msg) -> {
            System.out.println("收到消息 [" + channel + "]: " + msg);
        });
    }

    /**
     * 发布消息
     */
    public void publish(String message) {
        RTopic topic = redissonClient.getTopic("news:channel");
        
        // 发布消息(返回接收到消息的订阅者数量)
        long count = topic.publish(message);
        System.out.println("消息发送给 " + count + " 个订阅者");
    }
}

5. 两者对比与选择

5.1 功能对比表

功能 Spring Data Redis Redisson 推荐使用
基础数据操作 ✅ 完善(String/Hash/List/Set/ZSet) ✅ 支持 Spring Data Redis
声明式缓存 ✅ @Cacheable 注解 ❌ 不支持 Spring Data Redis
分布式锁 ❌ 需手动实现 ✅ 开箱即用(支持看门狗) Redisson
分布式对象 ❌ 不支持 ✅ RMap、RSet、RList Redisson
限流器 ❌ 需手动实现 ✅ RRateLimiter Redisson
布隆过滤器 ❌ 不支持 ✅ RBloomFilter Redisson
消息队列 ⚠️ 仅支持 List ✅ RQueue、RDelayedQueue、RTopic Redisson
学习成本 低(Spring 生态友好) 中(需了解分布式概念) -
性能 高(轻量级) 中(功能丰富,开销略大) -
适用场景 缓存、基础数据操作 分布式锁、分布式系统 -

5.2 使用场景建议

优先使用 Spring Data Redis:
  • 简单的缓存场景(@Cacheable)
  • 基础的 Redis 数据操作(CRUD)
  • 对性能要求极高的场景
  • 团队对 Spring 生态熟悉
优先使用 Redisson:
  • 分布式锁(防止超卖、重复执行)
  • 分布式限流(API 限流)
  • 延时任务(订单超时取消)
  • 分布式对象(跨 JVM 共享数据)
  • 布隆过滤器(缓存穿透防护)
结合使用(推荐):
java 复制代码
@Service
public class HybridService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;  // 基础操作
    
    @Autowired
    private RedissonClient redissonClient;  // 分布式锁
    
    /**
     * 秒杀场景: 结合两者优势
     */
    public boolean seckill(Long productId, Long userId) {
        String lockKey = "seckill:lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 使用 Redisson 分布式锁
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                // 使用 Spring Data Redis 操作库存
                String stockKey = "product:stock:" + productId;
                Long stock = redisTemplate.opsForValue().increment(stockKey, -1);
                
                if (stock >= 0) {
                    // 库存充足,创建订单
                    createOrder(productId, userId);
                    return true;
                } else {
                    // 库存不足,回滚
                    redisTemplate.opsForValue().increment(stockKey, 1);
                    return false;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        
        return false;
    }
}

6. 实战案例

6.1 案例1: 秒杀系统

java 复制代码
@Service
public class SeckillService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 初始化秒杀库存
     */
    public void initStock(Long productId, Integer stock) {
        String stockKey = "seckill:stock:" + productId;
        redisTemplate.opsForValue().set(stockKey, stock);
    }

    /**
     * 秒杀抢购
     */
    public boolean seckill(Long productId, Long userId) {
        String lockKey = "seckill:lock:" + productId;
        String stockKey = "seckill:stock:" + productId;
        String userKey = "seckill:user:" + productId + ":" + userId;

        // 防止重复购买
        if (Boolean.TRUE.equals(redisTemplate.hasKey(userKey))) {
            throw new RuntimeException("您已参与过此商品的秒杀");
        }

        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁(等待 0.5 秒)
            if (lock.tryLock(500, 3000, TimeUnit.MILLISECONDS)) {
                try {
                    // 扣减库存
                    Long stock = redisTemplate.opsForValue().increment(stockKey, -1);

                    if (stock < 0) {
                        // 库存不足,回滚
                        redisTemplate.opsForValue().increment(stockKey, 1);
                        throw new RuntimeException("库存不足");
                    }

                    // 记录用户购买记录
                    redisTemplate.opsForValue().set(userKey, "1", 24, TimeUnit.HOURS);

                    // 异步创建订单(实际应通过 MQ)
                    createOrderAsync(productId, userId);

                    return true;

                } finally {
                    lock.unlock();
                }
            } else {
                throw new RuntimeException("系统繁忙,请稍后再试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁失败");
        }
    }

    private void createOrderAsync(Long productId, Long userId) {
        // 发送到 MQ 异步创建订单
        System.out.println("创建订单: productId=" + productId + ", userId=" + userId);
    }
}

6.2 案例2: API 限流

java 复制代码
@Aspect
@Component
public class RateLimitAspect {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 自定义限流注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RateLimit {
        String key() default "";
        int rate() default 10;  // 每分钟请求次数
        int interval() default 60;  // 时间窗口(秒)
    }

    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
        String key = rateLimit.key();
        if (key.isEmpty()) {
            key = point.getSignature().toShortString();
        }

        String rateLimiterKey = "rate:limit:" + key;
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterKey);

        // 设置速率
        rateLimiter.trySetRate(RateType.OVERALL, rateLimit.rate(), 
                               rateLimit.interval(), RateIntervalUnit.SECONDS);

        // 尝试获取令牌
        if (rateLimiter.tryAcquire()) {
            return point.proceed();
        } else {
            throw new RuntimeException("请求过于频繁,请稍后再试");
        }
    }
}

// 使用示例
@RestController
@RequestMapping("/api")
public class ApiController {

    @GetMapping("/user/{id}")
    @RateLimit(key = "api:user:query", rate = 100, interval = 60)  // 每分钟 100 次
    public User getUser(@PathVariable Long id) {
        // 查询用户
        return userService.getUser(id);
    }
}

6.3 案例3: 订单超时自动取消

java 复制代码
@Service
public class OrderService {

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 创建订单
     */
    public String createOrder(Order order) {
        // 1. 保存订单到数据库
        String orderId = saveOrder(order);

        // 2. 加入延时队列(30 分钟后自动取消)
        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:timeout");
        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
        delayedQueue.offer(orderId, 30, TimeUnit.MINUTES);

        return orderId;
    }

    /**
     * 启动订单超时监听器
     */
    @PostConstruct
    public void startOrderTimeoutListener() {
        RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("order:timeout");

        new Thread(() -> {
            while (true) {
                try {
                    // 阻塞等待超时订单
                    String orderId = blockingQueue.take();

                    // 检查订单状态
                    Order order = getOrderById(orderId);
                    if (order != null && "UNPAID".equals(order.getStatus())) {
                        // 取消订单
                        cancelOrder(orderId);
                        System.out.println("订单超时自动取消: " + orderId);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "order-timeout-listener").start();
    }

    private String saveOrder(Order order) {
        // 保存订单逻辑
        return "ORDER_" + System.currentTimeMillis();
    }

    private Order getOrderById(String orderId) {
        // 查询订单
        return new Order();
    }

    private void cancelOrder(String orderId) {
        // 取消订单,释放库存
    }
}

6.4 案例4: 布隆过滤器防止缓存穿透

java 复制代码
@Service
public class UserCacheService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private UserRepository userRepository;

    private RBloomFilter<String> bloomFilter;

    /**
     * 初始化布隆过滤器
     */
    @PostConstruct
    public void initBloomFilter() {
        bloomFilter = redissonClient.getBloomFilter("user:exists");
        bloomFilter.tryInit(1000000, 0.01);  // 预计 100 万用户,误判率 1%

        // 加载所有用户 ID 到布隆过滤器
        List<Long> userIds = userRepository.findAllUserIds();
        for (Long userId : userIds) {
            bloomFilter.add("user:" + userId);
        }
    }

    /**
     * 查询用户(三级缓存)
     */
    public User getUser(Long userId) {
        String key = "user:" + userId;

        // 1. 布隆过滤器判断(防止恶意请求)
        if (!bloomFilter.contains(key)) {
            System.out.println("布隆过滤器拦截: " + key);
            return null;
        }

        // 2. 查询 Redis 缓存
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            System.out.println("命中缓存: " + key);
            return user;
        }

        // 3. 查询数据库
        user = userRepository.findById(userId).orElse(null);
        if (user != null) {
            // 写入缓存
            redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
        }

        return user;
    }

    /**
     * 新增用户时更新布隆过滤器
     */
    public void addUser(User user) {
        userRepository.save(user);
        bloomFilter.add("user:" + user.getId());
    }
}

7. 常见问题与最佳实践

7.1 序列化问题

问题描述

默认的 JDK 序列化会导致:

  • 存储空间浪费(字节数多)
  • 可读性差(二进制格式)
  • 跨语言不兼容
解决方案

推荐使用 JSON 序列化:

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

    // JSON 序列化
    Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
    ObjectMapper mapper = new ObjectMapper();
    mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    mapper.activateDefaultTyping(
        LaissezFaireSubTypeValidator.instance,
        ObjectMapper.DefaultTyping.NON_FINAL
    );
    serializer.setObjectMapper(mapper);

    // Key 使用 String 序列化
    template.setKeySerializer(new StringRedisSerializer());
    template.setHashKeySerializer(new StringRedisSerializer());

    // Value 使用 JSON 序列化
    template.setValueSerializer(serializer);
    template.setHashValueSerializer(serializer);

    return template;
}

7.2 缓存穿透、击穿、雪崩

7.2.1 缓存穿透(查询不存在的数据)

问题: 恶意请求不存在的数据,绕过缓存直击数据库

解决方案:

  1. 布隆过滤器(推荐)
  2. 缓存空值(设置较短过期时间)
java 复制代码
public User getUser(Long userId) {
    String key = "user:" + userId;
    
    // 查询缓存
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user != null) {
        return user;
    }
    
    // 查询数据库
    user = userRepository.findById(userId).orElse(null);
    
    if (user == null) {
        // 缓存空值,防止穿透(5 分钟)
        redisTemplate.opsForValue().set(key, new User(), 5, TimeUnit.MINUTES);
        return null;
    }
    
    // 正常缓存(1 小时)
    redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
    return user;
}
7.2.2 缓存击穿(热点 Key 过期)

问题: 热点数据过期瞬间,大量请求打到数据库

解决方案:

  1. 设置热点数据永不过期
  2. 互斥锁(Redisson 分布式锁)
java 复制代码
public User getHotUser(Long userId) {
    String key = "user:hot:" + userId;
    String lockKey = "lock:" + key;
    
    // 查询缓存
    User user = (User) redisTemplate.opsForValue().get(key);
    if (user != null) {
        return user;
    }
    
    // 获取分布式锁
    RLock lock = redissonClient.getLock(lockKey);
    try {
        lock.lock();
        
        // 双重检查
        user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }
        
        // 查询数据库
        user = userRepository.findById(userId).orElse(null);
        
        // 写入缓存(永不过期或超长时间)
        redisTemplate.opsForValue().set(key, user, 24, TimeUnit.HOURS);
        
    } finally {
        lock.unlock();
    }
    
    return user;
}
7.2.3 缓存雪崩(大量 Key 同时过期)

问题: 大量缓存同时失效,数据库压力激增

解决方案:

  1. 随机过期时间(避免集中过期)
  2. 多级缓存(本地缓存 + Redis)
  3. 限流降级(保护数据库)
java 复制代码
public void setWithRandomExpire(String key, Object value) {
    // 基础过期时间 1 小时 + 随机 0-300 秒
    long expire = 3600 + new Random().nextInt(300);
    redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}

7.3 分布式锁最佳实践

7.3.1 必须设置超时时间
java 复制代码
// ❌ 错误: 未设置超时时间,服务宕机导致死锁
lock.lock();

// ✅ 正确: 设置超时时间
lock.tryLock(10, 30, TimeUnit.SECONDS);
7.3.2 确保锁的释放
java 复制代码
RLock lock = redissonClient.getLock("my:lock");
try {
    lock.lock(30, TimeUnit.SECONDS);
    // 业务逻辑
} finally {
    // 判断是否是当前线程持有的锁
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}
7.3.3 使用看门狗机制
java 复制代码
// 不指定 leaseTime,Redisson 自动续期
lock.lock();  // 默认 30 秒,每 10 秒续期一次

// 长时间业务逻辑也不会释放锁
doLongTimeTask();

7.4 性能优化

7.4.1 Pipeline(管道)

批量操作减少网络往返:

java 复制代码
// ❌ 低效: 每次操作一个网络往返
for (int i = 0; i < 1000; i++) {
    redisTemplate.opsForValue().set("key" + i, "value" + i);
}

// ✅ 高效: 使用 Pipeline
redisTemplate.executePipelined(new RedisCallback<Object>() {
    @Override
    public Object doInRedis(RedisConnection connection) {
        for (int i = 0; i < 1000; i++) {
            connection.set(("key" + i).getBytes(), ("value" + i).getBytes());
        }
        return null;
    }
});
7.4.2 Lua 脚本(原子性操作)
java 复制代码
/**
 * 使用 Lua 脚本保证原子性
 * 场景: 扣减库存并检查是否充足
 */
public boolean decrementStock(String key, int count) {
    String script = 
        "local stock = redis.call('get', KEYS[1]) " +
        "if not stock or tonumber(stock) < tonumber(ARGV[1]) then " +
        "    return 0 " +
        "else " +
        "    redis.call('decrby', KEYS[1], ARGV[1]) " +
        "    return 1 " +
        "end";
    
    DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    redisScript.setScriptText(script);
    redisScript.setResultType(Long.class);
    
    Long result = redisTemplate.execute(redisScript, 
                                        Collections.singletonList(key), 
                                        String.valueOf(count));
    
    return result != null && result == 1;
}

7.5 监控与运维

7.5.1 开启 Redis 监控
yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
  metrics:
    export:
      prometheus:
        enabled: true
7.5.2 关键指标监控
  • 连接数: 监控连接池使用情况
  • 命中率 : (hits / (hits + misses)) * 100%
  • 内存使用: 避免内存溢出
  • 慢查询: 定位性能瓶颈
  • 网络延迟: 监控 Redis 响应时间

8. 进阶主题

8.1 Redis 集群配置

8.1.1 Spring Data Redis 集群配置
yaml 复制代码
spring:
  data:
    redis:
      cluster:
        nodes:
          - 127.0.0.1:7000
          - 127.0.0.1:7001
          - 127.0.0.1:7002
          - 127.0.0.1:7003
          - 127.0.0.1:7004
          - 127.0.0.1:7005
        max-redirects: 3
8.1.2 Redisson 集群配置
java 复制代码
@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useClusterServers()
        .addNodeAddress("redis://127.0.0.1:7000")
        .addNodeAddress("redis://127.0.0.1:7001")
        .addNodeAddress("redis://127.0.0.1:7002")
        .addNodeAddress("redis://127.0.0.1:7003")
        .addNodeAddress("redis://127.0.0.1:7004")
        .addNodeAddress("redis://127.0.0.1:7005");
    
    return Redisson.create(config);
}

8.2 Redis 哨兵模式

yaml 复制代码
spring:
  data:
    redis:
      sentinel:
        master: mymaster
        nodes:
          - 127.0.0.1:26379
          - 127.0.0.1:26380
          - 127.0.0.1:26381

8.3 事务支持

java 复制代码
/**
 * Redis 事务(不支持回滚)
 */
public void transaction() {
    redisTemplate.execute(new SessionCallback<List<Object>>() {
        @Override
        public List<Object> execute(RedisOperations operations) throws DataAccessException {
            operations.multi();  // 开启事务
            
            operations.opsForValue().set("key1", "value1");
            operations.opsForValue().set("key2", "value2");
            
            return operations.exec();  // 提交事务
        }
    });
}

9. 扩展阅读

9.1 官方文档

9.2 推荐书籍

  • 《Redis 设计与实现》(黄健宏)
  • 《Redis 实战》(Josiah L. Carlson)
  • 《Redis 深度历险》(钱文品)

9.3 学习路径

  1. 基础阶段: 掌握 Redis 基本数据结构和命令
  2. 框架阶段: 熟悉 Spring Data Redis 配置和使用
  3. 进阶阶段: 学习 Redisson 分布式特性
  4. 实战阶段: 缓存设计、分布式锁、秒杀系统
  5. 优化阶段: 性能调优、集群部署、监控运维

9.4 常用命令速查

bash 复制代码
# String
SET key value
GET key
INCR key
EXPIRE key seconds

# Hash
HSET key field value
HGET key field
HGETALL key

# List
LPUSH key value
RPUSH key value
LPOP key
LRANGE key start stop

# Set
SADD key member
SMEMBERS key
SINTER key1 key2

# ZSet
ZADD key score member
ZRANGE key start stop
ZREVRANGE key start stop

# 通用
DEL key
EXISTS key
TTL key
KEYS pattern

总结

核心要点

  1. Spring Data Redis 适合基础操作和缓存
  2. Redisson 适合分布式锁和高级功能
  3. 结合使用可发挥各自优势
  4. 缓存穿透/击穿/雪崩是必须防范的问题
  5. 分布式锁必须设置超时时间并正确释放
  6. 性能优化关注 Pipeline、Lua 脚本、连接池

学习建议

  • 先掌握 Spring Data Redis 基础操作
  • 理解 Redis 五种数据结构的应用场景
  • 熟练使用 Redisson 分布式锁
  • 实战秒杀、限流、延时队列等场景
  • 关注缓存设计模式和最佳实践
相关推荐
强子感冒了1 天前
Java网络编程学习笔记,从网络编程三要素到TCP/UDP协议
java·网络·学习
Quintus五等升1 天前
深度学习④|分类任务—VGG13
人工智能·经验分享·深度学习·神经网络·学习·机器学习·分类
二哈喇子!1 天前
Java框架精品项目【用于个人学习】
java·spring boot·学习
Mixtral1 天前
2026年4款学习转写工具测评:告别逐字整理,自动生成复习资料
笔记·学习·ai·语音转文字
鄭郑1 天前
【playwright 学习笔记】原理讲解与基础操作 --- day01
笔记·学习
曾经的三心草1 天前
redis-2-数据结构内部编码-单线程-String命令
数据结构·数据库·redis
二哈喇子!1 天前
基于JavaSE的淘宝卖鞋后端管理系统的设计与实现
java·spring boot·spring
代码游侠1 天前
学习笔记——时钟系统与定时器
arm开发·笔记·单片机·嵌入式硬件·学习·架构
小魏每天都学习1 天前
【计算机基础知识学习】
学习
罗伯特_十三1 天前
Spring AI ChatModel 使用记录
java·人工智能·spring