谷粒商城-缓存使用&分布式锁&SpringCache(5天)

这篇具有很好参考价值的文章主要介绍了谷粒商城-缓存使用&分布式锁&SpringCache(5天)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

缓存使用

1.1.1 哪些数据适合放入缓存
即时性、 数据一致性要求不高的
访问量大且更新频率不高的数据(读多, 写少)
例如:电商类应用, 商品分类, 商品列表等适合缓存
本地缓存
使用Map进行本地缓存
本地缓存在分布式下的问题
集群下的本地缓存不共享,存在于jvm中【并且负载均衡到新的机器后会重新查询】
数据一致性:如果一台机器修改了数据库+缓存,但是集群下其他机器的缓存未修改所以分布式情况下不使用本地缓存

redis的应用三级分类业务实现缓存

查询缓存中是否有数据
String catelogJSON = redisTemplate.opsForValue().get(“catelogJSON”);
将分类存为JSON数据,因为JSON数据是全平台兼容的
如何理解redis中的序列化和反序列化
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式

如何使对象保存到redis

1.使用序列化方法 这需要对象定义时实现Serializable接口
2.将对象数据使用 转换为Json数据
Json.toJsonString()

RedisTemplate底层原理

redis对外内存溢出问题解决

springboot2.0以后默认使用lettuce作为操作redis的客户端。他使用netty进行网络通信
lettuce的bug导致netty堆外内存溢出 -Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m,跟jvm设置的一样
Dio.netty.maxDirectMemory调大堆外内存,真正的原因在于netty没有及时释放资源
解决方案
升级lettuce客户端(推荐)
切换使用jedis客户端

缓存出现的问题
1.2.1 缓存穿透
缓存穿透:指查询一个数据库和缓存库都不存在的数据,每次查询都要查缓存库和数据库,一秒钟查一万次就要访问一万次数据库,这将导致数据库压力过大。如果我们在第一次查的时候就将查到的null加入缓存库并设置过期时间,这时一秒钟查一万次都不会再查数据库了,因为缓存库查到值了。

风险:利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃缓存
解决:
采用:数据库查的空值放入缓存,并加入短暂过期时间
布隆过滤器(请求先查布隆过滤器、再查缓存库、数据库)
例如字符串"null"放进Redis。
Redis存的值是fastjson转换后的对象字符串,null转字符串后是字符串“null”,存到Redis里是能查到的。
1.2.2 缓存雪崩
缓存雪崩:缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
解决:
原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
降级和熔断
采用哨兵或集群模式,从而构建高可用的Redis服务
如果已经发生缓存血崩:熔断、降级
1.2.3 缓存击穿 【分布式锁】
一条数据过期了,还没来得及存null值解决缓存穿透,高并发情况下导致所有请求到达DB
解决:加分布式锁,获取到锁,先查缓存,其他人就有数据,不用去DB
缓存击穿:
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到db,我们称为缓存击穿
解决:
采用:加互斥锁。大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
热点数据不设置过期时间

本地锁解决缓存击穿

synchronized只锁当前进程
BUG:本地锁时序问题
由于将数据写入redis这一步骤没有加锁 导致数据库被查询了两次

压测时,第一个线程刚释放锁,还没来得及将结果放入Redis缓存,第二个线程就拿到锁。
解决办法:
把存入缓存的操作放在锁中
本地锁缺点
本地锁只能锁住当前进程,高并发下,集群下有一百台机器,就会放一百个请求进锁查数据库。
分布式情况下,要用分布式锁。

分布式锁

什么是分布式情况
在分布式系统中,每个节点(计算机)都拥有自己的存储空间和计算能力,它们之间通过网络进行通信。
分布式情况下,服务部署在多台机器下时,本地锁只能锁住当前进程

分布式锁的原理

