项目使用分库分表,库表规模大约为10个库(包含 2019年到2020年)每个库约120张表,总共1200张表。引入sharding jdbc作为底层的分片框架已经一年多了,一直运行良好。虽然小问题不断,但大问题 没有,性能稳定,值得赞一个。
但由于sharding jdbc只是一个建基于mysql之上的数据分片引擎,其核心无非就是改写sql语句,归并数据集等功能。出于数据分片逻辑的复杂性,它对原生mysql语句的支持并不能做到十分完美,因此才有了博主自己的踩坑经验。
首先4.0.0版本之前的,sharding jdbc官方明确表示是不支持批量插入。虽然本人没有亲测过,但官方都放话不支持,我就没有试的必要了。所以在4.0.0版本出来之前,我们都是很听话的逐条插入,但随着数据量增大,无论我们后台购买了多牛X 的mysql服务器,插入的速度还是很大的一个瓶劲。于是把心一横,升级成4.x版本。
但升级版本,不是仅仅是改了依赖的版本那么简单 ,下面就是本人亲身的踩坑经历。
4.x版本除了包名全改了名之外(因为已经捐给了Apache),一些常用的接口也改了入口参数和运行逻辑。其中项目使用到的核心接口就是ComplexKeysShardingAlgorithm。虽然接口名字没变,但意义已经完全改变。
3.X版本这个接口是一个大一统接口,统合了精确查询(=),集合查询(IN)和范围查询(Between)这三种主要的查询操作。因此我们项目就很愉快地做了一个大一统的实现逻辑,能支持分片字段查询中出现上述三种情况。但4.0.0_RC改了,不支持范围查询了(这是本人亲测过,如果使用范围查询,框架会报ClassCastException,4.1.x版本后面才加进去)。因为项目之前是有拿年份,月份来分片,查询语句必须带范围查询,否则捞取不到该月分的数据。
之前想过在库表中再增加一个month,或者year字段,来绕过这个问题。但感觉这种方案太low,除了会增加多余的字段外,当分片键值更改,还要同步更新month和year的值,性能肯定会下降。即使性能没明显下降(因为没亲测,不好判断),这种low逼方案,也不适合我这种zhuangbier(自创的英文单 词,意思自个体会)的风格。
于是,想到了在sql 中 用内置函数进行转换。这样代码不用改,表结构也不用改。唯一担心的就是性能。由于本来分片键就没做索引,内置函数也没有消耗多大的性能,因此这个方案就这样神奇的 Pass了。
在原则上大家都知道批量插入数据比逐条插入数据要快得多。我当时也是这样想,所以才屁滚屁颠的把框架升级到4.x。但没想到新版本的批量插入功能,比单条插入还更慢。刚开始的时候,我一直担心自己是不是没用对,为了验证我的方法有没用错,我一边打断点,一边锚着数据库。一切看起来像是没问题,后台输出的sql是批量插入的形式,数据库的数据也是一批批的加。
那为什么还这么慢呢?难道是自己写的分片实现有问题?自己给sharding jdbc狗尾续貂了?于是我马上在自己的方法中埋点检测。终于发现问题的关键,原来4.0.0.RC(项目中用的是这个版本,后续版本有没有收复这个问题不清楚)中进行批量插入时,会把所有分片键的值逐次都送到我的方法中跑一次。换句话说,批量插入10000条数据,我的方法就跑10000次。这与我原来的构想完全不一样。因为看接口的描述和参数的样子,不像是会这样做的。难道框架怕会有内存泄露的问题,才用时间换空间?
因为在我的方法中,我会经常访问配置信息,还有做一些逻辑处理,虽然整个方法的平均运行时间不超过1毫秒,但堆积起来的时间,就不少了。所以我赶紧在自己的方法前面加个缓存。因为项目很多时候都只是访问同一组分片键值的数据,加缓存的动作立杆见影,速度马上提上去了。
自己方面的问题解决了,速度也提升了,但提升后的速度只是会比单 条插入快那么一丢丢,远达不到理想的要求。
到底哪里还有问题?我想通过升级框架版本来解决这个问题,但发现就算升级到最新的sharding-jdbc-spring-boot-starter 4.1.1(项目使用spring-boot来启动)还是有这个问题。2000条数据需要1秒多的时间才能插完。而使用单条插入,据业务开发说,插5000条快的时候也只需要7秒。也就是仅快了将近1倍左右。
为了体验真正批量插入的效果,我使用原生的jdbc接口进行测试,发觉批量插入(外网测试)2000条数据只需要100多毫秒,快了将近20多倍。
显然,这里面还有坑。我把自己加的代码全部都扫了一遍,所有可能导致性能下降的地方都埋了点,都没有发现异常情况。这事情困绕了我差不多一个星期,领导开始发话要我尽快解决。在时间压力下,我不可能把sharding-jdbc的每段代码都植入锚点监控(虽然技术上是可行的),想到自己都把最重要的分片逻辑实现了,干嘛不再狠一点,就批量插入这件事上面,绕过sharding-jdbc的逻辑,自己生成语句,自已插入。
本来我是抱着死马当活马医的心情来做,如果效果还是不好,只能硬着头皮向领导报告 。但无心插柳柳成荫,自己写的批量插入方案,居然跟原生的jdbc十分接近,10000条数据批量下,时间差不超过100毫秒。2000条一批下,跟原生jdbc不相伯仲。看来问题算是彻底解决了。
但屋漏偏逢连夜雨,就在我暗自庆幸问题得到完美解决之际,业务开发人员又报bug了。新版本sharding-jdbc跟MyMapper(国内比较出名的国人基于mybatis开发的一套框架)又扛上了。之前用3.X框架使用MyMapper一点问题都没有,结果升级成4.X,更新单表数据据说会报空指针。但我写测试代码,也没有发现什么问题,这个问题一直如幽灵一样的存在。截止本文写作之时,还没有解决。在这里还望有遇到过类似问题的大神不吝赐教。
本文写作的目的不是为了贬低Sharding-jdbc,而且从目前的应用情况来看,sharding jdbc是最好用的分布式数据源引擎之一(至少对于我们自己的项目来说是最合用的)。但人无完人,扒一下这个框架的一些鲜为人知的细节,也有助于框架的持续升级和避免大家踩和我一样的坑。