ShardingSphere分库分表schema名称导致NPE问题排查记录

前段时间把 ShardingSphere 升级到了 5.1.1 版本,奈何官方版本升级太快跟不上速度,这不最近又发现了一个 BUG。

问题现象

数据库做了分库分表,在需要查询多表数据进行 merge 的时候发生了一个 NPE 的异常。

Caused by: java.lang.NullPointerException
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitiveFromTables(OrderByValue.java:73) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.getOrderValuesCaseSensitive(OrderByValue.java:64) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByValue.<init>(OrderByValue.java:58) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.orderResultSetsToQueue(OrderByStreamMergedResult.java:56) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.orderby.OrderByStreamMergedResult.<init>(OrderByStreamMergedResult.java:50) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.build(ShardingDQLResultMerger.java:89) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger.merge(ShardingDQLResultMerger.java:63) ~[shardingsphere-sharding-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.infra.merge.MergeEngine.executeMerge(MergeEngine.java:90) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.infra.merge.MergeEngine.merge(MergeEngine.java:80) ~[shardingsphere-infra-merge-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.mergeQuery(ShardingSpherePreparedStatement.java:487) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
    at org.apache.shardingsphere.driver.jdbc.core.statement.ShardingSpherePreparedStatement.getResultSet(ShardingSpherePreparedStatement.java:435) ~[shardingsphere-jdbc-core-5.1.1.jar:5.1.1]
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getFirstResultSet(DefaultResultSetHandler.java:237) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.handleResultSets(DefaultResultSetHandler.java:187) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:65) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:324) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109) ~[mybatis-3.5.3.jar:3.5.3]
    at jdk.internal.reflect.GeneratedMethodAccessor346.invoke(Unknown Source) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) ~[mybatis-3.5.3.jar:3.5.3]
    at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
    at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:108) ~[pagehelper-5.1.11.jar:?]
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.3.jar:3.5.3]
    at com.sun.proxy.$Proxy410.query(Unknown Source) ~[?:?]
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147) ~[mybatis-3.5.3.jar:3.5.3]
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140) ~[mybatis-3.5.3.jar:3.5.3]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
    at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) ~[mybatis-spring-2.0.6.jar:2.0.6]
    ... 95 more

问题排查

跟踪到报错的地方,发现是这个地方的schema是个null,从而引发了 NPE。

32d552957ec59482068c1872124e1179.jpeg

一路往上看代码,最终定位到了这个获取schema的地方,也就是元数据去getDefaultSchema获取默认的schema名称改的时候拿到了一个空值。

003085a0dbdefbd598c6bab16e7dc350.jpeg

进入这个方法后发现通过schemaschemas这个map里获取名称的时候是个空值,debug到这个地方其实发现了问题。

我们的schemaName配置的是orderTrade包含有大写字符的,所以name传进来的是orderTrade,但是问题是这个schemas确是ordertrade

cbfb644891e49b3dd0110d79f5c9fb79.jpeg

所以很显然,这里获取不到正确的schema名称,导致了这个 NPE 的异常,那么问题是这个schemas是怎么加载进来的呢?

我们发现schemas是在创建元数据的时候,通过构造函数赋值的,那么只要找到这个赋值的地方应该就能发现问题了。

6c655eb2d1a95bf0fc44e4ce47a862cb.jpeg

通过一番查找,找到了调用的地方,这个schemas值就是databaseMap中的value,那么我们要继续看这个databaseMap是如何初始化来的。

fd0de0985da519b029fa6cd7f798e876.jpeg

继续看源码,找到了databaseMap进行初始化的地方,原来是通过DatabaseLoader去加载元数据的时候初始化的,那么这个load方法是怎么处理的呢?

463245eb4bd0699f6228a717f101eaa3.jpeg

从代码来看他包含了两部分的信息,第一个是我们自己通过schema配置的一些分库分表的配置信息,另外一部分则是数据库默认的一些表的元数据,比如mysqlinformation_schema这些,那我们只要看自己配置的那部分就可以了,也就是SchemaLoader.load(dataSourceMap, rules, props)方法。

ac738ab95b7aea882b013d86e10a9f92.jpeg

看他实际上就是获取数据库是什么类型,比如mysql,然后去加载表的元数据,最后new出来ShardingSphereSchema,直接看最后的new部分代码就行了。

49a19bb7424eb0773f02df585a8ab08d.jpeg

进入这个方法,瞬间就真相大白了,原来在put的时候对所有的schemaName进行了小写处理,所以在最上面我们去get的时候肯定会拿到一个空值,最终导致merge的时候发生了 NPE 异常。

c0d51767465440d04e1ed93e6ac642b1.jpeg

解决方案

现在问题原因已经发现了,那么该如何解决呢?总不能不让别人配置的时候不让写大写吧,本着能不能白嫖一个 PR 的想法,又去给 Sharding 提了一个 Issue。

f19b448fb7c28ad1c5b05aafb026c880.jpeg

就我点了根烟的功夫,回头就给我回复说新版本已经修复了,希望落空了,修复方案就是查询的时候也做小写处理了,好吧,那就这样吧。

34257843f4f16f6c800762e762c1c765.jpeg

往期推荐

RabbitMQ、RocketMQ、Kafka延迟队列实现

chatGPT辣么火,你却不会注册!

我今天吃了SHI,请对下联

你他喵的告诉我标题怎么起!

这可能是最全的SpringBoot3新版本变化了!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值