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

建筑行业信息查询平台seo搜索引擎优化技术教程

建筑行业信息查询平台,seo搜索引擎优化技术教程,建设网站案例,ssm框架做网站的优势文章目录 什么是缓存?添加Redis缓存店铺类型查询业务添加缓存练习题 缓存更新策略给查询商铺的缓存添加超时剔除和主动更新的策略 缓存穿透缓存空对象布隆过滤 缓存雪崩解决方案 缓存击穿解决方案基于互斥锁方式解决缓存击穿问题基于逻辑过期的方式解决缓存击穿问题…

文章目录

  • 什么是缓存?
  • 添加Redis缓存
    • 店铺类型查询业务添加缓存练习题
  • 缓存更新策略
    • 给查询商铺的缓存添加超时剔除和主动更新的策略
  • 缓存穿透
    • 缓存空对象
    • 布隆过滤
  • 缓存雪崩
    • 解决方案
  • 缓存击穿
    • 解决方案
    • 基于互斥锁方式解决缓存击穿问题
    • 基于逻辑过期的方式解决缓存击穿问题
  • 缓存工具封装

什么是缓存?

在这里插入图片描述

缓存也要考虑成本的问题,不是随便用的
在这里插入图片描述

添加Redis缓存

在这里插入图片描述
在这里插入图片描述

