1. Redis环境搭建、SpringBoot整合Redis

一、Redis环境搭建

Windows环境搭建
方案一:使用官方Windows版本(推荐)
# 1. 下载Redis for Windows
# 从 https://github.com/microsoftarchive/redis/releases 下载
# 选择 Redis-x64-3.2.100.zip

# 2. 解压到指定目录,如 D:\Redis

# 3. 启动Redis服务
# 打开cmd,进入Redis目录
redis-server.exe redis.windows.conf

# 4. 测试连接(另开一个cmd)
redis-cli.exe
ping  # 返回 PONG 表示成功
方案二:使用WSL2(Windows Subsystem for Linux)
# 1. 启用WSL2
wsl --install -d Ubuntu

# 2. 在Ubuntu中安装Redis
sudo apt update
sudo apt install redis-server

# 3. 启动Redis
sudo service redis-server start

# 4. 测试
redis-cli ping
Linux环境搭建
# Ubuntu/Debian
sudo apt update
sudo apt install redis-server

# CentOS/RHEL
sudo yum install epel-release
sudo yum install redis

# 启动Redis
sudo systemctl start redis
sudo systemctl enable redis

# 查看状态
sudo systemctl status redis

# 修改配置文件
sudo vi /etc/redis/redis.conf
# 修改:bind 0.0.0.0(允许远程连接)
# 修改:requirepass yourpassword(设置密码)

# 重启服务
sudo systemctl restart redis
Docker方式(推荐)
# 拉取Redis镜像
docker pull redis:latest

# 运行Redis容器
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v /myredis/conf/redis.conf:/usr/local/etc/redis/redis.conf \
  -v /myredis/data:/data \
  redis redis-server /usr/local/etc/redis/redis.conf \
  --appendonly yes \
  --requirepass "yourpassword"

# 进入容器
docker exec -it redis redis-cli

二、Spring Boot整合Redis

添加依赖
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot Starter Data Redis -->
    <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>
    
    <!-- 如果需要JSON序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>
配置Redis
# application.yml
spring:
  redis:
    # Redis服务器地址
    host: localhost
    # Redis服务器端口
    port: 6379
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器连接密码(默认为空)
    password: yourpassword
    # 连接超时时间
    timeout: 10s
    # 连接池配置
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池中的最小空闲连接
        min-idle: 0
Redis配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
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.StringRedisSerializer;

@Configuration
public class RedisConfig {

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

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        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);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}
工具类封装
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    
    /**
     * 指定缓存失效时间
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================
    
    /**
     * 普通缓存获取
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // ================================Map=================================
    
    /**
     * HashGet
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // ===============================list=================================
    
    /**
     * 获取list缓存的内容
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // ===============================set=================================
    
    /**
     * 根据key获取Set中的所有值
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将数据放入set缓存
     */
    public boolean sSet(String key, Object... values) {
        try {
            redisTemplate.opsForSet().add(key, values);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/redis")
public class RedisController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RedisUtil redisUtil;

    // 设置缓存
    @PostMapping("/set")
    public String set(@RequestParam String key, @RequestParam String value) {
        redisTemplate.opsForValue().set(key, value);
        return "OK";
    }

    // 获取缓存
    @GetMapping("/get/{key}")
    public Object get(@PathVariable String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 设置带过期时间的缓存
    @PostMapping("/setWithExpire")
    public String setWithExpire(@RequestParam String key, 
                               @RequestParam String value,
                               @RequestParam long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
        return "OK";
    }

    // 使用Hash结构
    @PostMapping("/hash")
    public String hash() {
        String key = "user:1001";
        redisTemplate.opsForHash().put(key, "name", "张三");
        redisTemplate.opsForHash().put(key, "age", "25");
        redisTemplate.opsForHash().put(key, "email", "zhangsan@example.com");
        return "OK";
    }

    // 使用List结构
    @PostMapping("/list")
    public String list() {
        String key = "messages";
        redisTemplate.opsForList().rightPush(key, "message1");
        redisTemplate.opsForList().rightPush(key, "message2");
        redisTemplate.opsForList().rightPush(key, "message3");
        return "OK";
    }

    // 使用Set结构
    @PostMapping("/set")
    public String set() {
        String key = "tags";
        redisTemplate.opsForSet().add(key, "java", "spring", "redis");
        return "OK";
    }
}
使用Redis作为缓存
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    // 缓存结果,key为方法参数
    @Cacheable(value = "userCache", key = "#id")
    public User getUserById(Long id) {
        // 模拟数据库查询
        System.out.println("查询数据库,id: " + id);
        return new User(id, "用户" + id);
    }

    // 更新缓存
    @CachePut(value = "userCache", key = "#user.id")
    public User updateUser(User user) {
        // 更新数据库
        return user;
    }

    // 删除缓存
    @CacheEvict(value = "userCache", key = "#id")
    public void deleteUser(Long id) {
        // 删除数据库记录
    }

    // 清空所有缓存
    @CacheEvict(value = "userCache", allEntries = true)
    public void clearCache() {
        System.out.println("清空缓存");
    }
}

// 启用缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
}
分布式锁实现
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

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

@Component
public class RedisDistributedLock {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    private static final long DEFAULT_EXPIRE_TIME = 30; // 秒
    private static final long DEFAULT_WAIT_TIME = 10; // 秒
    
    /**
     * 获取分布式锁
     */
    public String lock(String lockKey) {
        return lock(lockKey, DEFAULT_WAIT_TIME, DEFAULT_EXPIRE_TIME);
    }
    
