前 言
在客户现场处理各种各样的问题,很多问题会涉及到数据库、存储、操作系统甚至应用代码,只有先定位问题的根源在哪个或哪几个方向,才能做出针对性的分析,否则就很有可能在处理问题的过程中南辕北辙,无的放矢,无法快速解决问题 ;
今天分享的问题,可能到最后,并不是 DBA 可以控制的部分,然而作为 DBA ,时不时需要解释两个问题:
我们什么都没有变化,出了问题跟我们有关吗?
为什么以前没有问题,现在有问题了呢?
事实上,无论是作为 DBA 、系统工程师、开发等各类开发、维护人员,都不应该回避这个问题;往往是回答了上面的两个问题,我们才可以说真正理解和解决了问题 。
一.紧急问题
客户的应用维护同事找了过来:“ 我们的数据库应该是出问题了,我们的批量任务跑了好几次,好像都没跑过去,就一直挂起在那里,没法继续下去了,是不是要重启一下数据库看看 ?”
批量任务跑不下去了,都到了需要重启的阶段了???一了解才知道,问题已经出现了有一段时间了,各种分析也没有给出相关结论,最后认为是数据库出现了异常,可能需要重启数据库才能解决; 重启数据库可不是小动作,需要好好评估了。
二.了解情况
出现问题的这个系统是一个分析系统,它需要接收很多其他系统的数据,然后使用 impdp 工具将数据导入到数据库中,接下来再做一系列的聚合分析操作;而目前被卡住的步骤实际上是 impdp 的导入操作;
查看操作系统进程,可以在本机上看到impdp的进程
:
与应用确认就是这个
impdp
操作,本次操作的发起时间是
09:29
左右
;
看了看现在的时间,过去并没有太久,真的挂起了吗?不妨直接确认一下:
首先,可以看到这里的
impdp
操作指定了
logfile
,我们不妨看看现在
impdp
操作是阻塞在了哪个地方了,是导入数据?是创建索引?还是其他部分的导入操作?
截取日志看看,大吃一惊:
从这里的日志来看,这个导入任务名称叫做
SYS_IMPORT_FULL_01
;而这个导入在
09:39
就已经导入完成了
;
不着急,我们继续从数据库中确认这个任务的状态
:
从数据库中看,这个导入的任务也已经没有了;
那么这是个什么情况呢?看起来,问题还真是有点诡异,现在我们可以把问题定义为:
“
数据库的
导入
已经完成,但是操作系统的导入进程未结束
。
”
对于这样一个问题,如果是你,你该如何分析呢?
三.数据泵的那些事
通常来说,数据泵的导入导出任务会在数据库层面存在两类关键进程:一个
DM
进程和一个或多个
DW
进程,其中
DM
进程负责数据泵的管理调度任务,而
DW
进程作为导入
/
导出工作的实际工作者存在;然而,大多数朋友可能没有注意到实际上,在导入导出过程中,数据库中还存在一个进程用于最开始创建数据泵任务和退出数据泵任务
;
当然,我们在问题查到这里的时候,也会再多看一眼,看看操作系统层面的 impdp 进程到底在干什么;
1
我们使用
proctree
命令可以查看到进程的父子调用关系
:
可以看到,虽然
impdp
命令的日志显示已经导入完成了,但是
impdp
命令没有退出,而且其在数据库端的服务器进程也还没有退出;
2
那是不是数据库服务器端的进程遭遇了什么异常导致没有退出呢
?
如图显示对应
impdp
的数据库服务器进程,它的
program
中包含
udi
字样(如果是
expdp
这里是
ude
),它是负责创建与销毁
datapump
任务;
但是从数据库中看,这个会话还在,只不过这里它的状态是 inactive ,并非处于被阻塞状态,而 inactive 的状态已经持续了将近十几分钟了,也就是日志文件中导入完成到当前的时间;
现在问题更明确了
,
数据库导入完成后,客户端进程(
imdp
进程)和服务器进程(
udi
进程)都没有正常退出,而服务器进程(
udi
进程)看起来确实并没有再工作了
;
接下来该如何分析,如果是你,你会怎么进一步来分析呢?
四.操作系统的那些事
进程没有正常退出,首先想到的是所有的进程都没法正常退出问题吗??显然不是,数据库目前还在正常运行,正常的登入登出都没有问题;那么是 datapump 进程在退出时会出现异常吗??我们在问题时段测试导入导出同样都是没有问题的;是 dump 文件的问题吗??显然也不是,毕竟日志文件已经显示导出完成了,此时完全不需要 dump 文件了;
这个时候我们不妨看看,客户端进程和服务器进程在操作系统上的状态如何 :
如上图,其中服务器进程(
20906220
)并当前并没有系统调用,而客户端进程(
8323430
)则在调用
write
函数,似乎在写什么消息;而反复调用
procstack 8323430
我们看到其调用的
stack
函数都是一样的,这样似乎意味着
impdp
进程无法完成一个写消息的操作;抑或者是因为操作系统的进程已经处于异常状态了?
问题在哪呢?不妨思考一下?接下来该如何分析呢?
五.测试与沟通
前面我们 impdp 的实质工作都做完的情况下,它需要写什么消息呢?
我们来简单的看一下一个导入操作的过程 :
如上,在操作系统层面指向
impdp
命令,我们指定了
logfile
,但是执行过程中,
impdp
仍然会输出相关信息到我们的终端屏幕上;
我们再来看问题系统的 impdp 调用关系 :
很显然,
impdp
命令并不是终端也不是
shell
脚本调用的,而是通过
java
程序调用的,作为终端需要接收
impdp
的输出并打印在屏幕上,那作为
java
程序呢,它会不会去处理这些
impdp
的输出呢?
到这里,就需要联系开发人员进行沟通了,在与开发人员说明了我的想法后,开发表示并不认同,理由有几点:
开发同事认为,他们已经在
java
代码中处理标准输出流;他们的考虑是这样的,如果在
impdp
导入过程中,出现了某些表导入失败或者整个导入失败的情况,他们是希望打印到应用对应的日志文件中进行提示或者异常处理的,所以输出他们是有正常的处理逻辑的,并不会出现不接收输出的过程;
这个程序以往都能正常运行,为什么今天就不能正常运行了呢?应用代码层面并没有做出具体的改动,导入的
dump
文件大小也不是最大的;
对于开发的答复,我们需要验证两点 :
1
如果代码中确实有对标准输出的内容,那我们的推断就确实应该不成立了;
不过在这里我们还是多做了一步测试,大致测试内容如下 :
从上图能说明什么呢?对于一般的命令或者程序,如果没有报错,那么它的输出我们通常认为会输出到标准输出流,只有出现了异常信息才会输出到错误输出流,
从这里看,我们发现
impdp
命令的输出都打到了错误输出流中
;如果应用代码中只处理了标准输出流,而没有处理错误输出流,那么我们前面的怀疑就不无道理了
;
2
该程序以往在执行,没有报错的原因是啥?
其实,我们需要理解的一点是,如果 java 程序接收了错误流却没有处理, java 对于这些信息是不是会有缓存机制呢?缓存的大小是否有限制呢?是否以往并没有超出缓存的大小,而今天的这个导入的输出数据量已经超出了缓存的大小了呢?
我们简单对比了一下导出日志文件的大小(错误流输出的内容其实与 impdp logfile 的内容是一致的),发现当前出现问题的这个 impdp 的日志文件较以往都要大一些,很显然,日志文件的大小与导入的数据量并没有关系,而是与导入的表和分区的个数相关;而这个导入操作,主要是因为分区数过多而出现了挂起的操作 ;
六.验证&应急处理
解释完上面的信息,虽然开发的同事还是将信将疑,我们也只能通过将原来处理标准输出流的部分改成处理错误输出流了?可是现在无法修改 java 代码来解决这个问题;
从正确的处理流程上,其实还是需要改写代码,毕竟代码逻辑里还是需要处理 impdp 的相关输出的,不过如果我们了解到代码获取这些信息只是为了判断导入有没有报错的话,我们只要人工确认导入没有报错,那么绕过这个处理步骤也是可以的;
那么怎么处理呢?首先这里当然不是说直接不调用 java 程序,直接手动导入,毕竟 java 代码中在导入前后还会有一系列复杂的处理流程;我们再仔细看看 impdp 的调用关系 :
在这里
impdp
的命令是由
java
直接调用起来的,前面提出因为导入命令的错误输出流的数据量过大导致的
,那么我们是不是可以在这将标准输出流和错误输出流都重定向到其他地方,让
java
程序接收到的错误输出流为空或者极少,就不存在这个问题了
;
+
+
很幸运,与开发沟通确认这里的
impdp
命令是可以通过配置
xml
文件来改变的:
原命令(示例):
修改后的命令(示例):
+
+
修改完配置文件后,停止 java 程序, kill 相关进程,清理已导入的相关表之后,再次执行 java 程序,导入顺利完成,至此,问题得以解决,后续需要开发处理的,就是将原来对标准输出流的操作改为对错误输出流的操作即可 ;
七.这里的问题是什么??
OK,我们先梳理一下我们遇到的问题是什么,是如何一步一步分析下来的 :
java
程序调起
impdp
命令进行导入操作;
日志显示导入已经
“successfully completed”
;
但是
impdp
命令和服务器端的
udi
进程都没有正常结束;
通过
procstack
查看命令堆栈信息,发现
impdp
命令在写消息;
正常
impdp
命令会有输出,通过
java
调起的
impdp
命令,是否
java
没有正常接收处理输出;
确认
java
程序处理的是标准输出流,而没有考虑到错误输出流,是否会导致错误输出将程序的接收缓冲区撑满;
通过应用配置文件将处理命令的标准输出流和错误输出流重定向,让
java
接收的输出流数据都为空;重新执行成功;
以往能成功执行的原因也是因为错误输出流的数据量不够大,并不会撑满
java
的接收
buffer
;
后续修改相关
java
代码,及时、正确地处理错误流数据;
为什么以前没有出现这个问题呢?因为以前产生的错误流数据量足够小;
本质上我们可以认为这是应用程序的
bug
。
八.测试重现比处理问题更有趣
这个 CASE 处理完后,由于并没有拿到对应的应用代码,也就没能及时重现该问题;不过,最近我又遇到了同样类似的问题,开发同样是通过 java 代码调起脚本,进行 sqlldr 操作,而且在接收了 shell 脚本的标准输出流后,没能及时处理,导致 sqlldr 操作挂起,同样通过 procstack 查看进程堆栈,可以发现进程一直在写消息无法完成 ;
我首先开发那里拿到了相应的 java 测试代码 :
这段代码可以实现如下的功能
:
这段代码接收了来自
/home/oracle/java/test.sh
脚本的标准输出流;
通过传入的参数控制是否读取(消费)接收缓存中的数据,如果能及时读取接收缓存中的数据,缓存就不会撑满;
如果缓冲区满,
ps.getInputStream()
操作无法完成,
ps.waitFor()
操作将让程序一直等下去;
ps.waitFor()
的调用会等待
ps
的相关操作完成,才能继续
;
Here we go!!!
① 准备 test.sh 脚本:
② 传入参数 , java 代码不断读取(消费)掉接收到的标准输出信息,代码能正常执行:
③ 传入参数 1 , java 只接收标准输出信息,而不消费掉,那么代码将挂起:
④ 再调整 test.sh ,让其标准输出数据量足够少
⑤ 再执行 java 代码,即使传入的是参数 1 ,因为接收缓冲区足够接收完也能正常执行:
结束语
短短的篇幅,看起来轻松惬意,但实际上在处理过程中也是经历了一些曲折和争论的,不过最重要的是每个CASE分析透彻之后收获还不少,恰逢最近居然几次遇到类似的案例,不禁怀疑是不是程序员们处理调用shell脚本或者命令时,还是缺少经验,特此分享出来,希望大家有收获,另外关于java开发部分,如有理解错误的地方,还请大家指正^_^
本文转载于中亦安图
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31547066/viewspace-2284119/,如需转载,请注明出处,否则将追究法律责任。