商户查询
- 缓存(Cache):就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,缓存数据在内存中,内存的读写性能完全高于磁盘,使用缓存可以大大降低用户访问并发量带来的服务器读写压力。当数据量较大时,如果没有缓存来作为“避震器(防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪)”,系统很难支撑。
数据库直接查询
-
在没有缓存时,查询商户信息,我们直接操作从数据库中去进行查询,但是从数据库中查询肯定是个耗时操作。如下通过id在数据库中查询商铺。
- controller层
@RestController @RequestMapping("/shop") public class ShopController { @Resource public IShopService shopService; /** * 根据id查询商铺信息 * @param id 商铺id * @return 商铺详情数据 */ @GetMapping("/{id}") public Result queryShopById(@PathVariable("id") Long id) { Shop shop = shopService.getShopById(id); if (ObjectUtil.isNull(shop)){ return Result.fail("商铺不存在"); } return Result.ok(shop); } }
- service层
public interface IShopService extends IService<Shop> { Shop getShopById(Long id); }
@Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Override public Shop getShopById(Long id) { return this.getById(id); } }
缓存查询
-
缓存模型和思路
- 客户端查询数据
- 先在缓存中查询
- 缓存中存在,从缓存中返回
- 缓存中不存在。查询数据库,写入缓存并返回
- 先在缓存中查询
- 客户端查询数据
- 根据Id查询商铺信息
-
service层
@Service public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService { @Resource StringRedisTemplate stringRedisTemplate; @Override public Shop getShopById(Long id) { //组装redis中的key String cacheShopKey = CACHE_SHOP_KEY + id; //根据ID在redis中查询商铺信息 String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey); //redis中查询到商铺信息 if (StrUtil.isNotBlank(shopString)){ Shop shop = BeanUtil.toBean(shopString, Shop.class); return shop; } //根据商铺id查询商铺信息 Shop shop = this.getById(id); //数据库中没查询到该商铺信息 if (ObjectUtil.isNull(shop)){ return null; } //数据库中查询到了该商铺信息 stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop)); //返回给商铺信息 return shop; } }
-
增加相关常量
/** * redis中缓存商铺信息 */ public static final String CACHE_SHOP_KEY = "cache:shop:";
缓存更新
-
缓存更新是reids为了节约内存而设计的,主要是因为内存数据宝贵,当向redis插入太多数据,可能会导致缓存中的数据过多,所有redis会对部门数据进行更新(也许叫淘汰更合适)。
- 内存淘汰:redis自动更新,当redis内存叨叨我们设定的max-memery时,会自动出发淘汰机制,淘汰掉一些不重要的数据化(二阳自己设置策略方式)
- 超时剔除:为redis存储的数据设置过期时间(TTL),redis会将超时的数据进行删除
- 主动更新:活动调用方法删除缓存,通常用于解决缓存和数据库不一致问题
策略 | 内存淘汰 | 超时剔除 | 主动更新 |
---|---|---|---|
说明 | redis利用redis的内存淘汰机制自动维护,当内存不足时,自动淘汰部分数据,下次查询时更新缓存 | 为redis数据添加TTL时间,到期后redis自动删除,下次查询时更新缓存 | 开发人员编写业务逻辑,在修改数据库的同时,更新缓存。 |
一致性 | 差 | 一般 | 好 |
维护成本 | 无 | 低 | 高 |
-
使用场景
-
低一致性需求:使用内存淘汰机制。例如商铺类型查询缓存
-
查询商铺类型信息
public interface IShopTypeService extends IService<ShopType> { List<ShopType> queryTypeList(); }
@Service public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public List<ShopType> queryTypeList() { //从redis中获取缓存数据 Long size = stringRedisTemplate.opsForList().size(CACHE_SHOP_TYPE_KEY); //从redis中能够获取商铺类型数据 if (size > 0){ List<String> shopTypeListStr = stringRedisTemplate.opsForList().range(CACHE_SHOP_TYPE_KEY, 0, size); //将字符串类型转换为ShopType对象 List<ShopType> shopTypeList = shopTypeListStr.stream().map(shopTypeStr -> JSONUtil.toBean(shopTypeStr, ShopType.class)).collect(Collectors.toList()); return shopTypeList; } // 从redis中没有查询到商铺类型信息,那么去数据库中查询 List<ShopType> shopTypeList = this.list(new LambdaQueryWrapper<ShopType>().orderByAsc(ShopType::getSort)); // 数据库中有商铺类型信息 if (ObjectUtil.isNotNull(shopTypeList) && shopTypeList.size() > 0){ //缓存到redis中 List<String> shopTypeJsonList = shopTypeList.stream().map(shopType -> JSONUtil.toJsonStr(shopType)).collect(Collectors.toList()); stringRedisTemplate.opsForList().rightPushAll(CACHE_SHOP_TYPE_KEY, shopTypeJsonList); return shopTypeList; } return null; } }
@RestController @RequestMapping("/shopType") public class ShopTypeController { @Resource private IShopTypeService typeService; @GetMapping("list") public Result queryTypeList() { List<ShopType> typeList = typeService.queryTypeList(); return ObjectUtil.isNull(typeList) ? Result.fail("没有查询到商铺类型"): Result.ok(typeList); } }
-
-
高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询缓存
-
数据库缓存不一致解决方案
由于Redis缓存数据来源于数据库,当数据库中的数据发生变化时,如果当数据库中数据发生变化,Redis缓存却没有同步,此时就会出现数据一致性问题,可能会导致用户使用缓存中的过时数据,就会产生类型多线程数据安全问题。
- 解决方案:
- Cache Aside Pattern 人工编码方式:由缓存调用者在更新数据库的同时更新缓存,也称为双写方案
- Read/Write Through Pattern:缓存和数据库整合为一个服务,数据库和缓存的问题交由系统本身处理
- Write Behind Caching Pattern:调用者只操作缓存,其他线程去异步处理数据库,实现最终一致性
经综合考虑,一般采用方案一,采用方案一时,需要考虑的问题
-
删除缓存还是更新缓存
- 更新缓存:每次更新数据都更新缓存,无效写操作较多
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存
应该采用删除缓存,如果采用更新缓存,那么每次操作数据库之后,都要进行缓存更新,如果在反复操作数据库的过程中,没有人进行过查询操作,那么可以认为这些更新缓存的操作,只要最后一次是有效的,其他的都是无用功,没什么意义,所有我们可以把缓存进行删除,等待再次查询时,在进行缓存更新
-
需要保证缓存与数据库的操作的同时成功和失败
- 单体系统:将缓存与数据库操作放在一个事务
- 分布式系统:利用TCC等分布式事务方案
-
先操作缓存还是先操作数据库
-
先删除缓存,再操作数据库
-
先操作数据库,再删除缓存
应该先操作数据库,在删除缓存,因为我们先删除缓存,在操作数据库,假设两个线程并发访问时,线程1先进入,它先删除了缓存,还没操作数据库呢,线程2进来进行查询,它查询缓存数据并不存在,于是它从数据库中获取数据,并写入缓存,当线程2写入缓存后,线程1才完成数据库的更新操作,那么这个时候,数据库的数据是新数据,缓存的数据还是旧数据,会造成数据不一致问题。
-
总结:
-
缓存更新策略的最佳实践方案为:
-
① 低一致性需求:使用Redis自带的内存淘汰机制;
-
② 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作
- 先写数据库,然后在删除缓存
- 需要确保数据库与缓存操作的原子性
- 读操作
-
实现商铺的缓存与数据库双写一致
-
分析:
-
根据上面总结的读操作,需要修改根据ID查询商铺信息,
- 缓存命中则直接返回
- 缓存未命中,则进行数据库查询,并将数据库查询结果写入缓存,并设置超时时间
/** * redis中缓存商铺信息 */ public static final String CACHE_SHOP_KEY = "cache:shop:"; /** * redis中缓存商铺信息的有效时间 */ public static final Long CACHE_SHOP_TTL = 30L;
@Override public Shop getShopById(Long id) { //组装redis中的key String cacheShopKey = CACHE_SHOP_KEY + id; //根据ID在redis中查询商铺信息 String shopString = stringRedisTemplate.opsForValue().get(cacheShopKey); //redis中查询到商铺信息 if (StrUtil.isNotBlank(shopString)){ Shop shop = BeanUtil.toBean(shopString, Shop.class); return shop; } //根据商铺id查询商铺信息 Shop shop = this.getById(id); //数据库中没查询到该商铺信息 if (ObjectUtil.isNull(shop)){ return null; } //数据库中查询到了该商铺信息,写入缓存,并设置有效时间为30分钟 stringRedisTemplate.opsForValue().set(cacheShopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES); //返回给商铺信息 return shop; }
-
根据上面总结的写操作,需要编写根据ID更新店铺信息文章来源:https://www.toymoban.com/news/detail-527799.html
- 根据ID更新店铺信息时,先修改数据库,再删除缓存,并确保操作数据库和操作缓存的原子性
/** * 更新商铺信息 * @param shop 商铺数据 * @return 无 */ @PutMapping public Result updateShop(@RequestBody Shop shop) { // 写入数据库 if (ObjectUtil.isNull(shop.getId())){ return Result.fail("店铺Id不能为空"); } shopService.updateShopById(shop); return Result.ok(); }
void updateShopById(Shop shop);
/** * 根据id更新商铺信息 * @param shop */ @Transactional //通过事务,来保证数据库更新和缓存删除的一致性 @Override public void updateShopById(Shop shop) { this.updateById(shop); stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId()); }
-
本文由mdnice多平台发布文章来源地址https://www.toymoban.com/news/detail-527799.html
到了这里,关于Redis实战——商户查询(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!