    public String lock(String lockKey, long waitTime, long expireTime) {
        String lockValue = UUID.randomUUID().toString();
        String fullLockKey = LOCK_PREFIX + lockKey;
        
        long end = System.currentTimeMillis() + waitTime * 1000;
        
        while (System.currentTimeMillis() < end) {
            Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(fullLockKey, lockValue, expireTime, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(success)) {
                return lockValue;
            }
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        
        return null;
    }
    
    /**
     * 释放分布式锁(使用Lua脚本保证原子性)
     */
    public boolean unlock(String lockKey, String lockValue) {
        String fullLockKey = LOCK_PREFIX + lockKey;
        
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) " +
                       "else " +
                       "return 0 " +
                       "end";
        
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);
        
        Long result = redisTemplate.execute(redisScript, 
                                          Collections.singletonList(fullLockKey), 
                                          lockValue);
        
        return result != null && result > 0;
    }
}

三、最佳实践建议

配置建议
spring:
  cache:
    type: redis
    redis:
      time-to-live: 60000  # 缓存过期时间(毫秒)
      cache-null-values: false  # 是否缓存空值
      key-prefix: "cache:"  # key前缀
      use-key-prefix: true
安全建议
  • 设置Redis密码
  • 绑定特定IP(生产环境)
  • 禁用危险命令
  • 启用SSL(如果需要)
  • 定期备份数据
性能优化
  • 使用连接池
  • 合理设置过期时间
  • 使用Pipeline批量操作
  • 避免大Key
  • 使用合适的数据结构
监控和运维
# 查看Redis信息
redis-cli info

# 监控命令
redis-cli monitor

# 查看内存使用
redis-cli info memory

# 查看客户端连接
redis-cli client list

四、常见问题解决

连接超时
  • 检查防火墙设置
  • 确认Redis服务是否运行
  • 检查网络配置
序列化问题
  • 确保实体类实现Serializable接口
  • 使用Jackson2JsonRedisSerializer
内存不足
  • 设置maxmemory策略
  • 使用LRU淘汰策略
  • 定期清理无用数据

这样你就有了完整的Redis环境搭建和Spring Boot整合方案,可以根据实际需求进行调整和扩展。

2. RedisTemplate配置、序列化方式优化

一、RedisTemplate基础配置

基础配置类
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 设置序列化方式
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        
        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
            om.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        jacksonSerializer.setObjectMapper(om);
        
        // String序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        
        // key采用String序列化
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        
        // value采用Jackson序列化
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

二、序列化方式详解

常见序列化方式对比

序列化方式

优点

缺点

适用场景

Jackson2JsonRedisSerializer

可读性好,支持复杂对象

性能稍差,存储空间较大

复杂对象存储,需要可读性

GenericJackson2JsonRedisSerializer

自动类型信息,反序列化方便

存储空间更大

需要类型安全的场景

JdkSerializationRedisSerializer

Java原生,支持所有对象

不可读,兼容性差

简单场景,不关心可读性

StringRedisSerializer

性能最好,存储最小

只支持字符串

Key的序列化,简单值存储

OxmSerializer

XML格式,跨语言

性能差,存储大

XML交互场景

优化配置方案
方案一:混合序列化(推荐)
@Configuration
public class OptimizedRedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 配置序列化
        configureSerialization(template);
        
        // 配置连接池
        template.setEnableTransactionSupport(false);
        template.setExposeConnection(true);
        
        return template;
    }
    
    private void configureSerialization(RedisTemplate<String, Object> template) {
        // Key序列化:String
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        
        // Value序列化:Jackson(带类型信息)
        GenericJackson2JsonRedisSerializer valueSerializer = 
            new GenericJackson2JsonRedisSerializer(createObjectMapper());
        
        template.setValueSerializer(valueSerializer);
        template.setHashValueSerializer(valueSerializer);
        
        // 设置默认序列化器
        template.setDefaultSerializer(valueSerializer);
    }
    
    private ObjectMapper createObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.registerModule(new JavaTimeModule());
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        
        // 启用压缩(可选)
        mapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
        
        return mapper;
    }
}
方案二:自定义序列化(高性能)
@Component
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    
    private final Class<T> clazz;
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    
    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }
    
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
    
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    }
}

// 配置使用
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    
    // 使用FastJson序列化
    FastJsonRedisSerializer<Object> fastJsonSerializer = 
        new FastJsonRedisSerializer<>(Object.class);
    
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(fastJsonSerializer);
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(fastJsonSerializer);
    
    return template;
}

三、高级优化配置

连接池配置
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 20      # 最大连接数
        max-idle: 10        # 最大空闲连接
        min-idle: 5         # 最小空闲连接
        max-wait: 2000ms    # 获取连接最大等待时间
    # 或者使用jedis
    jedis:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
自定义RedisTemplate工厂
@Configuration
public class AdvancedRedisConfig {
    
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName("localhost");
        config.setPort(6379);
        config.setDatabase(0);
        
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
            .commandTimeout(Duration.ofSeconds(2))
            .shutdownTimeout(Duration.ZERO)
            .clientResources(DefaultClientResources.create())
            .build();
        
        return new LettuceConnectionFactory(config, clientConfig);
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        
        // 配置序列化
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        RedisSerializer<Object> jsonSerializer = createJsonSerializer();
        
        template.setKeySerializer(stringSerializer);
        template.setValueSerializer(jsonSerializer);
        template.setHashKeySerializer(stringSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        // 启用事务支持
        template.setEnableTransactionSupport(true);
        
        // 设置是否暴露连接
        template.setExposeConnection(true);
        
        return template;
    }
    
