最近有两次Oracle数据库故障与防火墙有关。这里的防火墙是硬件网络防火墙,而不是软件防火墙。

先说说简单的。一个运行在Windows系统上的Oracle 9i,客户端不能连接数据库,但是用tnsping测试没有问题。解决问题的办法很简单,但是我们仍然需要了解一下引起这个问题的原因。

这个问题首先得从客户通通过监听连接数据库的整个过程说起,此处指专用服务器连接模式:

  • 服务器上的监听进程在1521端口上进行侦听
  • 客户端发起一个数据库连接请求
  • 监听进程fork一个Oracle服务器进程(Server Process),也可称之为影子进程 (Shadow Process)。服务器进程选择一个大于1024的端口号进行侦听,监听进程把这个端口号发回到客户端,要求客户端重新连接这个指定的端口。
  • 客户端重新连接监听指定的新端口,也就是重新进行连接。
  • 客户端与Server Process直接对话,不再通过监听,进行会话认证(登录),执行SQL等等。

从上述过程可以看到,客户端最终连接的端口实际上并不是1521。由于防火墙一般只开放了几个端口,对Oracle数据库只开放了1521端口,这样在客户端进行第二次连接时,不能通过防火墙,导致连接数据库失败。

值得庆幸的是,只有Windows平台上的9i及以下版本的Oracle才会有这个问题。Oracle在Linux以及Unix平台下,多个进程间可以对端口进行复用,Oracle Server Process仍然使用的是跟监听进程一个端口(1521)。通过在linux使用strace跟踪客户端连接数据库的过程可以发现,客户端只连接了一次,并没有进行第二次连接,与上面描述的流程相比已经发生了变化。在Windows平台上,10g及以上版本的库,也同样利用端口复用,避免了这样的问题。

那么Windows上运行的Oracle 9i怎么解决这个问题呢?答案很简单,在Windows注册表的\HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\HOMEn(这里n指Oracle Home的序号,只有一个Oracle Home时是0)键下面增加一项USE_SHARED_SOCKET,其值为TRUE。然后重启监听及Oracle服务(注意要重启Oracle的服务,而不仅仅是重启数据库),就可以解决此问题。实际上10g就是默认USE_SHARED_SOCKET为TRUE。

对于这种问题,或者是让防火墙打开针对数据库主机的所有端口访问,也能解决。但是这种方案往往会被负责安全的人否决。

下面这个由防火墙导致的问题,就相对复杂一点了。

某个应用经常报ORA-3113错误,检查发现ORA-3113来源于数据库的一个db link。为了方便下面的描述,将应用直接连接的数据库称为DB_A,DB_A通过db link连接的对端的数据库称为DB_B。在DB_B主机上没有发现任何有关的trace和日志,应用执行的SQL也是非常简单的SELECT语句,返回的数据量也不大。但出错的语句并不是固定的某一个SQL。在应用连接的数据库DB_A上做ORA-3113 error stack的trace,也没有发现有价值的东西。

导致ORA-3113错误的原因很多。大家可以参考ITPUB上的一篇贴子《ORA-03113错误分析》

在这个ORA-3113错误的问题中,数据库DB_B没有任何日志,出现这种情况的一个很可能的原因是,DB_B上的Server Process已经中止,但又不是在执行SQL过程中出错异常中止了,比如被KILL掉,网络连接中断等。被KILL掉这个原因,首先被排除,因为这个错误出现得很多,每天都有。询问维护人员,称也没有进行过KILL操作。那么最大的可能性应该是网络了。顺着这条线索,我们在DB_A上用netstat -na命令检查到DB_B的网络连接,与DB_B中v$session中的会话进行比较,发现DB_A连接到DB_B的数据库会话,比netstat 命令看到的网络连接数少得多。

这是一个重大的突破。首先要怀疑的是防火墙。因为防火墙导致Oracle连接异常的情况非常多。访问数据库的DBA,这两个数据库分别在不同的业务网络中,中间使用了Cisco的防火墙。请防火墙维护工程师检查防火墙的设置,发现防火墙设置了TCP连接超时(这个术语是防火墙工程师告诉给我的,实际上我个人认为这个术语字面含义跟其实际的作用相差较大)设置为1小时。也就是,对于通过防火墙的所有TCP连接,如果在1小时内没有任何活动,就会被防火墙拆除,这样就会导致连接中断。在拆除连接时,也不会向连接的两端发送任何数据来通知连接已经拆除。