@Overridepublic Result queryById(Long id) {String redisKey = RedisConstants.CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isNotBlank(shopJson)){// 3. 存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}// 4. 不存在,根据id查询数据库Shop shop = getById(id);// 5. 不存在,写入redisif(shop == null){return Result.fail("店铺不存在!");}// 6. 存在,写入redisstringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop));// 7. 返回return Result.ok(shop);}

店铺类型查询业务添加缓存练习题

@Overridepublic Result queryTypeList() {// 1. 从redis查询店铺类别缓存List<String> shopTypeRedisKey = stringRedisTemplate.opsForList().range(RedisConstants.CACHE_SHOP_TYPE_KEY,0,-1);// 2. 判断是否命中缓存if(!CollectionUtils.isEmpty(shopTypeRedisKey)){// 3. 存在,直接返回,即是命中缓存// 使用stream流将json集合转为List<ShopType> shopTypeList = shopTypeRedisKey.stream().map(item -> JSONUtil.toBean(item, ShopType.class)).sorted(Comparator.comparingInt(ShopType::getSort)).collect(Collectors.toList());// 返回缓存数据return Result.ok(shopTypeList);}// 4. 不存在,查询数据库List<ShopType> shopTypes = query().orderByAsc("sort").list();// 判断数据库中是否有数据if(CollectionUtils.isEmpty(shopTypes)){// 不存在则缓存一个空集合,解决缓存穿透stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_TYPE_KEY, Collections.emptyList().toString(),RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);return Result.fail("商品分类信息为空");}// 5. 数据存在,先写入redis,再返回// 使用stream流将bean集合转为json集合List<String> shopTypeCache = shopTypes.stream().sorted(Comparator.comparingInt(ShopType::getSort)).map(item -> JSONUtil.toJsonStr(item)).collect(Collectors.toList());stringRedisTemplate.opsForList().rightPushAll(RedisConstants.CACHE_SHOP_TYPE_KEY,shopTypeCache);stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_TYPE_KEY,RedisConstants.CACHE_SHOP_TYPE_TTL, TimeUnit.MINUTES);// 6. 返回(按类别升序排序)return Result.ok(shopTypes);}

缓存更新策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
操作缓存和数据库的顺序,不论谁先进行都可能会有线程安全的问题
在这里插入图片描述

但方案二的发生可能性更小,所以更优
总结:
在这里插入图片描述

给查询商铺的缓存添加超时剔除和主动更新的策略

在这里插入图片描述
查询店铺:

  @Overridepublic Result queryById(Long id) {String redisKey = RedisConstants.CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isNotBlank(shopJson)){// 3. 存在,直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}// 4. 不存在,根据id查询数据库Shop shop = getById(id);// 5. 不存在,返回错误if(shop == null){return Result.fail("店铺不存在!");}// 6. 存在,写入redisstringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 7. 返回return Result.ok(shop);}

修改店铺:

@Override@Transactionalpublic Result update(Shop shop) {Long id = shop.getId();if(id == null){return Result.fail("店铺id不能为空");}// 更新数据库,在删除缓存updateById(shop);// 删除缓存stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);return Result.ok();}

缓存穿透

客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库

在这里插入图片描述

缓存空对象

在这里插入图片描述
可以设置一个TTL,解决内存消耗问题
可能存在短期不一致的问题,控制TTL的时间,可以一定程度的缓解这个问题。

布隆过滤

客户端个redis之间,在加一层过滤——布隆过滤器——哈希算法二进制位保存数据
布隆过滤器说如果不存在一定是不存在,但存在不一定是100% 的
在这里插入图片描述
先看一下之前查询商铺信息的业务流程
在这里插入图片描述
物品们采用方案一应该把空数据写入redis

在这里插入图片描述
在这里插入图片描述

缓存雪崩

在这里插入图片描述

解决方案

  • 给不同的key的TTL添加随机值——针对问题一
  • 利用redis集群提高服务的可用性——针对问题二
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

缓存击穿

在这里插入图片描述

解决方案

互斥锁和逻辑过期
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
获取锁:
- redis的setnx指令可以在key不存在的时候写,存在的时候不能写,就类似于互斥
释放锁:
- 删掉就行了
设置锁的时候要设置有效期,避免因为某种原因锁得不到释放

 @Overridepublic Result queryById(Long id) {// 缓存穿透
//        Shop shop = queryWithPassThrough(id);// 互斥锁解决缓存击穿Shop shop = queryWithMutex(id);if(shop == null){return Result.fail("店铺不存在!");}// 7. 返回return Result.ok(shop);}/*** 解决缓存击穿(互斥锁)的写法* @param id* @return*/public Shop queryWithMutex(Long id){String redisKey = RedisConstants.CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isNotBlank(shopJson)){// 3. 存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}// 命中的是否是空值if(shopJson != null){// 返回一个错误信息return null;}//4. 开始实现缓存重建// 4.1 获取互斥锁String lockKey = "lock:shop:" + id;Shop shop = null;try{boolean isLock = tryLock(lockKey);// 4.2 判断是否获取成功if(!isLock){// 4.3 如果失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}// 4.4 如果成功,根据id查询数据库shop = getById(id);// 模拟重建的延时——测试的时候打开
//            Thread.sleep(200);// 5. 不存在,返回错误if(shop == null){// 将空值写入redis——解决缓存穿透stringRedisTemplate.opsForValue().set(redisKey,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);// 返回错误信息return null;}// 6. 存在,写入redisstringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e){throw new RuntimeException(e);}finally {// 释放互斥锁unLock(lockKey);}// 7. 返回return shop;}private boolean tryLock(String key){Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);  // 因为flag是封装类,而要求的返回值是基本数据类型,在返回的时候就会进行自动的拆箱,拆箱的时候会出现空指针}private void unLock(String key){stringRedisTemplate.delete(key);}

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

在这里插入图片描述

有个小问题,我们想要给存入redis的数据添加过期时间,但是我们的Shop实体类中又没有过期时间这个字段怎么办呢?
我们去给这个Shop实体添加过期时间字段可行吗?可行,但是对代码有侵入性,而且这个字段除了这里其他地方都用不到。
那怎么办?
我们可以声明一个RedisData的实体类,里面有一个过期时间的属性,让Shop继承这个实体类,Shop也就有了过期时间的属性了,但还是有一点点不好,还是需要修改源代码,需要修改Shop,有一定的侵入性,虽然也蛮好的。
还有一种方案:在RedisData中在声明一个Object的字段,把想要存储的数据放到Object中。

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

实际的项目肯定会有管理系统在后台点击,把热点数据提前缓存进redis,我们这里用一个单元测试完成这个功能。
先写一个缓存进redis的方法

    public void saveShop2Redis(Long id, Long expireSeconds){// 1. 查询店铺数据Shop shop = getById(id);// 2. 封装逻辑过期时间RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));///3.写入redisstringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}

在编写一个单元测试

@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate ShopServiceImpl shopService;@Testvoid testSaveShop() {shopService.saveShop2Redis(1L, 10L);}}

下面我们完成基于逻辑过期的方式解决缓存击穿的商铺查询的代码

// 使用线程池来开辟新线程private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 解决缓存击穿(逻辑过期)的写法* @param id* @return*/public Shop queryWithLogicalExpire(Long id){String redisKey = RedisConstants.CACHE_SHOP_KEY + id;// 1. 从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isBlank(shopJson)){// 3. 不存在,直接返回return null;}// 4. 命中需要判断过期时间,需要先把json反序列化位对象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);JSONObject jsonData = (JSONObject) redisData.getData(); // 如果不强转就是一个Object,但本质上是JSONObject,所以先转成JSONObjectShop shop = JSONUtil.toBean(jsonData, Shop.class);LocalDateTime expireTime = redisData.getExpireTime();// 5. 判断是否过期if(expireTime.isAfter(LocalDateTime.now())){// 5.1 未过期,直接返回店铺信息return shop;}// 5.2 已过期,需要缓存重建// 6. 缓存重建// 6.1 获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2 判断是否获取锁成功if(isLock){//6.3成功 开启独立线程实现缓存重建CACHE_REBUILD_EXECUTOR.submit(()->{try {// 重建缓存this.saveShop2Redis(id,20L);}catch (Exception e){} finally {// 释放锁unLock(lockKey);}});}// 6.4 返回过期的商铺信息return shop;}

缓存工具封装

在这里插入图片描述
把封装的代码放到CacheClient这个类中,并添加@Component注解,把这个bean交给Spring管理,封装的工具类如下:


@Slf4j
@Component
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){// 设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入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 redisKey = keyPrefix + id;// 1. 从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isNotBlank(json)){// 3. 存在,直接返回return JSONUtil.toBean(json, type);}// 命中的是否是空值if(json != null){// 返回一个错误信息return null;}// 4. 不存在,根据id查询数据库——我们哪知道去查哪个数据库,只能调用者告诉我们,——函数式编程R r = dbFallback.apply(id);// 5. 不存在,返回错误if(r == null){// 将空值写入redis——解决缓存穿透stringRedisTemplate.opsForValue().set(redisKey,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);// 返回错误信息return null;}// 6. 存在,写入redisthis.set(redisKey, r, time, unit);// 7. 返回return r;}private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String redisKey = keyPrefix + id;// 1. 从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(redisKey);// 2. 判读是否存在if(StrUtil.isBlank(json)){// 3. 不存在,直接返回return null;}// 4. 命中需要判断过期时间,需要先把json反序列化位对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);JSONObject jsonData = (JSONObject) redisData.getData(); // 如果不强转就是一个Object,但本质上是JSONObject,所以先转成JSONObjectR r = JSONUtil.toBean(jsonData, type);LocalDateTime expireTime = redisData.getExpireTime();// 5. 判断是否过期if(expireTime.isAfter(LocalDateTime.now())){// 5.1 未过期,直接返回店铺信息return r;}// 5.2 已过期,需要缓存重建// 6. 缓存重建// 6.1 获取互斥锁String lockKey = RedisConstants.LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2 判断是否获取锁成功if(isLock){//6.3成功 开启独立线程实现缓存重建CACHE_REBUILD_EXECUTOR.submit(()->{try {// 重建缓存// 先查数据库R r1 = dbFallback.apply(id);// 写入redisthis.setWithLogicalExpire(redisKey, r1, time, unit);}catch (Exception e){} finally {// 释放锁unLock(lockKey);}});}// 6.4 返回过期的商铺信息return r;}private boolean tryLock(String key){Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);  // 因为flag是封装类,而要求的返回值是基本数据类型,在返回的时候就会进行自动的拆箱,拆箱的时候会出现空指针}private void unLock(String key){stringRedisTemplate.delete(key);}}

封装这个工具类,有很多的技巧要总结:

  1. 传递的参数和返回的数据类型要泛型
  2. 函数式编程:在封装queryWithPassThrough的时候,里面在redis查询不存在的时候,我们要去查询数据库,那查询数据库的代码,我们泛型传递的参数,调用哪个查询数据库的函数去查询数据库呢?这时要用函数式编程,把要用到的函数通过参数传递过来,有参数有返回值就用Function<ID, R> dbFallback,使用的时候直接R r = dbFallback.apply(id);即可,调用这个工具方法的时候把具体的查询函数作为参数传进去。

那这些工具类在调用的时候又该怎么调用呢?

  @Overridepublic Result queryById(Long id) {// 缓存穿透
//        Shop shop = cacheClient.queryWithPassThrough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);// 逻辑过期解决缓存击穿问题Shop shop = cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);if(shop == null){return Result.fail("店铺不存在!");}// 7. 返回return Result.ok(shop);}

那我们的缓存击穿想测试的话,还是得先用单元测试的方法,先往redis中写入点热点数据,现在就可以改进我们的单元测试代码


@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate CacheClient cacheClient;@Testvoid testSaveShop() {Shop shop = shopService.getById(1L);cacheClient.setWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY + 1L,shop,10L, TimeUnit.SECONDS);}
}

文章转载自:
http://symbion.zydr.cn
http://lacrymal.zydr.cn
http://unconfident.zydr.cn
http://gemmologist.zydr.cn
http://seroconvert.zydr.cn
http://omniphibious.zydr.cn
http://twitter.zydr.cn
http://disemployment.zydr.cn
http://skua.zydr.cn
http://peltate.zydr.cn
http://isoprenoid.zydr.cn
http://reemergence.zydr.cn
http://yellowtop.zydr.cn
http://acrobatic.zydr.cn
http://gettable.zydr.cn
http://cosmetic.zydr.cn
http://teleocracy.zydr.cn
http://terneplate.zydr.cn
http://manager.zydr.cn
http://spanless.zydr.cn
http://loyalize.zydr.cn
http://brumous.zydr.cn
http://squarely.zydr.cn
http://gpt.zydr.cn
http://sepal.zydr.cn
http://dolmus.zydr.cn
http://weatherwise.zydr.cn
http://nestful.zydr.cn
http://cathleen.zydr.cn
http://seizable.zydr.cn
http://psycholinguist.zydr.cn
http://the.zydr.cn
http://retrofocus.zydr.cn
http://probable.zydr.cn
http://janitor.zydr.cn
http://barrow.zydr.cn
http://woodenly.zydr.cn
http://ancestral.zydr.cn
http://necromancer.zydr.cn
http://vulpine.zydr.cn
http://embrocation.zydr.cn
http://orchotomy.zydr.cn
http://homeplace.zydr.cn
http://villain.zydr.cn
http://virelay.zydr.cn
http://ravenna.zydr.cn
http://caesaropapist.zydr.cn
http://sigmatropic.zydr.cn
http://rheologic.zydr.cn
http://itr.zydr.cn
http://zizit.zydr.cn
http://ratty.zydr.cn
http://testaceous.zydr.cn
http://nadir.zydr.cn
http://aplomb.zydr.cn
http://mosasaurus.zydr.cn
http://quatercentenary.zydr.cn
http://xenophobia.zydr.cn
http://fgetchar.zydr.cn
http://puttee.zydr.cn
http://quantitate.zydr.cn
http://hempy.zydr.cn
http://permillage.zydr.cn
http://savey.zydr.cn
http://finicky.zydr.cn
http://ballad.zydr.cn
http://apophatic.zydr.cn
http://estivation.zydr.cn
http://beanie.zydr.cn
http://lintwhite.zydr.cn
http://antiblack.zydr.cn
http://revengeful.zydr.cn
http://chanson.zydr.cn
http://capacitate.zydr.cn
http://monaker.zydr.cn
http://display.zydr.cn
http://electrodiagnosis.zydr.cn
http://incrassation.zydr.cn
http://nursing.zydr.cn
http://rieka.zydr.cn
http://muskeg.zydr.cn
http://lichenometric.zydr.cn
http://pottage.zydr.cn
http://mountain.zydr.cn
http://coreligionist.zydr.cn
http://skiascopy.zydr.cn
http://washiness.zydr.cn
http://basil.zydr.cn
http://cheiloplasty.zydr.cn
http://nonlinear.zydr.cn
http://bluebeard.zydr.cn
http://superparasite.zydr.cn
http://croma.zydr.cn
http://fdt.zydr.cn
http://glacis.zydr.cn
http://photoelectroluminescence.zydr.cn
http://insured.zydr.cn
http://jibboom.zydr.cn
http://ochratoxin.zydr.cn
http://licet.zydr.cn
http://www.dt0577.cn/news/99826.html

相关文章:

  • 容桂医疗网站建设宁德市安全教育平台
  • 注册域名后怎么做网站经典软文案例50字
  • 河南省网站建设中山谷歌推广
  • 网站建设业务怎么做广州seo优化推广
  • 华为公司网站建设相关内容电商平台推广方式有哪些
  • 东阳市建设规划局网站公司网站与推广
  • 西宁网站建设模板网络工程师培训机构排名
  • 网站开发工作室广告公司招聘
  • 网站改版阿里云怎么做网站301定向企业网站管理系统怎么操作
  • 网页设计实验报告用什么格式嘉峪关seo
  • 网站上地图是怎样做的百度人工客服
  • 足球网站怎么做手机百度搜索app
  • 网站备案委托书怎么创建自己的网站平台
  • 济南做公司网站seo相关ppt
  • 网站建设的新闻动态怎么在百度上推广自己的产品
  • 济阳做网站公司yandere搜索引擎入口
  • jira confluence做网站营销手段有哪些方式
  • 中国人做外贸网站都卖什么长沙网络推广外包
  • 在线制作钓鱼网站源码西安百度竞价外包
  • 北京建设工程交易信息网官网河北百度seo软件
  • 如何在网站做推广做网站优化哪家公司好
  • 表单大师做网站磁力宅在线搜种子
  • 优质网站建设的设计要点优化百度seo技术搜索引擎
  • 移动app与网站建设的区别分销平台
  • 有没有专门做翻译的网站代运营电商公司排行榜
  • 建网站需要什么资质活动推广宣传方案
  • 福鼎整站优化福州网站开发公司
  • wordpress第三方支付插件seo石家庄
  • 简单网站建设设计品牌网络seo方案外包
  • 新手做网站推荐济南seo培训