Redis缓存介绍
二级缓存(Secondary Cache)是指在系统中使用两个缓存层次的架构,通常用于优化数据读取效率。最常见的二级缓存结构包括 本地缓存(一级缓存) 和 远程缓存(Redis作为二级缓存)。
在很多应用中,为了提高性能,使用 Redis 作为第二层缓存(即二级缓存)。一级缓存通常是应用程序的内存缓存(例如 Java 中的 ConcurrentHashMap
或使用框架如 Caffeine、Ehcache 等),而 Redis 作为二级缓存,提供分布式的缓存层,能够支持多个应用实例共享缓存数据。
在这种模式下,一级缓存负责快速获取和存储数据,二级缓存(Redis)则是一个备份缓存,它的数据通常比一级缓存的存储数据更大,且存储时间较长,能够缓存大量的共享数据。
一级缓存与二级缓存的工作原理
- 一级缓存(本地缓存):一级缓存通常存在于应用的内存中,每次访问时都会首先检查本地缓存。本地缓存的优势是访问速度非常快,但缺点是只能在单一应用实例中使用,且缓存数据的量受到应用内存的限制。
- 二级缓存(Redis):Redis 作为二级缓存通常用来存储大量数据。它是分布式的,可以跨多个应用实例共享缓存数据,适合存储相对持久且访问频繁的数据。Redis 的访问速度也非常快,但相较于本地缓存稍慢。
Redis 二级缓存的工作流程
- 查询流程:首先检查一级缓存:当应用程序需要获取数据时,首先会检查本地缓存(一级缓存)。如果一级缓存中没有数据:然后查询 Redis 二级缓存,如果 Redis 中有数据,则从 Redis 中获取并将数据缓存到一级缓存中。如果 Redis 中也没有数据,则从数据库中查询,并将数据先写入 Redis,然后再缓存到一级缓存。
- 写入流程:当数据被更新时,先更新数据库,再更新 Redis(二级缓存),最后将更新的数据写入一级缓存。通常使用 缓存失效策略,如 TTL(过期时间)来保证缓存中的数据保持一致性。
- 一致性问题:由于本地缓存和 Redis 缓存存在不同步的问题,因此需要采取一些策略来保持缓存数据的一致性。例如:更新缓存策略:每次更新数据时,不仅更新数据库,也更新缓存。过期策略:通过定期过期或手动失效缓存,确保缓存中的数据不会过时。
Redis 二级缓存的优势
- 提高性能:通过将数据存储在内存中,无需每次都访问数据库,减少数据库的压力,提高响应速度。
- 减少数据库负载:只要数据存在缓存中,就无需查询数据库,减少数据库的压力。
- 扩展性:使用 Redis 作为二级缓存时,能够有效处理大规模的数据缓存,支持分布式环境下的多实例共享。
Redis 二级缓存的常见问题与解决方案
- 缓存穿透:即查询的数据在缓存和数据库中都不存在,解决方案是将空值缓存(空对象缓存),避免每次请求都查询数据库。
- 缓存击穿:某个热点数据的缓存过期,导致多个请求同时查询数据库,解决方案是使用互斥锁(SETNX 命令)来保证只有一个请求查询数据库,其他请求等待。
- 缓存雪崩:大量缓存同时过期,导致所有请求同时访问数据库,解决方案是设置不同的缓存过期时间,避免缓存集中失效。
- 缓存一致性:本地缓存和 Redis 中的数据可能不一致,解决方案是使用双写机制,即更新数据库时,同时更新 Redis 和本地缓存。
Redis二级缓存
为了实现 Redis 作为二级缓存,可以使用 Jedis 或 Spring Cache 等工具来管理缓存。以下是一个简单的 Jedis 和 ConcurrentHashMap 本地缓存实现二级缓存的示例。
1. 使用 Jedis 和本地缓存实现 Redis 二级缓存
Maven 依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.0</version> </dependency>
代码实现
import redis.clients.jedis.Jedis; import java.util.concurrent.ConcurrentHashMap; public class RedisSecondaryCacheExample { // 一级缓存:本地缓存,使用 ConcurrentHashMap private static final ConcurrentHashMap<String, String> localCache = new ConcurrentHashMap<>(); // Redis 客户端实例(假设 Redis 服务运行在本地) private static final Jedis jedis = new Jedis("localhost", 6379); public static void main(String[] args) { String key = "user:1001"; // 1. 尝试从一级缓存获取数据 String value = getFromLocalCache(key); if (value == null) { // 2. 如果一级缓存没有,从 Redis 获取数据 value = getFromRedis(key); if (value == null) { // 3. 如果 Redis 没有,从数据库获取数据 value = getFromDatabase(key); // 4. 更新 Redis 和一级缓存 setToRedis(key, value); setToLocalCache(key, value); } } System.out.println("Fetched value: " + value); } // 从本地缓存获取数据 public static String getFromLocalCache(String key) { return localCache.get(key); } // 设置数据到本地缓存 public static void setToLocalCache(String key, String value) { localCache.put(key, value); } // 从 Redis 获取数据 public static String getFromRedis(String key) { return jedis.get(key); } // 设置数据到 Redis public static void setToRedis(String key, String value) { jedis.setex(key, 3600, value); // 设置 1 小时过期时间 } // 模拟从数据库获取数据 public static String getFromDatabase(String key) { // 模拟从数据库查询数据 return "Database value for " + key; } }
- 本地缓存(一级缓存):使用 ConcurrentHashMap 作为本地缓存。通过 localCache.get(key) 方法检查是否有缓存数据。
- Redis 二级缓存:使用 Jedis 客户端连接 Redis。通过 jedis.get(key) 获取 Redis 中的缓存数据,若 Redis 中没有数据,则从数据库查询并更新 Redis 和本地缓存。
- 缓存操作:使用 setex 方法将数据写入 Redis,并设置过期时间(1 小时)。
- 模拟数据库查询:getFromDatabase 方法模拟从数据库获取数据的操作。实际生产中,应该替换为实际的数据库查询操作。
2. 使用 Spring Cache 配置 Redis 作为二级缓存
Spring 提供了 Spring Cache 抽象,可以非常方便地实现二级缓存,结合 Redis 和本地缓存。以下是 Spring Cache 配置示例。
Maven 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
Spring 配置示例
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.annotation.CacheEvict; 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.core.RedisTemplate; import org.springframework.cache.CacheManager; @Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig(); return RedisCacheManager.builder(connectionFactory) .cacheDefaults(cacheConfig) .build(); } }
使用 Spring Cache
import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class UserService { // Redis 作为二级缓存,使用 @Cacheable 注解来标记缓存操作 @Cacheable(value = "userCache", key = "#userId") public String getUserById(String userId) { // 模拟从数据库获取数据 return "Database value for user " + userId; } }
@Cacheable
注解:使用@Cacheable
注解标记需要缓存的方法,Spring 会自动将方法的返回值缓存到 Redis 中。- Redis 配置:通过
RedisCacheManager
配置 Redis 作为缓存提供者。 - 本地缓存与 Redis 缓存:Spring 会先查找本地缓存(如
ConcurrentHashMap
),如果没有,则从 Redis 中获取缓存。如果 Redis 中没有,则执行方法并缓存返回值。
Redis的碎碎念 文章被收录于专栏
Redis面试中的碎碎念