Spring Data Redis 使用详解

Spring Data Redis 使用详解

1. Spring Data Redis 核心组件

1.1 Maven 依赖配置

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

<!-- 连接池依赖(推荐使用 Lettuce,Spring Boot 2.x+ 默认使用) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- Jackson 序列化(用于 JSON 序列化) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

1.2 配置文件 (application.yml)

yaml 复制代码
spring:
  redis:
    # Redis服务器地址
    host: localhost
    # Redis服务器端口
    port: 6379
    # 数据库索引(默认0)
    database: 0
    # 连接超时时间
    timeout: 2000ms
    # 密码(没有密码则不设置)
    password: 
    
    # Lettuce 连接池配置
    lettuce:
      pool:
        # 最大连接数(默认8,-1表示无限制)
        max-active: 100
        # 最大空闲连接数
        max-idle: 10
        # 最小空闲连接数
        min-idle: 5
        # 获取连接时的最大等待时间(默认-1,不超时)
        max-wait: 1000ms
        # 空闲连接检查时间间隔
        time-between-eviction-runs: 30000ms

2. RedisTemplate 核心方法详解

2.1 RedisTemplate 配置类

java 复制代码
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis 配置类
 * 配置序列化方式,防止乱码
 */
@Configuration
public class RedisConfig {

    /**
     * 配置 RedisTemplate
     * 使用 Jackson2JsonRedisSerializer 替代默认的 JdkSerializationRedisSerializer
     * 
     * @param redisConnectionFactory Redis连接工厂
     * @return 配置好的 RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        
        // 创建 JSON 序列化器
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        
        // 配置 ObjectMapper,支持多态类型
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
        jsonSerializer.setObjectMapper(objectMapper);
        
        // 设置 key 的序列化器为 String
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        // 设置 value 的序列化器为 JSON
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
    
    /**
     * 配置 Redis String 序列化器(纯字符串操作时使用)
     */
    @Bean
    public RedisSerializer<String> stringRedisSerializer() {
        return new StringRedisSerializer();
    }
}

2.2 RedisTemplate 核心操作方法

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

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * RedisTemplate 核心操作方法示例
 * 包含了常用的 Redis 操作
 */