而出问题的业务系统,使用的高峰期是在正常的工作时间内,最高时会导致DB_A会产生数十个连接到DB_B。但是在业务低谷期或经过一个晚上,防火墙将拆除大部分甚至是所有的连接。而下一次使用时,应用通过连接池选择DB_A中的一个会话,这个会话的db link之前已经连接到DB_B,但是网络连接已经被防火墙拆除,但是这个会话并不知道,仍然会认为这个连接有效,结果试图向DB_B提交SQL时,就出现了ORA-3113错误。

实际上,很多使用网络连接的应用,可以使用称之为KeepAlive的特性,来保持TCP连接的活动性。在打开一个连接时,通过setsockopt函数,设置socket为SO_KEEPALIVE,这样,在OS层,如果一个TCP连接在指定的时间内没有活动,会发送一个探测包到连接的对端,检测连接的对端是否仍然存在。如果这个时间小于防火墙中设置的“超时”时间,防火墙就会检查到连接中仍然有数据,就不会断开这个连接。

操作系统中keep alive的相关设置,不同的系统有不同的设置方法。比如在Linux中,在sysctl中设置net.ipv4.tcp_keepalive_time = 120,表示探测时间为120秒,即2分钟。在AIX中,通过no命令将tcp_keepidle参数设置为240,表示探测时间为120秒。注意AIX中这个参数的单位是1/2秒,而在Linux中是1秒。

不幸的是,通过在Linux和AIX上对Oracle进行跟踪发现,Oracle客户端连接数据库时(包括Oracle Server作为客户端连接DB LINK上的数据库),并没有在TCP Socket上打开SO_KEEPALIVE,因此不能通过设置操作系统的tcp keep alive的设置来解决此问题。

(Update 2009-08-06:在对监听的跟踪时发现,连接时客户端没有设置SO_KEEPALIVE,但是Server Process却对TCP Socket设置了SO_KEEPALIVE属性。)

还好Oracle提供了类似的机制。也就是DCD(Dead Conneciton Detection)。在$ORACLE_HOME/network/admin/sqlnet.ora文件中增加如下一行:

sqlnet.expire_time=NNN

这里NNN为分钟数,Oracle数据库会在会话IDLE时间超过这个指定的时间时,检测这个会话的对端(即客户端)是否还有效。避免客户端由于异常退出,导致会话一直存在。

因此,我们可以通过在DB_B数据库中的sqlnet.ora文件中设置expire_time来解决上面提到的ORA-3113问题。

关于Tcp KeepAlive,DCD以及防火墙,可参考metalink的文档257650.1

Trackback

11 comments untill now

  1. 写的太好了,赞一个!

    [回复]

    老熊 回复:

    过奖了

    [回复]

  2. 学习了,很有用的东西

    [回复]

  3. bruceliu @ 2009-08-10 08:23

    非常感谢 最后修改DCD终于解决了ORACLE 断开的问题

    [回复]

  4. 我們現在也碰到類似的問題,一個程式空間一兩個小時,再執行的時侯就報ora-03113這個錯誤,當然空戶端與db中間是有防火牆的.照你的方法,我是不是只要在sqlnet.ora增加expire_time=NNN 就行了.

    [回复]

    老熊 回复:

    @muyu, 是,或者是修改防火墙的策略。

    [回复]

  5. 好的,非常感謝,我先試試看.

    [回复]

  6. bruceliu @ 2011-12-21 12:28

    防火墙策略 我们修改没成功 不知道是哪里怎么修改 我们用的是juniper
    不过从数据库里修改是成功的 请指教下 防火墙 谢谢

    [回复]

    老熊 回复:

    不好意思,我对防火墙的具体操作不熟。建议咨询防火墙技术支持。

    [回复]

  7. […] 在以前我写的一篇文章《Oracle与防火墙》中提到,网络防火墙会切断长时间空闲的TCP连接,这个空闲时间具体多长可以在防火墙内部进行设置。防火墙切断连接之后,会有下面的可能: […]

  8. […] 在以前我写的一篇文章《Oracle与防火墙》中提到,网络防火墙会切断长时间空闲的TCP连接,这个空闲时间具体多长可以在防火墙内部进行设置。防火墙切断连接之后,会有下面的可能: […]

Add your comment now