开始写商城购物的代码,第一步是首页的商品渲染:
一级、二级、三级菜单渲染:
在后台,建立一个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.失效模式:加读写锁
如果查数据的操作很频繁,就不要用缓存了,直接查数据库
缓存保证的是最终一致性,而不是强一致性