当前位置: 首页 > wzjs >正文

做网站累吗广州seo招聘信息

做网站累吗,广州seo招聘信息,树莓派下载wordpress,网站 推广商系统 设计目录 一 缓存穿透 二 缓存雪崩 三 缓存击穿 1 基于互斥锁方式解决缓存击穿问题 2 基于逻辑过期方式解决缓存击穿问题 四 缓存工具封装 1 我们首先需要将stringRedisTemplate注入(构造器注入) 2 向Redis当中写入数据实现缓存重建(第一…

目录

一 缓存穿透

二 缓存雪崩

三 缓存击穿

1 基于互斥锁方式解决缓存击穿问题

2 基于逻辑过期方式解决缓存击穿问题

四 缓存工具封装

1 我们首先需要将stringRedisTemplate注入(构造器注入)

2 向Redis当中写入数据实现缓存重建(第一种是对TTL时间的缓存重建,第二种加上了逻辑过期时间的重建)

3 解决缓存穿透问题

4 解决缓存击穿问题(逻辑过期时间解决)


一 缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库都不存在,这样缓存永远不会生效,这些请求都会到达数据库。

同时还有数据校验的方式

实现(商铺的查询)-使用缓存空对象

核心修改点:

先判断是否有实际数据,接着再判断是否为""这样的情况属于之前查询后存储到缓存当中的,最后再判断为null就是在缓存当中没查询到,那就要在数据库当中查询。

代码实现:

    /*** 根据id查询商铺信息** @param id 商铺id* @return 商铺详情数据*/@Overridepublic Result queryById(Long id) {// 1.从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//这个条件是缓存当中有具体的数据(将null与""放行)// 3.存在且有具体值,返回数据Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//判断是否是空值if(shopJson != null){//上面已经排除了不为空的情况。这里不为空只可能为"" (这是空串不是null)return Result.fail("店铺不存在");}//而这里是为null的情况// 4.不存在,根据id查询数据库Shop shop = getById(id);// 5.数据库存在,写入redis并返回if (shop != null) {stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shop);}// 6.数据库不存在,返回错误,将空值写入redisstringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail("店铺不存在");}

这里不存在的信息只会在数据库当中查询一次,查询结束将“”值存储在缓存当中

二 缓存雪崩

缓存雪崩是指在同一时段大量的缓存key同时失效或则和Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

三 缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

缓存方案

1 基于互斥锁方式解决缓存击穿问题

需求:修改根据id查询商铺的业务

锁的形式

代码实现:

首先我们先定义两个方法用来获取锁和释放锁

    /*** 尝试获取锁** @param key* @return*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return Boolean.TRUE.equals(flag);}/*** 释放锁** @param key*/private void unLock(String key) {stringRedisTemplate.delete(key);}

使用互斥锁解决缓存击穿

简单来说就是第一次获取缓存时没有存在那就去数据库当中查询获取最终将数据存储到缓存当中,但是第一个访问的线程的要将锁加上,其他线程再次访问时就会先sleep休眠一段时间,然后再调用方法获取缓存。

    /*** 互斥锁解决缓存击穿** @param id* @return*/private Shop queryWithMutex(Long id) {// 1.从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//这个条件是缓存当中有具体的数据(将null与""放行)// 3.存在且有具体值,返回数据return JSONUtil.toBean(shopJson, Shop.class);}//判断是否是空值if (shopJson != null) {//上面已经排除了不为空的情况。这里不为空只可能为"" (这是空串不是null)return null;}//4实现缓存重建//4.1.获取互斥锁boolean isLock = tryLock(RedisConstants.LOCK_SHOP_KEY + id);//4.2.判断是否获取成功if (!isLock) {//4.3.失败,则休眠并重试try {Thread.sleep(50);return queryWithMutex(id);} catch (InterruptedException e) {log.error("互斥锁获取失败");}}//4.4.成功,根据id查询数据库//缓存不存在,根据id查询数据库(这里是获取缓存为null的情况)Shop shop = getById(id);// 5.数据库不存在,返回错误,将空值写入redisif (shop == null) {stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 6.数据库存在,写入redis并返回stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//释放互斥锁unLock(RedisConstants.LOCK_SHOP_KEY + id);//返回数据return shop;}

2 基于逻辑过期方式解决缓存击穿问题

需求:修改根据id查询商铺的业务。基于逻辑过期方式来解决缓存击穿问题

代码实现:

在商铺的存储过程当中,商铺信息是没有逻辑过期这个字段的,这样的话我们为了解决,我们再重新定义了一个实体类,将店铺信息存储在当中,再将逻辑过期时间存储封装在里面。

package com.hmdp.entity;import lombok.Data;import java.time.LocalDateTime;@Data
public class RedisData {//逻辑过期时间private LocalDateTime expireTime;//数据private Object data;
}

同时我们定义一个方法用于存储封装,将店铺信息查询出来封装到RedisData类当中,再根据情况,写入逻辑过期时间这个字段。

    /*** 将店铺数据封装-用于逻辑过期解决缓存击穿** @param id*/public void saveShopRedis(Long id) {//1查询店铺数据Shop shop = getById(id);//2封装逻辑过期时间RedisData redisData = new RedisData();//店铺数据redisData.setData(shop);//逻辑过期时间redisData.setExpireTime(LocalDateTime.now().plusSeconds(RedisConstants.CACHE_SHOP_TTL-10));//20//3写入redisstringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData),RedisConstants.CACHE_SHOP_TTL, TimeUnit.SECONDS);//30s}

逻辑过期解决缓存击穿

首先从redis当中查询店铺缓存,判断缓存是否命中,未命中返回空,命中就判断是否过期,未过期返回店铺信息,过期实现缓存重建。

    /*** 逻辑过期解决缓存击穿** @param id* @return*/public Shop queryWithLogicalExpire(Long id) {// 1 从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);// 2 判断是否存在(缓存未命中返回空)if (StrUtil.isBlank(shopJson)) {return null;}//3 命中的话判断是否过期RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);//实现对json的反序列化JSONObject data = (JSONObject) redisData.getData();Shop shop = JSONUtil.toBean(data, Shop.class);//获取数据LocalDateTime expireTime = redisData.getExpireTime();//获取逻辑过期时间// 4 判断是否过期,未过期直接返回店铺数据if (expireTime.isAfter(LocalDateTime.now())) {return shop;}// 5 判断是否过期,过期需要缓存重建// 5.1获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 5.2判断是否获取锁成功if (isLock) {CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 5.3将店铺数据重新写入redis并设置逻辑过期时间this.saveShopRedis(id);} catch (Exception e) {log.error("写redis失败", e);} finally {// 5.4释放互斥锁try {unLock(lockKey);} catch (Exception e) {log.error("释放锁失败");}}});}//未成功返回商铺信息return shop;}/*** 开启一个线程池(开启一个十个容量的线程池)*/private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

四 缓存工具封装

代码实现

package com.hmdp.utils;import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.RedisData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;@Component
@Slf4j
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 1.封装逻辑过期时间数据RedisData redisData = new RedisData();redisData.setData(value);
//        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));redisData.setExpireTime(LocalDateTime.now().plus(time, unit.toChronoUnit()));// 2.写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 缓存穿透*/public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//这个条件是缓存当中有具体的数据(将null与""放行)// 3.存在且有具体值,返回数据return JSONUtil.toBean(shopJson, type);}//判断是否是空值if (shopJson != null) {//上面已经排除了不为空的情况。这里不为空只可能为"" (这是空串不是null)return null;}//而这里是为null的情况// 4.不存在,根据id查询数据库R r = dbFallback.apply(id);// 5.不存在,返回错误,将空值写入redisif (r == null) {//将空值写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);//返回错误信息return null;}//6.存在,写入redisthis.set(key, r, time, unit);return r;}/*** 逻辑过期解决缓存击穿** @param id* @return*/public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1 从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2 判断是否存在(缓存未命中返回空)if (StrUtil.isBlank(shopJson)) {return null;}//3 命中的话判断是否过期RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);//实现对json的反序列化JSONObject data = (JSONObject) redisData.getData();R r = JSONUtil.toBean(data, type);//获取数据LocalDateTime expireTime = redisData.getExpireTime();//获取逻辑过期时间// 4 判断是否过期,未过期直接返回店铺数据if (expireTime.isAfter(LocalDateTime.now())) {return r;}// 5 判断是否过期,过期需要缓存重建// 5.1获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 5.2判断是否获取锁成功if (isLock) {CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 5.3将店铺数据重新写入redis并设置逻辑过期时间R newData = dbFallback.apply(id);this.setWithLogicalExpire(key, newData, time, unit);} catch (Exception e) {log.error("缓存构建失败", e);} finally {// 5.4释放互斥锁try {unLock(lockKey);} catch (Exception e) {log.error("释放锁失败");}}});}//未成功返回商铺信息return r;}/*** 尝试获取锁** @param key* @return*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return Boolean.TRUE.equals(flag);}/*** 释放锁** @param key*/private void unLock(String key) {stringRedisTemplate.delete(key);}/*** 开启一个线程池(开启一个十个容量的线程池)*/private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);// 静态代码块:注册关闭钩子static {Runtime.getRuntime().addShutdownHook(new Thread(CacheClient::shutdownExecutor));}// 关闭线程池的具体逻辑private static void shutdownExecutor() {CACHE_REBUILD_EXECUTOR.shutdown(); // 平缓关闭(等待已有任务完成)try {// 等待线程池终止,最多等待 10 秒if (!CACHE_REBUILD_EXECUTOR.awaitTermination(10, TimeUnit.SECONDS)) {CACHE_REBUILD_EXECUTOR.shutdownNow(); // 强制关闭}} catch (InterruptedException e) {log.error("关闭线程池时发生中断", e);CACHE_REBUILD_EXECUTOR.shutdownNow(); // 强制关闭Thread.currentThread().interrupt(); // 重置中断状态}log.info("线程池已关闭");}
}

1 我们首先需要将stringRedisTemplate注入(构造器注入)

    private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}

2 向Redis当中写入数据实现缓存重建(第一种是对TTL时间的缓存重建,第二种加上了逻辑过期时间的重建)

    public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 1.封装逻辑过期时间数据RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plus(time, unit.toChronoUnit()));//这种形式可以实现对多种时间单位的转换但是对JDK的版本有所要求// 2.写入redisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}// redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));

3 解决缓存穿透问题

主要修改:数据的key类型关键字,序号id值,返回数据的类型值type,查询的方法function,时间,单位。(动态参数,可以应对多种情况)

  1. 缓存查询

    • 使用 keyPrefix + id 生成 Redis 缓存键。
    • 通过 stringRedisTemplate 查询缓存数据。
  2. 缓存命中处理

    • 如果缓存值非空(StrUtil.isNotBlank(shopJson) 为 true),直接反序列化为对象返回,避免数据库查询。
  3. 空值缓存处理

    • 若缓存值为 空字符串shopJson != null 但为空串),直接返回 null,避免重复查询数据库(防止缓存击穿)。
    • 若缓存值为 null(未缓存),触发数据库查询流程。
  4. 数据库回退机制

    • 调用 dbFallback.apply(id) 查询数据库。
    • 若数据库返回 空值r == null),将空字符串写入 Redis 并设置短过期时间(CACHE_NULL_TTL),防止后续请求穿透到数据库。
    • 若数据库返回 有效数据,写入 Redis 缓存并设置指定的过期时间。
  5. 核心目标

    • 防缓存击穿:通过缓存空值("")和合理设置过期时间,避免大量并发请求直击数据库。
    • 通用性:支持泛型参数,适用于任意类型数据的缓存处理。
    • 降级容错:当缓存失效时自动回退到数据库查询,并补全缓存。
    /*** 缓存击穿解决方法** @param id* @return*/public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//这个条件是缓存当中有具体的数据(将null与""放行)// 3.存在且有具体值,返回数据return JSONUtil.toBean(shopJson, type);}//判断是否是空值if (shopJson != null) {//上面已经排除了不为空的情况。这里不为空只可能为"" (这是空串不是null)return null;}//而这里是为null的情况// 4.不存在,根据id查询数据库R r = dbFallback.apply(id);// 5.不存在,返回错误,将空值写入redisif (r == null) {//将空值写入redisstringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);//返回错误信息return null;}//6.存在,写入redisthis.set(key, r, time, unit);return r;}