@Component
public class RedisTemplateService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate; // Spring Boot 自动配置的
    
    // ============ Key 相关操作 ============
    
    /**
     * 判断 key 是否存在
     * @param key 键
     * @return true-存在, false-不存在
     */
    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * 删除 key
     * @param key 可以传一个或多个
     * @return 删除的 key 数量
     */
    public Long delete(String... keys) {
        if (keys == null || keys.length == 0) {
            return 0L;
        }
        if (keys.length == 1) {
            return redisTemplate.delete(keys[0]) ? 1L : 0L;
        } else {
            return redisTemplate.delete(Arrays.asList(keys));
        }
    }
    
    /**
     * 设置过期时间
     * @param key 键
     * @param time 时间
     * @param unit 时间单位
     * @return true-成功, false-失败
     */
    public Boolean expire(String key, long time, TimeUnit unit) {
        try {
            if (time > 0) {
                return redisTemplate.expire(key, time, unit);
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * 获取过期时间
     * @param key 键
     * @param unit 时间单位
     * @return 剩余过期时间
     */
    public Long getExpire(String key, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }
    
    /**
     * 获取 key 的类型
     * @param key 键
     * @return 数据类型
     */
    public DataType type(String key) {
        return redisTemplate.type(key);
    }
    
    // ============ String 类型操作 ============
    
    /**
     * 设置 String 类型的值
     * @param key 键
     * @param value 值
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    /**
     * 设置 String 类型的值并设置过期时间
     * @param key 键
     * @param value 值
     * @param time 过期时间
     * @param unit 时间单位
     */
    public void set(String key, Object value, long time, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, time, unit);
    }
    
    /**
     * 获取 String 类型的值
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    /**
     * 获取 String 类型的值(指定类型)
     * @param key 键
     * @param clazz 返回值类型
     * @return 转换后的值
     */
    public <T> T get(String key, Class<T> clazz) {
        Object value = redisTemplate.opsForValue().get(key);
        return clazz.cast(value);
    }
    
    /**
     * 递增(原子操作)
     * @param key 键
     * @param delta 增加的值
     * @return 递增后的值
     */
    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }
    
    /**
     * 递减(原子操作)
     * @param key 键
     * @param delta 减少的值
     * @return 递减后的值
     */
    public Long decrement(String key, long delta) {
        return redisTemplate.opsForValue().decrement(key, delta);
    }
    
    /**
     * 设置新值并返回旧值(原子操作)
     * @param key 键
     * @param value 新值
     * @return 旧值
     */
    public Object getAndSet(String key, Object value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }
    
    // ============ Hash 类型操作 ============
    
    /**
     * 设置 Hash 类型的值
     * @param key 键
     * @param hashKey Hash键
     * @param value 值
     */
    public void hSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }
    
    /**
     * 批量设置 Hash 值
     * @param key 键
     * @param map 多个Hash键值对
     */
    public void hSetAll(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }
    
    /**
     * 获取 Hash 值
     * @param key 键
     * @param hashKey Hash键
     * @return 值
     */
    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }
    
    /**
     * 获取所有 Hash 键值对
     * @param key 键
     * @return 所有键值对
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    /**
     * 删除 Hash 键值对
     * @param key 键
     * @param hashKeys 要删除的Hash键
     * @return 删除的数量
     */
    public Long hDelete(String key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }
    
    /**
     * 判断 Hash 键是否存在
     * @param key 键
     * @param hashKey Hash键
     * @return true-存在, false-不存在
     */
    public Boolean hHasKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }
    
    /**
     * 获取 Hash 中所有键
     * @param key 键
     * @return 所有Hash键
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }
    
    /**
     * 获取 Hash 中所有值
     * @param key 键
     * @return 所有Hash值
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }
    
    // ============ List 类型操作 ============
    
    /**
     * 从列表左侧插入值
     * @param key 键
     * @param value 值
     * @return 插入后列表的长度
     */
    public Long lLeftPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }
    
    /**
     * 从列表右侧插入值
     * @param key 键
     * @param value 值
     * @return 插入后列表的长度
     */
    public Long lRightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
    
    /**
     * 从列表左侧弹出值
     * @param key 键
     * @return 弹出的值
     */
    public Object lLeftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    
    /**
     * 从列表右侧弹出值
     * @param key 键
     * @return 弹出的值
     */
    public Object lRightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }
    
    /**
     * 获取列表指定范围的元素
     * @param key 键
     * @param start 开始索引(0表示第一个)
     * @param end 结束索引(-1表示最后一个)
     * @return 元素列表
     */
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
    
    /**
     * 获取列表长度
     * @param key 键
     * @return 列表长度
     */
    public Long lSize(String key) {
        return redisTemplate.opsForList().size(key);
    }
    
    // ============ Set 类型操作 ============
    
    /**
     * 向集合添加元素
     * @param key 键
     * @param values 一个或多个值
     * @return 成功添加的元素数量
     */
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }
    
    /**
     * 从集合移除元素
     * @param key 键
     * @param values 一个或多个值
     * @return 移除的元素数量
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }
    
    /**
     * 判断元素是否在集合中
     * @param key 键
     * @param value 值
     * @return true-存在, false-不存在
     */
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
    
    /**
     * 获取集合所有元素
     * @param key 键
     * @return 所有元素
     */
    public Set<Object> sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }
    
    /**
     * 获取集合大小
     * @param key 键
     * @return 集合大小
     */
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }
    
    // ============ ZSet 类型操作 ============
    
    /**
     * 向有序集合添加元素
     * @param key 键
     * @param value 值
     * @param score 分数
     * @return true-成功, false-失败
     */
    public Boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
    
    /**
     * 从有序集合移除元素
     * @param key 键
     * @param values 一个或多个值
     * @return 移除的元素数量
     */
    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }
    
    /**
     * 获取有序集合指定范围的元素(按分数从小到大)
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @return 元素集合
     */
    public Set<Object> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }
    
    /**
     * 获取有序集合指定范围的元素(按分数从大到小)
     * @param key 键
     * @param start 开始位置
     * @param end 结束位置
     * @return 元素集合
     */
    public Set<Object> zReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }
    
    /**
     * 获取元素的分数
     * @param key 键
     * @param value 值
     * @return 分数
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }
    
    // ============ 事务操作 ============
    
    /**
     * 执行事务操作
     * @param callback 事务回调
     * @return 执行结果
     */
    public Object executeTransaction(RedisCallback<Object> callback) {
        return redisTemplate.execute(callback);
    }
    
    /**
     * 执行 SessionCallback(支持事务中的多个操作)
     * @param callback 会话回调
     * @return 执行结果
     */
    public Object executeSession(SessionCallback<Object> callback) {
        return redisTemplate.execute(callback);
    }
    
    // ============ 管道操作 ============
    
    /**
     * 执行管道操作(批量执行命令,减少网络开销)
     * @param callback 管道回调
     * @return 执行结果列表
     */
    public List<Object> executePipelined(RedisCallback<?> callback) {
        return redisTemplate.executePipelined(callback);
    }
    
    // ============ 发布订阅 ============
    
    /**
     * 发布消息到指定频道
     * @param channel 频道名称
     * @param message 消息内容
     */
    public void publish(String channel, Object message) {
        redisTemplate.convertAndSend(channel, message);
    }
}

