应用偶发性链接不上Oracle数据库的排查案例


问题描述

 

有一个应用,偶尔会出现访问不了的状况。具体表现为,当其它应用调用它的接口时,可能会出现超时,过一段时间后,再调用它的接口可能又正常了。观察应用日志发现,这个被调用的应用会偶发性地链接不上数据库。由于该应用没有办法及时地查询到数据库,应用返回数据时间过长,也就致使调用该应用接口的其它应用超时。这种状况比较偶发,一天大概几回,没什么特别规律。java

 

应用日志中只能大概看出,出现问题时该应用查询不了数据库,与数据库的链接丢失,没有太多其它的信息:web

2017-06-05 23:23:44,941 INFO  (?:?) - Get a connection...sql

2017-06-05 23:39:11,670 INFO  (?:?) - SearchPoList() has a Exception: java.sql.SQLException: ORA-03135: connection lost contact数据库

2017-06-05 23:39:11,670 WARN  (HouseKeeper.java:149) - #0001 was active for 324896 milliseconds and has been removed automaticaly. The Thread responsible was named 'http-8086-Processor24', but the last SQL it performed is unknown because the trace property is not enabled.bash

2017-06-05 23:39:11,671 INFO  (?:?) - Connection closed...服务器

2017-06-05 23:39:11,676 WARN  (RequestProcessor.java:516) - Unhandled Exception thrown: class java.sql.SQLException网络

2017-06-05 23:39:11,679 ERROR (StandardWrapperValve.java:253) - Servlet.service() for servlet action threw exception架构

java.sql.SQLException: ORA-03135: connection lost contactoracle

 

       at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)app

       at oracle.jdbc.driver.T2CConnection.checkError(T2CConnection.java:672)

       at oracle.jdbc.driver.T2CConnection.checkError(T2CConnection.java:598)

       at oracle.jdbc.driver.T2CPreparedStatement.executeForDescribe(T2CPreparedStatement.java:571)

       at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1038)

       at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1133)

       at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3285)

       at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3329)

       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

       at java.lang.reflect.Method.invoke(Method.java:585)

       at org.logicalcobwebs.proxool.ProxyStatement.invoke(ProxyStatement.java:100)

       at org.logicalcobwebs.proxool.ProxyStatement.intercept(ProxyStatement.java:57)

       at oracle.jdbc.OracleStatement$$EnhancerByProxool$$e42bfc21.executeQuery(<generated>)

       at com.sssoft.framework.db.DefaultDbManager.executeQuery(Unknown Source)

 

虽说已经肯定是应用链接不上数据库致使的故障,但,为何该应用会链接不上数据库而且仍是偶发性的呢?关于这个问题,开发和DBA等人一同排查了好几天,居然没有任何头绪,最后找到了我。而我,做为一个懂网络、操做系统、数据库(虽然是MySQL)和略懂程序的,具有全方面知识体系(稍微自诩下,但后面你就会知道,的确涉及到了各方面的知识)的运维工程师,天然不能放过这么有趣的问题了。好吧,实际上是由于这个应用当初是我部署上线的,我怎么能容许我本身部署的应用出现问题呢。何况,其它链接同一个数据库的应用也没说过有问题啊,然而,事实证实,这个问题涉及到的水比较深。

 

出现链接不上数据库的那个应用是Java程序来的,它使用了proxool链接池来链接数据库。而数据库自己是Oracle数据库。

 


问题初步排查

 

这个问题排查起来可能不是很容易有思路。固然,做为一个系统运维工程师,我仍是习惯于从操做系统层面开始排查起。因为问题是偶发性的,想要复现并不容易。但既然是应用链接不上数据库,那么能够观察下网络链接。

 

通过观察,我发现,在应用启动后的一段时间后,在应用服务器上大概有10多个与oracle数据库服务器的tcp链接:

但同一时刻,在Oracle数据库服务器上,却只能看到2个与应用服务器的tcp链接:

 