分布式锁最重要的是要保证占锁与删锁的原子性
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式
占锁可以都去redis占有
使用redis实现分布式锁
NX – 只有键key不存在的时候才会设置key的值。实现分布式锁
多个进程使用set lock NX占有锁 只有一个会占有成功
手动释放锁-出现死锁
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式
设置超时自动删除
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式
最终解决
1.加锁,只有键key不存在的时候才会设置key的值,加锁时将value设为UUID并构造方法设置锁的300秒自动过期。
2.如果加锁成功…执行业务后,使用lua脚本(保证原子性)判断当前锁的value是不是当前线程的UUID,是的话删除锁。
3.如果加锁失败,休眠100ms重试。
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式
注意
1.加锁保障原子性,删锁保证原子性。
2.为了避免死锁,加锁和设置锁的自动过期必须是原子操作,使用构造方法设置过期时间,过期时间要久一点,作为删锁失败的保险操作,这里设置成300秒。
3.为了防止删成上个线程的锁,将锁的value设成当前线程的UUID;
4.判断锁的值是不是当前线程的UUID,以及删除锁,这两个操作必须是原子操作,使用lua脚本判断锁和删除锁。
只能保证当前服务(非分布式)情况加锁成功

Redisson分布式锁 单节点配置

redis官方推荐Redisson
注册一个Redission Client

可重入锁

可重入锁(ReentranRank)到底是什么?
允许同一个线程多次获得同一把锁。这种锁的主要特点是避免了同一个线程在尝试再次获取已持有的锁时产生死锁。
锁计数:可重入锁内部维护一个计数器,以跟踪同一线程对锁的获取次数。每当线程重新获取这个锁时,计数器就会增加;当线程释放锁时,计数器就会减少。只有当计数器回到零时,锁才真正释放,其他线程才能获取它。
实现

lock.lock();

如何保证代码出问题,所仍被正常释放
Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟 该做法有死锁风险??执行删锁逻辑的时候,自己的锁已经被删了

避免看门狗三十秒之后 删除锁 该业务

指定锁的过期时间,看门狗不会自动续期:

//在自定义锁的存在时间时不会自动解锁
lock.lock(30, TimeUnit.SECONDS);
注意:

设置的自动解锁时间一定要稳稳地大于业务时间

分布式下 lock()方法的两大特点:

1、会有一个看门狗机制,在我们业务运行期间,将我们的锁自动续期
2、为了防止死锁,加的锁设置成30秒的过期时间,不让看门狗自动续期,如果业务宕机,没有手动调用解锁代码,30s后redis也会对他自动解锁。

公平锁

它保证了当多个Redisson客户端线程同时请求加锁时,优先分配给先发出请求的线程。所有请求线程会在一个队列中排队,当某个线程出现宕机时,Redisson会等待5秒后继续下一个线程,也就是说如果前面有5个线程都处于等待状态,那么后面的线程会等待至少25秒

RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

读写锁(ReadWriteLock)可重入读写锁

分布式可重入读写锁,允许同时有多个读锁和一个写锁处于加锁状态。
Redisson的读写锁实现了JUC.locks.ReadWriteLock接口,读读不互斥,读写互斥,写写互斥
写锁会阻塞读锁,读锁会阻塞写锁,但是读锁和读锁不会互相阻塞

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();

闭锁 (CountDownLatch)

redisson.getCountDownLatch();

可以理解为门栓,使用若干个门栓将当前方法阻塞,只有当全部门栓都被放开时,当前方法才能继续执行。
以下代码只有gogogo被调用5次后 lockDoor()才能继续执行
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式

信号量(Semaphore)

