Skip to content
清晨的一缕阳光
返回

Spring Boot Redis 缓存集成实战

前言

Redis 是高性能的键值存储数据库,广泛用于缓存、分布式锁、计数器等场景。Spring Boot 提供了 RedisTemplate 和 Spring Cache 两种 Redis 集成方式。

快速开始

1. 添加依赖

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

2. 配置 Redis

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: 
      database: 0
      timeout: 3000ms
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-idle: 0
          max-wait: -1ms

3. 配置序列化

@Configuration
public class RedisConfig {
    
    @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);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

RedisTemplate 使用

1. 基本操作

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置字符串
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 设置带过期时间
     */
    public void set(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 Boolean.TRUE.equals(redisTemplate.delete(key));
    }
    
    /**
     * 判断是否存在
     */
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
    
    /**
     * 设置过期时间
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
    }
}

2. Hash 操作

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * Hash 设置
     */
    public void hSet(String key, String field, Object value) {
        redisTemplate.opsForHash().put(key, field, value);
    }
    
    /**
     * Hash 获取
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }
    
    /**
     * Hash 获取所有
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * Hash 删除
     */
    public void hDelete(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }
}

3. List 操作

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 左侧入队
     */
    public long lPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }
    
    /**
     * 右侧入队
     */
    public long rPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
    
    /**
     * 出队
     */
    public Object lPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    
    /**
     * 获取范围
     */
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
}

4. Set 操作

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加
     */
    public long sAdd(String key, Object value) {
        return redisTemplate.opsForSet().add(key, value);
    }
    
    /**
     * 获取所有
     */
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 判断是否包含
     */
    public boolean sIsMember(String key, Object value) {
        return Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(key, value));
    }
    
    /**
     * 删除
     */
    public long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }
}

5. ZSet 操作

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 添加
     */
    public boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
    
    /**
     * 获取排名
     */
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }
    
    /**
     * 获取前 N 名
     */
    public Set<Object> zTopN(String key, long n) {
        return redisTemplate.opsForZSet().reverseRange(key, 0, n - 1);
    }
    
    /**
     * 获取分数
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
}

Spring Cache

1. 启用缓存

@SpringBootApplication
@EnableCaching
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2. 配置缓存管理器

@Configuration
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withInitialCacheConfigurations(singletonMap("users", config.entryTtl(Duration.ofMinutes(60))))
            .build();
    }
}

3. 使用缓存

@Service
@RequiredArgsConstructor
public class UserService {
    
    private final UserRepository userRepository;
    
    /**
     * 缓存查询结果
     */
    @Cacheable(value = "users", key = "#id")
    public UserDTO findById(Long id) {
        return userRepository.findById(id)
            .map(this::convertToDTO)
            .orElseThrow(() -> BusinessException.notFound("用户"));
    }
    
    /**
     * 更新缓存
     */
    @CachePut(value = "users", key = "#user.id")
    public UserDTO update(UserDTO user) {
        return userRepository.save(convertToEntity(user));
    }
    
    /**
     * 删除缓存
     */
    @CacheEvict(value = "users", key = "#id")
    public void delete(Long id) {
        userRepository.deleteById(id);
    }
    
    /**
     * 清空缓存
     */
    @CacheEvict(value = "users", allEntries = true)
    public void clearCache() {
        log.info("清空用户缓存");
    }
}

4. 条件缓存

@Service
public class UserService {
    
    /**
     * 条件缓存
     */
    @Cacheable(value = "users", key = "#id", condition = "#id > 10")
    public UserDTO findById(Long id) {
        return userRepository.findById(id)
            .map(this::convertToDTO)
            .orElse(null);
    }
    
    /**
     * 除非条件
     */
    @Cacheable(value = "users", key = "#id", unless = "#result == null")
    public UserDTO findById(Long id) {
        return userRepository.findById(id)
            .map(this::convertToDTO)
            .orElse(null);
    }
}

分布式锁

1. 使用 Redisson

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.24.3</version>
</dependency>