    private RedisSerializer<Object> createJsonSerializer() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
        
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}

四、场景化配置

缓存场景配置
@Configuration
@EnableCaching
public class CacheRedisConfig extends CachingConfigurerSupport {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认30分钟过期
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(createJsonSerializer()))
            .disableCachingNullValues();  // 不缓存null值
        
        // 针对不同缓存设置不同的过期时间
        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
        cacheConfigs.put("userCache", config.entryTtl(Duration.ofHours(1)));
        cacheConfigs.put("productCache", config.entryTtl(Duration.ofMinutes(10)));
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withInitialCacheConfigurations(cacheConfigs)
            .transactionAware()
            .build();
    }
}
分布式锁配置
@Component
public class RedisLockService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
    
    public boolean tryLock(String key, String value, long expireSeconds) {
        Boolean result = redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            // 使用String序列化确保原子性
            byte[] keyBytes = STRING_SERIALIZER.serialize(key);
            byte[] valueBytes = STRING_SERIALIZER.serialize(value);
            
            return connection.set(
                keyBytes,
                valueBytes,
                Expiration.seconds(expireSeconds),
                RedisStringCommands.SetOption.SET_IF_ABSENT
            );
        });
        return Boolean.TRUE.equals(result);
    }
}

五、性能优化建议

序列化优化
  1. Key使用String序列化 :确保Key的可读性和性能
  2. Value根据场景选择 :
  1. 简单对象:StringRedisSerializer
  2. 复杂对象:GenericJackson2JsonRedisSerializer
  3. 高性能要求:自定义序列化(如FastJson)
配置优化
// 1. 禁用事务支持(除非需要)
template.setEnableTransactionSupport(false);

// 2. 启用连接暴露
template.setExposeConnection(true);

// 3. 配置连接池参数
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
    LettucePoolingClientConfiguration poolConfig = 
        LettucePoolingClientConfiguration.builder()
            .poolConfig(new GenericObjectPoolConfig<>())
            .build();
    
    return new LettuceConnectionFactory(
        new RedisStandaloneConfiguration("localhost", 6379),
        poolConfig
    );
}
监控配置
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    
    // 添加监控拦截器
    template.setClientStatistics(true);
    
    // 配置序列化
    // ...
    
    return template;
}

六、完整最佳实践配置

@Configuration
@EnableCaching
public class BestPracticeRedisConfig {
    
    /**
     * 主RedisTemplate:用于常规缓存
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // Key序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);
        
        // Value序列化(带类型信息)
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(),
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        
        GenericJackson2JsonRedisSerializer jsonSerializer = 
            new GenericJackson2JsonRedisSerializer(mapper);
        
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        
        template.setEnableTransactionSupport(false);
        template.afterPropertiesSet();
        
        return template;
    }
    
    /**
     * 字符串专用RedisTemplate:性能最优
     */
    @Bean(name = "stringRedisTemplate")
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        template.setEnableTransactionSupport(false);
        return template;
    }
    
    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}

总结

  1. Key一定要用String序列化 :保证可读性和性能
  2. Value根据场景选择 :
  1. 简单值:StringRedisSerializer
  2. 复杂对象:GenericJackson2JsonRedisSerializer
  3. 高性能:自定义序列化
  1. 配置连接池 :根据业务量调整连接数
  2. 禁用事务 :除非确实需要
  3. 分场景配置 :不同业务使用不同的RedisTemplate实例

这样的配置既保证了性能,又提供了良好的可读性和扩展性。

3. 五种数据类型实战使用

准备工作

添加依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>

<!-- 或者使用 Lettuce -->
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.2.4.RELEASE</version>
</dependency>
配置 Redis 连接
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisUtil {
    private static JedisPool jedisPool;
    
    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(20);
        config.setMaxIdle(10);
        config.setMinIdle(5);
        
        jedisPool = new JedisPool(config, "localhost", 6379);
    }
    
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}

String(字符串)实战

2.1 基本操作
public class StringDemo {
    
    public void stringOperations() {
        try (Jedis jedis = RedisUtil.getJedis()) {
            // 设置值
            jedis.set("user:1001:name", "张三");
            jedis.set("user:1001:age", "25");
            
            // 获取值
            String name = jedis.get("user:1001:name");
            System.out.println("用户名: " + name);
            
            // 设置过期时间(30秒)
            jedis.setex("session:token123", 30, "user_data");
            
            // 自增操作 - 计数器
            jedis.set("article:1001:views", "0");
            Long views = jedis.incr("article:1001:views");
            System.out.println("浏览量: " + views);
            
            // 批量操作
            jedis.mset("product:1001:name", "iPhone",
                      "product:1001:price", "6999",
                      "product:1001:stock", "100");
            
            List<String> values = jedis.mget("product:1001:name", 
                                            "product:1001:price");
        }
    }
    
    // 分布式锁实现
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            // SET key value NX EX time - 原子操作
            String result = jedis.set(lockKey, requestId, 
                                     SetParams.setParams().nx().ex(expireTime));
            return "OK".equals(result);
        }
    }
    
    public void releaseLock(String lockKey, String requestId) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            // 使用Lua脚本确保原子性
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                           "then return redis.call('del', KEYS[1]) " +
                           "else return 0 end";
            jedis.eval(script, Collections.singletonList(lockKey), 
                      Collections.singletonList(requestId));
        }
    }
}

Hash(哈希)实战

3.1 用户信息存储
public class HashDemo {
    