因为应用采用数据库链接池来链接Oracle数据库,也就是说,应用与数据库之间的链接是tcp长链接。那么正常状况下,在任一时刻,应用与数据库服务器两端的ESTABLISHED状态的tcp链接数量应该都是一致的。然而,我实际观察到的状况,正如上面所示,却不是这样。通过更加细致的观察,能够发现,一开始,在该应用刚刚启动时,两端的tcp链接数量是相同的。可是,过了一段时间后,oracle数据库服务器端与应用服务器的tcp链接数量开始减小,甚至出现为0的现象。这时就出现了我上面所观察到的奇异现象了。

两端的ESTABLISHED状态的tcp链接数量不一致是否是必定就有问题的呢?答案是确定的。由于tcp是面向链接的协议,链接的两端只有经过"三次握手"正常创建了tcp链接后,它们才能正式经过该tcp链接开始交互数据。若是其中一端不存在该tcp链接的信息,那么即使另外一端经过该tcp链接发送带应用数据的数据包过来了,不存在tcp链接信息的这一端收到数据包后也会直接将该数据包丢弃,从而致使两端没法正常交互数据。所以,问题就出在这了,正是由于应用和数据库服务器两端的tcp链接不对称,才致使了应用没法正常访问数据库。

 

可是,为何会出现这种两端的tcp链接数不对称的状况呢?为了方便排查故障,我另外克隆了一台应用服务器。这台新的应用服务器只有我在使用,当我不访问上面的应用时,上面的应用也就不会访问数据库,这样便于排查。这台新的应用服务器的IP是x.x.x.125,所链接的数据库服务器的IP是y.y.y.27。当通过一段时间后,这台应用服务器与数据库服务器各自上面的tcp链接数也开始出现不对称了。在应用服务器和数据库服务器上使用tcpdump抓包,再下载下来使用wireshark打开,能够发现下面三个比较怪异的现象:

 

一、 在应用刚启动时,保持应用空闲(即我不去访问应用,也就不会触发应用去访问数据库),此时抓包能够发现,虽然应用和数据库之间已正常创建了链接,但它们之间却没有任何数据包交互。

 

二、 当应用启动后通过一段时间后,数据库服务器端查看到的tcp链接数开始降低。若是我不去访问应用的话,数据库服务器端的tcp链接数甚至会降为0。应用服务器端的tcp链接数仍然保持跟应用刚启动时同样,有10多个。此时,我经过网页来访问应用,从而触发应用去访问数据库,我对这个过程进行抓包:

上面是我在应用服务器上的抓包结果。能够发现,应用服务器125的确有去往数据库服务器27的数据包,但却没有来自数据库服务器27的回包。因此应用服务器125一直在进行tcp重传(Retransmission)。此时,在数据库服务器27上面抓包,也的确没有抓到来自应用服务器125的数据包。这说明,由于某种缘由,从应用服务器125发出的数据包在网络中丢失了。具体是什么缘由,如今还没法肯定。

 

三、 当数据库服务器端的tcp链接数开始降低时,此时在数据库服务器端抓包:

能够发现,数据库服务器27尝试发送Keep-Alive包给应用服务器125,但并无收到应用服务器125的回包。数据库服务器27在后面还发送了一个RST包重置了y.y.y.27:1521  x.x.x.125:46343这个tcp链接,这直接致使了数据库服务器上的这个tcp链接被清理掉。同一时刻,在应用服务器125也没有抓到来自数据库服务器27的包。这也说明,由于某种缘由,从数据库服务器27发出的数据包在网络中丢失了。

此外,也还有不少疑问尚待解决。好比,那个Keep-Alive包是干什么的,为何会有Keep-Alive包?又为何会有RST重置包?

 

目前尚未明显的解决问题的思路,但我想,若是能搞清楚以上三个怪异现象/疑点,搞清楚哪些是正常的哪些是异常的,应该能够定位到出现异常的哪一部分(源端、目标端、网络仍是数据库),从而帮助解决问题。

 


应用的网络架构

 

既然涉及到了数据包在网络中丢失,那么,就有必要介绍一下应用服务器与数据库服务器之间的网络架构。

 

网络拓扑图可简化以下:

