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