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

怎样做建网站做淘客郑州网站开发培训班

怎样做建网站做淘客,郑州网站开发培训班,小型企业网络设计方案报告,充值中心网站怎么做文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景 缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库 常见的解决方案有两种,分别…

文章目录

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

背景

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

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

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://www.yayakq.cn/news/399113/

相关文章:

  • 百度网盘搜索引擎网站手机贷款注册
  • 做外贸网站的经验wordpress 手册
  • 百度地图手机网站开发建筑工程包括哪些项目
  • 苏州市建设局网站阜阳讯拓网站建设
  • 程序员找工作网站数字广东网络建设有限公司是国企
  • 宁波网站备案幕布拍照最新搜索引擎排名
  • 做网站想要中立东莞是什么风险等级
  • 备案 网站建设方案书可以用自己的电脑做网站主机
  • 昌平电子网站建设全网引流推广
  • 网站转载代码arial 网站开发是用犀利
  • 建设银行网站能不能注销卡凡科官网免费制作小程序
  • 运营和广告投放的区别seo关键词排名优化怎样
  • 网站建设服务合同 印花税百度推广管理
  • 品牌网站建设968常州网约车驾驶员资格证网上报名
  • 自助建站源码php屏蔽阿里云网站吗
  • 深圳网络营销全网推广抖音优化
  • 自己建设网站流程icp备案查看网站内容吗
  • 网站开发的目的相关书籍网址缩短在线生成app
  • 网站建设答辩ppt模板组织建设是什么意思
  • 三雷网站程序微信商城怎么开通教程
  • 网站底部连接怎么做马来西亚服务器租用
  • 广州专业网站改版设计公司百度收录入口提交查询
  • 重庆网站建设公司价钱德州做网站最好的公司有哪些
  • php网站开发环境配置合同无效的12种情形
  • 网站类型的销售wordpress tag分类
  • 合肥做网站123cms徐州建立网站
  • 做配电箱的专门网站网站开发的英文书有什么软件
  • 服务 好的网站制作遵义软件制作平台
  • 怎么整理网站杭州百度seo
  • 如何上传网站程序网站建设背景及目的