网站里的地图定位怎么做的,多语言网站建设推广,浙江省网站备案注销申请表,如何做php游戏介绍网站文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在#xff0c;这样缓存永远不会生效#xff0c;这些请求都会打到数据库
常见的解决方案有两种#xff0c;分别… 文章目录 背景代码实现前置实体类常量类工具类结果返回类控制层 缓存空对象布隆过滤器结合两种方法 背景
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在这样缓存永远不会生效这些请求都会打到数据库
常见的解决方案有两种分别是缓存空对象和布隆过滤器
1.缓存空对象 优点实现简单维护方便
缺点额外的内存消耗、可能造成短期的不一致
2.布隆过滤器 优点内存占用较少没有多余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 MapString, String obj2Map(Object obj) throws IllegalAccessException {MapString, 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(MapObject, Object map, Class? clazz) throws Exception {Object obj clazz.getDeclaredConstructor().newInstance();for (Map.EntryObject, 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);}
}缓存空对象
流程图为 服务层代码
public Result queryShopById(Long id) {// 从 redis 查询String shopKey RedisConstants.CACHE_SHOP_KEY id;MapObject, 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 依赖的布隆过滤器
依赖为
dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion30.1.1-jre/version
/dependency封装了布隆过滤器的类注意初始化时要把数据库中已有的 id 加入布隆过滤器
public class ShopBloomFilter {private BloomFilterLong bloomFilter;public ShopBloomFilter(ShopMapper shopMapper) {// 初始化布隆过滤器设计预计元素数量为100_0000L误差率为1%bloomFilter BloomFilter.create(Funnels.longFunnel(), 100_0000, 0.01);// 将数据库中已有的店铺 id 加入布隆过滤器ListShop 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;MapObject, 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;MapObject, 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);
}