    public void userProfileDemo() {
        try (Jedis jedis = RedisUtil.getJedis()) {
            // 存储用户信息
            Map<String, String> userMap = new HashMap<>();
            userMap.put("username", "zhangsan");
            userMap.put("email", "zhangsan@example.com");
            userMap.put("phone", "13800138000");
            userMap.put("age", "28");
            
            jedis.hset("user:1001", userMap);
            
            // 获取单个字段
            String email = jedis.hget("user:1001", "email");
            
            // 获取所有字段
            Map<String, String> userInfo = jedis.hgetAll("user:1001");
            
            // 更新部分字段
            jedis.hset("user:1001", "age", "29");
            
            // 增加数值
            jedis.hincrBy("user:1001", "login_count", 1);
            
            // 判断字段是否存在
            boolean hasPhone = jedis.hexists("user:1001", "phone");
            
            // 获取所有字段名
            Set<String> fields = jedis.hkeys("user:1001");
            
            // 获取所有值
            List<String> values = jedis.hvals("user:1001");
        }
    }
    
    // 购物车实现
    public class ShoppingCartService {
        private static final String CART_KEY_PREFIX = "cart:user:";
        
        public void addToCart(String userId, String productId, int quantity) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String cartKey = CART_KEY_PREFIX + userId;
                jedis.hincrBy(cartKey, productId, quantity);
            }
        }
        
        public void removeFromCart(String userId, String productId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String cartKey = CART_KEY_PREFIX + userId;
                jedis.hdel(cartKey, productId);
            }
        }
        
        public Map<String, String> getCart(String userId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String cartKey = CART_KEY_PREFIX + userId;
                return jedis.hgetAll(cartKey);
            }
        }
    }
}

List(列表)实战

4.1 消息队列和最新列表
public class ListDemo {
    
    // 消息队列实现
    public class SimpleMessageQueue {
        private static final String QUEUE_KEY = "queue:messages";
        
        public void produce(String message) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                // 从左边插入
                jedis.lpush(QUEUE_KEY, message);
                
                // 限制队列长度,防止内存溢出
                jedis.ltrim(QUEUE_KEY, 0, 9999);
            }
        }
        
        public String consume() {
            try (Jedis jedis = RedisUtil.getJedis()) {
                // 从右边弹出 - 阻塞版本
                List<String> messages = jedis.brpop(30, QUEUE_KEY);
                return messages != null ? messages.get(1) : null;
            }
        }
    }
    
    // 最新文章列表
    public void latestArticles() {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "articles:latest";
            
            // 添加新文章
            jedis.lpush(key, "article:1001", "article:1002", "article:1003");
            
            // 获取最新的10篇文章
            List<String> latest = jedis.lrange(key, 0, 9);
            
            // 维护列表长度(只保留最新的100条)
            jedis.ltrim(key, 0, 99);
            
            // 分页获取
            int page = 1;
            int pageSize = 10;
            int start = (page - 1) * pageSize;
            int end = start + pageSize - 1;
            List<String> pageData = jedis.lrange(key, start, end);
        }
    }
    
    // 用户操作日志
    public void userActivityLog(String userId, String action) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String logKey = "user:" + userId + ":activities";
            String logEntry = System.currentTimeMillis() + ":" + action;
            
            jedis.lpush(logKey, logEntry);
            jedis.ltrim(logKey, 0, 99); // 只保留最近的100条
        }
    }
}

Set(集合)实战

5.1 标签系统和共同好友
public class SetDemo {
    
    // 标签系统
    public void tagSystem() {
        try (Jedis jedis = RedisUtil.getJedis()) {
            // 给文章添加标签
            String articleKey = "article:1001:tags";
            jedis.sadd(articleKey, "java", "redis", "database", "缓存");
            
            // 给用户添加兴趣标签
            String userKey = "user:1001:interests";
            jedis.sadd(userKey, "java", "python", "AI", "大数据");
            
            // 查找共同标签(推荐系统基础)
            Set<String> commonTags = jedis.sinter(articleKey, userKey);
            
            // 获取所有标签
            Set<String> allTags = jedis.smembers(articleKey);
            
            // 判断是否包含某个标签
            boolean hasJava = jedis.sismember(articleKey, "java");
            
            // 随机获取标签
            String randomTag = jedis.srandmember(articleKey);
            
            // 移除标签
            jedis.srem(articleKey, "database");
        }
    }
    
    // 共同好友实现
    public class SocialNetworkService {
        
        public void addFriend(String userId, String friendId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String userFriendsKey = "user:" + userId + ":friends";
                String friendFriendsKey = "user:" + friendId + ":friends";
                
                // 双向添加
                jedis.sadd(userFriendsKey, friendId);
                jedis.sadd(friendFriendsKey, userId);
            }
        }
        
        public Set<String> getCommonFriends(String userId1, String userId2) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String key1 = "user:" + userId1 + ":friends";
                String key2 = "user:" + userId2 + ":friends";
                
                // 求交集
                return jedis.sinter(key1, key2);
            }
        }
        
        public Set<String> getAllFriends(String userId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String key = "user:" + userId + ":friends";
                return jedis.smembers(key);
            }
        }
    }
    
    // 抽奖活动
    public void lotterySystem() {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String lotteryKey = "lottery:2024:participants";
            
            // 用户参与抽奖
            jedis.sadd(lotteryKey, "user:1001", "user:1002", "user:1003");
            
            // 抽奖 - 随机抽取3个中奖者
            Set<String> winners = jedis.srandmember(lotteryKey, 3);
            
            // 或者抽取并移除(不重复中奖)
            String winner = jedis.spop(lotteryKey);
        }
    }
}

