分布式缓存问题1 缓存失效问题1.1 缓存穿透查询不存在的数据1.1.1 问题描述1.1.2 解决方案1.2 缓存雪崩大批key同时过期海量流量瞬时穿透到DB1.2.1 问题描述1.2.2 解决方案1.3 缓存击穿热点key过期瞬间大并发同时打向数据库1.3.1 问题描述1.3.2 解决方案2 分布式锁解决方案2.1 redisson1 缓存失效问题先来解决大并发读情况下的缓存失效问题1.1 缓存穿透查询不存在的数据1.1.1 问题描述缓存穿透是指查询一个一定不存在的数据由于缓存是不命中将去查询数据库但是数据库也无此记录我们没有将这次查询的null 写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。在流量大时可能DB 就挂掉了要是有人利用不存在的key 频繁攻击我们的应用这就是漏洞。1.1.2 解决方案1 对空值缓存如果一个查询返回的数据为空不管是数据是否不存在我们仍然把这个空结果null进行缓存设置空结果的过期时间会很短最长不超过五分钟。2 设置可访问的名单白名单使用bitmaps类型定义一个可以访问的名单名单id作为bitmaps的偏移量每次访问和bitmap里面的id进行比较如果访问id不在bitmaps里面进行拦截不允许访问。3 采用布隆过滤器(布隆过滤器Bloom Filter是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数哈希函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法缺点是有一定的误识别率和删除困难。)将所有可能存在的数据哈希到一个足够大的bitmaps中一个一定不存在的数据会被 这个bitmaps拦截掉从而避免了对底层存储系统的查询压力。4 进行实时监控当发现Redis的命中率开始急速降低需要排查访问对象和访问的数据和运维人员配合可以设置黑名单限制服务1.2 缓存雪崩大批key同时过期海量流量瞬时穿透到DB1.2.1 问题描述缓存雪崩是指在我们设置缓存时采用了相同的过期时间导致缓存在某一时刻同时失效请求全部转发到DBDB 瞬时压力过重雪崩。1.2.2 解决方案1 构建多级缓存架构nginx缓存 redis缓存 其他缓存ehcache等2 使用锁或队列用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况3 设置过期标志更新缓存记录缓存数据是否过期设置提前量如果过期会触发通知另外的线程在后台去更新实际key的缓存。4 将缓存失效时间分散开原有的失效时间基础上增加一个随机值比如1-5 分钟随机这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。1.3 缓存击穿热点key过期瞬间大并发同时打向数据库1.3.1 问题描述对于一些设置了过期时间的key如果这些key 可能会在某些时间点被超高并发地访问是一种非常“热点”的数据。对于“热点”的数据key需要考虑一个问题如果这个key 在大量请求同时进来前正好失效那么所有对这个key 的数据查询都落到db我们称为缓存击穿。1.3.2 解决方案1预先设置热门数据在redis高峰访问之前把一些热门数据提前存入到redis里面加大这些热门数据key的时长2实时调整现场监控哪些数据热门实时调整key的过期时长3使用加锁在获得锁的前后都进行缓存查询都查不到才进行数据库查询,在分布式情况下本地锁也有用哦最好使用分布式锁。1 就是在缓存失效的时候判断拿出来的值为空不是立即去load db。2 先使用缓存工具的某些带成功操作返回值的操作比如Redis的SETNX去set一个mutex key3 当操作返回成功时再进行load db的操作并回设缓存,最后删除mutex key4 当操作返回失败证明有线程在load db当前线程睡眠一段时间再重试整个get缓存的方法。2 分布式锁解决方案SETkey value[EXseconds][PXmilliseconds][NX|XX]set product_lock1EX2NXTTLproduct_lockStringuuidUUID.randomUUID().toString();BooleanlockFlagredisTemplate.opsForValue().setIfAbsent(product_lock,uuid,300,TimeUnit.SECONDS);/* 此段代码加锁有问题存在死锁问题 Boolean lockFlag redisTemplate.opsForValue().setIfAbsent(product_lock, 1); */if(lockFlag){// redisTemplate.expire(product_lock, 30, TimeUnit.SECONDS);// TODO 执行业务逻辑/* 此段代码解锁有问题存在死锁问题 String lockValue redisTemplate.opsForValue().get(product_lock); if(uuid.equals(lockValue)) { redisTemplate.delete(product_lock); } */Stringscriptif redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end ;IntegerexecuteredisTemplate.execute(newDefaultRedisScriptInteger(script,Integer.class),Arrays.asList(product_lock),uuid);// TODO}2.1 redissonConfigurationpublicclassRedissonConfig{BeanpublicRedissonClientredissonClient(){ConfigconfignewConfig();config.useSingleServer().setAddress(redis://192.168.6.6:6379).setPassword(DkyNqi2021);RedissonClientredissonRedisson.create(config);returnredisson;}}ControllerpublicclassMichaelController{AutowiredprivateRedissonClientredissonClient;GetMapping(/hello)ResponseBodypublicStringhello(){// 1、获取一把锁只要锁的名字一样就是同一把锁RLockrLockredissonClient.getLock(my-first-lock);/// 2、加锁rLock.lock();// 阻塞式等待默认加的锁是30s时间//1锁的自动续期如果业务超长运行期间自动给锁续上新的30s。不用担心业务时间长锁自动过期被删除。//2加锁的业务只要运行完成就不会给当前锁续期即使不手动解锁锁默认在30s以后会自动删除。try{System.out.println(加锁成功执行业务...Thread.currentThread().getId());Thread.sleep(10000);// 睡10秒}catch(Exceptione){// TODO: handle exception}finally{// 3、解锁System.out.println(释放锁...Thread.currentThread().getId());rLock.unlock();}returnhello;}}同时执行两个请求效果如下// 加锁成功执行业务...78// 释放锁...78// 加锁成功执行业务...79// 释放锁...79