简单来讲,应用服务器(x.x.x.125)与数据库服务器(y.y.y.27)分属不一样网段,中间有台防火墙,各个网段的网关(x.x.x.254y.y.y.254)也位于防火墙上。虽然中间还有不少二层交换机、三层交换机等,但对问题的产生没什么影响,全部就省略不表。

 

按理说,数据包在网络中丢失了,那么,就应该找网络工程师一块儿排查下是什么问题。但无奈,我司的网络工程师水平过低,对网络底层原理、概念等的掌握还不如我(这并非我自诩自擂)。简单来讲,我从网络工程师那里没有获取到任何有价值的信息,他们也不认为网络自己有什么问题,也不肯意协助排查,我也没有权限看不了防火墙,因此我只能本身想办法排查了。

 


tcp链接的保持

 

前面我提到,在抓包时,我发现了3个怪异的现象。其中,第一个现象是,在应用刚启动,应用空闲时(即应用不主动去访问数据库),虽然应用和数据库之间已正常创建了链接,在应用和数据库服务器两端都可看到等量的并对称的ESTABLISHED状态的链接,但抓包却发现它们之间没有传递任何数据包。

 

关于这个问题,我查阅了<<TCP/IP Illustrated, Volume 1: The Protocols>>。其中,TCP Keepalive这章里面有提到:

Many newcomers to TCP/IP are surprised to learn that no data whatsoever flows across an idle TCP connection. That is, if neither process at the ends of a TCP connection is sending data to the other, nothing is exchanged between the two TCP endpoints. There is no polling, for example, as you might find with other networking protocols. This means that we can start a client process that establishes a TCP connection with a server and walk away for hours, days, weeks, or months, and the connection should remain up. In theory, intermediate routers can crash and reboot, data lines may go down and back up, but as long as neither host at the ends of the connection reboots (or changes its IP address), the connection remains established. This is how TCP/IP was designed.

上面这段话的意思是:空闲的TCP链接是没有数据流动的,也就是说,若是TCP链接的两端若是都不发送数据给对方,就没有任何东西在它们之间交互;也不会有定时的数据包轮询;当一个客户端和一台服务器创建TCP链接以后,若服务器离开几个小时、几天或几个月,链接也都还在的,即使中间的路由器和线路挂了、重启了或断了,但只要TCP链接两端的主机都没有重启(或更改它的IP地址),链接会一直保持ESTABLISHED状态;TCP/IP就是这么设计的。

 

也就是说,我观察到的第一个现象(应用空闲时,虽然应用服务器和数据库服务器之间创建了ESTABLISHED状态的链接,但却没有数据包交互),该现象是正常的。TCP/IP就是这么设计的。

可是,在该章的其它地方,它说:存在一种TCP keepalive机制,但它是可选的。TCP keepalive特性并非TCP规范的一部分(由于可能会有误判及产生额外的流量等),然而,大多数TCP实现都提供了keepalive能力。即使如此,TCP keepalive功能默认也多是关闭的。

 

TCP keepalive功能能够在TCP链接的其中一端,或两端启用,或两端都不启用。它的运做机制是这样的:若是TCP链接上有一段时间(叫作keepalive time)没有活动,那么,启用了keepalive的一端就会发送存活探测数据包给它的对端。若是没有收到任何响应数据包,该探测包就会周期性地(周期间隔为keepalive interval)重复发送,直到探测次数达到keepalive probes的设置值。当发生这种状况时,对端的系统将被断定为是不可恢复的而且该TCP链接会被终止。

变量keepalive time、keepalive interval和keepalive probes的值一般是能够修改的。一些系统容许在per-connection的基础上进行这些修改,另外一些系统则只能在全局范围修改(或可能两种都支持)。在Linux中,这些值能够经过sysctl系统参数来相应修改:net.ipv4.tcp_keepalive_time、net.ipv4.tcp_keepalive_intvl和net.ipv4.tcp_keepalive_probes。默认值分别是7200 (seconds,或2 hours)、75 (seconds)和9 (probes)。