ZSet(有序集合)实战

6.1 排行榜和延迟队列
public class ZSetDemo {
    
    // 游戏排行榜
    public class GameRanking {
        
        public void updateScore(String playerId, double score) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String rankingKey = "game:ranking";
                // 更新分数,如果不存在则添加
                jedis.zadd(rankingKey, score, playerId);
            }
        }
        
        public List<String> getTopPlayers(int topN) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String rankingKey = "game:ranking";
                // 获取前N名(降序)
                Set<String> topPlayers = jedis.zrevrange(rankingKey, 0, topN - 1);
                return new ArrayList<>(topPlayers);
            }
        }
        
        public Long getRank(String playerId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String rankingKey = "game:ranking";
                // 获取排名(降序排名,从0开始)
                return jedis.zrevrank(rankingKey, playerId);
            }
        }
        
        public Double getScore(String playerId) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String rankingKey = "game:ranking";
                return jedis.zscore(rankingKey, playerId);
            }
        }
        
        public List<String> getPlayersByRange(int startRank, int endRank) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                String rankingKey = "game:ranking";
                return new ArrayList<>(jedis.zrevrange(rankingKey, startRank, endRank));
            }
        }
    }
    
    // 热搜榜实现
    public class HotSearchService {
        private static final String HOT_SEARCH_KEY = "hot:search";
        
        public void search(String keyword) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                // 每次搜索增加1分
                jedis.zincrby(HOT_SEARCH_KEY, 1, keyword);
                
                // 定期清理过期的热搜(比如只保留7天)
                // 可以使用时间戳作为score的一部分
            }
        }
        
        public List<String> getTopKeywords(int limit) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                Set<String> keywords = jedis.zrevrange(HOT_SEARCH_KEY, 0, limit - 1);
                return new ArrayList<>(keywords);
            }
        }
    }
    
    // 延迟队列实现
    public class DelayQueue {
        private static final String DELAY_QUEUE_KEY = "delay:queue";
        
        public void addTask(String taskId, long delaySeconds) {
            try (Jedis jedis = RedisUtil.getJedis()) {
                long executeTime = System.currentTimeMillis() / 1000 + delaySeconds;
                jedis.zadd(DELAY_QUEUE_KEY, executeTime, taskId);
            }
        }
        
        public List<String> getReadyTasks() {
            try (Jedis jedis = RedisUtil.getJedis()) {
                long currentTime = System.currentTimeMillis() / 1000;
                
                // 获取所有到期的任务
                Set<String> tasks = jedis.zrangeByScore(DELAY_QUEUE_KEY, 0, currentTime);
                
                // 移除这些任务
                if (!tasks.isEmpty()) {
                    jedis.zremrangeByScore(DELAY_QUEUE_KEY, 0, currentTime);
                }
                
                return new ArrayList<>(tasks);
            }
        }
    }
}

综合实战案例

7.1 电商系统缓存设计
public class ECommerceCacheService {
    
    // 商品详情缓存
    public void cacheProductInfo(String productId, Map<String, String> productInfo) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "product:" + productId + ":info";
            jedis.hset(key, productInfo);
            jedis.expire(key, 3600); // 1小时过期
        }
    }
    
    // 商品库存缓存(使用String)
    public void cacheProductStock(String productId, int stock) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "product:" + productId + ":stock";
            jedis.setex(key, 300, String.valueOf(stock)); // 5分钟过期
        }
    }
    
    // 用户浏览历史(使用List)
    public void addToBrowseHistory(String userId, String productId) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "user:" + userId + ":browse_history";
            jedis.lpush(key, productId);
            jedis.ltrim(key, 0, 49); // 只保留最近50条
        }
    }
    
    // 商品分类(使用Set)
    public void addProductToCategory(String categoryId, String productId) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "category:" + categoryId + ":products";
            jedis.sadd(key, productId);
        }
    }
    
    // 商品销量排行榜(使用ZSet)
    public void updateProductSales(String productId, int sales) {
        try (Jedis jedis = RedisUtil.getJedis()) {
            String key = "product:sales:ranking";
            jedis.zadd(key, sales, productId);
        }
    }
}
7.2 使用 Spring Boot + RedisTemplate
@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        return template;
    }
}

@Service
public class RedisService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // String 操作
    public void setString(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    
    public Object getString(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    
    // Hash 操作
    public void setHash(String key, Map<String, Object> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }
    
    public Map<Object, Object> getHash(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    
    // List 操作
    public void pushToList(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }
    
    public List<Object> getList(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

4. SpringCache注解缓存(@Cacheable、@CachePut、@CacheEvict)

一、@Cacheable - 缓存查询

基本用法
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
    return userRepository.findById(id);
}
参数详解
  • value/cacheNames : 缓存名称(必填),可指定多个
  • key : 缓存键,支持 SpEL 表达式
  • condition : 条件判断,满足时才缓存
  • unless : 条件判断,满足时不缓存
  • keyGenerator : 自定义键生成器
  • cacheManager : 指定缓存管理器
  • cacheResolver : 自定义缓存解析器
常见 SpEL 表达式示例
@Cacheable(value = "users", 
           key = "#user.id", 
           condition = "#user.age > 18",
           unless = "#result == null")
public User getUser(User user) {
    // ...
}

@Cacheable(value = "products", key = "#root.methodName")
public List<Product> getAllProducts() {
    // ...
}

二、@CachePut - 更新缓存

特点
  • 总是执行方法体
  • 用方法返回值更新缓存
  • 通常用于新增/修改操作
示例
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    return userRepository.save(user);
}

@CachePut(value = "users", key = "#result.id")
public User createUser(User user) {
    return userRepository.save(user);
}

三、@CacheEvict - 删除缓存

基本用法
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}
重要参数
  • allEntries : 是否清空整个缓存区域
  • beforeInvocation : 是否在方法执行前清除缓存
示例
// 清空整个 users 缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
    // 无需实际逻辑,仅用于触发缓存清除
}

