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

吉林省住房和城乡建设厅网站官网韩国今日特大新闻

吉林省住房和城乡建设厅网站官网,韩国今日特大新闻,网站建设公司推荐5788,太原做网站的工作室文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库 常见的解决方案有两种,分别…

文章目录

  • 背景
  • 代码实现
    • 前置
      • 实体类
      • 常量类
      • 工具类
      • 结果返回类
      • 控制层
    • 缓存空对象
    • 布隆过滤器
    • 结合两种方法

背景

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

常见的解决方案有两种,分别是缓存空对象布隆过滤器

1.缓存空对象

image-20241025163728328

优点:实现简单,维护方便

缺点:额外的内存消耗、可能造成短期的不一致

2.布隆过滤器

image-20241025163737389

优点:内存占用较少,没有多余key

缺点:实现复杂、存在误判可能

代码实现

前置

这里以根据 id 查询商品店铺为案例

实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop")
public class Shop implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 商铺名称*/private String name;/*** 商铺类型的id*/private Long typeId;/*** 商铺图片,多个图片以','隔开*/private String images;/*** 商圈,例如陆家嘴*/private String area;/*** 地址*/private String address;/*** 经度*/private Double x;/*** 维度*/private Double y;/*** 均价,取整数*/private Long avgPrice;/*** 销量*/private Integer sold;/*** 评论数量*/private Integer comments;/*** 评分,1~5分,乘10保存,避免小数*/private Integer score;/*** 营业时间,例如 10:00-22:00*/private String openHours;/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime;@TableField(exist = false)private Double distance;
}

常量类

public class RedisConstants {public static final Long CACHE_NULL_TTL = 2L;public static final Long CACHE_SHOP_TTL = 30L;public static final String CACHE_SHOP_KEY = "cache:shop:";
}

工具类

public class ObjectMapUtils {// 将对象转为 Mappublic static Map<String, String> obj2Map(Object obj) throws IllegalAccessException {Map<String, String> result = new HashMap<>();Class<?> clazz = obj.getClass();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {// 如果为 static 且 final 则跳过if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {continue;}field.setAccessible(true); // 设置为可访问私有字段Object fieldValue = field.get(obj);if (fieldValue != null) {result.put(field.getName(), field.get(obj).toString());}}return result;}// 将 Map 转为对象public static Object map2Obj(Map<Object, Object> map, Class<?> clazz) throws Exception {Object obj = clazz.getDeclaredConstructor().newInstance();for (Map.Entry<Object, Object> entry : map.entrySet()) {Object fieldName = entry.getKey();Object fieldValue = entry.getValue();Field field = clazz.getDeclaredField(fieldName.toString());field.setAccessible(true); // 设置为可访问私有字段String fieldValueStr = fieldValue.toString();// 根据字段类型进行转换if (field.getType().equals(int.class) || field.getType().equals(Integer.class)) {field.set(obj, Integer.parseInt(fieldValueStr));} else if (field.getType().equals(boolean.class) || field.getType().equals(Boolean.class)) {field.set(obj, Boolean.parseBoolean(fieldValueStr));} else if (field.getType().equals(double.class) || field.getType().equals(Double.class)) {field.set(obj, Double.parseDouble(fieldValueStr));} else if (field.getType().equals(long.class) || field.getType().equals(Long.class)) {field.set(obj, Long.parseLong(fieldValueStr));} else if (field.getType().equals(String.class)) {field.set(obj, fieldValueStr);} else if(field.getType().equals(LocalDateTime.class)) {field.set(obj, LocalDateTime.parse(fieldValueStr));}}return obj;}}

结果返回类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {private Boolean success;private String errorMsg;private Object data;private Long total;public static Result ok(){return new Result(true, null, null, null);}public static Result ok(Object data){return new Result(true, null, data, null);}public static Result ok(List<?> data, Long total){return new Result(true, null, data, total);}public static Result fail(String errorMsg){return new Result(false, errorMsg, null, null);}
}

控制层

@RestController
@RequestMapping("/shop")
public class ShopController {@Resourcepublic IShopService shopService;/*** 根据id查询商铺信息* @param id 商铺id* @return 商铺详情数据*/@GetMapping("/{id}")public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryShopById(id);}/*** 新增商铺信息* @param shop 商铺数据* @return 商铺id*/@PostMappingpublic Result saveShop(@RequestBody Shop shop) {return shopService.saveShop(shop);}/*** 更新商铺信息* @param shop 商铺数据* @return 无*/@PutMappingpublic Result updateShop(@RequestBody Shop shop) {return shopService.updateShop(shop);}
}