Linux并未提供一个原生的工具来为那些未请求启用TCP keepalive的应用强制开启TCP keepalive功能。换句话说,虽然Linux支持TCP keepalive特性,也能够调整相关的定时器设置,但某一个TCP链接到底是否启用TCP keepalive特性,由上层的应用程序说了算(须要应用程序调用系统的API),Linux自己并无工具/设置项来让你为全部TCP链接都强制开启TCP keepalive特性。

 

1)    回到问题自己,应用服务器是RHEL系统,属于Linux的一种,天然也支持上面提到的三个属性:

[root@gw ~]# sysctl -a | grep 'keepalive'

net.ipv4.tcp_keepalive_time = 7200

net.ipv4.tcp_keepalive_probes = 9

net.ipv4.tcp_keepalive_intvl = 75

各参数含义:

  • net.ipv4.tcp_keepalive_time:若是TCP链接启用了TCP keepalive特性,那么,当TCP链接空闲时间达到该项设定值时,服务器就会发送存活探测数据包给它的对端。默认值是7200 (seconds,或2 hours)。

  • net.ipv4.tcp_keepalive_probes:发送存活探测数据包后,若未收到来自对端的响应数据包,服务器会尝试从新发送探测数据包,直到总的探测次数达到该项设定值。默认值是9 (probes)次。若最后一次探测时仍未收到对端的响应数据包,服务器会清除掉本身本端的该TCP链接。

  • net.ipv4.tcp_keepalive_intvl:每次发送探测数据包的间隔时间。默认值是75 (seconds)。那么来讲,从开始探测到最后清除TCP链接,总共须要通过tcp_keepalive_intvl * tcp_keepalive_probes的时间,若以默认值计算,为75 * 9 = 675秒,约为11分钟多。

 

即使如此,我又怎么知道应用服务器去往数据库服务器的TCP链接是否启用了TCP keepalive呢?毕竟,我没法知道应用程序是否有要求启用TCP keepalive。其实,经过netstat命令的 -o 选项能够查看到,在应用服务器上执行如下命令:

最后一列都是off的,说明在应用服务器125这端,全部与数据库服务器27的TCP链接都是没有启用TCP keepalive特性的。若是有启用的话,那么最后一列会显示keepalive的字样。

 

2)    数据库服务器是AIX系统,它也有一些相似的属性:

bash-4.2# no -a | grep 'tcp_keep'

tcp_keepcnt = 8

tcp_keepidle = 14400

tcp_keepinit = 150

tcp_keepintvl = 150

各参数含义:

  • tcp_keepcnt:等同于Linux的net.ipv4.tcp_keepalive_probes参数。默认值是8次。

  • tcp_keepidle:等同于Linux的net.ipv4.tcp_keepalive_time参数。单位为0.5秒,默认值是14400(即7200秒或2小时)。

  • tcp_keepinit:TCP链接的初始超时时间。AIX的官方文档中是这么说的,但没详细解释。单位为0.5秒,默认值是150(即75秒)。

  • tcp_keepintvl:等同于Linux的net.ipv4.tcp_keepalive_intvl参数。单位为0.5秒,默认值是150(即75秒)。那么来讲,从开始探测到最后清除TCP链接,总共须要通过tcp_keepintvl * tcp_keepcnt的时间,若以默认值计算,为75 * 8 = 600秒,即10分钟。

 

在AIX上,也可使用netstat命令的 -o选项,但需结合awk来处理。想要查看ipv4协议的与某一台服务器(假设为x.x.x.x)的TCP链接是否启用了TCP keepalive特性,可使用下面的命令,只须要把x.x.x.x替换为相应的IP就行:

netstat -nao -f inet | awk '{ p2line=pline; pline=cline; cline=$0; if(p2line ~ /x.x.x.x/) {print p2line cline}; }'

 

# 解释:变量p2line存储前2行(不包括前1行)的内容,变量pline存储前1行的内容,变量cline存储当前行的内容。当

# p2line的内容匹配到指定IP时,就输出p2line和cline的内容。

命令输出结果以下:

能够看到,在数据库服务器27这端,全部与应用服务器125的TCP链接都有启用TCP keepalive特性。能够看到KEEPALIVE字样。