3. Redis Repository 使用(类似 JPA)

3.1 实体类定义

java 复制代码
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 用户实体类
 * @RedisHash 注解指定 Redis 中存储的 Hash 名称
 */
@RedisHash(value = "user", timeToLive = 3600) // 设置过期时间(秒)
public class User implements Serializable {
    
    @Id  // 标记为主键
    private String id;
    
    @Indexed  // 创建索引,可以按此字段查询
    private String username;
    
    private String email;
    
    private Integer age;
    
    @Indexed
    private LocalDateTime createTime;
    
    // 省略构造器、getter、setter、toString等方法
}

3.2 Repository 接口

java 复制代码
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;

/**
 * User Repository 接口
 * 继承 CrudRepository 获得基本的 CRUD 操作
 * 可以通过方法命名约定自动生成查询方法
 */
@Repository
public interface UserRepository extends CrudRepository<User, String> {
    
    /**
     * 根据用户名查找用户
     * Spring Data Redis 会自动实现这个方法
     */
    List<User> findByUsername(String username);
    
    /**
     * 根据邮箱查找用户
     */
    User findByEmail(String email);
    
    /**
     * 根据年龄范围查找用户
     */
    List<User> findByAgeBetween(Integer minAge, Integer maxAge);
    
    /**
     * 根据创建时间查找用户
     */
    List<User> findByCreateTimeAfter(LocalDateTime createTime);
    
    /**
     * 根据用户名模糊查询
     */
    List<User> findByUsernameContaining(String keyword);
    
    /**
     * 分页查询用户
     */
    Page<User> findAll(Pageable pageable);
    
    /**
     * 统计用户数量
     */
    Long countByAgeGreaterThan(Integer age);
    
    /**
     * 删除用户
     */
    void deleteByUsername(String username);
}