4 解决缓存击穿问题(逻辑过期时间解决)

主要修改:数据的key类型关键字,序号id值,返回数据的类型值type,查询的方法function,时间,单位。(动态参数,可以应对多种情况)

1. 缓存查询与反序列化

  • 生成缓存键:使用 keyPrefix + id 组合生成 Redis 键。
  • 查询缓存:通过 stringRedisTemplate 获取 JSON 格式的缓存数据。
  • 判断缓存是否存在
    • 若缓存为空(StrUtil.isBlank(shopJson) 为 true),直接返回 null(缓存未命中)。
    • 若缓存存在,反序列化为 RedisData 对象,提取数据和逻辑过期时间expireTime)。

2. 逻辑过期判断

  • 未过期处理:若当前时间在 expireTime 之前(expireTime.isAfter(LocalDateTime.now()) 为 true),直接返回缓存数据。
  • 已过期处理:若当前时间超过 expireTime,触发缓存重建流程

3. 缓存重建(互斥锁控制)

  • 获取互斥锁
    • 使用 lockKey = RedisConstants.LOCK_SHOP_KEY + id 构造锁的键。
    • 调用 tryLock(lockKey) 尝试获取锁,确保只有一个线程进入重建流程。
  • 锁成功时
    • 提交异步任务到线程池 CACHE_REBUILD_EXECUTOR,执行以下操作:
      1. 查询数据库:调用 dbFallback.apply(id) 获取最新数据。
      2. 更新缓存:将新数据写入 Redis,并设置新的逻辑过期时间(通过 setWithLogicalExpire 方法)。
      3. 异常处理:捕获异常并记录日志,确保流程健壮性。
      4. 释放锁:无论是否成功,最终释放锁(unLock(lockKey))。
  • 锁失败时
    • 不执行重建操作,直接返回旧数据(避免并发重建)。
    /*** 逻辑过期解决缓存击穿** @param id* @return*/public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1 从redis中商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(key);// 2 判断是否存在(缓存未命中返回空)if (StrUtil.isBlank(shopJson)) {return null;}//3 命中的话判断是否过期RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);//实现对json的反序列化JSONObject data = (JSONObject) redisData.getData();R r = JSONUtil.toBean(data, type);//获取数据LocalDateTime expireTime = redisData.getExpireTime();//获取逻辑过期时间// 4 判断是否过期,未过期直接返回店铺数据if (expireTime.isAfter(LocalDateTime.now())) {return r;}// 5 判断是否过期,过期需要缓存重建// 5.1获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 5.2判断是否获取锁成功if (isLock) {CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 5.3将店铺数据重新写入redis并设置逻辑过期时间R newData = dbFallback.apply(id);this.setWithLogicalExpire(key, newData, time, unit);} catch (Exception e) {log.error("缓存构建失败", e);} finally {// 5.4释放互斥锁try {unLock(lockKey);} catch (Exception e) {log.error("释放锁失败");}}});}//未成功返回商铺信息return r;}

