MyBatis二级缓存的坑

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

问题背景

近期项目中使用MyBatis3.1.0。为了进一步提升性能,针对缓慢变化的基础数据开启了MyBatis二级缓存(备注:二级缓存是mapper级别共享的缓存,相对于一级缓存,它可以跨SqlSession共享),单机测试结果达到预期,即非首次数据库 访问有明显的性能提升,因为命中了MyBatis缓存;但是进行压力测试以后,性能急剧下降,出现大面积线程阻塞甚至死锁;

原因分析

通过dump线程堆栈查看阻塞线程如下:     image.png 分析线程发现,大量线程处于WATTING状态,普遍阻塞在ReentrantReadWriteLock$NonfairSync非公平锁上面;

然后逐层排查线程堆栈涉及的源码,找哪里加的锁,在MyBatis找到的加锁操作代码如下:

为了保证缓存操作的线程安全,Mybtis进行了加锁操作,关键问题在于89、90这两行。

89行的问题,它在并发情况一定会导致大面积阻塞:

加锁本身是保证操作缓存的线程安全,但MyBatis居然把缓存不命中时的DB操作也加入了锁定范围,这也就意味着针对同一个DAO接口操作,所有DB操作都串行同步排队了,显然这个封锁粒度过大了,因为一般Map缓存操作也就是微秒级别(1/1000毫秒),而DB操作一般都达到几十毫秒甚至几百毫秒;它带来的问题就是开启二级缓存情况下,并发访问会引起大面积阻塞;

image.png

再说90行的问题,它可能会引发死锁:

查看tcm的源码它是一个线程安全的数据结构,它本身内部有加锁机制,但是它又被嵌入了另外一个代码锁定区域,也就是会出现已持有一个锁的情况下去申请另外的锁,这种情况容易导致死锁;

问题核心在于MyBatis把DB操作和写缓存放入了锁定区,似乎这就一个设计不当导致的性能BUG;

怎么印证这个分析结论呢?既然MyBatis使用如此广泛,二级缓存并发场景有这么糟糕的表现,那高版本的 MyBatis应该一定会有修复的;

查看更高版本MyBatis源码,果然,这里有故事:

image.png 高版本已经把DB访问和写缓存放到临界区外了,从代码注释上面也可以印证这点

解决方案

在Maven依赖里面锁定MyBatis高版本(MyBatis3.1.1)重新压测,无死锁和大面积阻塞,问题解决。

猜你喜欢

转载自juejin.im/post/7115037877641150495