15.缓存、分布式锁

本文介绍了在商城购物系统中,如何利用缓存和分布式锁优化性能。详细讲述了缓存的一级、二级、三级菜单渲染,以及缓存的三大问题——缓存穿透、雪崩和击穿及其解决方案。还探讨了分布式锁的实现,包括本地锁的不足,自定义Redis分布式锁的注意事项,以及Redisson的优势。最后提到了SpringCache的使用,并讲解了如何通过配置和注解实现缓存管理和一致性策略。
摘要由CSDN通过智能技术生成

开始写商城购物的代码,第一步是首页的商品渲染:

一级、二级、三级菜单渲染:

在后台,建立一个web包,与app、service包同级,但这个是管理商城购物业务的代码

 

查询一级菜单:

这个方法用于,商城首页的跳转,定制多个首页地址

例如:"/"和"index.html"

逻辑是:

父分类id为0就表明是一级分类

 

 

查询二级、三级分类:

利于一个自定义的Catelog2Vo来封装

Catelog2Vo:

二级分类中包含:

1*)父分类的id,也就是一级分类的id

2*)二级分类所包含的三级分类集合

3*)当前分类的id,也就是二级分类的id

4)当前分类名称

三级分类中包含:

1*)父分类的id,也就是二级分类的id

2*)当前分类的id,也就是三级分类的id

3)当前分类名称

 

由于查询一级、二级分类的方法访问频繁,所以将其保存至缓存中,可以减少访问数据库的次数,优化性能

第一次数据,缓存中没有,从数据库中查,查完后将数据保存到缓存中,之后就可以直接从缓存中拿数据了

 

缓存有三大问题:

1)缓存穿透:缓存没有存null值,所以当要查询的数据不存在时,会查询数据库。如果此时有大量线程要访问该数据,就会使数据库瘫痪。

解决方案:在缓存中加null值

 

2)缓存雪崩:缓存一大堆数据同时过期,则会使大量访问击中数据库,仍然会使数据库瘫痪

解决方案:保存到缓存中时,给数据设置随机的过期时间

 

3)缓存击穿:有一个热点的数据在一个时刻在缓存中过期,此时如果有大量线程访问,仍然会使数据库瘫痪

解决方案:加锁

 

分布式锁:

讲一下加锁,有3种锁可以选择:

最开始就进行了查缓存的操作,再封装一个方法,这个方法里还要进行一次查缓存的操作,以防万一,这个方法是下面所有方法的上一个方法,通用的,改的是第二块的内容:

1)本地锁:synchronized(this)

只能锁住当前线程,不能锁住别的线程。这样仍然阻挡不了高并发的访问量

 

 

2)自定义分布式锁(redis):

加锁和解锁操作都是要原子的:

加锁:加锁+设置随机过期时间

如果不是原子性,设置过期时间这个操作之前如果出现宕机,就没有设置过期时间这一操作了,

这样后面释放锁的过程也没有了,就会出现死锁

 

解锁:获得自己的锁+释放锁

如果不是原子性,可能会出现,在获得锁时,返回来的数据确实是自己的锁,但就在返回的途中,锁到了过期时间,被另一个线程抢占了,此刻得到的锁就与真实锁不相符了

 

加锁会出现的问题:

1)redis中设置的是setNX,当存在时才存入,也就是java中的setIfAbsent

2)要设置过期时间,防止解锁之前出现宕机,没有解锁导致死锁

3)保证设置锁和设置过期时间是原子性操作

 

解锁会出现的问题:

1)把解锁放在finally模块中

2)保证获得锁+解锁的原子性:使用lua脚本

 

3)redisson

redisson封装了读写锁、可重入锁等,比自定义的分布式锁强大,所以用redis自己的redisson

 

 

getCatelogJSONDataFromDB()查询数据库的核心方法:

进锁之后,先查缓存,如果缓存没有,就查询数据库:

第一块:从redis中查询key,相当于查询缓存中是否有该数据

第二块:缓存没击中,也就是没有数据,就从数据库中查询

第三块:命中缓存,利用JSON.parseObject方法将catelogJSON变为Catelog2Vo类的格式

 

由于锁的时序问题,将从数据库中查到的数据存到缓存中的操作要放在释放锁之前

如果放在释放锁之后,在释放锁的那一刻,有线程查询数据,此刻缓存中还没有数据,就会访问数据库,这样数据库就被访问了2次,不严谨

放入缓存的操作要放在return map之前,也就是这个方法结束之前。这个方法外面包围了锁,出了这个方法就会将锁释放掉,所以要在之前放入缓存

 

 

缓存:

注意:

使用redis作为缓存时,可能会出现堆外内存溢出

原因:

redis使用lettuce作为客户端,而lettuce使用netty进行通信,由于这个bug,会导致netty堆外内存溢出

解决方案:

1)升级lettuce客户端(很久没更新了)

2)使用jedis

(不能只改变参数-Dio.netty.maxDirectMemory进行堆外内存设置,这样只是变大了,但问题仍然存在,当运行一段时间后,内容还是不够)

 

区分:设置缓存是在redis中设置key为catelogJSON的set,设置锁是在redis中设置key为lock的set

除了自定义的redis缓存之外,还可以用spring的SpringCache,只需要使用几个注解即可

 

 

使用SpringCache

1.引入依赖

2.写配置:

    i)CacheAutoConfiguration会导入RedisCacheConfiguration,自动配好了缓存管理器

    ii)配置文件要配置使用redis缓存(application.properties)

               spring.cache.type=redis

3)开启缓存功能:@EnableCaching

 

 

1.缓存数据按照业务类型来划分取名,存在对应的名字下,例如:category

2.指定缓存生成指定的key,默认按照方法名,否则就按照配置文件里的信息(key="#root.method.name")

这里如果是一个字符串就要在双引号里面加单引号,例如:"'category1'"

3.加锁(sync)

 

需要自定义的内容:

1)缓存生成指定的key

2)配置文件修改过期时间ttl

3)将缓存的value保存为json格式

 

解决三大问题:

1.缓存穿透:

设置null值

2.缓存雪崩:

随机ttl

3.缓存击穿:

加锁:上面的sync=true

 

成功实现:

有2个缓存信息,分别是一级分类和二级三级分类

 

问题:

如何保持缓存和数据库的数据一致性:

1.双写模式:出现的脏数据问题,可以为缓存设置一个过期时间,可以运用于一致性不是要求太高的业务

 

2.失效模式:加读写锁

 

如果查数据的操作很频繁,就不要用缓存了,直接查数据库

缓存保证的是最终一致性,而不是强一致性

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值