Redis 缓存穿透介绍
缓存穿透(Cache Penetration)是指客户端请求的数据既不在缓存中,也不在数据库中,导致每次请求都直接访问数据库。这种情况不仅无法发挥缓存的性能优势,还会加重数据库的负担,导致系统性能下降。
缓存穿透的原因
缓存穿透通常有以下几种原因:
- 查询的数据不存在:客户端请求的数据在缓存中和数据库中都没有,例如,用户请求的商品或用户数据在系统中根本不存在。
- 恶意请求:攻击者故意发送不存在的数据请求,绕过缓存,直接向数据库发起请求。
- 缓存未命中数据:由于缓存没有存储数据(可能是由于缓存过期或删除),每次都需要访问数据库。
缓存穿透的影响
- 增加数据库负载:每次请求都需要访问数据库,即使数据库中没有数据,也会浪费数据库资源,增加数据库的负载。
- 浪费带宽:客户端无效的请求造成了系统资源的浪费。
- 性能下降:频繁的缓存穿透会使得缓存的优势无法发挥,系统性能和响应时间会受到影响。
如何解决缓存穿透
- 缓存空对象:当查询的结果为空时,可以将空对象缓存,避免相同的无效请求反复查询数据库。
- 使用布隆过滤器:布隆过滤器是一种概率型数据结构,适合用来判断某个元素是否在一个集合中。通过使用布隆过滤器,可以在访问缓存之前先进行快速判断,如果请求的数据不在布隆过滤器中,就可以直接拒绝请求,避免不必要的数据库查询。
- 请求校验和防火墙:通过验证请求是否合法,限制无效或恶意请求的访问,从而减少无效请求导致的缓存穿透。
Redis 缓存穿透的 Java 实现
1. 使用布隆过滤器防止缓存穿透
布隆过滤器是一种空间效率极高的概率型数据结构,适合用来判断某个元素是否在一个集合中。布隆过滤器能够快速判断某个元素是否存在于数据库中,从而减少不必要的缓存查询。
我们可以通过 Redis 实现布隆过滤器,防止缓存穿透。
步骤:
- 安装依赖:使用 Redisson,它是 Redis 的 Java 客户端,支持布隆过滤器功能。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.16.1</version> </dependency>
2.代码实现:
import org.redisson.api.RBloomFilter; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.Redisson; public class RedisBloomFilterExample { // 初始化 Redisson 客户端 private static RedissonClient redisson; public static void main(String[] args) { // 配置 Redisson 连接 Redis Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); redisson = Redisson.create(config); // 创建布隆过滤器 RBloomFilter<String> bloomFilter = redisson.getBloomFilter("userExistsFilter"); // 初始化布隆过滤器,预计存储100万个元素,误判率设置为0.03% bloomFilter.tryInit(1000000, 0.03); // 添加数据到布隆过滤器 bloomFilter.add("user:1001"); bloomFilter.add("user:1002"); bloomFilter.add("user:1003"); // 模拟检查数据是否存在 String userId = "user:1001"; if (bloomFilter.contains(userId)) { // 如果布隆过滤器包含该数据,则从缓存获取数据 System.out.println(userId + " exists in cache."); } else { // 如果布隆过滤器没有该数据,则查询数据库 System.out.println(userId + " does not exist in cache, querying database..."); } // 关闭连接 redisson.shutdown(); } }
- Redisson 客户端初始化:通过
Redisson.create()
方法初始化客户端连接 Redis。 - 创建布隆过滤器:使用
redisson.getBloomFilter()
方法创建布隆过滤器。tryInit
方法用于初始化布隆过滤器的预期容量和误判率。 - 添加数据:使用
add()
方法将数据添加到布隆过滤器。 - 查询数据:使用
contains()
方法检查数据是否在布隆过滤器中存在。如果存在则从缓存中获取数据,否则查询数据库。
布隆过滤器的优势在于,它能够在大多数情况下快速判断数据是否存在,避免了无效查询数据库的情况。虽然布隆过滤器会存在一定的误判率(可能返回“存在”的错误结果),但它绝对不会漏掉真实存在的数据。
2. 缓存空对象
如果查询到的数据为空,可以将空对象缓存一段时间,避免后续相同的数据查询再次访问数据库。
代码实现:
import redis.clients.jedis.Jedis; public class CacheEmptyObjectExample { private static Jedis jedis = new Jedis("localhost", 6379); public static void main(String[] args) { String key = "user:9999"; // 假设这个用户不存在 // 尝试从缓存获取数据 String value = getFromCache(key); if (value == null) { // 如果缓存中没有,查询数据库 value = getFromDatabase(key); // 如果数据库中没有数据,缓存一个空值(避免下次重复查询) if (value == null) { setToCache(key, "null"); } else { setToCache(key, value); } } System.out.println("Fetched value: " + value); } // 从缓存获取数据 public static String getFromCache(String key) { return jedis.get(key); } // 设置数据到缓存 public static void setToCache(String key, String value) { jedis.setex(key, 3600, value); // 设置 1 小时过期时间 } // 模拟数据库查询 public static String getFromDatabase(String key) { // 假设数据库中没有这个数据 return null; } }
代码解析:
- 查询缓存:首先从 Redis 获取缓存,如果返回为
null
,则说明缓存中没有数据。 - 查询数据库:如果 Redis 缓存没有数据,则从数据库查询。
- 缓存空值:如果数据库查询结果为空(如用户不存在),则将
"null"
存入 Redis 中,表示该数据不存在。这样可以避免频繁访问数据库,提高性能。 - 缓存设置过期时间:使用
setex
设置缓存的过期时间(如 1 小时)。