您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 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 run --name my-redis -p 6379:6379 -d redis
使用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>
在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
在主应用类上添加@EnableCaching
注解:
@SpringBootApplication
@EnableCaching
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
@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(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
// 更新数据库逻辑
System.out.println("Updating product in database...");
return product;
}
@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(
cacheable = {
@Cacheable(value = "products", key = "#name")
},
put = {
@CachePut(value = "products", key = "#result.id")
}
)
public Product getProductByName(String name) {
// 查询逻辑
return productRepository.findByName(name);
}
@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) {
// ...
}
@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;
}
}
@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();
}
}
实现本地缓存(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接口方法...
}
@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);
}
}
@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);
}
}
@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());
}
}
问题:大量请求查询不存在的key,导致请求直接打到数据库
解决方案:
@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;
}
@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[]{/* 计算出的位置数组 */};
}
}
问题:大量缓存同时失效,导致所有请求直接访问数据库
解决方案:
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30 + new Random().nextInt(15))) // 30-45分钟随机
.disableCachingNullValues();
// ...
}
@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));
}
}
问题:数据库更新后,缓存数据不一致
解决方案:
”`java @Transactional public Product updateProduct(Product product) { // 先更新数据库 Product updated = productRepository.save(product); // 再更新缓存 redisTemplate.ops
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。