3.3 Repository 使用示例

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Repository 使用示例服务
 */
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 保存用户
     */
    public User saveUser(User user) {
        user.setCreateTime(LocalDateTime.now());
        return userRepository.save(user);
    }
    
    /**
     * 批量保存用户
     */
    public Iterable<User> saveAllUsers(List<User> users) {
        users.forEach(user -> user.setCreateTime(LocalDateTime.now()));
        return userRepository.saveAll(users);
    }
    
    /**
     * 根据ID查找用户
     */
    public Optional<User> findById(String id) {
        return userRepository.findById(id);
    }
    
    /**
     * 查找所有用户
     */
    public Iterable<User> findAllUsers() {
        return userRepository.findAll();
    }
    
    /**
     * 分页查询用户
     */
    public Page<User> findUsersByPage(int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size, Sort.by("createTime").descending());
        return userRepository.findAll(pageRequest);
    }
    
    /**
     * 根据用户名查询
     */
    public List<User> findUsersByUsername(String username) {
        return userRepository.findByUsername(username);
    }
    
    /**
     * 根据年龄范围查询
     */
    public List<User> findUsersByAgeRange(Integer minAge, Integer maxAge) {
        return userRepository.findByAgeBetween(minAge, maxAge);
    }
    
    /**
     * 统计用户数量
     */
    public long countUsers() {
        return userRepository.count();
    }
    
    /**
     * 删除用户
     */
    public void deleteUser(String id) {
        userRepository.deleteById(id);
    }
    
    /**
     * 删除所有用户
     */
    public void deleteAllUsers() {
        userRepository.deleteAll();
    }
}

4. 高级特性:Redis 缓存

4.1 启用缓存配置

java 复制代码
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
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.serializer.RedisSerializationContext;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * Redis 缓存配置
 */
@Configuration
@EnableCaching  // 启用缓存注解
public class RedisCacheConfig extends CachingConfigurerSupport {
    
    /**
     * 配置缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 默认缓存配置
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))  // 默认过期时间1小时
                .disableCachingNullValues()      // 不缓存null值
                .serializeValuesWith(
                    RedisSerializationContext.SerializationPair.fromSerializer(
                        new Jackson2JsonRedisSerializer<>(Object.class)
                    )
                );
        
        // 为特定缓存名称设置不同的配置
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        
        // user 缓存:2小时过期
        cacheConfigurations.put("user", defaultCacheConfig.entryTtl(Duration.ofHours(2)));
        
        // product 缓存:30分钟过期
        cacheConfigurations.put("product", defaultCacheConfig.entryTtl(Duration.ofMinutes(30)));
        
        // order 缓存:1天过期
        cacheConfigurations.put("order", defaultCacheConfig.entryTtl(Duration.ofDays(1)));
        
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig)
                .withInitialCacheConfigurations(cacheConfigurations)
                .transactionAware()  // 支持事务
                .build();
    }
    
    /**
     * 自定义缓存key生成器
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(".");
                sb.append(method.getName());
                if (params.length > 0) {
                    sb.append("[");
                    for (Object param : params) {
                        sb.append(param.toString());
                        sb.append(",");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                    sb.append("]");
                }
                return sb.toString();
            }
        };
    }
}

4.2 使用缓存注解

java 复制代码
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 缓存注解使用示例
 */
@Service
public class CacheService {
    
    /**
     * @Cacheable - 缓存查询结果
     * key: 缓存键,支持SpEL表达式
     * value/cacheNames: 缓存名称
     * unless: 条件表达式,为true时不缓存
     */
    @Cacheable(value = "user", key = "#id", unless = "#result == null")
    public User getUserById(String id) {
        // 模拟数据库查询
        System.out.println("查询数据库获取用户: " + id);
        return new User(id, "用户" + id);
    }
    
    /**
     * @CachePut - 更新缓存
     * 总是执行方法,并用结果更新缓存
     */
    @CachePut(value = "user", key = "#user.id")
    public User updateUser(User user) {
        System.out.println("更新用户信息: " + user.getId());
        // 更新数据库操作
        return user;
    }
    
    /**
     * @CacheEvict - 删除缓存
     * allEntries: 是否删除缓存中所有数据
     * beforeInvocation: 是否在方法执行前删除缓存
     */
    @CacheEvict(value = "user", key = "#id")
    public void deleteUser(String id) {
        System.out.println("删除用户: " + id);
        // 删除数据库操作
    }
    
