这篇文章不是介绍mybatis二级缓存,而是基于我们目前业务一些痛点,思考如何使用mybatis二级Redis缓存,首先mybatis也提供了一个Redis缓存类,但是那个类并不能解决我们目前业务痛点
一、业务痛点 #
首先我来介绍一下我们目前的两个业务痛点,第一个就是热key问题,由于我们一个服务全部使用Redis作为缓存,当业务量大起来之后,发现Redis内存占用量不大,但是程序卡顿丢包,一查发现原来是Redis带宽跑满
这个解决方法有两种,一种是内存做一层缓存热key,第二种就是减少网络包传输大小,可以采取新的序列号算法(目前我们使用的是JSON)如google的 protocolBuffer,后面我们采用的是第一种解决方法
我们第二个业务痛点就是缓存失效的峰刺,当大量key失效的时候,同一时间大量相同的请求打到数据库,虽然目前还没有压垮,但是等后面业务量大起来其实这个也是个瓶颈
二、MyBatis缓存介绍 #
我们这里简单介绍一下Mybatis缓存,它分两张,一种是基于session的一级缓存,一种是基于mapper的namespace二级缓存(也就是全局缓存),一级缓存只能对同一个请求有用,所以其实对于我们目前业务来说用处不大
mybatis二级缓存是个好东西,你可以理解为单个实例共享的cache,它是基于内存的,非常快,基本上没有延迟
mybatis默认没有开始缓存,你可以简单的在mapper
的xml文件里面加个<cache></cache>
就开启了或者使用@CacheNamespace
注解
但是目前mybatis提供的全部是基于内存缓存,当数据量大的情况下,很容易内存溢出
三、实现Redis二级缓存 #
mybatis官方实现了一个基于redis二级缓存,这个缓存目前对于我们两个痛点来说,只能解决第二个,只需要在二级缓存中配置blocking=true
我们就可以在更新缓存的时候锁住所有线程,避免大量请求数据库
所以我基于官方实现做了下面修改:
- 继承原始cache类,可以兼容使用LRU这些插件
- 使用普通redis值替换原来的哈希表,支持插入时候添加失效时间
- 当删除的时候,将数据放置到Redis
- 读取时候先读内存,在读Redis,假如Redis读到了,再更新到内存
通过上面4个修改,就很能实现第一个痛点,而且通过第四步,你可以解决掉Lru扫表问题
PS: 扫表,假如使用Lru你把所有Key都扫一遍,热key会被删除到Redis上去,假如你不更新到缓存,热key永远在redis上面,导致热key还是会对Redis造成压力
四、总结 #
当然这种实现目前还有下面两个弊端,不过这两个弊端现在看也很难解决
- 使用Java自带序列化对Redis消耗大(理论上使用google的 protocolBuffer是最优的)但是目前除非你能知道Redis键值,否则也是难,目前我测试在20个属性下,使用JSON要比原生序列号小,但是超过这个值其实两者开始拉近,在大量属性下原生序列号还是要比Json要省空间
- 在执行更新插入删除的时候,会把内存缓存清空,但是不会清空redis,这个会导致丢失部分实时性,但是目前我们使用场景可以在保证业务可靠性情况下,两三分钟的缓存时延