2. 配置 Redisson

@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setPassword(null)
            .setDatabase(0);
        
        return Redisson.create(config);
    }
}

3. 使用分布式锁

@Service
@RequiredArgsConstructor
public class OrderService {
    
    private final RedissonClient redissonClient;
    
    public void createOrder(Long userId) {
        RLock lock = redissonClient.getLock("order:lock:" + userId);
        
        boolean locked = false;
        try {
            locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException("获取锁失败");
            }
            
            // 业务逻辑
            // ...
            
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BusinessException("获取锁中断");
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

4. 使用 Spring Cache Lock

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id", cacheManager = "redisCacheManager")
    public UserDTO findById(Long id) {
        return userRepository.findById(id)
            .map(this::convertToDTO)
            .orElse(null);
    }
}

Lua 脚本

1. 执行 Lua 脚本

@Service
@RequiredArgsConstructor
public class RedisService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 限流 Lua 脚本
     */
    private static final String LIMIT_SCRIPT = """
        local key = KEYS[1]
        local limit = tonumber(ARGV[1])
        local expire = tonumber(ARGV[2])
        
        local current = redis.call('INCR', key)
        if current == 1 then
            redis.call('EXPIRE', key, expire)
        end
        
        if current > limit then
            return 0
        else
            return 1
        end
        """;
    
    private final RedisScript<Long> limitScript;
    
    /**
     * 限流
     */
    public boolean isAllowed(String key, int limit, int expire) {
        Long result = redisTemplate.execute(
            limitScript,
            Collections.singletonList(key),
            String.valueOf(limit),
            String.valueOf(expire)
        );
        return result != null && result == 1;
    }
}

2. 配置 RedisScript

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisScript<Long> limitScript() {
        RedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText("""
            local key = KEYS[1]
            local limit = tonumber(ARGV[1])
            local expire = tonumber(ARGV[2])
            
            local current = redis.call('INCR', key)
            if current == 1 then
                redis.call('EXPIRE', key, expire)
            end
            
            if current > limit then
                return 0
            else
                return 1
            end
            """);
        script.setResultType(Long.class);
        return script;
    }
}

最佳实践

1. 缓存穿透

@Service
public class UserService {
    
    /**
     * 缓存穿透 - 缓存空值
     */
    @Cacheable(value = "users", key = "#id")
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id).orElse(null);
        if (user == null) {
            // 缓存空值,防止穿透
            redisTemplate.opsForValue().set("user:null:" + id, "", 5, TimeUnit.MINUTES);
            return null;
        }
        return convertToDTO(user);
    }
}

2. 缓存击穿

@Service
public class UserService {
    
    public UserDTO findById(Long id) {
        String key = "user:" + id;
        
        // 尝试从缓存获取
        Object cached = redisTemplate.opsForValue().get(key);
        if (cached != null) {
            return (UserDTO) cached;
        }
        
        // 尝试获取分布式锁
        RLock lock = redissonClient.getLock("lock:" + key);
        try {
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                // 双重检查
                cached = redisTemplate.opsForValue().get(key);
                if (cached != null) {
                    return (UserDTO) cached;
                }
                
                // 从数据库查询
                User user = userRepository.findById(id).orElse(null);
                if (user != null) {
                    UserDTO dto = convertToDTO(user);
                    redisTemplate.opsForValue().set(key, dto, 30, TimeUnit.MINUTES);
                    return dto;
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        
        return null;
    }
}

3. 缓存雪崩

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public UserDTO findById(Long id) {
        User user = userRepository.findById(id).orElse(null);
        return convertToDTO(user);
    }
}
# 配置不同的过期时间,避免同时过期
spring:
  cache:
    redis:
      time-to-live: 1800000  # 30 分钟
      random-ttl: true       # 随机过期时间

总结

Redis 集成要点:

Redis 是 Spring Boot 应用中不可或缺的缓存和分布式组件。


分享这篇文章到:

上一篇文章
Java 设计模式实战
下一篇文章
RocketMQ 集成