    /**
     * @CacheEvict - 清空整个缓存
     */
    @CacheEvict(value = "user", allEntries = true)
    public void clearAllUserCache() {
        System.out.println("清空用户缓存");
    }
    
    /**
     * @Caching - 组合多个缓存操作
     */
    @Caching(
        cacheable = {
            @Cacheable(value = "user", key = "#username")
        },
        put = {
            @CachePut(value = "user", key = "#result.id")
        }
    )
    public User getUserByUsername(String username) {
        System.out.println("根据用户名查询用户: " + username);
        // 查询数据库
        return new User("123", username);
    }
    
    /**
     * @CacheConfig - 类级别的缓存配置
     */
    @Service
    @CacheConfig(cacheNames = "product")  // 指定默认缓存名称
    public class ProductService {
        
        @Cacheable(key = "#id")
        public Product getProduct(String id) {
            return new Product(id, "商品" + id);
        }
        
        @CacheEvict(key = "#id")
        public void deleteProduct(String id) {
            // 删除操作
        }
    }
}

5. 实际应用场景示例

5.1 分布式锁实现

java 复制代码
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 基于 Redis 的分布式锁
 */
@Component
public class DistributedLock {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 锁前缀
    private static final String LOCK_PREFIX = "lock:";
    // 锁的默认过期时间(秒)
    private static final long DEFAULT_EXPIRE_TIME = 30;
    // 获取锁的等待时间(毫秒)
    private static final long DEFAULT_WAIT_TIME = 3000;
    
    // Lua脚本:释放锁(确保原子性)
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";
    
    /**
     * 尝试获取锁
     * @param lockKey 锁的key
     * @param expireTime 过期时间(秒)
     * @return 锁的value(用于释放锁)
     */
    public String tryLock(String lockKey, long expireTime) {
        String lockValue = UUID.randomUUID().toString();
        String fullKey = LOCK_PREFIX + lockKey;
        
        Boolean success = redisTemplate.opsForValue().setIfAbsent(
            fullKey, 
            lockValue, 
            expireTime, 
            TimeUnit.SECONDS
        );
        
        return Boolean.TRUE.equals(success) ? lockValue : null;
    }
    
    /**
     * 获取锁(阻塞等待)
     */
    public String lock(String lockKey) throws InterruptedException {
        return lock(lockKey, DEFAULT_EXPIRE_TIME, DEFAULT_WAIT_TIME);
    }
    
    /**
     * 获取锁(可设置等待时间)
     */
    public String lock(String lockKey, long expireTime, long waitTime) throws InterruptedException {
        String lockValue;
        long startTime = System.currentTimeMillis();
        
        while ((lockValue = tryLock(lockKey, expireTime)) == null) {
            // 检查是否超时
            if (System.currentTimeMillis() - startTime > waitTime) {
                throw new RuntimeException("获取锁超时");
            }
            // 等待一段时间再重试
            Thread.sleep(100);
        }
        
        return lockValue;
    }
    
    /**
     * 释放锁
     */
    public boolean unlock(String lockKey, String lockValue) {
        String fullKey = LOCK_PREFIX + lockKey;
        
        // 使用Lua脚本确保原子性
        RedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
        Long result = redisTemplate.execute(
            script, 
            Collections.singletonList(fullKey), 
            lockValue
        );
        
        return result != null && result > 0;
    }
    
    /**
     * 使用锁执行任务(模板方法)
     */
    public <T> T executeWithLock(String lockKey, long expireTime, LockTask<T> task) {
        String lockValue = null;
        try {
            // 获取锁
            lockValue = lock(lockKey, expireTime, 5000);
            // 执行任务
            return task.execute();
        } catch (Exception e) {
            throw new RuntimeException("执行任务失败", e);
        } finally {
            // 释放锁
            if (lockValue != null) {
                unlock(lockKey, lockValue);
            }
        }
    }
    