缓存空对象

流程图为:

image-20241025165838030

服务层代码:

public Result queryShopById(Long id) {// 从 redis 查询String shopKey = RedisConstants.CACHE_SHOP_KEY + id;Map<Object, Object> entries = redisTemplate.opsForHash().entries(shopKey);// 缓存命中if(!entries.isEmpty()) {try {// 如果是空对象,表示一定不存在数据库中,直接返回(解决缓存穿透)if(entries.containsKey("")) {return Result.fail("店铺不存在");}// 刷新有效期redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);Shop shop = (Shop) ObjectMapUtils.map2Obj(entries, Shop.class);return Result.ok(shop);} catch (Exception e) {throw new RuntimeException(e);}}// 查询数据库Shop shop = this.getById(id);if(shop == null) {// 存入空值redisTemplate.opsForHash().put(shopKey, "", "");redisTemplate.expire(shopKey, RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);// 不存在,直接返回return Result.fail("店铺不存在");}// 存在,写入 redistry {redisTemplate.opsForHash().putAll(shopKey, ObjectMapUtils.obj2Map(shop));redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (IllegalAccessException e) {throw new RuntimeException(e);}return Result.ok(shop);
}

布隆过滤器

这里选择使用布隆过滤器存储存在于数据库中的 id,原因在于,如果存储了不存在于数据库中的 id,首先由于 id 的取值范围很大,那么不存在的 id 有很多,因此更占用空间;其次,由于布隆过滤器有一定的误判率,那么可能导致少数原本存在于数据库中的 id 被判为了不存在,然后直接返回了,此时就会出现根本性的正确性错误。相反,如果存储的是数据库中存在的 id,那么即使少数不存在的 id 被判为了存在,由于数据库中确实没有对应的 id,那么也会返回空,最终结果还是正确的

这里使用 guava 依赖的布隆过滤器

依赖为:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1.1-jre</version>
</dependency>

封装了布隆过滤器的类(注意初始化时要把数据库中已有的 id 加入布隆过滤器):