// 方法执行前清除缓存
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUser(Long id) {
    // 即使方法抛出异常,缓存也会被清除
}
// 清空整个 users 缓存
@CacheEvict(value = "users", allEntries = true)
public void clearAllUsersCache() {
    // 无需实际逻辑,仅用于触发缓存清除
}

// 方法执行前清除缓存
@CacheEvict(value = "users", key = "#id", beforeInvocation = true)
public void deleteUser(Long id) {
    // 即使方法抛出异常,缓存也会被清除
}

四、组合使用示例

完整 CRUD 示例
@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
    
    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void refreshAllUsers() {
        // 刷新逻辑
    }
}
多缓存操作
@Caching(
    evict = {
        @CacheEvict(value = "users", key = "#user.id"),
        @CacheEvict(value = "userList", allEntries = true)
    },
    put = {
        @CachePut(value = "userDetails", key = "#user.id")
    }
)
public User updateUserWithMultipleCaches(User user) {
    return userRepository.save(user);
}

五、配置示例

启用缓存
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
            new ConcurrentMapCache("users"),
            new ConcurrentMapCache("products")
        ));
        return cacheManager;
    }
}
Redis 缓存配置
@Configuration
@EnableCaching
public class RedisCacheConfig {
    
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(10))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}

六、最佳实践

键设计原则
// 好的键设计
@Cacheable(value = "orders", key = "'user:' + #userId + ':status:' + #status")
public List<Order> getOrdersByUserAndStatus(Long userId, String status) {
    // ...
}
条件缓存
@Cacheable(value = "products", 
           condition = "#pageSize <= 100",
           unless = "#result == null or #result.isEmpty()")
public List<Product> getProducts(int page, int pageSize) {
    // 只缓存小数据量查询
}
异常处理
@Cacheable(value = "data", unless = "#result == null")
public Data getDataWithFallback(String key) {
    try {
        return expensiveOperation();
    } catch (Exception e) {
        log.error("操作失败", e);
        return null; // 异常时不缓存
    }
}

七、常见问题

自调用失效
@Service
public class UserService {
    
    public User getAndUpdate(Long id) {
        // 自调用,@Cacheable 不会生效
        User user = getUser(id);
        return updateUser(user);
    }
    
    @Cacheable("users")
    public User getUser(Long id) {
        // ...
    }
    
    @CachePut("users")
    public User updateUser(User user) {
        // ...
    }
}
解决方案
  • 使用 AopContext.currentProxy()
  • 将方法拆分到不同类
  • 使用 @Autowired 注入自身代理

八、性能监控

@Component
public class CacheMetrics {
    
    @EventListener
    public void handleCacheEvent(CacheEvent event) {
        log.info("缓存事件: {} - {} - {}", 
            event.getSource(), 
            event.getCacheName(), 
            event.getKey());
    }
}

总结

Spring Cache 注解提供了声明式的缓存管理:

  • @Cacheable : 优先从缓存读取,适合查询操作
  • @CachePut : 更新缓存,适合写操作
  • @CacheEvict : 删除缓存,适合删除或更新操作

合理使用这些注解可以显著提升应用性能,但需要注意缓存一致性、内存使用和异常处理等问题。

5. 缓存穿透、缓存击穿、缓存雪崩解决方案

一、缓存问题概述

缓存穿透

定义 :查询不存在的数据,导致请求直接打到数据库

原因 :恶意攻击或业务查询不存在的数据

缓存击穿

定义 :热点key过期瞬间,大量请求直接访问数据库

原因 :热点数据过期,并发访问量大

缓存雪崩

定义 :大量key同时过期或Redis宕机,导致数据库压力激增

原因 :缓存集中过期、Redis服务不可用

二、Spring Boot + Redis 完整解决方案

项目依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.27.0</version>
    </dependency>
    <!-- 可选:使用JetCache -->
    <dependency>
        <groupId>com.alicp.jetcache</groupId>
        <artifactId>jetcache-starter-redis</artifactId>
        <version>2.7.5</version>
    </dependency>
</dependencies>

缓存穿透解决方案

方案一:布隆过滤器(推荐)
@Component
public class BloomFilterHelper {
    
    private final RedissonClient redissonClient;
    private static final String BLOOM_FILTER_NAME = "userBloomFilter";
    
    public BloomFilterHelper(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
        initBloomFilter();
    }
    
    private void initBloomFilter() {
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        // 初始化:预期元素数量100万,误判率0.01%
        bloomFilter.tryInit(1000000L, 0.001);
        // 预热数据到布隆过滤器
        loadExistingDataToBloomFilter(bloomFilter);
    }
    
    public boolean mightContain(Long id) {
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        return bloomFilter.contains(id);
    }
    