我并无查到有任何AIX官方资料说,AIX会强制为TCP链接启用TCP keepalive特性,因此建议仍是以实际观察为准。

 

通过上述的分析,如今咱们能够解答以前观测到的一些现象了。为何应用启动后通过一段时间后,在数据库服务器端观察到的TCP链接会逐渐减小了,以及,在抓包观察到的第3点里,为何会出现Keep-Alive包和RST包以及它们的做用了:

总的来讲,由于应用服务器与数据库服务器之间的某些TCP链接在创建后的2个小时内(tcp_keepidle值)都没有数据包交互,而且在AIX数据库服务器这端,它与应用服务器125的TCP链接都有启用TCP keepalive特性,因此AIX服务器会尝试发送Keep-Alive包以肯定这些空闲的TCP链接是否仍然可用,因为它最终都没有收到对端的回包,因此它就发送了RST包来断开该TCP链接并清除掉本地系统中的该TCP链接的信息。最终就表现为,在数据库服务器端观察到与应用服务器的TCP链接逐渐减小了。

 


防火墙的会话特性

 

通过前面一大堆的分析,咱们尚不知道为何数据包会在网络中丢失呢。根据个人理论知识及过往的经验,我猜测问题应该出在防火墙。虽然我没有权限看不了防火墙的配置,但我知道防火墙是有"会话"这一律念的。

(这部分我没有严格的查询官方文档,但大致应该是没错的)在防火墙中,防火墙会经过五元组来标识一个链接会话,即源IP地址、源端口、目的IP地址、目的端口、协议(tcp或udp)。例如,172.16.1.1 36524 TCP 192.168.1.1 1521就构成了一个五元组,其含义是,IP地址为172.16.1.1的源主机使用源端口36524,使用TCP协议,访问IP地址为192.168.1.1的目标主机的1521端口。可是呢,防火墙中是有会话过时时间的。当一个TCP链接长时间没有数据包交互,防火墙将会将该TCP链接会话清除。会话信息被清除后,当该TCP链接的源端或目标端再发送数据包过来时,防火墙收到后,会直接将该数据包丢弃。除非从新创建TCP链接(经历TCP 3次握手),不然,属于该五元组的TCP链接的数据包将不会被放行。

 

这就解释了现象2和3中为何数据包会在网络中丢失。当应用服务器或数据库服务器发送数据包时,因为此时防火墙中相应的TCP链接会话已通过期,防火墙收到数据包后就直接把包丢弃了,因此对端就没法收到数据包,它们自己也就没法收到回包。

 

后来查询得知,那个防火墙的会话超时时间是1小时。我后来通过实验证明应该是没错的。那么,整个问题就是这样的:当应用服务器与数据库服务器创建TCP链接以后,某些TCP链接因为长时间没有数据包交互,达到防火墙的会话超时时间1小时,故防火墙清除了这些未活动的TCP链接的会话信息;此时,无论是应用服务器仍是数据库服务器经过这些TCP链接来访问对端都是不通的,这就致使了最开始应用服务器访问不了数据库服务器的现象;又因为数据库服务器端对这些TCP链接启用了TCP keepalive特性,那么当时间达到2小时(即防火墙清除会话信息后又过了1小时)后,数据库服务器开始发送Keep-Alive包检测这些空闲的TCP链接是否仍然可用,因为这些链接早在1小时以前就已经不通了,在通过屡次尝试后仍然未收到对端的回包,数据库服务器便清除了这些不可用的TCP链接,这就出现了最开始观察到的数据库服务器上TCP链接不断减小的现象。

 

简单来讲就是,因为应用与数据库长时间没有流量交互,致使防火墙中的会话超时,就致使了应用访问不了数据库。

 


正确地配置proxool.xml

 

虽然已经找出了问题的根本缘由,但这个问题怎么解决呢?有几种解决办法:调整防火墙会话超时时间大于2小时(AIX数据库服务器的tcp_keepidle值);调整AIX数据库服务器的tcp_keepidle值小于1小时(防火墙会话超时时间);调整应用程序。固然,各有优缺啦。

 