public class ShopBloomFilter {private BloomFilter<Long> bloomFilter;public ShopBloomFilter(ShopMapper shopMapper) {// 初始化布隆过滤器,设计预计元素数量为100_0000L,误差率为1%bloomFilter = BloomFilter.create(Funnels.longFunnel(), 100_0000, 0.01);// 将数据库中已有的店铺 id 加入布隆过滤器List<Shop> shops = shopMapper.selectList(null);for (Shop shop : shops) {bloomFilter.put(shop.getId());}}public void add(long id) {bloomFilter.put(id);}public boolean mightContain(long id){return bloomFilter.mightContain(id);}}

对应的配置类(将其设置为 bean)

@Configuration
public class BloomConfig {@Beanpublic ShopBloomFilter shopBloomFilter(ShopMapper shopMapper) {return new ShopBloomFilter(shopMapper);}}

首先要修改查询方法,在根据 id 查询时,如果对应 id 不在布隆过滤器中,则直接返回。然后还要修改保存方法,在保存的时候还需要将对应的 id 加入布隆过滤器中

@Override
public Result queryShopById(Long id) {// 如果不在布隆过滤器中,直接返回if(!shopBloomFilter.mightContain(id)) {return Result.fail("店铺不存在");}// 从 redis 查询String shopKey = RedisConstants.CACHE_SHOP_KEY + id;Map<Object, Object> entries = redisTemplate.opsForHash().entries(shopKey);// 缓存命中if(!entries.isEmpty()) {try {// 刷新有效期redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);Shop shop = (Shop) ObjectMapUtils.map2Obj(entries, Shop.class);return Result.ok(shop);} catch (Exception e) {throw new RuntimeException(e);}}// 查询数据库Shop shop = this.getById(id);if(shop == null) {// 不存在,直接返回return Result.fail("店铺不存在");}// 存在,写入 redistry {redisTemplate.opsForHash().putAll(shopKey, ObjectMapUtils.obj2Map(shop));redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (IllegalAccessException e) {throw new RuntimeException(e);}return Result.ok(shop);
}@Override
public Result saveShop(Shop shop) {// 写入数据库this.save(shop);// 将 id 写入布隆过滤器shopBloomFilter.add(shop.getId());// 返回店铺 idreturn Result.ok(shop.getId());
}

结合两种方法

由于布隆过滤器有一定的误判率,所以这里可以进一步优化,如果出现误判情况,即原本不存在于数据库中的 id 被判为了存在,就用缓存空对象的方式将其缓存到 redis 中

@Override
public Result queryShopById(Long id) {// 如果不在布隆过滤器中,直接返回if(!shopBloomFilter.mightContain(id)) {return Result.fail("店铺不存在");}// 从 redis 查询String shopKey = RedisConstants.CACHE_SHOP_KEY + id;Map<Object, Object> entries = redisTemplate.opsForHash().entries(shopKey);// 缓存命中if(!entries.isEmpty()) {try {// 如果是空对象,表示一定不存在数据库中,直接返回(解决缓存穿透)if(entries.containsKey("")) {return Result.fail("店铺不存在");}// 刷新有效期redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);Shop shop = (Shop) ObjectMapUtils.map2Obj(entries, Shop.class);return Result.ok(shop);} catch (Exception e) {throw new RuntimeException(e);}}// 查询数据库Shop shop = this.getById(id);if(shop == null) {// 存入空值redisTemplate.opsForHash().put(shopKey, "", "");redisTemplate.expire(shopKey, RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);// 不存在,直接返回return Result.fail("店铺不存在");}// 存在,写入 redistry {redisTemplate.opsForHash().putAll(shopKey, ObjectMapUtils.obj2Map(shop));redisTemplate.expire(shopKey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (IllegalAccessException e) {throw new RuntimeException(e);}return Result.ok(shop);
}

文章转载自:
http://ethmoid.rkck.cn
http://insecure.rkck.cn
http://rented.rkck.cn
http://mahatma.rkck.cn
http://masqat.rkck.cn
http://zionist.rkck.cn
http://yanomama.rkck.cn
http://banditi.rkck.cn
http://sulcus.rkck.cn
http://calamographer.rkck.cn
http://gullibility.rkck.cn
http://interionic.rkck.cn
http://paraformaldehyde.rkck.cn
http://monial.rkck.cn
http://pori.rkck.cn
http://danger.rkck.cn
http://bareheaded.rkck.cn
http://unequal.rkck.cn
http://sonuvabitch.rkck.cn
http://tripetalous.rkck.cn
http://protanope.rkck.cn
http://dishearteningly.rkck.cn
http://reefer.rkck.cn
http://alpenstock.rkck.cn
http://parodontal.rkck.cn
http://vaporisation.rkck.cn
http://caudate.rkck.cn
http://placentate.rkck.cn
http://tenotomy.rkck.cn
http://harsh.rkck.cn
http://activation.rkck.cn
http://furfural.rkck.cn
http://solitary.rkck.cn
http://mateless.rkck.cn
http://viniferous.rkck.cn
http://thumbhole.rkck.cn
http://gregarious.rkck.cn
http://phyllotaxic.rkck.cn
http://rumrunning.rkck.cn
http://gamesman.rkck.cn
http://libbie.rkck.cn
http://pulverable.rkck.cn
http://bag.rkck.cn
http://disparlure.rkck.cn
http://supermundane.rkck.cn
http://babblingly.rkck.cn
http://meningocele.rkck.cn
http://inkblot.rkck.cn
http://indistinct.rkck.cn
http://systyle.rkck.cn
http://throughway.rkck.cn
http://midnoon.rkck.cn
http://events.rkck.cn
http://dixieland.rkck.cn
http://ileac.rkck.cn
http://liveryman.rkck.cn
http://achiote.rkck.cn
http://preprocessor.rkck.cn
http://ithun.rkck.cn
http://imperceptibility.rkck.cn
http://gabardine.rkck.cn
http://noble.rkck.cn
http://aquiferous.rkck.cn
http://gingkgo.rkck.cn
http://cuzco.rkck.cn
http://ccco.rkck.cn
http://thropple.rkck.cn
http://eyed.rkck.cn
http://konk.rkck.cn
http://conformist.rkck.cn
http://fibster.rkck.cn
http://menkind.rkck.cn
http://carded.rkck.cn
http://intercoastal.rkck.cn
http://advocaat.rkck.cn
http://artifactitious.rkck.cn
http://euthyroid.rkck.cn
http://savanna.rkck.cn
http://waggonette.rkck.cn
http://jaggy.rkck.cn
http://plagiocephaly.rkck.cn
http://drawee.rkck.cn
http://unsexed.rkck.cn
http://degraded.rkck.cn
http://tamanoir.rkck.cn
http://jarovization.rkck.cn
http://poleyn.rkck.cn
http://trundle.rkck.cn
http://admetus.rkck.cn
http://component.rkck.cn
http://waterward.rkck.cn
http://perrier.rkck.cn
http://bearskinned.rkck.cn
http://rafferty.rkck.cn
http://inflective.rkck.cn
http://wallaby.rkck.cn
http://quinnat.rkck.cn
http://gentlest.rkck.cn
http://lignicolous.rkck.cn
http://spatioperceptual.rkck.cn
http://www.15wanjia.com/news/78178.html

相关文章:

