做好大型数据中心的运维不是一件容易的事,因为光技术层面,就涉及到众多的技术领域。
日常运维中,有一类问题是大家比较头疼的,就是边界问题,例如数据库内存泄露的问题。
处理该类问题,既需要懂系统DBA的知识,又需要懂开发DBA的知识,还需要对操作系统、中间件的原理有足够的了解。
例如下图,Oracle数据库服务器占用的私有内存(PGA)在不断增长,如果不及时处理,服务器由于内存耗尽甚至无法telnet/ssh登陆。
北京,晴,碧空如洗,万里无云。
正在工位上远程分析一个客户问题的时候,销售L来电话了,来活了。
“小y,有一家潜在的银行客户,关键业务系统出现了一个很严重的问题,困扰他们很久了。已经找好多人查过了,但一直没找到根本原因,如果我们可以找到原因并顺利解决掉该问题,后面的事情就交给我吧”
“好吧,我尽力”。实际上,听到这里,我就一下来了精神,有点迫不及待了。
赶紧要到客户的联系方式,约了第二天到现场进行分析,顺便了解了一下问题的情况。
原来,他们一个重要的业务系统,ORACLE数据库服务器采用的是IBM的小机,内存使用率随着系统运行,呈现逐渐增长的趋势,如果不及时处理,服务器由于内存耗尽甚至无法telnet/ssh登陆。私有内存增长趋势如下图所示:
内存耗尽导致无法登陆服务器的情况已经发生好几次了。一开始,通过定期重启数据库来解决,后来,他们发现重启中间件服务器也可以解决,就一直沿用定期重启中间件的方式了。
前期找的人的分析无非是两种方向:
一种怀疑是oracle本身的BUG,但不知道是哪个BUG;另外一种怀疑是应用程序的问题,但也说不出是哪支程序哪段代码有问题,拿不出证据,项目组也不承认,最后的结果是陷入人肉运维,只能定期重启。只是,如果重启必须在白天完成的话,影响就比较恶劣了,领导很重视这个问题。
难怪,原来是内存泄露的问题。
如前面所提到的,处理该类问题,既需要既懂系统DBA的知识,又需要懂开发DBA的知识,还需要对操作系统、中间件有足够的了解。
系统环境基本信息如下
Topas中可以看到,计算内存占了96%,换页空间使用率高达40%。说明数据库服务器已经由于内存不足发生了大量换页,系统已经处于非常危险的状态,如果不及时处理,将可能出现宕机的情况。解决问题前,可通过定期逐个重启WAS或者数据库服务器来临时解决。
(2)那么内存到底用到了哪里呢
该服务器只部署了数据库。数据库对内存的占用为SGA加PGA,以及每个服务进程在操作系统级的消耗。
SGA是全局的共享内存,PGA是每个服务进程的私有内存。
其中SGA参数设置为10G,PGA参数设置为2560M,服务进程个数为500个,每个进程在操作系统级是固定的,占用大概为4M,即数据库对内存的使用最预期为:10G + 2560M + 500 * 4 =15G左右。
从内存的使用规划上来说,占LPAR总内存大小的62.5%,是一个比较合理的值。
其中SGA是一个硬限制,但是PGA部分不是个硬限制,PGA只限制工作区workarea(Hash Join或排序)的大小,并不限制变量、数组的内存占用。如果应用程序使用了数组,并且数组的元素的个数在持续增大,PGA是限制不了这部分内存的。
下图是PGA内存部分的统计情况,关注红色加框部分,可以看到,虽然PGA参数设置的参数是2560M,但是PGA总分配的大小达到9776680960即9.1G,其中工作区的峰值为372M.也就是说,PGA应该是被变量、数组这些不受工作区限制的部分给占用掉了!
通常来说,PGA一般是SQL、存储过程执行结束后就应该立即释放,但是这里为什么执行结束了还不释放呢?此时我们需要继续深入分析,对不释放的进程做heapdump,看看内存中导致存储的是什么内容,做heapdump方式如下
通常来说,PGA一般是SQL、存储过程执行结束后就应该立即释放。那么什么情况下会导致内存不释放呢?
答案是使用全局变量的时候。
全局变量的作用域是会话级(进程级)的,不会随着SQL、存储过程执行结束而结束生命周期,之所以是全局的,意味着同一个会话(进程)可以在下次调用时,依然看到该变量的值。
对应到XXX这个具体的CASE,导致内存泄漏的原因如下
应用程序在数据库中存在一个package,即PKG_XX。
该package中的存储过程sp_xx定义的参数p_target 是IN OUT 类型。参数p_target传入的是一个集合,即o_target。该type中包含一个数组ret。
存储过程中参数p_target被定义成了IN OUT,这意味着p_target的生命周期实际上是在存储过程之外,即上述存储过程执行完毕后p_target还会存在。该参数的生命周期并不是存储过程调用结束就释放,而是session级,意味着需要等到会话断开,生命周期才结束。
存储过程里有如下代码:
以下是利用全局变量/数组(与XXX系统sp_xx存储过程in out参数的作用域一样,全局作用域在会话级)的编程缺陷来重现非活动会话不释放PGA的过程。
可以看到,虽然存储过程执行结束,全局变量/数组被赋予了指,但是再次调用时由于全局变量/数组没有重新初始化,而是不断extend来扩展数组中元素的个数,该编程方式必然会导致PGA不释放,随着执行次数增大,PGA继续增大,既重现了内存泄漏的过程。XXX系统的变成问题与该测试案例类似,因此需要开发团队修改程序,利用重新初始化来限制数组中元素的个数,从而保证内存占用的稳定性。
(1)原因总结如下
经分析,造成内存增长的原因是应用程序未正确初始化数组的BUG导致内存泄漏。
应用程序具体内存泄漏点如下:
应用程序在数据库中存在一个package,即PKG_XX。
该package中的存储过程sp_xx定义的参数p_target 是IN OUT 类型。参数p_target传入的是一个集合,即o_target。该type中包含了一个数组ret。
存储过程中参数p_target被定义成了IN OUT,这意味着p_target的生命周期实际上是在存储过程之外,即上述存储过程执行完毕后p_target还会存在,即还占着内存。该参数的生命周期并不是以存储过程调用结束为周期,而是以session断开做为结束周期,意味着需要等到会话断开,生命周期才结束。因为使用了jdbc连接池技术,因此会话是一个长连接,且连接重用比较频繁,因此会话不会轻易结束。
其中存储过程sp_xx里有如下代码:
1)运维需要添加对非活动进程依然占用较多PGA内存的监控。
2)开发在使用全局变量时,务必关注是否存在内存泄露的问题。
建议记住下列查询,不定期监控,如果出现多余1行的返回结果,即非活动进程依然在消耗PGA内存,则可能存在全局变量导致内存泄露的问题,需要提前解决。
如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!
赞0
添加新评论0 条评论