    public void add(Long id) {
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(BLOOM_FILTER_NAME);
        bloomFilter.add(id);
    }
}
方案二:缓存空值
@Service
public class ProductService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String PRODUCT_KEY_PREFIX = "product:";
    private static final long NULL_CACHE_TTL = 300; // 5分钟
    
    public Product getProductById(Long id) {
        String key = PRODUCT_KEY_PREFIX + id;
        
        // 1. 先从缓存查询
        Product product = (Product) redisTemplate.opsForValue().get(key);
        
        if (product != null) {
            // 空值对象判断
            if (product.getId() == -1L) {
                return null; // 缓存空值,直接返回null
            }
            return product;
        }
        
        // 2. 布隆过滤器检查(如果使用)
        // if (!bloomFilterHelper.mightContain(id)) return null;
        
        // 3. 查询数据库
        product = productDao.findById(id);
        
        if (product == null) {
            // 缓存空值,防止穿透
            Product nullProduct = new Product();
            nullProduct.setId(-1L); // 标记为空值
            redisTemplate.opsForValue().set(key, nullProduct, NULL_CACHE_TTL, TimeUnit.SECONDS);
            return null;
        }
        
        // 4. 写入缓存
        redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
        return product;
    }
}

缓存击穿解决方案

方案一:互斥锁(分布式锁)
@Service
public class HotspotService {
    
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String HOT_PRODUCT_KEY = "hot:product:";
    private static final String LOCK_KEY_PREFIX = "lock:product:";
    
    /**
     * 使用分布式锁解决缓存击穿
     */
    public Product getHotProductWithLock(Long productId) {
        String cacheKey = HOT_PRODUCT_KEY + productId;
        String lockKey = LOCK_KEY_PREFIX + productId;
        
        // 1. 尝试从缓存获取
        Product product = (Product) redisTemplate.opsForValue().get(cacheKey);
        if (product != null) {
            return product;
        }
        
        // 2. 获取分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试加锁,最多等待3秒,锁持有10秒
            boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
            if (!locked) {
                // 获取锁失败,短暂休眠后重试或返回旧数据
                Thread.sleep(100);
                return getHotProductWithLock(productId); // 重试
            }
            
            // 3. 双重检查(Double Check)
            product = (Product) redisTemplate.opsForValue().get(cacheKey);
            if (product != null) {
                return product;
            }
            
            // 4. 查询数据库
            product = productDao.findById(productId);
            if (product != null) {
                // 5. 写入缓存
                redisTemplate.opsForValue().set(
                    cacheKey, 
                    product, 
                    // 基础过期时间 + 随机值,避免同时过期
                    30 + new Random().nextInt(30), 
                    TimeUnit.MINUTES
                );
            }
            
            return product;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁失败", e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}
方案二:逻辑过期(热点数据永不过期)
@Component
public class HotspotDataManager {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 逻辑过期方案
     */
    public Product getProductWithLogicalExpire(Long productId) {
        String cacheKey = "product:logic:" + productId;
        
        // 1. 从缓存获取数据
        CacheWrapper<Product> wrapper = (CacheWrapper<Product>) redisTemplate
            .opsForValue().get(cacheKey);
        
        // 2. 缓存不存在,直接查库
        if (wrapper == null) {
            return loadAndCacheProduct(productId);
        }
        
        // 3. 判断是否逻辑过期
        if (wrapper.isExpired()) {
            // 异步更新缓存
            CompletableFuture.runAsync(() -> refreshProductCache(productId));
        }
        
        return wrapper.getData();
    }
    
    /**
     * 缓存包装类,包含逻辑过期时间
     */
    @Data
    @AllArgsConstructor
    private static class CacheWrapper<T> implements Serializable {
        private T data;
        private long expireTime; // 逻辑过期时间戳
        
        public boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }
    
    private Product loadAndCacheProduct(Long productId) {
        Product product = productDao.findById(productId);
        if (product != null) {
            // 设置逻辑过期时间为10分钟后
            long expireTime = System.currentTimeMillis() + 10 * 60 * 1000;
            CacheWrapper<Product> wrapper = new CacheWrapper<>(product, expireTime);
            // 物理缓存永不过期
            redisTemplate.opsForValue().set(
                "product:logic:" + productId, 
                wrapper
            );
        }
        return product;
    }
    
    private void refreshProductCache(Long productId) {
        // 使用分布式锁,防止并发重建
        // ... 实现缓存重建逻辑
    }
}

缓存雪崩解决方案

方案一:随机过期时间
@Component
public class CacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 设置缓存时添加随机过期时间
     */
    public void setWithRandomTTL(String key, Object value, long baseTTL, TimeUnit timeUnit) {
        Random random = new Random();
        // 基础TTL ± 随机值(20%波动)
        long randomOffset = (long) (baseTTL * 0.2 * random.nextDouble());
        long finalTTL = baseTTL + (random.nextBoolean() ? randomOffset : -randomOffset);
        
        redisTemplate.opsForValue().set(
            key, 
            value, 
            finalTTL, 
            timeUnit
        );
    }
    
    /**
     * 批量设置缓存,使用不同的过期时间
     */
    public void batchSetWithStaggeredExpiry(Map<String, Object> dataMap, 
                                           long baseTTL, 
                                           TimeUnit timeUnit) {
        Random random = new Random();
        dataMap.forEach((key, value) -> {
            long offset = random.nextInt(600); // 随机0-10分钟
            long ttl = timeUnit.toSeconds(baseTTL) + offset;
            redisTemplate.opsForValue().set(
                key, 
                value, 
                ttl, 
                TimeUnit.SECONDS
            );
        });
    }
}
方案二:缓存预热 + 定时更新
@Component
@Slf4j
public class CacheWarmUpService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private ProductDao productDao;
    
