本次故障排查了三天,最后WireShark分析出结果,总结了一下原因,就是对Oracle的Rac工作方式不了解。
环境:
起因:
公网ip有限,不能做一对一静态NAT,只能做端口映射,而且是在一台linux服务器上,两个节点的网关为这个服务器,将SCANIP:1521端口映射为公网IP:1521
iptables -A PREROUTING -d 172.16.15.88/32 -p tcp -m tcp --dport 1521 -j DNAT --to-destination 192.168.200.15:1521
这里的172.16.15.88地址在公网路由器有公网IP的Static NAT,所以可以处理这个ip的所有端口转发
客户端使用PL/SQL+instantclient_11_2连接公网ip:1521
故障:
测试转发VIP的1521端口并且连接两个节点的VIP没有问题,但是转发SCANIP1521端口连接SCANIP报ORA-12545错误,排查了一下local_listener,发现监听为主机名
尝试将其改为ip地址
alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.200.13)(PORT=1521))))' scope=both sid='test1';
alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.200.14)(PORT=1521))))' scope=both sid='test2';
alter system set remote_listener='scanip:1521';
再次尝试连接,报ORA-12170错误。
ORA-12170: TNS:Connect timeout occurred
TNS连接超时,在SCANIP所在服务器上尝试抓包
发现数据可以到达服务器,接下来看一下日志
服务器确实收到了请求
看下alter下的log
好吧,看不出什么鸟东西。
到这里我怀疑数据库安装有问题,将RAC和DB重新安装了一次,折腾了两天,然并卵,问题没解决。
由于不清楚RAC是怎么工作的,所以怀疑是否客户端在请求SCANIP后,RAC将请求分发给其他节点,问题先这么猜想,但是这里还有一个问题,分发给其他节点后,是由其他节点主动连接客户端,还是给客户端重定向一个新的服务器地址。
猜想1
客户端ip:随机端口号-->SCANIP:1521
RAC将请求分发给次节点
次节点vip:1521-->客户端ip:随机端口号
猜想2
客户端ip:随机端口号-->SCANIP:1521
SCANIP:1521(次节点vip:1521)->客户端:源端口号
由于问题无法解决,只能暂时这么猜想,进行逐一验证。
验证猜想1
在网关上将所有192.168.200.0/24的源ip:1521 SNAT --> scanip:1521
iptables -t nat -A POSTROUTING -s 192.168.200.0/24 -p tcp --sport 1521 -j SNAT --to 192.168.200.15:1521
再次尝试连接,依然报
ORA-12170: TNS:Connect timeout occurred
iptables -t nat -nvL --line
发现并没有数据通过本条策略,
第一个假设不成立
验证猜想2
wireshark抓包分析
果不其然
在SCANIP回给客户端的数据中发现这条,里面写的看起来像是tnsnames.ora的内容,感觉像向客户端重定向一个次节点的VIP,里面的IP恰恰是次节点的VIP。
到这里问题终于有了进展,
既然是重定向到次节点的VIP,那么接下来验证一下。
果然,有到VIP的新连接产生。
这里因为VIP为内网ip,并无NAT映射,导致TCP三次握手失败,TCP连接不成功,最终客户端报错
ORA-12170: TNS:Connect timeout occurred
也验证了之前的猜想。
结论
客户端->scanip
scanip(次节点ip)->客户端
客户端->次节点ip
完成连接
总结
RAC的工作并不像nginx那样代理转发请求,而是向客户端重定向目标服务器来达到负载均衡的效果,由于同一个IP的同一个端口无法映射多个内部主机,也借此验证了,RAC的工作,需要确保客户端到每个次节点的VIP至少能够直接访问1521端口,并不能一个ip代理所有请求,这也恰好解释了为什么rac的每个节点的VIP都监听0.0.0.0:1521的原因。
参考文献
https://m.linuxidc.com/Linux/2015-09/122731.htm&http:/m.linuxidc.com/Linux/2015-09/122731.htm
解决方案
经过一下午的研究,想出了一个不得已的解决办法,办法有点LOW,但是经过测试,可以连接scanip,并且可以负载均衡。
解决办法如下
- 修改数据库的local_listener改为vip对应的主机名
- 修改数每个据库实例的监听端口为非目前scanip的监听端口不同端口
- 网关上做端口转发将不同实例的ip映射到公网ip的不同端口
- 修改客户端hosts表,客户端连接
修改数据库local_listener
alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ora01-vip)(PORT=1511))))' sid='test1';
alter system set local_listener='(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=ora02-vip)(PORT=1512))))' sid='test2';
这么做的原因是,因为local_listener如果配置ip地址,rac返回给客户端的连接方式就是ip,因为是内网ip,所以显然是不行,此处改为主机名,客户端在收到之后,需要对主机名进行dns解析。
那么解决问题的关键点就是在这。
修改数据库的监听端口
节点1
+ASM1:/home/grid@ora01-16:39/>cat /u01/app/oracle/product/11.2/dbhome_1/network/admin/listener.ora
# listener.ora Network Configuration File: /u01/app/oracle/product/11.2/dbhome_1/network/admin/listener.ora
# Generated by Oracle configuration tools.
ENABLE_GLOBAL_DYNAMIC_ENDPOINT_LISTEN_1 = ON
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = ora01-vip)(PORT = 1511))
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
)
)
+ASM1:/home/grid@ora01-16:39/>
节点2
+ASM2:/home/grid@ora02-16:05/>cat /u01/app/oracle/product/11.2/dbhome_1/network/admin/listener.ora
# listener.ora Network Configuration File: /u01/app/oracle/product/11.2/dbhome_1/network/admin/listener.ora
# Generated by Oracle configuration tools.
ENABLE_GLOBAL_DYNAMIC_ENDPOINT_LISTEN_1 = ON
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = ora02-vip)(PORT = 1512))
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
)
)
+ASM2:/home/grid@ora02-16:40/>
为什么要修改数据库的监听端口,这里解释一下,
因为RAC会负载均衡,客户端可能会收到不同的次节点VIP,如果端口号为scanip的监听1521端口,那么在网关上将无法再做端口转发,所以这里两个次节点的端口分别修改为1511/1512。
现在用客户端进行连接,wireshark抓个包看看
红线部分为要达到的效果,
既然返回了TNSNAME,那么就可以在DNS解析上做点手脚
修改客户端hosts表
这里的1.1.1.1为服务器的公网ip
到了这里,就差在网关上做端口映射了
端口映射
iptables -t nat -A PREROUTING -p tcp --dport 1511 -d 172.16.15.88/32 -j DNAT --to 192.168.200.13:1511
iptables -t nat -A PREROUTING -p tcp --dport 1512 -d 172.16.15.88/32 -j DNAT --to 192.168.200.14:1512
映射过后的效果如下
- 客户端->scanip:1521
- scanip此时认为ora01节点负载较小,向客户端重定向目标服务器ora01-vip:1511
- 客户端dns解析ora01-vip -> 1.1.1.1
- 客户端->1.1.1.1:1511
- 网关端口转发 1.1.1.1:1511 -> 192.168.200.13:1511
- 192.168.200.13:1511(ora01-vip) -> 1.1.1.1:1511(PREROUTING) -> 客户端
- 客户端 <-> ora01-vip
到这里来回都没问题了,测试一下
测试一下负载均衡
import cx_Oracle
USER = 'system'
PASSWD = 'system'
DB='x.x.x.x/1521/test'
def conn():
try:
conn = cx_Oracle.connect(USER , PASSWD ,DB)
cursor = conn.cursor()
sql = 'SELECT INSTANCE_NAME FROM V$INSTANCE '
cursor.execute(sql)
data = cursor.fetchall()
cursor.close()
conn.commit()
conn.close()
return data[0][0]
except Exception as e:
return e
if __name__ == '__main__':
for i in range(1, 11):
print("第%s次,连接到实例:%s" % (i, conn()))