其实,在我刚开始发现应用和数据库服务器的TCP链接不对等时,我告知了开发人员,开发同事就尝试修改了应用的数据库链接池配置。结果误打误撞,问题就没有再发生了。固然,他并不清楚为何会这样。可是,这引出了我这里要讲的一个问题。

 

应用程序使用的是proxool链接池,在最开始,它的配置文件proxool.xml的内容相似于:

<?xml version="1.0" encoding="UTF-8"?>

<something-else-entirely>

       <proxool>

               <alias>defaultDatabase</alias>

               <driver-url>jdbc:oracle:oci8:@exampledb</driver-url>

               <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>

               <driver-properties>

                       <property name="user" value="example_user" />

                       <property name="password" value="example_password" />

               </driver-properties>

               <prototype-count>3</prototype-count>

               <maximum-connection-count>50</maximum-connection-count>

               <minimum-connection-count>4</minimum-connection-count>

       </proxool>

</something-else-entirely>

而后呢,开发人员加了下面两条语句(红色字体的):

<?xml version="1.0" encoding="UTF-8"?>

<something-else-entirely>

       <proxool>

               .  .  .  .  .  .

               <test-before-use>true</test-before-use>

               <house-keeping-test-sql>SELECT 'x' from dual</house-keeping-test-sql>

       </proxool>

</something-else-entirely>

这两条语句呢,其实真正解决了问题的是house-keeping-test-sql这条语句,test-before-use语句则是没有必要的。加上test-before-use语句除了会增长查询数据库所消耗的时间以外,通常来讲,真的不会起什么有效的做用。

 

网络上不少文章常常犯的一个错误就是,使用了house-keeping-test-sql语句就会同时使用test-before-use语句,认为前者须要跟后者一同使用才能生效,直观上也很容易搞错。实际上,house-keeping-test-sql语句能够单独使用。使用了test-before-use语句就必定要使用house-keeping-test-sql语句,但反过来就不对了。结合proxool官网中的文档,这两条语句的实际做用为:

  • house-keeping-test-sql语句:当设置该项时,house keeping thread会找出数据库链接池中空闲的TCP链接并逐一地经过这些链接来执行该项指定的SQL语句,根据执行成功与否来肯定该链接是否仍然可用。该操做每隔一段时间就会从新进行一次,周期为house keeping thread的睡眠时间间隔(house-keeping-sleep-time属性的值,默认为30秒)。为了减小开销,该SQL语句应该是执行起来很是快速的。当未设置该项时,该检测操做会被省略。

    house keeping thread的检测操做自己能够断定TCP链接是否仍然可用。但更重要的是,若是应用与数据库之间有防火墙,定时的检测能够产生定时的数据包交互,刷新防火墙中该链接的会话的定时器,保证该会话不会超时而被清除。为了起到该做用,house-keeping-sleep-time的值须要小于防火墙会话超时时间(我这里是1小时),但为了不产生过多网络流量,也为了不对数据库产生没必要要的压力,也不建议设置为过小。能够设置为10分钟或20分钟检测一次之类的。

    另外还需特别说明,这种定时检测操做是属于应用层面的实现,跟以前所说的TCP keepalive特性没有任何关系。

  • test-before-use语句:当设置该项为true时,应用在使用数据库链接池中的某一个链接以前,会先使用house-keeping-test-sql语句中定义的SQL语句来检测该链接的可用性。若是检测失败了,就会选择另一个链接。若是全部链接都检测失败了,就会建立一个新的链接。若是那个新链接也检测失败了,就会返回SQLException信息。

    能够看到,该语句是依赖于house-keeping-test-sql语句的,反过来则不对。即使test-before-use语句的值被设为false(默认值),house-keeping-test-sql语句也仍然生效,house keeping thread仍然会按期执行house-keeping-test-sql语句中定义的SQL语句(若是有设置的话)。使用test-before-use语句虽能够最大限度保证链接可用性,但会产生额外的延迟(至少是应用服务器到数据库服务器的网络round trip time),并且,程序须要多久才能断定一个链接是不可用的,这也是一个很重要的问题的。所以,我我的是不推荐使用该语句的。

 

总的来讲,我建议在设置proxool链接池至少加上下面两条语句(红色字体的):

<?xml version="1.0" encoding="UTF-8"?>

<something-else-entirely>

       <proxool>

               <alias>defaultDatabase</alias>

               <driver-url>jdbc:oracle:oci8:@exampledb</driver-url>

               <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>

               <driver-properties>

                       <property name="user" value="example_user" />

                       <property name="password" value="example_password" />

               </driver-properties>

               <prototype-count>3</prototype-count>

               <maximum-connection-count>50</maximum-connection-count>

               <minimum-connection-count>4</minimum-connection-count>

               <house-keeping-test-sql>SELECT 'x' from dual</house-keeping-test-sql>

               <house-keeping-sleep-time>600000</house-keeping-sleep-time>

       </proxool>

</something-else-entirely>

house-keeping-test-sql语句可让house keeping thread按期地使用SQL语句检测链接池中空闲的TCP链接,house-keeping-sleep-time语句则设置这个时间间隔(单位为毫秒),不设置时默认为30秒,建议不要设得太大或过小,但要小于硬件防火墙的会话超时时间。

 


问题总结

 

到这里为止,问题已经很清楚了。这种问题的发生其实并不局限于访问Oracle数据库的状况,只要是长链接,当知足如下全部条件时,这种问题就会发生:

一、 两台服务器之间存在有硬件防火墙。

二、 使用了TCP长链接,好比在使用TCP链接池的场景中。

三、 在TCP链接的两端均未启用TCP keeplive特性,或任意一端或两端启用了TCP keeplive特性,但空闲检测时间(如Linux的net.ipv4.tcp_keepalive_time参数,AIX的tcp_keepidle参数)大于硬件防火墙的会话超时时间。是否有启用TCP keeplive特性一般由上层应用程序决定,但能够经过命令来观察。

四、 不存在应用层的定时轮询,或存在应用层的定时轮询,但轮询间隔大于硬件防火墙的会话超时时间。是否存在应用层的定时轮询一般要根据具体的应用程序及配置(如数据库链接池的配置)而定。

五、 在硬件防火墙的会话超时时间内,长链接中没有数据包传输。一般,若是应用自己没什么访问量,或者是在凌晨等时段访问量比较低,又或者是长链接数量太多,都有可能使某些长链接一直空闲,长时间没有数据包传输。

 


其它问题

 

一、 有时,咱们会想,客户端的数据库链接池中有不少与数据库服务器端的链接,假设其中仍然有一部分链接是仍然能够访问到数据库的,那么这时候应用访问数据库会不会有问题呢?

 

仍然是可能有问题的。根据个人观察,当应用访问数据库时,应用多是从链接池中随机选取一个链接的(具体的选取规则我不是很肯定),但重点是,在没有作检测的状况下,应用并不知道链接池中的哪些链接是实际可用的。若是应用选取到的是一个实际不可用的链接,那么这时,因为数据包发送出去了但没有回包,(至少在Linux上)操做系统会一直尝试重传并超过最大重试次数(正如前面所说),这个过程至少也要10分钟以上,而应用层一般早就超时报错了。当肯定了链接不可用以后,应用会再从链接池中选取一个链接。若这个链接仍然不可用,应用才会尝试建立一个新链接。整个过程下来,可能要20多分钟,而应用层一般都不会等待这么久的时间。

固然,若是应用最开始从链接池中选取到的那个链接刚好仍然可用,此时应用访问数据库是没有问题的。所以,总的来讲,这种状况就是看几率啦。不过,一般,出问题的几率比较大。

 

二、 本文讲的是与Oracle数据库之间的问题,若是换成MySQL数据库,会是什么状况呢?

MySQL数据库有一点不一样,据我观察,应用经过MySQL驱动与MySQL数据库创建长链接时,应用这一端是启用了TCP keepalive特性的。可能并非全部MySQL驱动都是这样的,但假设是这样的话,那么,问题的发生会变得更加隐晦。其实原理都是同样的,套用原理分析一下就行,我就不啰嗦了。

相关文章
相关标签/搜索