您的位置:首页 > 娱乐 > 八卦 > 公司展览厅设计_东莞最近发生了什么大事_推广项目的平台_湖南靠谱seo优化报价

公司展览厅设计_东莞最近发生了什么大事_推广项目的平台_湖南靠谱seo优化报价

2024/12/27 23:40:09 来源:https://blog.csdn.net/m0_71338251/article/details/143903180  浏览:    关键词:公司展览厅设计_东莞最近发生了什么大事_推广项目的平台_湖南靠谱seo优化报价
公司展览厅设计_东莞最近发生了什么大事_推广项目的平台_湖南靠谱seo优化报价

文章目录

  • 缓存雪崩
  • 缓存穿透
  • 缓存击穿

缓存雪崩

缓存雪崩产生原因:由于缓存在同一时间大面积失效或者Redis宕机导致大量请求落入数据库,给数据库造成巨大的压力
解决方案:
1、当将数据添加到缓存中时,给缓存时间添加随机的过期时间。可以防止缓存在同一时间大面积失效。
2、使用Redis集群。当有节点宕机时使用其他节点恢复数据。
3、做好限级限流的策略
4、使用多级缓存

缓存穿透

缓存穿透产生原因:被恶意者攻击,发送大量不存在的数据请求,导致请求不会命中缓存,直接落到数据库,给数据库带来巨大的压力
解决方案:
1、缓存空值键到缓存中,适用于大量请求所带的id相同。为了避免内存的浪费,可以给空值键设置一个过期时间
优点:实现简单
缺点:当大量请求所带的id不同时,需要缓存多个空值键,占用额外的内存空间

保证空键值的实现:

    @Autowiredprivate StringRedisTemplate redisTemplate;public void set(String key, Object o, Long time, TimeUnit unit) {redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(o), time, unit);}/*** 缓存空值键(解决缓存穿透)*/public  <R, Id> R querySaveNull(String prefixKey, Id id, Class<R> type, Function<Id, R> dbFunction, Long time, TimeUnit unit) {System.out.println("缓存空值");String cacheKey = prefixKey + id;String objectStr = redisTemplate.opsForValue().get(cacheKey);if (StrUtil.isNotBlank(objectStr)) {return JSONUtil.toBean(objectStr, type);}
//        shopStr不为null说明为'',即命中空值键的缓存if (objectStr != null) {return null;}R r = dbFunction.apply(id);
//        当查出来的数据为null时,往redis中添加空值的键if (r == null) {redisTemplate.opsForValue().set(cacheKey, "", CACHE_NULL_TTL,TimeUnit.SECONDS);return null;}set(cacheKey,r,time,unit);return r;}

2、适用布隆过滤器。将数据库所有数据的id放入布隆过滤器中,当有请求到达时,先经过布隆过滤器,判断布隆过滤器中是否存在请求id,如果存在则通过,如果不存在则直接返回。
缺点:实现复杂,存在误差
3、可以增加id的复杂性,添加请求所带id的检验,可以防止被攻击者猜到id并发出攻击

缓存击穿

缓存击穿产生原因:存在高并发的热点键,并且其重建缓存业务复杂,重建缓存耗时长,当热点键过期失效时,大量线程进入重建缓存,查询数据库,导致数据库压力暴涨
解决方案:
1、基于互斥锁的实现。
基于Redis的setnx命令实现互斥锁,当数据不存在时可以添加数据成功,数据存在时添加数据失败,当线程适用setnx命令添加成功数据时则成功抢到了锁,添加失败则获取锁失败,线程执行结束后需要及时释放锁,为了防止因为其他原因锁没有被释放,可以给锁添加一个过期时间。

/*** 获取互斥锁*/private Boolean getLock(String lockKey) {
//        返回的布尔类型使包装型,可能为null值,调用布尔工具类,当包装类为true时才返回true,否则返回falseBoolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);return BooleanUtil.isTrue(aBoolean);}/*** 释放互斥锁*/private void releaseLock(String lockKey) {redisTemplate.delete(lockKey);}

当多线程到达时,首先查看能否命中缓存,如果缓存失效我们尝试让这些线程竞争锁,竞争到锁的线程负责进行缓存重建的业务,其他的线程则使其休眠一段时间后重新执行业务。以下是基于Shop对象的实现。

  private Shop queryWithMutex(Long id) {
//       首先从缓存中查询数据String cacheKey = CACHE_SHOP_KEY + id;String shopStr = redisTemplate.opsForValue().get(cacheKey);if (StrUtil.isNotBlank(shopStr)) {return JSONUtil.toBean(shopStr, Shop.class);}String lockKey = LOCK_SHOP_KEY + id;Boolean aBoolean = getLock(lockKey);Shop shop = null;
//       如果获取锁失败,则进行递归重新执行try {if (!aBoolean) {Thread.sleep(50);return queryWithMutex(id);}Thread.sleep(500);shop = getById(id);
//        当查出来的数据为null时,往redis中添加空值的键if (shop == null) {redisTemplate.opsForValue().set(cacheKey, "", CACHE_NULL_TTL, TimeUnit.SECONDS);return null;}redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();} finally {
//            当抛出异常或者业务执行完毕之后释放锁releaseLock(lockKey);}return shop;}

优点:保证了数据的高一致性,所有请求都能够返回最新的数据
缺点:重建缓存耗时较长,其他线程都需要等待缓存重建消耗的时间

2、基于逻辑过期实现。我们不给键添加过期时间,让其一直存在缓存中,但是我们在将数据其添加进缓存中时,会给其添加一个逻辑过期的字段,当多线程到达时,我们首先命中缓存,取出逻辑过期的字段,判断数据是否过期,如果数据过期,让线程竞争锁,获取到锁的线程新创建一个线程来进行缓存重建的业务,然后主线程和其他未竞争到锁的线程直接返回过期的数据。以下时基于Shop对象的实现

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}
private Shop queryWithLogicalExpire(Long id) {String key = CACHE_SHOP_KEY + id;String redisDataStr = redisTemplate.opsForValue().get(key);if (StrUtil.isBlank(redisDataStr)) {return null;}RedisData redisData = JSONUtil.toBean(redisDataStr, RedisData.class);Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
//        逻辑时间未过期LocalDateTime expireTime = redisData.getExpireTime();System.out.println(expireTime);System.out.println(LocalDateTime.now());if (expireTime.isAfter(LocalDateTime.now())) {return shop;}
//        逻辑时间过期后,竞争锁String lockKey = LOCK_SHOP_KEY + id;
//        竞争成功锁的线程new线程来进行缓存重建Boolean isLock = getLock(lockKey);if (isLock){executorService.execute(()->{try {Thread.sleep(500);System.out.println("缓存重建");saveShopRedis(id,CACHE_SHOP_TTL);} catch (Exception e) {e.printStackTrace();} finally {releaseLock(lockKey);}});}return shop;}/**逻辑过期进行缓存重建*/public void saveShopRedis(Long id, Long time) {String key = CACHE_SHOP_KEY + id;Shop shop = getById(id);RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(time));redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}

优点:线程无需等待,直接返回数据
缺点:不保证数据的高一致性,部分线程可能返回以及过期的数据

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com