SpringBoot如何使用Redis缓存

发布时间:2021-07-06 10:44:14 作者:chen
来源:亿速云 阅读:246
# SpringBoot如何使用Redis缓存

## 1. Redis缓存概述

### 1.1 什么是Redis

Redis(Remote Dictionary Server)是一个开源的、基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据结构,包括字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。

Redis的主要特点:
- 高性能:基于内存操作,读写速度极快
- 持久化:支持RDB和AOF两种持久化方式
- 丰富的数据结构:支持多种高级数据结构
- 原子性:所有操作都是原子性的
- 发布/订阅:支持消息发布订阅模式

### 1.2 为什么选择Redis作为缓存

在SpringBoot应用中使用Redis作为缓存有诸多优势:

1. **性能提升**:相比直接访问数据库,Redis的响应时间通常在微秒级别
2. **减轻数据库压力**:高频访问数据可以从缓存获取,减少数据库查询
3. **分布式支持**:Redis天然支持分布式,适合微服务架构
4. **丰富的数据结构**:可以灵活处理各种缓存场景
5. **过期策略**:支持设置缓存过期时间,自动清理旧数据

### 1.3 Spring缓存抽象

Spring框架提供了缓存抽象,通过注解的方式简化缓存操作。主要注解包括:

- `@EnableCaching`:启用缓存支持
- `@Cacheable`:在方法执行前检查缓存
- `@CachePut`:将方法返回值放入缓存
- `@CacheEvict`:清除缓存
- `@Caching`:组合多个缓存操作
- `@CacheConfig`:类级别的共享缓存配置

## 2. 环境准备与配置

### 2.1 安装Redis

#### Windows安装

1. 下载Redis for Windows:https://github.com/microsoftarchive/redis/releases
2. 解压zip包
3. 运行redis-server.exe启动服务
4. 使用redis-cli.exe连接测试

#### Linux安装

