背景在最近的工作中,笔者负责开发了 ShardingSphere 系统表
功能,该功能会在 ShardingSphere 启动时模拟不同数据库的系统表,从而兼容各种数据库客户端,避免客户端查询系统表时出现报错。按照正常开发的流程,笔者对功能进行了较为全面的测试,然后将功能提测给测试同学。本以为一切会很顺序,但在功能测试的第一步就出现了问题——ShardingSphere 打包后启动起来,Zookeeper 中无法查询到系统表的元数据。**为什么直接通过 IDEA 启动测试正常,打包之后启动就无法加载系统表呢?**为了搞清问题的原因,笔者开始了后文中的一番调查。
问题排查根据 打包后 ShardingSphere 无法加载系统表
这个现象,首先想到的就是打包后的程序是否丢失了系统表配置文件。为了排查丢失配置文件的可能性,笔者使用 JD-GUI
对打包后的 jar 包进行反编译,得到如下结果:
从结果可以看出,ShardingSphere 加载系统表所使用的配置文件都存在,并未出现配置文件丢失的情况。为了进一步定位打包程序的问题,我们需要使用远程调试技术,了解打包程序内部运行的逻辑。
远程调试Java 远程调试技术主要是基于 JDWP(Java Debug Wire Protocol)
协议,而 JDWP
协议是 Java 语言中用于调试程序和被调试程序之间进行通信的协议。调试程序和被调试程序可以位于同一台机器上,也可以位于不同的机器上。要使用 JDWP 进行远程调试,首先需要在被调试程序中配置如下参数:
1 java -agentlib:jdwp=transport=dt_socket,server=y,suspend =n,address=8888 -jar xxx.jar
java -agentlib:jdwp
用于开启远程调试功能,它会在 address
参数对应的端口上开启监听,等待调试程序连接。JDWP
中的具体参数含义,我们可以使用 java -agentlib:jdwp=help
进行查看,具体结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Java Debugger JDWP Agent Library -------------------------------- (see http://java.sun.com/products/jpda for more information) jdwp usage: java -agentlib:jdwp=[help]|[<option>=<value>, ...] Option Name and Value Description Default --------------------- ----------- ------- suspend=y|n wait on startup? y transport=<name> transport spec none address=<listen/attach address> transport spec "" server=y|n listen for debugger? n launch=<command line> run debugger on event none onthrow=<exception name> debug on throw none onuncaught=y|n debug on any uncaught? n timeout=<timeout value> for listen/attach in milliseconds n mutf8=y|n output modified utf-8 n quiet=y|n control over terminal messages n Obsolete Options ---------------- strict=y|n stdalloc=y|n Examples -------- - Using sockets connect to a debugger at a specific address: java -agentlib:jdwp=transport=dt_socket,address=localhost:8000 ... - Using sockets listen for a debugger to attach: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y ... Notes ----- - A timeout value of 0 (the default) is no timeout. Warnings -------- - The older -Xrunjdwp interface can still be used, but will be removed in a future release, for example: java -Xdebug -Xrunjdwp:[help]|[<option>=<value>, ...]
transport
:表示调试程序和被调试程序间的通信协议,dt_socket
表示使用 socket
方式进行通信;server
:表示是否为调试程序开启监听,y
表示开启,默认为 n
表示不开启;suspend
:表示是否在被调试程序启动阶段等待调试程序连接,配置成 y
可以用来调试启动流程,如果不需要调试启动流程,设置为 n
即可;address
:被调试程序监听的端口;除了上面介绍的常用配置方式之外,还可以通过 java -Xdebug -Xrunjdwp:transport
开启远程调试,这种方式是 JDK 1.5
及之前版本开启远程调试的方式,在 JDK 1.5
之后的版本,官方都推荐使用 java -agentlib:jdwp
方式。
1 java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend =n,address=8888 -jar xxx.jar
在了解了 JDWP
远程调试的使用方式后,笔者为 ShardingSphere 启动脚本添加了如下参数,由于加载系统表逻辑是在启动流程中,因此使用了 suspend=y
参数:
1 2 3 4 JAVA_OPTS=" -Djava.awt.headless=true -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8000" JAVA_MEM_OPTS=" -server -Xmx2g -Xms2g -Xmn1g -Xss1m -XX:AutoBoxCacheMax=4096 -XX:+UseNUMA -XX:+DisableExplicitGC -XX:LargePageSizeInBytes=128m ${VERSION_OPTS} -Dio.netty.leakDetection.level=DISABLED " MAIN_CLASS=org.apache.shardingsphere.proxy.Bootstrap nohup java ${JAVA_OPTS} ${JAVA_MEM_OPTS} -classpath ${CLASS_PATH} ${MAIN_CLASS} >> ${STDOUT_FILE} 2>&1 &
启动 ShardingSphere 后,我们在 IDEA 中配置远程调试,选择 Run/Debug Configurations -> Remote JVM Debug
,然后配置被调试程序的 Host 和 Port。
保存之后,我们使用 IDEA Debug 模式启动程序,可以看到在 ShardingSphere 启动过程中,程序会停在我们设置的断点处。
问题优化通过 JDWP 远程调试技术,笔者在系统表加载的流程中发现了问题的原因,在 IDEA 中可以正常运行的 File#listFiles()
方法,打包之后返回的结果却为 null,这导致系统表元数据为空。
查阅了一些资料后发现,当源码打成 JAR 包后,由于 JAR 包是一个压缩包,无法直接使用 File API
去访问压缩包中的文件,需要使用流进行文件的读写。针对这个问题,笔者对系统表配置读取的逻辑进行了如下调整:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private static Collection<InputStream> getSchemaStreams (final String schemaName, final DatabaseType databaseType) { SystemSchemaBuilderRule builderRule = SystemSchemaBuilderRule.valueOf(databaseType.getName(), schemaName); Collection<InputStream> result = new LinkedList <>(); for (String each : builderRule.getTables()) { result.add(SystemSchemaBuilder.class.getClassLoader().getResourceAsStream("schema/" + databaseType.getName().toLowerCase() + "/" + schemaName + "/" + each + ".yaml" )); } return result; } private static ShardingSphereSchema createSchema (final Collection<InputStream> schemaStreams, final TableMetaDataYamlSwapper swapper) { Map<String, TableMetaData> tables = new LinkedHashMap <>(schemaStreams.size(), 1 ); for (InputStream each : schemaStreams) { YamlTableMetaData metaData = new Yaml ().loadAs(each, YamlTableMetaData.class); tables.put(metaData.getName(), swapper.swapToObject(metaData)); } return new ShardingSphereSchema (tables); }
经过调整之后,重新进行了打包测试,问题终于迎刃而解。通过这次问题的调查,笔者深刻认识到功能自测不能只在开发环境中进行测试,更应该考虑实际部署的环境,按照真实场景进行测试,从而充分暴露这些潜在的问题,希望后续的开发工作中能避免类似的问题。
参考文档