    /**
     * 锁任务接口
     */
    public interface LockTask<T> {
        T execute();
    }
}

5.2 限流器实现

java 复制代码
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 基于 Redis 的限流器(令牌桶算法)
 */
@Component
public class RateLimiter {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // Lua脚本:令牌桶算法
    private static final String RATE_LIMIT_SCRIPT = 
        "local key = KEYS[1] " +
        "local limit = tonumber(ARGV[1]) " +
        "local interval = tonumber(ARGV[2]) " +
        "local current = tonumber(redis.call('get', key) or '0') " +
        "if current + 1 > limit then " +
        "    return 0 " +
        "else " +
        "    redis.call('incrby', key, 1) " +
        "    if current == 0 then " +
        "        redis.call('expire', key, interval) " +
        "    end " +
        "    return 1 " +
        "end";
    
    /**
     * 尝试获取令牌
     * @param key 限流key(如:user:1001)
     * @param limit 限制数量(如:每分钟100次)
     * @param interval 时间间隔(秒)
     * @return true-获取成功,false-被限流
     */
    public boolean tryAcquire(String key, int limit, int interval) {
        List<String> keys = Arrays.asList(key);
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(RATE_LIMIT_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(
            script, 
            keys, 
            limit, 
            interval
        );
        
        return result != null && result == 1;
    }
    
    /**
     * 滑动窗口限流
     */
    public boolean slidingWindowLimit(String key, int limit, int windowSeconds) {
        long currentTime = System.currentTimeMillis();
        long windowStartTime = currentTime - windowSeconds * 1000L;
        
        // 使用ZSet存储请求时间戳
        redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStartTime);
        
        // 获取窗口内的请求数量
        Long count = redisTemplate.opsForZSet().zCard(key);
        
        if (count != null && count >= limit) {
            return false;
        }
        
        // 添加当前请求
        redisTemplate.opsForZSet().add(key, String.valueOf(currentTime), currentTime);
        redisTemplate.expire(key, windowSeconds, TimeUnit.SECONDS);
        
        return true;
    }
}

6. 性能优化建议

  1. 序列化选择

    • 优先使用 Jackson2JsonRedisSerializer(可读性好)
    • 高并发场景考虑使用 Kryo(性能好)
  2. 连接池配置

    yaml 复制代码
    spring:
      redis:
        lettuce:
          pool:
            max-active: 200      # 根据并发量调整
            max-idle: 50
            min-idle: 20
            max-wait: 1000ms
  3. 批量操作

    • 使用 pipeline 减少网络开销
    • 批量操作使用 mget、mset
  4. 监控和调试

    java 复制代码
    // 开启 Redis 命令监控
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        LettuceConnectionFactory factory = new LettuceConnectionFactory();
        factory.setShareNativeConnection(false); // 关闭连接共享,便于监控
        return factory;
    }

Spring Data Redis 提供了丰富的功能和良好的抽象,大大简化了 Redis 操作。掌握这些核心方法和最佳实践,可以帮助你在项目中高效、安全地使用 Redis。

相关推荐
invicinble2 小时前
java集合类(二)--map
java·开发语言·python
Mr-Wanter2 小时前
搭建局域网时间同步服务器
java·运维·服务器
代码笔耕2 小时前
我们这样设计消息中心,解决了业务反复折腾的顽疾
java·后端·架构
间彧2 小时前
Redis 7.0的CRDT与Raft协议:脑裂风险的终结者与分布式架构的突破
redis
没有bug.的程序员2 小时前
负载均衡的真正含义:从算法到架构的深度解析
java·jvm·算法·微服务·架构·负载均衡
yc_xym2 小时前
[项目实践]言聚论坛(后端)
java·项目开发
多仔ヾ2 小时前
Solon + EasyQuery + ElementPlus 实现后台管理系统之 08-权限认证优化
java
LambdaCat2 小时前
如何用 Prompt 让 AI 主动发现设计问题
java·ai·ai编程
changlianzhifu12 小时前
分账系统:从“资金管道“到“增长引擎“,重塑商业价值分配新范式
java·服务器·前端