Mybatis的缓存机制、redis数据库缓存实现和相关问题

Mybatis的缓存机制、redis数据库缓存实现和相关问题

    高并发环境下,数据库要承受非常大的压力,我们不能奢求每一次都只依赖分布式结构的读写分离数据库来解决问题,所以引入了数据库缓存的概念,这里的缓存不是具体的memcache或是redis,可能只是一块内存区域。此文介绍Mybatis的缓存机制。

    SQLSession

    SqlSession是Mybatis创建数据库链接的会话,当度使用Mybatis需要对SqlSesssion的生命周期有一个把控,但是在Spring的集成中这个会话会被自动创建,周期只是对应一个方法(例如Service层的一个方法),所以每个请求就会对应一个或是多个SqlSession,SQLSession的主要实现是其中的Exector,对应了三种策略:

    BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。开启cache的话,就会创建CachingExecutor,它以前面创建的Executor作为唯一参数。CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。

    缓存机制

    Mybatis默认开启一级缓存,它的作用域是SqlSession,作用条件是相同的SQL,key为hashCode+sqlId+Sql语句,当重复执行同一个SQL语句时,会从缓存中读取结果,当然为了保证数据的可靠性,用户进行任何的修改(update、add、delete)操作都会导致缓存清空,并且就作用域而言,一级缓存能够派上用场的地方其实非常有限,因为我们业务中经常遇到的是不同的SqlSession查找相同的数据。

    二级缓存

    在Mybatis中设置cacheEnabled为true,则会开启二级缓存。二级缓存默认是关闭的,他的生效范围是整个Mapper,除了同个Mapper带来的修改请求外,也会定时进行缓存清理,这主要依托于用户的参数设定。

    本地缓存的问题

    在分布式架构下,高并发的SQL请求使用缓存会产生脏读问题,例如其他服务器中修改了数据但是本地缓存中没有产生修改请求,此时本地的缓存不会更新数据,就产生了脏读问题,因此如果数据库访问压力很大并且业务对实时性要求很高。而且有些业务中,两个不相干的表更新也会导致缓存很“意外”的刷新。我们并不建议使用Mybatis自带的缓存机制,而是建议使用集中式缓存服务解决数据库缓存问题,这时我们就需要自定缓存规则,根据具体的业务存储分布式数据缓存,可靠性和数据安全性也能得到保障。

    更好的解决方案

    相比Mybatis自带的二级缓存,总有更靠谱的相关解决方案,前面提到了,将数据缓存委托给redis。

    手动同步方式举例

    

    使用canel进行MySQL与redis同步

    缓存带来的问题和解决方案

    缓存使用不当会产生很多问题,常见的有:

    缓存穿透

    缓存本是为了缓解数据库的压力,若是缓存未击中,仍会去查询数据库,频繁的未击中数据会给数据库服务器带来很大的压力,解决方案是针对未击中数据也存在redis中,只是存储空值。还可以设定主键的规则(布隆过滤器),不符合规则的请求均视为非法请求,将不进行查询。

    缓存雪崩

    redis作为数据库缓存一般不进行删除和更新,而是设定过期时间,当缓存数据集中过期时,突然发来了很多请求(流量激增、恶意攻击等),这些请求会一并转发到数据库中,引起数据库服务器“雪崩”。redis服务失效(例如redis服务器宕机)也可称作缓存雪崩,解决方案:

    缓存击穿

    区别于缓存雪崩,缓存击穿强调单条热点数据的过期,当一个热点key值保持着持续的高并发,而此时该条缓存失效,瞬时就会有大量请求涌入,后果不堪想象(举个例子:商品秒杀),解决方案:设定一个合适的过期时间(甚至可以不过期,后面手动删除),或是维持另一个setnx的键,当缓存中不存在热点数据时,存入键,当做数据锁,每次只有单一线程访问,直到新值被存入缓存中。


2019-08-29鱼鱼

{{blog.title}}

创建于 {{blog.createTimeStr}}   created  by  {{blog.author}} {{tag}}
最后修改于 {{blog.timelineStr}}
修改文档