首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

开发的bug,运维别接-技术人生系列第五十八期-我和数据中心的故事

前 言

在客户现场处理各种各样的问题,很多问题会涉及到数据库、存储、操作系统甚至应用代码,只有先定位问题的根源在哪个或哪几个方向,才能做出针对性的分析,否则就很有可能在处理问题的过程中南辕北辙,无的放矢,无法快速解决问题;

今天分享的问题,可能到最后,并不是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开发部分,如有理解错误的地方,还请大家指正^_^

如果读完您有所收获,不如一起转发分享,您的转发是我们持续分享的动力!

我们的新公众号中亦课堂已搭建完成,继我们的【技术人生系列】后【中亦实验室】会在中亦课堂里正式和大家见面,后面我们会持续给大家分享工作中的实战案例,以案例分析为形式,原理讲解为内涵,传递学习方法为核心,让你的工作更轻松,给你带来持续的竞争优势、绩效突破与提升。

更多内容请关注——中亦课堂

中亦课堂,带你从容进步

往期经典回顾

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180810B0RNTQ00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券