redis布隆过滤器解决缓存击穿问题

在电商系统中,商品详情页是一个典型的高频访问场景。当用户请求某个商品的详情时,系统会优先从缓存中获取数据。如果缓存中没有该商品的详情,系统会去数据库查询并更新缓存。然而,如果某个热门商品的缓存失效,大量请求会同时查询数据库,导致数据库压力骤增,这就是缓存击穿问题。
以下是一个结合布隆过滤器防止缓存击穿的Java伪代码实现案例:
场景描述
商品详情查询:用户通过商品ID查询商品详情。
缓存层:使用Redis作为缓存,存储商品详情。
布隆过滤器:使用RedisBloom模块实现布隆过滤器,存储所有可能被查询的商品ID。
数据库层:存储商品详情的数据库。
实现思路
初始化布隆过滤器:
在系统启动时,将数据库中所有商品的ID插入到布隆过滤器中。
查询流程:
当用户请求商品详情时,先通过布隆过滤器判断该商品ID是否存在。
如果布隆过滤器判断不存在,则直接返回“商品不存在”。
如果布隆过滤器判断可能存在,则去缓存中查询。
如果缓存中有数据,则直接返回缓存结果。
如果缓存中没有数据,则去数据库查询,并将结果放入缓存。
Java伪代码实现

import io.lettuce.core.RedisClient;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.bloom.BloomOptions;
import io.lettuce.core.bloom.RedisBloomCommands;

import java.util.concurrent.locks.ReentrantLock;

public class ProductService {

    // Redis客户端
    private RedisClient redisClient;
    private RedisCommands<String, String> syncCommands;
    private RedisBloomCommands<String, String> bloomCommands;

    // 数据库客户端
    private DatabaseClient databaseClient;

    // 布隆过滤器的Key
    private static final String BLOOM_FILTER_KEY = "product:bloomfilter";

    // 缓存Key前缀
    private static final String CACHE_KEY_PREFIX = "product:cache:";

    // 锁,用于防止缓存击穿时的并发问题
    private ReentrantLock lock = new ReentrantLock();

    public ProductService(RedisClient redisClient, DatabaseClient databaseClient) {
        this.redisClient = redisClient;
        this.syncCommands = redisClient.connect().sync();
        this.bloomCommands = redisClient.connect().sync();
        this.databaseClient = databaseClient;
    }

    // 初始化布隆过滤器
    public void initBloomFilter() {
        // 获取所有商品ID
        List<String> productIds = databaseClient.getAllProductIds();
        // 初始化布隆过滤器
        bloomCommands.bfCreate(BLOOM_FILTER_KEY, BloomOptions.defaults(), productIds.size());
        // 将所有商品ID插入布隆过滤器
        for (String productId : productIds) {
            bloomCommands.bfAdd(BLOOM_FILTER_KEY, productId);
        }
    }

    // 查询商品详情
    public Product getProductDetails(String productId) {
        // 1. 使用布隆过滤器判断商品ID是否存在
        boolean exists = bloomCommands.bfExists(BLOOM_FILTER_KEY, productId);
        if (!exists) {
            // 如果布隆过滤器判断不存在,直接返回商品不存在
            return null;
        }

        // 2. 从缓存中查询商品详情
        String cacheKey = CACHE_KEY_PREFIX + productId;
        String productDetails = syncCommands.get(cacheKey);
        if (productDetails != null) {
            // 如果缓存中有数据,直接返回
            return new Product(productDetails);
        }

        // 3. 缓存中没有数据,加锁防止缓存击穿
        lock.lock();
        try {
            // 再次检查缓存,防止并发问题
            productDetails = syncCommands.get(cacheKey);
            if (productDetails != null) {
                return new Product(productDetails);
            }

            // 4. 查询数据库
            Product product = databaseClient.getProductById(productId);
            if (product != null) {
                // 将查询结果放入缓存
                syncCommands.set(cacheKey, product.toJson());
            }
            return product;
        } finally {
            lock.unlock();
        }
    }
}

// 数据库客户端
class DatabaseClient {
    // 获取所有商品ID
    public List<String> getAllProductIds() {
        // 查询数据库,返回所有商品ID
        return database.query("SELECT id FROM products");
    }

    // 根据商品ID查询商品详情
    public Product getProductById(String productId) {
        // 查询数据库,返回商品详情
        return database.query("SELECT * FROM products WHERE id = ?", productId);
    }
}

// 商品类
class Product {
    private String id;
    private String name;
    private double price;

    public Product(String details) {
        // 从JSON字符串解析商品详情
        this.id = parseId(details);
        this.name = parseName(details);
        this.price = parsePrice(details);
    }

    public String toJson() {
        // 将商品详情转换为JSON字符串
        return "{\"id\":\"" + id + "\",\"name\":\"" + name + "\",\"price\":" + price + "}";
    }
}

代码说明
布隆过滤器初始化:
在系统启动时,调用initBloomFilter方法,将所有商品ID插入到布隆过滤器中。
查询流程:
使用布隆过滤器判断商品ID是否存在。
如果布隆过滤器判断不存在,则直接返回null。
如果布隆过滤器判断可能存在,则去缓存中查询。
如果缓存中没有数据,则加锁并查询数据库,将结果放入缓存。
锁机制:
使用ReentrantLock防止缓存击穿时的并发问题。
在加锁后再次检查缓存,确保只有一个线程去查询数据库。
优点
减少无效查询:布隆过滤器可以快速判断商品ID是否存在,减少对不存在商品的查询。
减轻数据库压力:即使缓存失效,也能通过布隆过滤器减少对数据库的直接查询。
缺点
布隆过滤器误判:虽然误判率可以通过调整参数降低,但无法完全避免。
锁机制的开销:在高并发场景下,锁可能会成为性能瓶颈。
通过以上实现,电商系统可以在商品详情查询场景中有效缓解缓存击穿问题,同时结合布隆过滤器减少对数据库的无效查询。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值