多监听器故障案例分析
⒈ 案例概述
一天晚上,朋友A给我打电话聊天,分享了他近期的一个案例。
他们有一套11g RAC数据库,SCAN监听和本地监听端口都为1521。为了搭建DataGuard环境,于是创建了第二个监听LISTENER_DG(相同监听地址,端口为1522),专门给DataGuard使用。这样的规划,主要是为了分隔业务系统与DataGuard之间的网络影响,业务系统走1521端口,而DataGuard走1522端口。当他们创建完第二监听后,业务人员反映业务系统通过SCAN地址连接数据库时,有时能够成功,但有时连接失败。
经过排查,他发现刚刚建立的第二监听已经自动进行动态注册到local_listener参数中,SCAN监听将业务系统发出的新连接请求转发到了本地监听LISTENER_DG的1522端口上,而业务系统所在主机与数据库主机之间由于安全策略限制,仅仅开通了1521端口的网络通信,所以业务系统无法连接数据库。
最终,朋友A处理该故障的解决办法是:将local_listener中的1522端口条目清除,同时修改listener.ora文件,将第二监听从动态监听修改为静态监听。
对于朋友A分享的这个案例,其实当晚只是记住了故障的现象和解决办法,也没有深究其中的技术细节。过了大概一个多月,朋友B给我打电话,说他遇到一个故障,让我帮着分析分析。当朋友B向我描述完故障现象后,我立马反应过来,这个故障不就是朋友A刚刚遇到的故障吗,于是将朋友A的案例转述给了朋友B。
为了弄清楚这个故障的一些技术细节,我在测试环境进行了故障再现,以及给出这个故障的第二种解决方案。
2. 故障再现
为了进一步了解整个故障的技术细节,我搭建了测试环境,具体如下所示。
1、测试环境与监听相关的参数说明
[grid@11grac1 admin]$ lsnrctl status listener_scan1 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.124)(PORT=1521))) Services Summary... Service "cdb" has 2 instance(s). Instance "cdb1", status READY, has 1 handler(s) for this service... Instance "cdb2", status READY, has 1 handler(s) for this service... The command completed successfully [grid@11grac1 admin]$ [grid@11grac1 admin]$ lsnrctl status listener 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.120)(PORT=1521))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.121)(PORT=1521))) Services Summary... Service "cdb" has 1 instance(s). Instance "cdb1", status READY, has 2 handler(s) for this service... The command completed successfully [grid@11grac1 admin]$ SQL> show parameter listener NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ listener_networks string local_listener string (ADDRESS=(PROTOCOL=TCP)(HOST= 192.168.56.121)(PORT=1521)) remote_listener string rac11g-scan:1521 SQL> 实例2中与监听相关的参数: [grid@11grac2 admin]$ lsnrctl status listener 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.122)(PORT=1521))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.123)(PORT=1521))) Services Summary... Service "cdb" has 1 instance(s). Instance "cdb2", status READY, has 2 handler(s) for this service... The command completed successfully [grid@11grac2 admin]$ SQL> show parameter listener NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ listener_networks string local_listener string (ADDRESS=(PROTOCOL=TCP)(HOST= 192.168.56.123)(PORT=1521)) remote_listener string rac11g-scan:1521 SQL> |
在创建第二个监听之前,我们的SCAN监听端口为1521,listener本地监听的端口也是1521。此时,业务系统通过SCAN地址可以正常连接数据库。
2、通过网络抓包的数据可以看出,当客户端通过SCAN地址(192.168.56.124)连接数据库时,先与SCAN地址的1521端口通信,此时SCAN监听将连接请求转发节点2的本地监听(192.168.56.123),节点2本地监听的端口是1521。
3、下面,我们模拟故障重现,创建第二个本地监听,监听名为LISTENER_DG,监听的端口为1522。
[grid@11grac1 ~]$ srvctl add listener -l LISTENER_DG -p 1522 -k 1 [grid@11grac1 ~]$ srvctl start listener -l LISTENER_DG -n 11grac1 [grid@11grac1 ~]$ srvctl start listener -l LISTENER_DG -n 11grac2 [grid@11grac1 ~]$ [grid@11grac1 ~]$ crsctl status resource -t -------------------------------------------------------------------------------- NAME TARGET STATE SERVER STATE_DETAILS -------------------------------------------------------------------------------- Local Resources -------------------------------------------------------------------------------- 。。。。。。(略) ora.LISTENER.lsnr ONLINE ONLINE 11grac1 ONLINE ONLINE 11grac2 ora.LISTENER_DG.lsnr ONLINE ONLINE 11grac1 ONLINE ONLINE 11grac2 。。。。。。(略) [grid@11grac1 ~]$ lsnrctl status listener_dg 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.120)(PORT=1522))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.121)(PORT=1522))) The listener supports no services The command completed successfully [grid@11grac1 ~]$ |
可以看出,第二个本地监听(LISTENER_DG)已经创建并且启动,但由于监听的端口是1522,所以此时的本地监听(LISTENER_DG)没有注册上任何的service。
4、重启CRS集群,或者手动修改local_listener参数,集群就会自动将本地监听(LISTENER_DG)进行动态注册。如下配置信息是CRS集群重启后,截取的配置信息。
SQL> show parameter listener NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ listener_networks string local_listener string (ADDRESS=(PROTOCOL=TCP)(HOST= 192.168.56.123)(PORT=1521)), ( ADDRESS=(PROTOCOL=TCP)(HOST=19 2.168.56.123)(PORT=1522)) remote_listener string rac11g-scan:1521 SQL> [grid@11grac2 ~]$ lsnrctl status listener_dg 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.122)(PORT=1522))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.123)(PORT=1522))) Services Summary... Service "cdb" has 1 instance(s). Instance "cdb2", status READY, has 1 handler(s) for this service... 。。。。。。(略) [grid@11grac2 ~]$ |
可以看出,集群重启后,CRS将第二个本地监听(LISTENER_DG)也进行了动态注册,写入了local_listener参数中。此时第二个本地监听(LISTENER_DG)可以正常对外工作。
5、故障再现。为了模拟故障,我将客户端主机开启防火墙,限制数据库主机的1522端口与客户端主机之间的通信。此时,当客户端通过SCAN地址连接数据库时,就会偶尔出现如下类似的错误。
当SCAN监听将连接请求转发给本地监听时,local_listener参数中,有LISTENER监听的1521端口,同时也有LISTENER_DG监听的1522端口,所以在转发的过程中会按照一定的算法将连接请求分配给这两个本地监听,当将请求分配到LISTENER监听的1521端口时,就能连接成功;而当请求分配到LISTENER_DG监听的1522端口时,由于安全策略未开通1522端口,就会连接失败。这就是为什么业务系统会出现有时能够连接成功,而有时会出现连接失败的原因。
6、为了更加说明这个故障,我此时将客户端防火墙中设置的那条限制访问1522端口的规则删除,再次模拟客户端连接数据库,继续抓包分析。
删除了限制访问1522端口的规则后,客户端可以正常连接数据库。从上面的网络抓包也可以看出,客户端先访问SCAN监听(192.168.56.124)的1521端口,然后SCAN监听将连接请求转发给了LISTENER_DG本地监听的1522端口。
3. 解决方案
通过上面的故障重现,我们已经非常清楚整个故障的触发原因。下面,我们来谈谈对应的解决办法。
方案一:
也即我朋友A所使用的办法。将local_listener中的1522端口条目清除,同时修改listener.ora文件,将第二监听从动态监听修改为静态监听。
SQL> alter system set local_listener='(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.56.121)(PORT=1521))' scope=both sid='cdb1'; System altered. SQL> alter system set local_listener='(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.56.123)(PORT=1521))' scope=both sid='cdb2'; System altered. SQL> 在listener.ora文件中添加如下内容,SID_NAME根据节点名进行修改: SID_LIST_LISTENER_DG = (SID_LIST = (SID_DESC = (GLOBAL_DBNAME = cdb) (ORACLE_HOME = /u01/app/oracle/product/11.2.0.4/dbhome_1) (SID_NAME = cdb1) ) ) [grid@11grac2 admin]$ lsnrctl status listener_dg 。。。。。。(略) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.122)(PORT=1522))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.56.123)(PORT=1522))) Services Summary... Service "cdb" has 1 instance(s). Instance "cdb2", status UNKNOWN, has 1 handler(s) for this service... The command completed successfully [grid@11grac2 admin]$ |
创建的第二个监听(LISTENER_DG)使用静态注册后,客户端通过SCAN地址连接数据库时进行网络抓包。
从上面的网络抓包也可以看出,客户端先访问SCAN监听(192.168.56.124)的1521端口,然后SCAN监听每次都会将连接请求转发给了LISTENER本地监听的1521端口,再也不会转发给LISTENER_DG本地监听的1522端口。
方案二:
利用LISTENER_NETWORKS参数,进行网络分离。具体如下所示:
(1). 在数据库主机的tnsnames.ora中添加如下解析项:
CDB1_LOCAL_NET1 =(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.121 )(PORT = 1521)))
CDB2_LOCAL_NET1 =(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.123 )(PORT = 1521)))
CDB1_LOCAL_NET2 =(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.121 )(PORT = 1522)))
CDB2_LOCAL_NET2 =(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.123 )(PORT = 1522)))
CDB_REMOTE_NET2 =(DESCRIPTION_LIST =(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.121 ) (PORT = 1522)))(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.56.123 )(PORT = 1522))))
(2). local_listener和remote_listener置空:
alter system set local_listener='' scope=both sid='*';
alter system set remote_listener='' scope=both sid='*';
(3). 设置LISTENER_NETWORKS参数:
alter system set LISTENER_NETWORKS='((NAME=network1)(LOCAL_LISTENER=CDB1_LOCAL_NET1)(REMOTE_LISTENER=rac11g-scan:1521))','((NAME=network2)(LOCAL_LISTENER=CDB1_LOCAL_NET2)(REMOTE_LISTENER=CDB_REMOTE_NET2))' scope=both sid='cdb1';
alter system set LISTENER_NETWORKS='((NAME=network1)(LOCAL_LISTENER=CDB2_LOCAL_NET1)(REMOTE_LISTENER=rac11g-scan:1521))','((NAME=network2)(LOCAL_LISTENER=CDB2_LOCAL_NET2)(REMOTE_LISTENER=CDB_REMOTE_NET2))' scope=both sid='cdb2';
SQL> show parameter listener NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ listener_networks string ((NAME=network1)(LOCAL_LISTENE R=CDB1_LOCAL_NET1)(REMOTE_LIST ENER=rac11g-scan:1521)), ((NAM E=network2)(LOCAL_LISTENER=CDB 1_LOCAL_NET2)(REMOTE_LISTENER= CDB_REMOTE_NET2)) local_listener string remote_listener string SQL> |
设置LISTENER_NETWORKS参数之前,LISTENER_DG本地监听的是1522端口,由于未配置静态监听,所以无法注册任何的service。当设置LISTENER_NETWORKS参数之后,LISTENER_DG本地监听能够动态注册数据库服务。
此时,业务系统通过network1访问数据库时,只会将连接请求分配给1521端口。