redisson.getSemaphore("semaphore")
@GetMapping("/park")
    @ResponseBody
    public String park() {
        RSemaphore park = redissonClient.getSemaphore("park");
        try {
            park.acquire(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "停车,占一个车位1";
    }
 
    @GetMapping("/go")
    @ResponseBody
    public String go() {
        RSemaphore park = redissonClient.getSemaphore("park");
        park.release(1);
        return "开走,放出一个车位1";
    }

信号量与闭锁的区别
他们都是标志位为0时解锁
但是信号量的标志位可以加,但是闭锁不能,闭锁是能减,直到标志位为0解锁

缓存数据一致性问题(redis 与数据库数据一致性问题)

双写模式

**双写模式:**在数据库进行写操作的同时对缓存也进行写操作,确保缓存数据与数据库数据的一致性
**脏数据问题:**在A修改数据库后,更新缓存时延迟高, 在延迟期间,B已经有修改数据并更新缓存,过了一会A才更新缓存完毕。此时数据库里是B修改的内容,缓存库里是A修改的内容。
解决方式:加锁 但redis中数据有过期时间 最终会删除脏数据 保持数据一致性
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式

失效模式

**失效模式:**在数据库进行更新操作时,删除原来的缓存,再次查询数据库就可以更新最新数据
存在问题(写多读少时 读数据更新缓存时不能保证是最新写入的数据)
脏数据问题:当两个请求同时修改数据库,A已经更新成功并删除缓存时又有读数据的请求进来,这时候发现缓存中无数据就去数据库中查询并放入缓存,在放入缓存前第二个更新数据库的请求B成功,这时候留在缓存中的数据依然是A更新的数据
谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式

解决方法
1、缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新
2、读写数据的时候(并且写的不频繁),加上分布式的读写锁。

总结:

• 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
• 我们不应该过度设计,增加系统的复杂性
• 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

SpringCache-简化缓存操作

谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式

使用CacheManager管理Cache

谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式
示例
getLevel1Categorys方法加上@Cacheable(“category”)注解

/**
     * 查询一级分类。
     * 父ID是0, 或者  层级是1
     */
    @Cacheable("category") //写入缓存
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("调用了 getLevel1Categorys  查询了数据库........【一级分类】");
        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
    }

测试结果
指定一个名字,放入哪个分区@Cacheable({“category”})
1)当前方法的结果需要缓存,如果缓存中有,方法不被调用
2)默认缓存数据的key: category::SimpleKey []
3)默认使用jdk序列化机制,将序列化后的数据存到redis
4)默认过期时间-1,永不过期

自定义数据的key 数据格式 过期时间

谷粒商城-缓存使用&分布式锁&SpringCache(5天),谷粒商城,缓存,分布式


@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching
@Configuration
public class MyCacheConfig {
 
//    @Autowired
//    CacheProperties cacheProperties;
 
    /**
     * 需要将配置文件中的配置设置上
     * 1、使配置类生效
     * 1)开启配置类与属性绑定功能EnableConfigurationProperties
     *
     * @ConfigurationProperties(prefix = "spring.cache")  public class CacheProperties
     * 2)注入就可以使用了
     * @Autowired CacheProperties cacheProperties;
     * 3)直接在方法参数上加入属性参数redisCacheConfiguration(CacheProperties redisProperties)
     * 自动从IOC容器中找
     * <p>
     * 2、给config设置上
     */
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //指定缓存序列化方式为json
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        // 配置文件生效:RedisCacheConfiguration
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //设置配置文件中的各项配置,如过期时间,如果此处以下的代码没有配置,配置文件中的配置不会生效
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }
}

@CacheEvict使用 将数据从缓存中删除

@Transactional
    @CacheEvict(value = {"category"},key ="'getLevel1Categorys'")//删除指定key值的缓存
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

删除其他关联的缓存
删除带category前缀的所有缓存allEntries = true

@Transactional
    @CacheEvict(value = {"category"},allEntries = true)   //调用该方法(updateCascade)会删除缓存category下的所有cache
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

@Caching 的使用

作用:在数据修改时需要对多个缓存进行操作时使用

 @Transactional
    @Caching(evict = {
            @CacheEvict(value = {"category"},key ="'getLevel1Categorys'"),
            @CacheEvict(value = {"category"},key ="'getCatalogJson'")
    })
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }

@CacheEvict:触发将数据从缓存删除的操作【删除缓存】【可实现失效模式】
@CachePut:不影响方法执行更新缓存【更新缓存】【可实现双写模式】
@Caching:组合以上多个操作【实现双写+失效模式】

SpringCache的不足

1、读模式:
缓存穿透:查询一个DB不存在的数据。解决:缓存空数据;ache-null-values=true【布隆过滤器】
缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁; 默认未加锁【sync = true】本地锁

 @Cacheable(value = "category",key = "#root.method.name",sync = true)

