缓存的定义
缓存(Cache),就是数据互换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码。防止过高的数据访问猛冲体系,导致其操纵线程无法及时处理信息而瘫痪,这在实际开辟中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技能,redis作为最常用的缓存中间件,也是面试的高频考点。
使用缓存的目的
缓存数据存储于代码中,而代码运行在内存中,内存的读写性能远高于磁盘,缓存可以大大降低用户访问并发量带来的服务器读写压力。实际开辟过程中,企业的数据量,少则几十万,多则几千万,这么大数据量,如果没有缓存,体系是几乎撑不住的,所以企业会大量运用到缓存技能。
怎样使用缓存
实际开辟中,会构筑多级缓存来使体系运行速率进一步提升,例如:本地缓存与redis中的缓存并发使用
欣赏器缓存:主要是存在于欣赏器端的缓存
应用层缓存:可以分为tomcat本地缓存,比如之前提到的map,或者是使用redis作为缓存
数据库缓存:在数据库中有一片空间是 buffer pool,增改查数据都会先加载到mysql的缓存中
CPU缓存:当代计算机最大的题目是 cpu性能提升了,但内存读写速率没有跟上,所以为了顺应当下的环境,增加了cpu的L1,L2,L3级的缓存
缓存商店信息
商店信息接口具有很高的并发量,查询数据不能每次都从数据库查询,要将商店数据缓存到redis中,来应对高并发
- @GetMapping("/{id}")
- public Result queryShopById(@PathVariable("id") Long id) {
- //这里是直接查询数据库
- return shopService.queryById(id);
- }
复制代码
缓存模子和思绪
标准的操纵方式就是查询数据库之前先查询缓存,如果缓存数据存在,则直接从缓存中返回,如果缓存数据不存在,再查询数据库,然后将数据存入redis。
代码实现
留意此时缓存数据不设置过期时间,为了减轻数据库压力,缓存应该常驻在内存中,但也会带来一个那就是缓存数据与数据库数据不划一的题目,这就引出了缓冲更新策略这一题目
- @Override
- public Result queryById(Long id) {
- //根据业务代码组装key
- String key = CACHE_SHOP_KEY + id;
- //从redis中获取商铺信息
- String shopJson = stringRedisTemplate.opsForValue().get(key);
- if (StrUtil.isNotBlank(shopJson)) {
- //将json转化为shop对象直接返回
- Shop shop = JSONUtil.toBean(shopJson, Shop.class);
- return Result.ok(shop);
- }
- Shop shop = getById(id);
- if (shop == null) {
- return Result.fail("店铺不存在");
- }
- //将数据库查询的数据写入缓存
- stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
- //返回
- return Result.ok(shop);
复制代码 缓存更新策略
更新策略主要根据业务来选择,在本项目中采用的是自动更新+超时剔除的更新策略,超时剔除主要是作为保底的更新策略,保证缓存在没有触发自动更新的环境下,每隔一段时间就会清理缓存
缓存更新是redis为了节约内存而计划出来的一个东西,主要是因为内存数据名贵,当我们向redis插入太多数据,此时就可能会导致缓存中的数据过多,所以redis会对部分数据举行更新,或者把他叫为淘汰更符合。
内存淘汰:redis自动举行,当redis内存达到咱们设定的max-memery的时间,会自动触发淘汰机制,淘汰掉一些不重要的数据(可以本身设置策略方式)
超时剔除:当我们给redis设置了过期时间ttl之后,redis会将超时的数据举行删除,方便咱们继续使用缓存
自动更新:我们可以手动调用方法把缓存删掉,通常用于解决缓存和数据库不划一题目
数据库缓存数据不划一解决方案
由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步,此时就会有划一性题目存在,其后果是:
用户使用缓存中的过时数据,就会产生类似多线程数据安全题目,从而影响业务,产品口碑等;怎么解决呢?有如下几种方案
Cache Aside Pattern 人工编码方式:缓存调用者在更新完数据库后再去更新缓存,也称之为双写方案
Read/Write Through Pattern : 由体系本身完成,数据库与缓存的题目交由体系本身去处理
Write Behind Caching Pattern :调用者只操纵缓存,其他线程去异步处理数据库,实现最终划一
使用方案二增加了体系复杂度,倒霉于调用者排查有关题目,方案三会有一系列线程安全,造成数据库缓存不划一的环境,经过综合考虑选用人工编码的方式较为稳妥
人工编码步骤
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存,(如果是更新数据库的同时,更新缓存,会有太多更新动作,无法保证性能)
- 在单体体系中,将缓存与数据库操纵放在一个事务,保证更新数据库成功时,缓存也要添加成功,即保证两个操纵同时成功或失败
- 先操纵数据库,再删除缓存,在多线程的环境下,操纵数据库的时间要比操纵redis缓存的时间多得多,出现数据库写完,缓存失效的可能性较小
实现商店和缓存与数据库双写划一
- 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
- 根据id修改店铺时,先修改数据库,再删除缓存
添加缓存时设置redis缓存时添加过期时间
- @Override
- public Result queryById(Long id) {
- //根据业务代码组装key
- String key = CACHE_SHOP_KEY + id;
- //从redis中获取商铺信息
- String shopJson = stringRedisTemplate.opsForValue().get(key);
- if (StrUtil.isNotBlank(shopJson)) {
- //将json转化为shop对象直接返回
- Shop shop = JSONUtil.toBean(shopJson, Shop.class);
- return Result.ok(shop);
- }
- Shop shop = getById(id);
- if (shop == null) {
- return Result.fail("店铺不存在");
- }
- //将数据库查询的数据写入缓存,并设置过期时间
- stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), 30L,TimeUnit.MINUTES);
- //返回
- return Result.ok(shop);
- }
复制代码 我们确定了采用删除策略,来解决双写题目,当我们修改了数据之后,然后把缓存中的数据举行删除,查询时发现缓存中没有数据,则会从mysql中加载最新的数据,从而避免数据库和缓存不划一的题目,此方法必要加@Transactional注解来声明事务
- @Transactional
- @Override
- public Result update(Shop shop) {
- Long id = shop.getId();
- //判断id是否为空,因为可以绕过前端直接发送请求,此步必须判断
- if (id == null) {
- return Result.fail("店铺id不能为空");
- }
- //更新数据库
- updateById(shop);
- //删除缓存
- stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
- return Result.ok();
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |