润普挂卷失败之老卷宗对接NP无法获取案件信息问题排查

润普挂卷失败之老卷宗对接NP无法获取案件信息问题排查

写在最前面

根因:NP的dzjzzzfw与老卷宗dzjz服务用的zookeeper不是同一个,且老卷宗指向的zookeeper没有任何一个匹配的dzjzzzfw。仅有消费者,没有任何生产者,导致老卷宗通过dubbo获取案件信息失败。

非以下情况本文基本不具有参考价值,可不继续观看

  • 润普通过老卷宗挂卷,且堆栈错误明确指向获取案件信息空指针
  • 老卷宗刷新卷宗失败,且堆栈错误明确指向获取案件信息空指针
  • 非卷宗服务,但使用了zookeeper、dubbo组件,且堆栈错误明确指向获取服务提供者空指针

问题现象

现场反馈润普所有案件均挂卷失败,从润普提供的信息来看,润普调用多个案件多个接口,老卷宗均返回了:{“code”:201,“compressData”:false,“message”:“接口调用不成功,action【fileXXXAndDirectoryInfoSharedStorage】”,反复重试很多次结果都一样,已经持续几天了,客户着急了,卷宗的研发还在忙别的,正好我在现场,之前也有卷宗问题处理经验,我就先自己上手排查一下吧。

初步排查

根据之前的经验,按照润普提供的接口路径,在老卷宗代码里面找到了对应的接口,但接口中没有润普提供的错误信息,后来在接口的父类com.t.s.c.httpapi.AbstractHttpApi#execute中找到了对应代码

    @Override
    public ApiResult execute(String action, Map<String, String> headers,
            Map<String, Object> params) {
        if (action.indexOf('.') > 0) {
            action = action.substring(action.indexOf('.') + 1);
        }
        try {
            Method method = this.getClass().getDeclaredMethod(action,
                Map.class, Map.class);
            method.setAccessible(true);
            return (ApiResult) method.invoke(this, headers, params);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return new ApiResult(ApiResultCodeConsts.API_NOT_FOUND这里是201,
                "接口调用不成功,action【" + action + "】");
    }

不过这里是通用的报错,并不能直接定位具体问题。还好这里有输出堆栈信息,根据代码路径,去logback.xml中找到对应logger,确认日志输出到了 KaTeX parse error: Expected group after '_' at position 29: …lassifiBusiness_̲{ip}_${port}.log 文件中。

	<!-- 忽略不相关内容 -->
	<appender name="rpClassifiBusiness" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${baseHome}/${appname}_rpClassifiBusiness_${ip}_${port}.log</File>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss:SSS , GMT+8} [%-5level] [%-5thread] [%logger{20}:%line] - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>info</level>
        </filter>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>
                ${baseHome}/${appname}_rpClassifiBusiness_${ip}_${port}.%d{yyyy-MM-dd}.log.zip
            </fileNamePattern>
        </rollingPolicy>
    </appender>
	<!-- 忽略不相关内容 -->
    <logger name="com.thunisoft.fy.dzjz.httpapi.ZnfzbaxtHttpApi" level="info" additivity="false">
        <appender-ref ref="rpClassifiBusiness" />
    </logger>

跟现场要来了对应日志,并使用调用参数搜索,定位到获取案件信息空指针

2023-02-08 08:39:52:340 [INFO ] [qtp970538683-19221] [c.t.f.d.h.ZnfzbaxtHttpApi:2312] - 从np获取案件信息
2023-02-08 08:39:52:341 [ERROR] [qtp970538683-19221] [c.t.f.d.h.ZnfzbaxtHttpApi:53] - null
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.GeneratedMethodAccessor888.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.thunisoft.summer.component.httpapi.AbstractHttpApi.execute(AbstractHttpApi.java:51)
	at com.thunisoft.fy.dzjz.httpapi.DzjzHttpApiServlet.doPost(DzjzHttpApiServlet.java:83)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:713)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:806)
	at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:652)
    ... 去掉不相关内容
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:608)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:543)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException: null
	at com.thunisoft.fy.dzjz.httpapi.ZnfzbaxtHttpApi.getAjxx(ZnfzbaxtHttpApi.java:1637)
	at com.thunisoft.fy.dzjz.httpapi.ZnfzbaxtHttpApi.getSpAjxx(ZnfzbaxtHttpApi.java:2313)
	at com.thunisoft.fy.dzjz.httpapi.ZnfzbaxtHttpApi.fileClassificationInfoAndDirectoryInfoSharedStorage(ZnfzbaxtHttpApi.java:1879)
	... 58 common frames omitted

而对应业务代码长下面这样

    private CaseInfoBean getAjxx(Long NAjlbs, Integer NFyDm, Integer NAjlb) {
        BaseInfoBean req = new BaseInfoBean();
        req.setNAjbs(NAjlbs);
        req.setNAjlb(NAjlb);
        req.setNFyid(NFyDm);
        if (ajxxDubboService == null) {
            ajxxDubboService = (IAjxxDubboService) ArteryUtil.getBean("ajxxDubboService");
        }
        CaseInfoBean ajxx = ajxxDubboService.getAjxx(req); // 这一行报错了。ajxxDubboService类型为com.thunisoft.dzjz.dubbo.ywxtProvider.IAjxxDubboService
        if (ajxx == null) {
            return null;
        }
        return ajxx;
    }