​缓存雪崩:大量的key同时过期。解决:加上过期时间。: spring.cache.redis.time-to-live= 360000s
2.写模式:(缓存与数据库一致)(没有解决)
​ 1)、读写加锁。
​ 2)、引入canal,感知mysql的更新去更新缓存
​ 3)、读多写多,直接去查询数据库就行
总结:
​ 常规数据(读多写少,即时性,一致性要求不高的数据)﹔完全可以使用Spring-Cache,写模式(只要缓存的数据有过期时间就可以)文章来源地址https://www.toymoban.com/news/detail-787442.html

到了这里,关于谷粒商城-缓存使用&分布式锁&SpringCache(5天)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • keycloak~分布式缓存的使用

    keycloak目前提供了几种分布式缓存,我们自己的缓存,如果希望是分布式的,可以将缓存添加到以下几个缓存里即可 actionTokens clientSessions loginFailures offlineClientSessions offlineSessions sessions work 如果你希望自己开发分布式缓存,还是需要复杂的,需要配置,检查,获取远程分布式缓

    2024年02月05日
    浏览(27)
  • 【业务功能100】补充代码【业务功能88】微服务-springcloud-分布式锁-redis-redisson-springcache

    采用redisson做分布式锁,完成数据的查询接口功能getCatelog2JSONRedis 原先从mysql数据库查询的效率较低,现在将部分固定数据展示比如页面的树形栏目信息等,存储到 redis缓存 ,然后基于分布式集群,需要结合本地锁(synchronized )与分布式锁(redissonClient.getLock(“catelog2JSON-lock”

    2024年02月09日
    浏览(31)
  • SpringBoot使用Redis实现分布式缓存

    ✅作者简介:2022年 博客新星 第八 。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:SpringBoot 框架从入门到精通 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:SpringBoot使用

    2023年04月09日
    浏览(26)
  • 【分布式缓存】springboot整合jetcache使用详解

    目录 一、前言 二、多级缓存问题 2.1 缓存分类 2.1.1 本地缓存 2.1.2 分布式缓存

    2024年02月17日
    浏览(32)
  • 一文拿捏分布式、分布式缓存及其问题解决

    1.集中式 传统的计算模型通常是集中式的,所有的计算任务和数据处理都由 单一的计算机或服务器 完成。然而,随着数据量和计算需求的增加,集中式系统可能会面临性能瓶颈和可靠性问题。 故而引出了分布式↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

    2024年02月07日
    浏览(33)
  • 商城-学习整理-基础-分布式组件(三)

    搭建分布式的基本环境,主要是注册中心、配置中心、网关。 在分布式开发中,每一个微服务上线都应该将自己注册到注册中心,这样做的好处就是订单服务想要调用商品服务,可以去注册中心看一下,哪几个商品服务注册进来了,1号、2号,可以挑一个可用的商品服务去远

    2024年02月16日
    浏览(33)
  • 分布式系统架构设计之分布式缓存技术选型

    随着互联网业务的快速发展,分布式系统已经成为了解决大规模并发请求、高可用性、可扩展性等问题的重要手段。在分布式系统中,缓存作为提高系统性能的关键技术,能够显著降低数据库负载、减少网络延迟、提高数据访问速度。当面对大量并发请求时,如果每次都直接

    2024年02月03日
    浏览(42)
  • SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

    上一篇实现了单体应用下如何上锁,这一篇主要说明如何在分布式场景下上锁 上一篇地址:加锁 需要注意的点是: 在上锁和释放锁的过程中要保证 原子性操作 核心是上锁和解锁的过程 关于解锁使用脚本参考:SET key value [EX seconds] [PX milliseconds] [NX|XX] 3.1 一个服务按照多个端口同时

    2023年04月10日
    浏览(34)
  • Redis 分布式缓存

    单点 Redis 的问题及解决 数据丢失:实现Redis数据持久化 并发能力:搭建主从集群,实现读写分离 存储能力:搭建分片集群,利用插槽机制实现动态扩容 故障恢复能力:利用哨兵机制,实现健康检测和自动恢复 RDB RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做

    2024年02月10日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包