Redis实际生产中遇到的三大缓存问题

缓存穿透

查询一个不存在的数据,mysql查不到数据也不会写入缓存,每次请求都会请求到数据库,如果请求太多了会压倒数据库。

解决方案:布隆过滤器;

在添加缓存的时候,也同时把数据写到一下布隆过滤器上。这样每次请求过来的时候就会先请求到布隆过滤器上,布隆过滤器会检索一个元素是否在一个集合中。看这个请求数据经过三次哈希计算得到的槽点,有没有打到布隆过滤器是bitmap数组值为1的节点。

缓存击穿

大量请求同时针对一个缓存资源的请求,这个时候缓存资源又刚刚过期,这个时候请求会直接达到数据库上面,导致宕机。

解决方案:

强一致方案:加互斥锁:线程1没有命中缓存以后,会获取一个redisson的非公平锁,然后查询数据库并且完成缓存重建,然后释放锁;如果在线程1还没有释放锁的时候,线程2来了,他会获取锁失败,然后它会不断地尝试获取锁,直到线程1释放锁,才能拿到缓存数据。

高可用方案:添加逻辑过期时间;在我们添加缓存数据的时候,再添加一个过期时间字段。

当线程1来的时候,如果发现逻辑时间已经过期了,他会获得redisson非公平锁,然后开启一个新线程去查询数据库和完成缓存重建,并且重置逻辑过期时间。这个时候线程1会返回过期数据,然后释放锁。如果有个线程3在线程1还没有释放锁的时候来了,它会获取锁失败,然后放回过期数据。如果有个线程4,它是在完成缓存重建以后来的,那么他会获取到没有过期的缓存数据。

注意:一开始这个key过期是因为系统中给这个缓存设置了过期时间,而我们添加的逻辑过期时间是数据的一部分,线程请求资源的时候会先查看这个时间戳有没有过期,过期了就返回过期数据,并且开启一个新线程去查询数据库完成缓存重建。

import redis.clients.jedis.Jedis;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CacheExample {

    private Jedis jedis;
    private Lock lock = new ReentrantLock();

    public CacheExample(Jedis jedis) {
        this.jedis = jedis;
    }

    public String get(String key) {
        String value = jedis.get(key);
        if (value == null) {
            return "Not found";
        }

        // 解析缓存中的值和逻辑过期时间
        CacheValue cacheValue = parseCacheValue(value);
        long currentTime = System.currentTimeMillis();

        // 如果逻辑过期时间已到
        if (currentTime > cacheValue.getExpirationTime()) {
            // 返回过期数据
            new Thread(() -> {
                try {
                    // 尝试获取锁
                    if (lock.tryLock()) {
                        try {
                            // 再次检查缓存,确保没有其他线程已经更新
                            value = jedis.get(key);
                            if (value != null) {
                                cacheValue = parseCacheValue(value);
                                if (currentTime <= cacheValue.getExpirationTime()) {
                                    return; // 已经有其他线程更新了缓存
                                }
                            }

                            // 加载数据并更新缓存
                            String dataFromDB = loadDataFromDatabase(key);
                            long newExpirationTime = currentTime + 60 * 1000; // 设置新的过期时间为 60 秒后
                            jedis.set(key, new CacheValue(dataFromDB, newExpirationTime).toString());
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();

            // 返回过期数据
            return cacheValue.getValue();
        } else {
            // 过期时间未到,直接返回缓存中的值
            return cacheValue.getValue();
        }
    }

    private String loadDataFromDatabase(String key) {
        // 从数据库加载数据
        return "Data from DB for " + key;
    }

    private CacheValue parseCacheValue(String value) {
        // 解析缓存中的值和过期时间
        String[] parts = value.split(",");
        return new CacheValue(parts[0], Long.parseLong(parts[1]));
    }

    private static class CacheValue {
        private String value;
        private long expirationTime;

        public CacheValue(String value, long expirationTime) {
            this.value = value;
            this.expirationTime = expirationTime;
        }

        public String getValue() {
            return value;
        }

        public long getExpirationTime() {
            return expirationTime;
        }

        @Override
        public String toString() {
            return value + "," + expirationTime;
        }
    }
}

缓存雪崩

是指同一时间大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力,压倒数据库。

解决方案:

第一种情况:给不同的Key的TTL添加随机值

第二种情况:利用Redis集群提高服务的可用性

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值