这里我使用了内存增长,不是内存泄露,因为内存增长的原因除了内存泄露,还可能是其他问题引起的,比如这个系列文章中提到的,容器数据未释放。内存增长的问题遇到过一次,当时的应用场景是这样的:

        生产环境的oracle分为两个RAC,需要做容灾演练,就把其中的一个RAC给停掉了,看看程序能否连接到另外一个RAC。有一个程序在这种情况下,出现了内存不断增长的情况,内存疯狂增长,最终内存耗尽,导致业务主机宕机。后来派出框架部门和业务部门一起来分析,最终得出的结论是由于数据库连接没有释放导致数据处理时,一直在使用一个失效的连接,所以不断抛出OTL的异常。

        大概的逻辑是这样的,程序启动时,会对每一个数据库的做一个连接池,当需要数据库连接时,就从连接池中获取一个数据库连接即可,正常情况下,连接可用,可以直接做数据处理,当数据库发生异常时,比如说某一个RAC宕机时,数据库连接已经失效,此时需要连接池把这个连接回收掉,回收连接有两个条件,1、连接没有被占用;2、连接已经失效。如果程序不去主动把连接释放,连接会一直处于被占用的状态,导致这个连接一直存在在连接池中,但是连接确实是失效的,下次去连接池申请连接时,申请到的,还是这个错误的连接,就会出现不断去尝试执行SQL,但是却不断失败的情况。当时一位数据库接口开发人员分析说当数据库宕机的时候,不断的尝试,OCI会有内存泄露的情况,由于当时没什么经验,也就相信了。后来就修改了业务代码,在异常的情况下,把数据库连接释放掉,连接池会把数据库连接回收掉,这个问题也就“愉快“的解决了。

        问题“愉快”解决的原因是多方面的:

   第一:领导希望能看到问题尽快解决,不管解决的方式是怎样的;

        第二:框架部门的技术是比较强大的,作为业务部门,我们选择相信框架的解释;

        第二:单点故障测试不是每天都进行的,如何对我们分析出的问题原因做验证,是一个复杂的问题。

        据我所知,后来客户也没在做过单点故障的测试,所以说这个问题,其实只是做了修改,但是却没有验证之前的分析和修改。

        最近由于客户要上一个新的版本的库,需要进行全量的测试,这个程序再次被测试到,这次的场景发生了变化,是这样的:

        客户在测试时,人工的把数据库给停掉了,程序在另一台主机,程序都没有停,最后发现这个程序占用的内存达到了260多G,一下子又把这个问题暴漏了出来。其实一开始听到这个问题时,我主观的想到了是OCI的问题,因为数据库已经停掉,不断的去尝试,导致OCI内存泄露,最终内存涨到了260多个G。这样分析似乎没有”问题“,可是却有一个最大的问题,因为当时给出这个结论的框架部门的员工已经离职,如何让客户信服。我决定自己测试下这个问题。我写了一个测试的程序,来测试OTL的问题的方法,主要分为几种Case:

  1、数据库主机不存在,不断尝试连接数据库

  2、数据库主机正常,oracle服务启动,不断尝试连接数据库

  3、数据库主机正常,oracle服务正常,当连接上后,把数据库服务停掉,然后程序不释放连接,不断的使用旧的连接去尝试执行SQL

  代码如下:

  



#include <iostream> 
using namespace std;

#define OTL_ORA10G  //不可缺少
#include "otlv4.h" //注意OTL头文件位置

otl_connect db;
string strMonitorSql = "select 1 from dual";

int main() 
{ 
    otl_connect::otl_initialize();  

    cout << "try to connect oracle" << endl;

    while(!db.connected)
    {

        try 
        {
            db.rlogon("ad/ad@192.168.80.13:1521/dev"); 
        }
        catch(otl_exception& p) 
        {  
            cerr<<p.msg<<endl;  
            cerr<<p.stm_text<<endl;  
            cerr<<p.sqlstate<<endl;  
            cerr<<p.var_info<<endl;  
            cout<<"Connected to Database fail "<<endl;
            continue;
        }
    }

    while(true)
    {
        try
        {
            cout << "try to select to oracle" << endl;
            otl_nocommit_stream os(1, strMonitorSql.c_str(), db);
            os.close();
        } 
        catch(otl_exception& p) 
        {  
            cerr<<p.msg<<endl;  
            cerr<<p.stm_text<<endl;  
            cerr<<p.sqlstate<<endl;  
            cerr<<p.var_info<<endl;  
            cout<<"select fail "<<endl;
            continue;
        }

        sleep(3);
    }
    
    cout<<"Connected to Database success"<<endl;
    db.logoff(); 
    
  return 0; 
}



  Makefile如下



objects = connect_oracle
SUFFIX=

CC = g++

CPPFLAGS =  -O2 -fpic -ftemplate-depth-64  -m64 -ggdb  
# CPPFLAGS =  -g  

#编译OCI程序时所用到的头文件路径   
INCLUDE_PATH = -I ${ORACLE_HOME}/rdbms/public -I $(OB_REL)/include/ -I $(OB_REL)/include/3rd/otl -I .
#编译OCI程序时所用到的静态链接库路径   
LIB_PATH = -L${ORACLE_HOME}/lib/       
#编译OCI程序时所用到的静态链接库     
LIBS = -lclntsh   

.PHONY:all
	
all:$(objects) 

$(objects):%$(SUFFIX):%.cpp 
	$(CC)  $< $(CPPFLAGS) ${INCLUDE_PATH}  ${LIBS} ${LIB_PATH} -o  $@    

.PHONY:clean
clean:     
	rm -f ./core* ${objects}



  对于三种情况,OTL抛出的oracle的异常,都是不同的

  第一种:ORA-12543: TNS:destination host unreachable

  第二种:ORA-12541: TNS:no listener

  第三种:ORA-03114: not connected to ORACLE

  对于这三种情况,循环连接,使用top或者pmap,都没有发现内存增长的情况,这让我对之前框架人员说的OCI的内存泄露产生了怀疑。有时候,只需一小段程序,就能检验出来某个重大的问题,就像这个问题,我选择了相信了一个同事,却忽略了如果OCI有问题,ORACLE这么大的商业公司,应该很及时的发现,怎么会任由这个问题一直存在,并且网络上没有关于任何OCI内存泄露的帖子。

  排除了OCI和OTL的问题,则可能是框架程序的问题,我使用了valgrind这个工具,对程序进行了内存泄露的测试,下一篇文章,介绍下valgrind的用法。