问题:在事务中在进行数据库查询所有的数据后,将其中的数据更改几个后,重新进行查询。发现此时返回的数据并不是修改之后的数据。
经过学习后发现是Mybatis的一级缓存问题,同时也了解的Mybatis的二级缓存:
Mybatis一级缓存:
-
当在一个方法中执行多次,查询同一个sql语句的命令的方法
-
当不加事务的时候执行sql,执行完之后会话就会结束,一级缓存就会消失。
-
当加事务的时候,一级缓存不会消失,再次查询同一个sql的时候(参数也得一样)就会使用前面查出的数据。
-
-
一级缓存可以帮助我们减少重复的数据库查询
-
缺点:但是当我们第一次查询之后对相关数据进行更改后,依旧会得到一级缓存的数据
-
解决方式:将一级缓存关闭在配置文件中
-
参数:statement:sql语句 session:会话 默认为会话,换为statement则每执行一个sql本地缓存就清空
mybatis.configuration.local-cache-scope=statement
-
Mybatis二级缓存:
多次执行同一个sql,如果二级缓存生效则只执行一次(适合读多写少的接口,比如查询所有车次接口和查询所有车站接口)
如何开启:在mapper.xml中在需要开启的sql下加入<cache></cache>即可。同时需要将实体类做一个序列化
失效:对同一个namespace做增删改操作时二级缓存就会清空
序列化:实现Serializable接口
-
当一个类需要保存起来,下次再还原成类是就需要序列化,或者需要远程传输,比如放到redis里,也需要序列化
-
缺点:当有多个节点时,都执行了二级缓存查找,当进行删除命令时只会进入到一个节点去删除,那么数据就会改变。而另一个节点并不知道,拿原来的数据导致数据不一致问题。
接着对Springboot的内置缓存以及redis进行了学习
springboot内置缓存
引入依赖,在各模块添加注解@EnableCaching,开启缓存,在需要开启事务的方法上添加@Cacheable(value = "t")//注解。value为缓存的名字。
相当于开辟了一块空间,根据不同的请求参数,空间内会缓存多个结果,会根据请求参数生产一个key,需要对请求参数生产hashcode和equals方法,用于生产key。
根据不同的参数重写hashCode()与equals()方法。
强制刷新缓存方法 @CachePut,每次去数据库中查找,查完会将结果放到缓存中去。
实现:将两个方法结合
程序去查询queryList()方法,自动读取缓存。当数据发生变化时主动执行queryList2()强制刷新同一缓存名区的值,将数据放到缓存里面去。
问题:但是并没有设置缓存的过期时间。存在多台缓存的问题。
集成redis:
第一行为将spring的cache放在redis中,
接着两行为放在redis中的key的前缀(防止多个项目冲突)
接着一行为是否可以为空
最后一行为设置redis缓存为60s
将类放到redis中,有一个远程传输的动作,对类进行远程传输就需要将类进行序列化
redis解决问题:
-
高性能:提高访问速度,mysql单机版QPS约为2000,redis为10万
-
数据的持久化:解决多节带你共享缓存,机械重启也不会丢失数据
缓存击穿与解决方案:
在抢票功能中,很可能出现。
缓存击穿:热点的一个key失效,大量的请求直接访问数据库,导致数据库压力增大
解决方案:
-
(解决缓存超时问题)主动刷新缓存。假设时间为60s,当时间快到时我们可以主动调用@CachePut注解中的方法,主动刷新缓存时间(定时任务)。
-
(如果缓存直接失效:比如缓存机器换了,热点key突然没有了)使用分布式锁,只让一个请求拿到锁并查询数据库,其他请求都失败,告诉其他用户稍后等待。
缓存穿透与解决方案:
第一步:判断当前缓存是否有数据
-
没有则去数据库去读取->数据库中也没有数据就会造成缓存穿透的问题
-
有则直接返回数据
解决方案:
-
分布式锁,100个请求只能让一个请求进入
-
如果查询到空列表,那么也将其放到缓存中去。在第一步中区分null和[] 如果是null去数据库中查,如果是[]就不去数据库中查找(允许缓存为空)。
缓存雪崩与解决方案:
缓存雪崩:由于短时间内,大量的key失效,导致数据库压力剧增。
常见缓存过期策略:
-
TTL 超时时间
-
LRU 最近最少使用
-
LFU 最近最不经常使用
-
FIFO先进先出
-
Random 随机淘汰策略
解决方案:
-
将热点KEY主动打散 :定时任务将热点KEY主动刷新 超时时间再加上一个随机值
-
给接口限流,让访问数不过大