从上面代码来看,NPE的话,只可能是 ajxxDubboService 这个对象未实例化,而这个对象是使用一个dubbo组件注册到spring里面的,这个逻辑已经用了好多年了,结合以上情况分析,是老卷宗服务和审判系统之间的通道哪儿出问题了。

到这里根据经验,让现场检查dzjzzzfw是否正常,现场说NP研发已经检查过了,服务正常的。然后又想起来,之前好像谁说过老卷宗是有启动顺序要求的,没按照顺序启动可能会导致无法正常调用dzjzzzfw,但我TSTC、ADC、既往卷宗相关文档扒了一圈都没找到哪儿有这个说明,现场也不知道。还好来回换了好几个关键字,一页页扒,最终在TSTC中找到了徐海明明哥的一篇帖子电子卷宗启动方法及FAQ2.2版,里面描述了老卷宗的启动步骤,让现场按照帖子顺序验证,说好像好了,但润普下班了,需要明天验证。

  • zookeeper
  • …有些跟本文无关的省略了
  • dzjzzzfw
    • dzjz(老卷宗服务)

第二天一验证,凉凉,问题依旧,报错相同,这???船到桥头自然沉呀,不科学…

再次定位

第二天现场反馈问题依旧,客户又催了,得抓紧搞定了。

既然还不行,那就跟启动顺序没关系了,下一步干啥呢?网上看了下zookeeper、dubbo的一些使用教程,了解到zookeeper是一个服务注册中心,生产者和消费者都是跟注册中心交互,dubbo是一个分布式服务框架,卷宗使用dubbo框架的组件去跟注册中心交互,实现RPC调用。既然消费者初始化失败了,那么我们去注册中心里面看看生产者是否正常吧(在本文中dzjzzzfw就是生产者,dzjz就是消费者)。

记得之前搜过有个zkcli工具可以查看zookeeper服务情况,所以下载这个工具,解压到磁盘,结合网上的教程,连上注册中心,并使用zkcli的ls命令,观察服务情况

# 连接注册中心
E:\xxx\apache-zookeeper-3.8.1-bin\bin\zkCli.cmd -server 131.16.xx.xx:2181
# 查看根路径都有哪些节点
[zk:131.16.xx.xx:2181<CONNECTED> x]ls /
# 输出 [dubbo, zookeeper]
# zookeeper中基本没什么东西,着重看dubbo
[zk:131.16.xx.xx:2181<CONNECTED> x]ls /dubbo
# 这里输出一堆节点,[com.thunisoft.clfx.ajxx.provider.IAjDataDubboService, com.thunisoft.clfx.ajxx.provider.IAjSaveDubboService ... com.thunisoft.summer.component.approve.service.IProcessService] 
# 然后查看报错代码类型定义的那个接口是否存在
[zk:131.16.xx.xx:2181<CONNECTED> x]ls /dubbo/com.thunisoft.dzjz.dubbo.ywxtProvider.IAjxxDubboService
# 输出 [configurators, consumers, providers, routers]
# 继续查看消费者
[zk:131.16.xx.xx:2181<CONNECTED> x]ls /dubbo/com.thunisoft.dzjz.dubbo.ywxtProvider.IAjxxDubboService/consumers
# 输出 [consumer%3A%2F%2F131.16.xx.xx%2F...] 代表已经有消费者注册进来了
# 查看生产者
[zk:131.16.xx.xx:2181<CONNECTED> x]ls /dubbo/com.thunisoft.dzjz.dubbo.ywxtProvider.IAjxxDubboService/providers
# 输出 [] 空,从这里分析,应该是生产者没成功注册到zookeeper中

用另一家正常的单位重新走了一遍以上zkCli命令,确认正常的单位中providers列表也是有数据的,所以更加明确,当前问题就是生产者没注册成功,那就看看两边zookeeper配置有没有不同。

要来dzjzzzfw和dzjz两个服务的远程,查看两个服务的配置,发现dzjz(/bin/StartTAS.sh的export ZOOKEEPER_URL参数)及dzjzzzfw(/webapp/WEB-INF/classes/props/config.properties的zookeeper.dzjz.address参数)的zookeeper用的不是同一个。

再次用zkCli连上dzjzzzfw指向的zookeeper,使用 ls /dubbo/com.thunisoft.dzjz.dubbo.ywxtProvider.IAjxxDubboService/providers命令确认列表不为空,让现场调整后重启验证,确认相关功能正常,功能恢复。

现场为啥这么配置,什么时候改的,不好追溯,不过好在问题最终解决了。整理到这里,后续有类似问题直接来复制代码检查,加快问题处理效率。不过还是希望以后不会遇到这个问题,不然搞了一半天,就是这么一个简单配置问题,还是有点儿浪费资源的。

参考资料

zkCli 下载
https://dlcdn.apache.org/zookeeper/zookeeper-3.8.1/apache-zookeeper-3.8.1-bin.tar.gz

Zookeeper之zkCli.sh客户端的使用:https://blog.51cto.com/u_12564104/2896709

你可能感兴趣的:(经验分享,java,dubbo)