redis解决缓存击穿问题(本地锁)

本地锁锁住当前进程

//从数据库查询并封装分类数据
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {
 
        //只要是同一把锁,就能锁住需要这个锁的所有线程,使用this,this代表的就是当前对象
        //1.synchronized(this):SpringBoot所有的组件在容器中都是单例的
        /**
         * 100万个请求同时进来,进来以后就先锁住,接下来这100万个请求就来竞争锁,假设有一个竞争上来了,那他就去执行数据库查询,查询完以后返回,释放锁
         * 别的请求再一进来,再去查数据库就是不合理的,相当于我们虽然锁住了,相当于再去排队查数据库。
         * 所以拿到锁以后,进来要做的第一件事,就是再看一下缓存里面有没有,如果有了说明是上一个人执行完放好的,如果没有你才需要再去查
         */
        //TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有,必须使用分布式锁
        synchronized (this) {
            //得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
            String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
            if (!StringUtils.isEmpty(catalogJSON)) {
                //缓存不为null直接返回
                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>(){});
                return result;
            }
            System.out.println("查询了数据库....");
            //将数据库的多次查询变为一次
            Map<String, List<Catelog2Vo>> parentCid  = selectFromDB();
            return parentCid;
        }
    }

如果是在单体应用的情况下,也就是我们这个项目只会部署在一个Tomat里面,一台服务器,这样加锁没问题。

### Redis 缓存击穿的原因 缓存击穿是指在高并发情况下,大量请求访问同一个 key,在该 key 正好过期的时候,这些请求会穿透缓存直接打到数据库上,造成数据库压力骤增甚至崩溃的情况[^1]。 由于缓存中的数据具有时效性,通常会对存储的数据设定一个合理的过期时间。一旦到达这个时间点,缓存将不再保留这条记录,此时如果多个客户端几乎同时发起对该条目对应的资源请求,则可能导致上述现象的发生[^2]。 ### 解决办法 #### 方法一:逻辑过期机制 为了避免因物理删除而导致的瞬时热点问题,可以引入逻辑上的“软删除”。即给每一条缓存增加额外的有效期限字段(比如 TTL),即使实际的缓存已经达到了预设的最大存活周期而被标记为可回收状态,但在一定时间内仍然允许读取操作成功返回旧版本的内容;只有当真正超过此附加有效期之后才会彻底移除该项,并触发一次完整的加载流程来更新最新值[^3]。 ```java // Java 实现逻辑过期伪代码 public class CacheItem { private Object value; private long expireAt; // 绝对过期时间戳 public boolean isExpired() { return System.currentTimeMillis() >= this.expireAt; } } ``` #### 方法二:互斥锁控制 通过分布式环境下的唯一标识符——如基于 Redis 自身实现简单的信号量/红绿灯模型——使得同一时刻只有一个进程能够执行特定的任务序列,从而有效防止了多实例竞争条件下可能引发的一系列连锁反应。具体来说就是在尝试获取某项不存在于本地副本里的对象之前先去争抢一把全局唯一的令牌,拿到手后再继续往下走;反之则需耐心排队等候直至轮到自己为止[^4]。 ```java // Java 实现互斥锁示例代码 String lockKey = "mutex_lock"; try (Jedis jedis = new Jedis()) { while (!jedis.setnx(lockKey, "locked").equals(1L)) { Thread.sleep(random.nextInt(10)); // 随机休眠减少冲突概率 } try { // 执行缓存未命中的处理逻辑... } finally { jedis.del(lockKey); // 清理工作务必放在finally里确保释放 } } ``` #### 方法三:布隆过滤器前置校验 利用布隆过滤器这种空间效率极高的随机化算法结构作为第一道防线,可以在正式查询前快速判断目标是否存在可能性。对于那些肯定不在集合内的询问可以直接拒绝掉,进而减轻后续环节的工作负担。不过需要注意的是这种方法存在一定误判率,因此只适用于容忍少量假阳性的业务场景下使用。 ```go package main import ( "fmt" bloom "github.com/steakknife/bloomfilter" ) func initBloomFilter(size int) *bloom.BloomFilter { return bloom.NewWithEstimates(float64(size), 0.01) } func checkExistence(filter *bloom.BloomFilter, item string) bool { return filter.Test([]byte(item)) } func addNewItem(filter *bloom.BloomFilter, item string) { filter.Add([]byte(item)) } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值