```bash
# Ubuntu/Debian
sudo apt update
sudo apt install redis-server

# CentOS/RHEL
sudo yum install epel-release
sudo yum install redis
sudo systemctl start redis
sudo systemctl enable redis

Docker运行Redis

docker run --name my-redis -p 6379:6379 -d redis

2.2 SpringBoot项目创建

使用Spring Initializr创建项目,添加以下依赖:

或手动添加Maven依赖:

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2.3 基本配置

在application.properties或application.yml中配置Redis连接:

# application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0

# 连接池配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms

或YAML格式:

# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms

3. 基本缓存操作

3.1 启用缓存支持

在主应用类上添加@EnableCaching注解:

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

3.2 缓存注解使用

@Cacheable

@Service
public class ProductService {
    
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        // 模拟数据库查询
        System.out.println("Fetching product from database...");
        return new Product(id, "Product " + id, 100.00);
    }
}

@CachePut

@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
    // 更新数据库逻辑
    System.out.println("Updating product in database...");
    return product;
}

@CacheEvict

@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
    // 删除数据库记录
    System.out.println("Deleting product from database...");
}

// 清除所有缓存
@CacheEvict(value = "products", allEntries = true)
public void clearAllCache() {
    System.out.println("Clearing all products cache...");
}

@Caching

@Caching(
    cacheable = {
        @Cacheable(value = "products", key = "#name")
    },
    put = {
        @CachePut(value = "products", key = "#result.id")
    }
)
public Product getProductByName(String name) {
    // 查询逻辑
    return productRepository.findByName(name);
}

3.3 自定义Key生成器

@Configuration
public class RedisConfig {
    
    @Bean
    public KeyGenerator customKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName());
            sb.append(":");
            sb.append(method.getName());
            sb.append(":");
            for (Object obj : params) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
}

// 使用自定义Key生成器
@Cacheable(value = "products", keyGenerator = "customKeyGenerator")
public Product getProduct(Long id) {
    // ...
}

4. 高级缓存配置

4.1 自定义RedisTemplate

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer序列化value
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            mapper.getPolymorphicTypeValidator(), 
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        serializer.setObjectMapper(mapper);
        
        // 设置key和value的序列化规则
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        
        template.afterPropertiesSet();
        return template;
    }
}

4.2 自定义缓存管理器

@Configuration
@EnableCaching
public class RedisCacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))  // 默认过期时间30分钟
            .disableCachingNullValues()        // 不缓存null值
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
        
        // 针对不同cacheName设置不同的过期时间
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        cacheConfigurations.put("products", 
            config.entryTtl(Duration.ofHours(1)));
        cacheConfigurations.put("users", 
            config.entryTtl(Duration.ofMinutes(10)));
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withInitialCacheConfigurations(cacheConfigurations)
            .transactionAware()
            .build();
    }
}

4.3 多级缓存策略

实现本地缓存(Caffeine)与Redis的多级缓存:

@Configuration
@EnableCaching
public class MultiLevelCacheConfig {
    
    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofMinutes(10))
            .maximumSize(1000));
        return caffeineCacheManager;
    }
    
    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofHours(1));
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
    
    @Bean
    public CacheManager multiLevelCacheManager(
            @Qualifier("cacheManager") CacheManager localCacheManager,
            @Qualifier("redisCacheManager") CacheManager redisCacheManager) {
        return new MultiLevelCacheManager(localCacheManager, redisCacheManager);
    }
}

public class MultiLevelCacheManager implements CacheManager {
    
    private final CacheManager localCacheManager;
    private final CacheManager redisCacheManager;
    
    public MultiLevelCacheManager(CacheManager localCacheManager, 
                                 CacheManager redisCacheManager) {
        this.localCacheManager = localCacheManager;
        this.redisCacheManager = redisCacheManager;
    }
    
    @Override
    public Cache getCache(String name) {
        return new MultiLevelCache(
            localCacheManager.getCache(name),
            redisCacheManager.getCache(name)
        );
    }
    
    @Override
    public Collection<String> getCacheNames() {
        return redisCacheManager.getCacheNames();
    }
}

public class MultiLevelCache implements Cache {
    
    private final Cache localCache;
    private final Cache redisCache;
    
    public MultiLevelCache(Cache localCache, Cache redisCache) {
        this.localCache = localCache;
        this.redisCache = redisCache;
    }
    
    @Override
    public String getName() {
        return redisCache.getName();
    }
    
    @Override
    public Object getNativeCache() {
        return redisCache.getNativeCache();
    }
    
    @Override
    public ValueWrapper get(Object key) {
        // 先从本地缓存获取
        ValueWrapper value = localCache.get(key);
        if (value != null) {
            return value;
        }
        
        // 本地缓存没有,从Redis获取
        value = redisCache.get(key);
        if (value != null) {
            // 将Redis中的数据放入本地缓存
            localCache.put(key, value.get());
        }
        return value;
    }
    
    // 实现其他Cache接口方法...
}

5. 实战应用场景

5.1 商品信息缓存

@Service
public class ProductServiceImpl implements ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Override
    @Cacheable(value = "products", key = "#id", unless = "#result == null")
    public Product getProductById(Long id) {
        return productRepository.findById(id).orElse(null);
    }
    
    @Override
    @CachePut(value = "products", key = "#product.id")
    public Product updateProduct(Product product) {
        return productRepository.save(product);
    }
    
    @Override
    @CacheEvict(value = "products", key = "#id")
    public void deleteProduct(Long id) {
        productRepository.deleteById(id);
    }
    
    @Override
    @Cacheable(value = "products", key = "'list:' + #pageable.pageNumber")
    public Page<Product> listProducts(Pageable pageable) {
        return productRepository.findAll(pageable);
    }
}

5.2 用户会话管理

@Service
public class SessionService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String SESSION_PREFIX = "session:";
    
    public void createSession(User user, String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.opsForValue().set(key, user, Duration.ofHours(2));
    }
    
    public User getSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        return (User) redisTemplate.opsForValue().get(key);
    }
    
    public void refreshSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.expire(key, Duration.ofHours(2));
    }
    
    public void deleteSession(String sessionId) {
        String key = SESSION_PREFIX + sessionId;
        redisTemplate.delete(key);
    }
}

5.3 热点数据缓存

@Service
public class HotDataService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String HOT_PRODUCTS_KEY = "hot:products";
    private static final String TOP_SEARCHES_KEY = "top:searches";
    
    public void incrementProductView(Long productId) {
        String key = "product:views:" + productId;
        redisTemplate.opsForValue().increment(key);
        
        // 添加到热门商品ZSET
        redisTemplate.opsForZSet().incrementScore(HOT_PRODUCTS_KEY, productId.toString(), 1);
    }
    
    public List<Long> getHotProducts(int limit) {
        Set<Object> productIds = redisTemplate.opsForZSet()
            .reverseRange(HOT_PRODUCTS_KEY, 0, limit - 1);
        
        return productIds.stream()
            .map(id -> Long.parseLong(id.toString()))
            .collect(Collectors.toList());
    }
    
    public void recordSearch(String keyword) {
        redisTemplate.opsForZSet().incrementScore(TOP_SEARCHES_KEY, keyword, 1);
    }
    
    public List<String> getTopSearches(int limit) {
        Set<Object> keywords = redisTemplate.opsForZSet()
            .reverseRange(TOP_SEARCHES_KEY, 0, limit - 1);
        
        return keywords.stream()
            .map(Object::toString)
            .collect(Collectors.toList());
    }
}

6. 性能优化与最佳实践

6.1 缓存穿透解决方案

问题:大量请求查询不存在的key,导致请求直接打到数据库

解决方案

  1. 缓存空对象
@Cacheable(value = "products", key = "#id", unless = "#result == null")
public Product getProductById(Long id) {
    Product product = productRepository.findById(id).orElse(null);
    if (product == null) {
        // 缓存一个空对象,设置较短过期时间
        return new Product(); // 空对象
    }
    return product;
}
  1. 布隆过滤器
@Service
public class BloomFilterService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    private static final String BLOOM_FILTER_KEY = "product:bloom:filter";
    
    public void initBloomFilter(List<Long> productIds) {
        for (Long id : productIds) {
            addToBloomFilter(id);
        }
    }
    
    public void addToBloomFilter(Long productId) {
        // 使用多个hash函数计算位位置
        int[] positions = getHashPositions(productId.toString());
        for (int pos : positions) {
            redisTemplate.opsForValue().setBit(BLOOM_FILTER_KEY, pos, true);
        }
    }
    
    public boolean mightContain(Long productId) {
        int[] positions = getHashPositions(productId.toString());
        for (int pos : positions) {
            if (!redisTemplate.opsForValue().getBit(BLOOM_FILTER_KEY, pos)) {
                return false;
            }
        }
        return true;
    }
    
    private int[] getHashPositions(String value) {
        // 使用多个hash函数计算位置
        // 实际实现可以使用Guava的BloomFilter或Redisson的RBloomFilter
        return new int[]{/* 计算出的位置数组 */};
    }
}

6.2 缓存雪崩解决方案

问题:大量缓存同时失效,导致所有请求直接访问数据库

解决方案

  1. 设置不同的过期时间
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofMinutes(30 + new Random().nextInt(15))) // 30-45分钟随机
        .disableCachingNullValues();
    
    // ...
}
  1. 永不过期+后台更新
@Service
public class ProductService {
    
    @Cacheable(value = "products", key = "#id")
    public Product getProductById(Long id) {
        return loadFromDb(id);
    }
    
    // 后台定时任务更新缓存
    @Scheduled(fixedRate = 3600000) // 每小时更新一次
    public void refreshCache() {
        List<Product> products = productRepository.findAll();
        products.forEach(p -> updateProduct(p));
    }
}

6.3 缓存一致性保障

问题:数据库更新后,缓存数据不一致

解决方案

  1. 双写模式

”`java @Transactional public Product updateProduct(Product product) { // 先更新数据库 Product updated = productRepository.save(product); // 再更新缓存 redisTemplate.ops

推荐阅读:
  1. springboot redis缓存配置
  2. SpringBoot怎么整合Redis缓存

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

redis spring spring boot

上一篇:vue2.0如何实现前端星星评分功能组件

下一篇:Spring Boot/VUE中如何实现路由传递参数

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》