补充的方法

锁的释放与获取

    /*** 尝试获取锁** @param key* @return*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return Boolean.TRUE.equals(flag);}/*** 释放锁** @param key*/private void unLock(String key) {stringRedisTemplate.delete(key);}

线程池的开启与释放

    /*** 开启一个线程池(开启一个十个容量的线程池)*/private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);// 静态代码块:注册关闭钩子static {Runtime.getRuntime().addShutdownHook(new Thread(CacheClient::shutdownExecutor));}// 关闭线程池的具体逻辑private static void shutdownExecutor() {CACHE_REBUILD_EXECUTOR.shutdown(); // 平缓关闭(等待已有任务完成)try {// 等待线程池终止,最多等待 10 秒if (!CACHE_REBUILD_EXECUTOR.awaitTermination(10, TimeUnit.SECONDS)) {CACHE_REBUILD_EXECUTOR.shutdownNow(); // 强制关闭}} catch (InterruptedException e) {log.error("关闭线程池时发生中断", e);CACHE_REBUILD_EXECUTOR.shutdownNow(); // 强制关闭Thread.currentThread().interrupt(); // 重置中断状态}log.info("线程池已关闭");}

http://www.dtcms.com/wzjs/186819.html

相关文章:

  • 济南建设集团有限公司seo网页优化公司
  • 可视化网页开发互联网seo是什么意思
  • 上海做网站公司哪家好seo短视频网页入口引流下载
  • 公司制作一个网站泉州seo代理计费
  • 黔江区建设委员会网站打不开seo网站有优化培训班吗
  • 建设网站的主要流程百度网盘客服中心电话
  • 网站界面设计实训的意义广州seo站内优化
  • 网站业务怎么做网站代运营多少钱一个月
  • 无锡网站建设优化公司seo优化教程视频
  • 华润置地建设事业部网站太原自动seo
  • 如何使用网站模板网店代运营一年的费用是多少
  • b2b商城网站方案电商运营培训哪个机构好
  • 长春做网站的公司有哪些网页设计与制作代码
  • 网站开发技术包括什么网络推广渠道都有哪些
  • 织梦体育网站模板优化电池充电什么意思
  • 模板做网站优缺点网站建设介绍ppt
  • 视频网站自己怎么做的微信营销技巧
  • 免费做翻页页面的网站提高百度搜索排名工具
  • 如何制作产品网站重庆seo网络推广关键词
  • 去哪里建设自己的网站?搜索数据
  • 珠海公司做网站google建站推广
  • 北京网站建设制作哪家公司好潍坊关键词优化软件
  • 上海公司网站建设价格互联网营销师培训费用是多少
  • wordpress引入js插件seo优化网站
  • wordpress部分内容定时可见前端优化
  • 选择做印象绍兴网站的原因最近的热点新闻
  • 网站 推送长沙网红奶茶
  • 旅游电子商务网站开发商务网站如何推广
  • 杭州拱墅网站建设seo百度关键字优化
  • 网站内部结构优化关键词优化收费标准