    /**
     * 应用启动时缓存预热
     */
    @PostConstruct
    public void warmUpCache() {
        log.info("开始缓存预热...");
        
        // 1. 预热热点数据
        warmUpHotProducts();
        
        // 2. 预热分类数据
        warmUpCategories();
        
        log.info("缓存预热完成");
    }
    
    /**
     * 定时更新缓存
     */
    @Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行
    public void scheduledCacheUpdate() {
        log.info("定时缓存更新开始...");
        updateHotProductsCache();
    }
    
    private void warmUpHotProducts() {
        List<Product> hotProducts = productDao.findHotProducts(100);
        CacheService cacheService = new CacheService();
        
        Map<String, Object> cacheMap = hotProducts.stream()
            .collect(Collectors.toMap(
                p -> "product:hot:" + p.getId(),
                Function.identity()
            ));
        
        // 使用错峰过期
        cacheService.batchSetWithStaggeredExpiry(cacheMap, 30, TimeUnit.MINUTES);
    }
}
方案三:多级缓存架构
@Component
public class MultiLevelCacheService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 本地缓存(Caffeine)
    private final Cache<String, Object> localCache = Caffeine.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(5, TimeUnit.MINUTES)
        .recordStats()
        .build();
    
    /**
     * 多级缓存查询
     */
    public Product getProductMultiLevel(Long productId) {
        String cacheKey = "product:" + productId;
        
        // 1. 查询本地缓存
        Product product = (Product) localCache.getIfPresent(cacheKey);
        if (product != null) {
            return product;
        }
        
        // 2. 查询Redis
        product = (Product) redisTemplate.opsForValue().get(cacheKey);
        if (product != null) {
            // 回填本地缓存
            localCache.put(cacheKey, product);
            return product;
        }
        
        // 3. 查询数据库(使用互斥锁防止击穿)
        product = getProductWithLock(productId);
        if (product != null) {
            // 写入多级缓存
            localCache.put(cacheKey, product);
            redisTemplate.opsForValue().set(
                cacheKey, 
                product, 
                30 + new Random().nextInt(30), 
                TimeUnit.MINUTES
            );
        }
        
        return product;
    }
}

Redis高可用配置

spring:
  redis:
    # Redis集群配置
    cluster:
      nodes:
        - 192.168.1.101:6379
        - 192.168.1.102:6379
        - 192.168.1.103:6379
      max-redirects: 3
    # 哨兵模式配置
    # sentinel:
    #   master: mymaster
    #   nodes:
    #     - 192.168.1.101:26379
    #     - 192.168.1.102:26379
    #     - 192.168.1.103:26379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms
      shutdown-timeout: 100ms

降级和熔断策略

@Component
@Slf4j
public class CacheDegradeService {
    
    @Autowired
    private CircuitBreakerFactory circuitBreakerFactory;
    
    /**
     * 使用Resilience4j熔断器
     */
    public Product getProductWithCircuitBreaker(Long productId) {
        CircuitBreaker circuitBreaker = circuitBreakerFactory.create("productService");
        
        return circuitBreaker.run(
            () -> {
                // 正常业务逻辑
                return getProductFromCacheOrDB(productId);
            },
            throwable -> {
                // 降级逻辑
                log.warn("缓存服务降级,返回默认产品", throwable);
                return getDefaultProduct();
            }
        );
    }
    
    /**
     * 服务降级:返回默认数据
     */
    private Product getDefaultProduct() {
        Product defaultProduct = new Product();
        defaultProduct.setId(0L);
        defaultProduct.setName("默认产品");
        defaultProduct.setPrice(BigDecimal.ZERO);
        return defaultProduct;
    }
    
    /**
     * 限流保护
     */
    @ServiceLimit(limit = 100, timeout = 1000) // 自定义注解实现限流
    public Product getProductWithRateLimit(Long productId) {
        return getProductFromCacheOrDB(productId);
    }
}

监控和告警

@Component
@Slf4j
public class CacheMonitor {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 监控缓存命中率
     */
    @Scheduled(fixedRate = 60000) // 每分钟统计
    public void monitorCacheHitRate() {
        // 获取缓存统计信息
        Map<String, String> cacheStats = getCacheStatistics();
        
        double hitRate = calculateHitRate(cacheStats);
        
        if (hitRate < 0.8) { // 命中率低于80%告警
            log.warn("缓存命中率过低: {}%", hitRate * 100);
            sendAlert("缓存命中率过低警告");
        }
    }
    
    /**
     * 监控Redis健康状态
     */
    public boolean checkRedisHealth() {
        try {
            String result = redisTemplate.getConnectionFactory()
                .getConnection()
                .ping();
            return "PONG".equals(result);
        } catch (Exception e) {
            log.error("Redis健康检查失败", e);
            // 触发降级策略
            return false;
        }
    }
}

三、最佳实践总结

  1. 缓存穿透 :
  1. 使用布隆过滤器 + 缓存空值
  2. 接口层增加参数校验
  1. 缓存击穿 :
  1. 热点数据永不过期 + 逻辑过期
  2. 使用分布式锁重建缓存
  3. 提前续期热点数据
  1. 缓存雪崩 :
  1. 设置随机过期时间
  2. 使用多级缓存架构
  3. 保证Redis高可用(集群/哨兵)
  4. 实施缓存预热策略
  1. 通用建议 :
  1. 监控缓存命中率
  2. 设置合理的过期时间
  3. 实现降级熔断机制
  4. 定期进行压力测试

这些方案可以根据实际业务场景组合使用,构建健壮的缓存体系。

更多推荐