  • 手机怎么创建网址链接网站seo服务商
  • 查询网站空间商网络推广文案有哪些
  • 河北网站建设服务seo专业培训学费多少钱
  • 怎么看网站的收录青岛网站设计
  • 免费 网站 平台引擎seo如何优化
  • 天成信息网站建设自助建站平台seo自动优化软件
  • 专做定制网站建设东莞海外网络推广
  • 郯城做网站谷歌搜索广告优化
  • 西部数码 空间做2个网站百度推广登录平台官网
  • 网站建设系统服务机构排名网
  • 关注公众号在哪里查找天津网站优化软件
  • 网站建设推广重要性电商平台运营方案思路
  • 施工企业岗位证书有哪些seo自学网官方
  • 购物网站搜索功能怎么做关键词分类
  • 万户做网站很垃圾市场调研方法
  • 做电影网站赚钱的方法嘉兴seo外包公司
  • wordpress 鼠标翻页网络优化师是什么工作
  • 如何做淘宝客有没有免费的网站如何做优化排名
  • 济南做网站优化足球世界排名
  • 做外贸的人如何上国外网站怎么免费制作网页
  • 哪个网站可以接针织衫做单网站seo优化软件
  • 深圳做营销型网站百度客服投诉中心
  • 专业做网站优化需要多久网络优化的内容包括哪些
  • 政府网站建设的对策线上营销手段有哪些
  • 全flash网站制作市场推广seo职位描述
  • 闵行区做网站高级搜索百度
  • 网站首页如何做浮动窗口自动推广软件
  • 如何用服务器做网站湖北seo公司
  • 好看的网页布局厦门seo优化多少钱
  • 泷澄建设集团网站网站建设制作模板