上周处理完一次凌晨的数据库切换,回到座位上我一直在想一个问题:为什么每次ADG切换完之后,最难的不是数据库本身的switchover,而是跟业务方一遍遍确认“现在应该连哪个IP”。如果业务连接串里写的是主库的物理IP,切换后这个IP对应的机器已经变成备库,应用自然全部失联;如果你让业务方同时配置两个IP做故障转移,很多老系统根本做不到。最终把问题解决干净的方案,就是给ADG环境单独规划一个VIP(虚拟IP),让应用永远只认这一个地址。这篇文章我重点讲Oracle ADG环境下的VIP高可用部署,完整过一遍网络规划、绑定脚本、监听器整合、主备切换联动,以及我在真实环境里踩过的坑。
这个场景适合谁?如果你正在做Oracle Data Guard的运维,或者你的ADG环境经常要手动切换、甚至已经配了Fast-Start Failover但发现切换后客户端不知道怎么连,那这篇文章应该能帮你省不少试错时间。哪怕你还没搭过ADG,先把连接层的思路理清楚,后面再倒回去看RAC和ADG的配合也会容易很多。
1. 为什么ADG高可用里非要多养一个VIP:主备切换的连接困境
1.1 先看清楚ADG环境长什么样
一个典型的ADG环境,两台数据库服务器,一台是Primary(主库),一台是Physical Standby(物理备库)通过Data Guard实时同步。ADG模式下备库可以以只读方式打开,平时还能承担一些报表查询,这一点和普通Data Guard有很大的体验差异。
两台服务器各自有自己的物理IP,比如:
| 机器 | 角色 | 物理IP |
|---|---|---|
| DB01 | Primary | 192.168.1.101 |
| DB02 | Physical Standby | 192.168.1.102 |
| VIP | 应用访问入口 | 192.168.1.200 |
这个VIP平时绑在DB01上,应用通过192.168.1.200连接数据库。一旦发生主备切换,VIP自动从DB01漂移到DB02,DB02变成新的Primary,应用连接串不用做任何修改,依旧连192.168.1.200就能访问到新主库。
听着很简单,但这个“自动漂移”包含了一层非常关键的设计思路:应用和数据库之间不应该绑定物理机器的IP,应该绑定一个“逻辑入口”。这个逻辑入口就是VIP。
1.2 没有VIP时,应用连接是怎么断的
没有VIP的情况下,业务方通常会有两种连法。
第一种,连接串写主库物理IP。比如应用配置里写死192.168.1.101。切换前没问题,切换后DB01降级为备库,数据库处于只读或者挂载状态,应用再往这个IP上写数据,轻则报错重则完全无法连接。DBA要做的事情就是通知业务方修改配置,把连接串改成新主库的192.168.1.102,然后重启应用。这个操作在计划内切换还能接受,最怕的是半夜故障,业务方没有人能马上改配置,恢复时间被拉得很长。
第二种,连接串里写两个物理IP做故障转移。Oracle客户端本身是支持多地址failover的,在tnsnames.ora里可以配两个ADDRESS,客户端会按顺序尝试。但如果应用用的是连接池,很多连接池在建立连接后会一直复用同一个物理连接,只有连接池本身发现连接断开并重试时,才会走到第二个地址。这里的变量太多:连接池是否配置了连接检测、检测间隔多久、failover模式是session还是select……任何一个环节不配合,就会出现“数据库已经切换好了,但应用迟迟连不上”的尴尬局面。
VIP方案解决的就是这个“入口漂移”问题。不管背后是哪台机器承担Primary,应用只认192.168.1.200这一个地址,入口不变,切换对应用层透明。严格来说,VIP并不能保证会话在切换过程中不断,真正的会话恢复还是需要应用侧重连,但它至少解决了“应用知道往哪连”的问题。
1.3 VIP在这里扮演的角色和边界
很多人会混淆VIP和其他Oracle高可用组件的关系。RAC里有SCAN IP和VIP,那是集群内部用来做实例负载均衡和故障转移的;ADG里讲的VIP是自己规划的虚拟地址,和RAC没有直接关系。
这里要明确边界:VIP只负责网络访问入口的漂移,它不负责数据库角色切换,也不负责任何数据同步。主备切换的决策和执行由Data Guard、DG Broker或者运维人员完成,VIP只是跟在Primary屁股后面的“影子”。切换发生在数据库层,漂移发生在网络层,两者必须通过脚本来联动。
这个边界想清楚了,后面做方案设计时就不会把逻辑搞混。我在早期刚接触ADG时,曾经想过用VIP的存活来做个数据库切换判断,后来想想就离谱:VIP在主库上,不代表数据库一定健康;反过来,VIP漂走了,也不代表原主库一定挂了。判断数据库角色,必须查数据库自身的状态,而不是通过网络地址的归属来判断。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. VIP方案最初的几个选择:网段、绑定方式和工具
2.1 网络规划:VIP放在哪个网段,会不会被DHCP抢走
VIP规划最容易被忽略的一个点,就是地址来源。这个VIP必须是一个固定的、没有被占用的、不会被DHCP自动分配的IP。
实操中我一般会做三件事:
- 在交换机或者防火墙侧把VIP地址加入静态ARP白名单,确保不会因为其他设备的MAC广播导致地址冲突;
- 检查DHCP地址池,确认VIP不在自动分配范围内;
- 在资产管理或者IP登记表里注明这个IP是ADG VIP,避免后续其他项目误申请。
VIP和数据库物理IP尽量放在同一个网段。原因是VIP漂移本身是二层网络行为,同网段内通过ARP广播就可以快速切换路由指向。跨网段的VIP漂移需要三层路由协议配合,复杂度会上升一个量级,不是DBA自己写个脚本就能搞定的。我在多个环境里都验证过,同网段内VIP漂移的时间可以达到秒级,跨网段则需要依赖VLAN间路由的收敛,通常不建议这么做。
另外,VIP不能和任何物理机的实际IP冲突。有些生产环境会直接把两台机器的某一个备用IP地址规划为VIP,但后来另一台机器上线时又把这个地址配上,造成地址冲突,这种事故并不少见。规划完成后,至少要用ping和arping测试一下这个VIP当前是否有响应,确保它是干净的。
2.2 绑定工具选型:ifconfig、keepalived、pacemaker该怎么选
VIP绑定工具的选择,直接决定了整个方案的复杂度和可靠性。我列一个对比表,后面详细说。
| 工具/方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| ip addr脚本 | 手工或脚本执行ip addr add/del | 轻量、可控、与Oracle角色结合紧密 | 需要自己处理双绑、脑裂 | 已用DG Broker+FSFO或人工切换为主 |
| keepalived | VRRP协议,通过组播选举Master | 自带防双绑、漂移速度快 | 感知不到Oracle角色,数据库故障时VIP可能误判 | 只有网络层高可用需求 |
| pacemaker/corosync | 资源管理器统一管理VIP、服务 | 功能强大,支持复杂依赖 | 对DBA来说太重,配置复杂 | 已有上层集群管理系统或多种资源依赖 |
如果你已经使用DG Broker并且配置了Observer来做主备切换,我强烈建议直接用简单的ip addr脚本配合systemd来管理VIP。为什么?因为数据库角色切换决策已经由DG Broker做了,VIP需要做的仅仅是跟随角色变化,这时候一套状态检测脚本就完全够了。keepalived是用VRRP心跳来判断主机存活,它根本不关心Oracle数据库是否健康。主库数据库实例挂了但操作系统还活着,keepalived会认为主库正常,VIP不漂移,应用依旧连着一台数据库已经Down掉的机器,这是非常危险的情况。
pacemaker的问题在于太重了。DBA为了一套ADG环境去引入Pacemaker,要维护配置、资源约束还有fencing策略,一旦出了问题排查起来成本很高。除非这个环境已经有Pacemaker在管理其他资源,否则不建议单独为ADG的VIP引入。
2.3 单VIP还是双VIP,读写分离场景下的取舍
大多数ADG环境只需要一个VIP就够了,这个VIP永远跟着Primary走。但如果你想让备库承担只读业务,比如报表查询走备库,那么可以考虑给备库也配一个只读VIP。
双VIP模式常见的设计是:
- 主库VIP:192.168.1.200,漂移目标跟随Primary;
- 备库VIP:192.168.1.201,漂移目标跟随Physical Standby。
切换发生后,主库VIP和备库VIP会反向漂移。比如原主库DB01变成备库,那么192.168.1.200要从DB01摘掉,192.168.1.201要绑定到DB01上;原备库DB02变成主库,192.168.1.200要绑到DB02上,192.168.1.201要从DB02上摘掉。
双VIP的脚本逻辑会比单VIP复杂,因为同一台机器上要同时管理两个VIP的绑定和释放状态,而且还要处理一个非常危险的情况:系统刚启动时,两个VIP都还没有绑定,脚本需要根据当前角色把对应的VIP绑定上。我在测试过程中遇到过备库只读VIP绑到了新主库上,导致应用通过只读VIP连上了主库,报表业务直接把数据写进去了,所幸是测试环境,但这个问题暴露了双VIP脚本的状态判断必须非常严谨。
如果业务没有强制的读写分离需求,就老老实实用单VIP。不要为了“看起来高可用”去增加不必要的复杂度,生产环境里越简单的东西越可靠。
3. 核心脚本怎么写:状态检测、绑定释放和防双绑
3.1 脚本的状态机设计
VIP绑定脚本的核心,是每台数据库主机上都要有一个独立的进程或者定时任务,周期性地检测本机当前的数据库角色,然后根据角色决定VIP绑定的目标状态。
状态机可以概括为以下几点:
- 本机是PRIMARY且VIP未绑定,绑定VIP;
- 本机是PRIMARY且VIP已绑定,什么都不做;
- 本机是PHYSICAL STANDBY且VIP已绑定,释放VIP;
- 本机是PHYSICAL STANDBY且VIP未绑定,什么都不做;
- 数据库角色获取失败,什么都不做。
看到这里你可能已经注意到,最后一条“角色获取失败什么都不做”是防脑裂的关键。如果数据库实例连不上,或者SQL执行失败,脚本必须放弃本次动作,而不是直接把VIP释放掉。因为你不知道当前数据库到底是主库还是备库,乱动VIP可能造成两端都没有VIP,或者更严重的双端都有VIP。
另一个容易被忽略的点是“幂等性”。当VIP已经绑定后再次执行绑定命令,ip addr add会报错“RTNETLINK answers: File exists”。这时候脚本不能因为报错就退出,而应该继续往下走,确保后续的免费ARP通告能执行。同理,释放VIP时如果VIP本来就不存在,ip addr del也会报错,脚本同样要能容忍。
3.2 可用的脚本骨架与systemd托管
下面给一个我在Linux环境下实际使用的脚本骨架。这个脚本用bash编写,核心逻辑就是上面的状态机。
bash复制#!/usr/bin/env bash
VIP=192.168.1.200
VIP_PREFIX=24
ETH_IF=ens33
VIP_LABEL="${ETH_IF}:vip"
ORACLE_HOME=/u01/app/oracle/product/19.0.0/dbhome_1
ORACLE_SID=orcl
export ORACLE_HOME ORACLE_SID
export PATH=$ORACLE_HOME/bin:$PATH
LOCK_FILE=/tmp/vip_dg.lock
LOG_FILE=/var/log/vip_dg.log
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"
}
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log "another instance is running, exit"
exit 1
fi
get_role=$(sqlplus -s / as sysdba <<'EOF' | tr -d ' \r'
set heading off feedback off pages 0
select database_role from v$database;
EOF
)
case "$get_role" in
PRIMARY)
if ! ip addr show "$ETH_IF" | grep -q "$VIP/"; then
ip addr add "$VIP/$VIP_PREFIX" dev "$ETH_IF" label "$VIP_LABEL"
log "VIP bound on PRIMARY: $VIP"
fi
arping -I "$ETH_IF" -c 3 -A "$VIP" >/dev/null 2>&1
;;
PHYSICAL\ STANDBY)
if ip addr show "$ETH_IF" | grep -q "$VIP/"; then
ip addr del "$VIP/$VIP_PREFIX" dev "$ETH_IF"
log "VIP released on STANDBY: $VIP"
fi
;;
*)
log "unknown role: $get_role, skip VIP action"
;;
esac
这里有几个关键点要说明:
sqlplus -s / as sysdba的输出经常带有换行和空格,用tr -d清理掉,否则case匹配可能失败;v$database里的$在here-doc中如果不用引号包裹会被shell展开,所以这里用<<'EOF';grub命令判断VIP是否存在时用grep -q "$VIP/",避免IP地址前缀匹配错误;arping -A发送免费ARP,作用是主动更新交换机和客户端缓存,让VIP漂移后其他设备能立刻知道新的MAC地址,这一步不能省;- 用
flock -n 9加了一个文件锁,防止两个脚本实例同时执行导致VIP状态错乱。
脚本写好后,执行权限要配好,然后通过systemd服务来托管。不建议用cron,因为cron最小粒度是1分钟,对于数据库切换场景来说,VIP等待60秒才漂移太慢了。
ini复制[Unit]
Description=DG VIP Manager
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/adg_vip.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
adg_vip.sh里本身要加一个循环:
bash复制while true; do
/usr/local/bin/adg_vip_check.sh
sleep 10
done
这样一个服务跑起来后,每10秒检查一次,VIP漂移的延迟基本在10秒到20秒之间,可以满足绝大多数场景。
3.3 关键防双绑逻辑的再说明
防双绑(split-brain)是VIP脚本设计里最值得重视的问题。双绑的意思是同一个VIP同时绑在两个节点上,这在网络中会造成地址冲突,轻则丢包,重则整个网段内所有同IP的设备都无法正常通信。
简单的本地脚本方案天然存在一个隐患:如果旧主库没有正常优雅降级,比如突然宕机,但网卡仍然通电,VIP可能还留在旧主库上。这时候新主库的脚本检测到自己已经是PRIMARY,尝试绑定同一个VIP,就会产生双绑。
我的实践是:在绑定VIP前,先尝试用ping探测VIP是否已经有响应。如果VIP有响应,说明这个地址还在网络上活跃,不要盲目绑定;如果VIP无响应,说明旧主库可能已经完全不可达或者网络层已断开,这时候再绑定。注意这个探测不是绝对的,如果旧主库只是数据库宕机但操作系统还在、网络还通,那么VIP其实还在旧主库上,ping也会有响应。此时新主库必须等待,等旧主库上的脚本检测到本机不再是PRIMARY并释放VIP,或者运维人员介入。
所以我在实际方案里增加了一个“等待窗口”策略:当脚本认为本机是PRIMARY但VIP已经被其他设备响应时,连续记录日志,直到达到阈值(比如3分钟)后仍然如此,说明本地脚本可能无法正常释放对端VIP,此时告警通知人工介入,而不是强制绑定VIP。
这个“宁可不绑、不可双绑”的原则,在FSFO自动切换场景下尤其重要。下文第5章我会再展开。
4. 把VIP接到Oracle监听器和客户端链路里
4.1 listener.ora里要不要写VIP地址
VIP漂移只是网络层的事,要让数据库客户端通过VIP地址访问到数据库,监听器必须监听VIP地址。这里就有一个常见的困惑:listener.ora配置里应不应该把VIP地址写进去?
我的答案是:要写。而且两台机器上的listener.ora里都要写这个VIP地址。
原因很简单:客户端连接VIP时,VIP所在的主机会收到访问VIP:1521的请求,这个端口必须有一个监听器在监听,才能把请求转发给数据库实例。如果listener只监听物理IP,VIP地址的1521端口没有进程监听,连接会直接被拒绝。
一个典型的listener.ora配置片段:
code复制LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.101)(PORT = 1521))
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.200)(PORT = 1521))
)
)
DB02上的listener.ora也类似,只是物理IP换成192.168.1.102,VIP地址192.168.1.200保持一致。
但这里有一个大坑:如果本机当前没有绑定VIP,但listener.ora里配置了VIP地址,那么监听器启动时可能会绑定失败。有些版本的Oracle监听器遇到这种问题会直接启动失败,有些则只是跳过VIP地址的监听。我遇到最多的是12c和19c,行为略有差异,19c通常启动时要求所有ADDRESS都能绑定成功,否则报错。所以在手动启动监听器前,务必确保VIP已经绑到本机,或者采用先启动监听器后绑定VIP再执行lsnrctl reload的方式,让监听器重新读取配置并完成VIP地址的绑定。
4.2 实例动态注册与local_listener的配合
监听器配置好之后,接下来是实例动态注册。Oracle实例默认会自动向本地监听器注册服务名,但local_listener参数的值决定了实例向哪个地址注册。
在ADG场景下,我建议显式设置local_listener,把本机物理IP和VIP都包含进去:
sql复制alter system set local_listener=
'(DESCRIPTION=
(ADDRESS_LIST=
(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.101)(PORT=1521))
(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.200)(PORT=1521))
)
)' scope=both;
随后执行:
sql复制alter system register;
这样实例会同时向物理IP地址和VIP地址上的监听器注册服务名。就算客户端从VIP地址进来,监听器也能根据注册信息找到服务的实例,完成转发。
需要注意,两台机器上的local_listener配置不一样,因为各自的物理IP不同。VIP地址那一段则保持一致的192.168.1.200。
4.3 客户端TNSNAMES和连接池要注意什么
客户端的连接串几乎不需要为ADG和VIP做额外调整,只需要把HOST写成VIP地址就行:
code复制ADG_DB =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.200)(PORT = 1521))
(CONNECT_DATA =
(SERVICE_NAME = orcl)
)
)
这里要明确一个认识:VIP负责的是“切换后应用用同一个地址找到新主库”,但TCP连接本身在切换过程中一定会断开。为什么?因为主备切换时,旧主库上的数据库实例要么被shutdown,要么被activate成备库,原有会话都会被终止。客户端和连接池如果检测到连接断了,必须重新发起连接,此时VIP已经漂移到新主库,应用重连就能成功。
所以应用侧一定要配好连接池的连接检测和重试机制。比如Oracle JDBC连接池里配置validateConnectionOnBorrow,或者使用testOnBorrow策略,确保拿到的连接一定可用。否则,即使VIP已经漂移成功,连接池还握着旧连接不放,业务依旧会报错。
5. 主备切换全过程实测:手动switchover与FSFO联动
5.1 计划内切换时VIP的漂移时序
计划内的switchover是最好处理的场景,因为整个过程都有人盯着,数据库角色是平滑切换的。
以DB01(旧主库)和DB02(旧备库)为例,手动切换时的大致流程是:
- 确认Data Guard同步状态正常,无gap;
- 在DB01上执行
ALTER DATABASE COMMIT TO SWITCHOVER TO PHYSICAL STANDBY;,DB01转为备库; - 在DB02上执行
ALTER DATABASE CONVERT TO PHYSICAL STANDBY;,然后ALTER DATABASE OPEN;,DB02变成新主库; - 观察VIP漂移:DB01上的脚本检测到角色已变成PHYSICAL STANDBY,释放VIP;DB02上的脚本检测到角色已变成PRIMARY,绑定VIP。
这一步的时序有一个特点:DB01先释放VIP,DB02后绑定VIP,中间会有一个VIP空窗期,持续几秒到几十秒。这个空窗期取决于脚本的检测频率。我在前面配置的是10秒一次检测,最坏情况下空窗期可能到20秒左右。
如果你希望缩短空窗期,可以在切换流程中手动执行一次脚本,或者临时把检测频率调短。但生产环境的经验是,几十秒的空窗期通常是可以接受的,因为切换流程本身就会耗费几分钟甚至更长时间,应用方的维护窗口期足够覆盖这段时间。真正的关键是,VIP漂移完成后监听器能马上响应。
5.2 FSFO自动failover场景下的VIP处理
FSFO(Fast-Start Failover)是DG Broker里最常用的自动故障转移机制。Observer节点监控主备状态,主库故障时自动把备库提升为新主库。FSFO本身能做到数据库角色的自动切换,但Observer并不管理VIP,VIP还是需要我们自己的脚本来处理。
自动failover场景下,VIP处理的难点在于:旧主库可能不是优雅降级,而是突然宕机,或者网络分区。此时旧主库上的VIP脚本可能根本来不及执行“释放VIP”,VIP就一直挂在旧主库上。
我在测试环境模拟过几种故障场景,单靠每台机器自己的脚本很难完美处理:
- 如果旧主库直接断电或宕机,网卡停止工作,那么VIP实际上已经失效,新主库再绑定同一个VIP不会冲突。因为旧主库的ARP条目最终会被交换机老化掉,新主库绑定VIP后通过免费ARP可以快速接管。
- 如果旧主库只是数据库实例崩溃、操作系统和网卡还正常,那么VIP依然挂在旧主库上且能响应。新主库不能贸然绑定同一个VIP,否则双绑冲突。
针对第二种情况,最稳妥的做法是引入一个仲裁点来控制VIP的漂移。实际操作中我用的方案是:由Observer所在主机统一控制VIP的漂移动作。Observer检测到failover发生后,通过SSH登录旧主库执行VIP释放(如果旧主库还能登录),然后再登录新主库执行VIP绑定。如果旧主库已经完全不可达,就直接在新主库绑定VIP。
这个方案可以让VIP的绑定状态始终有且只有一个决策者,避免两个节点的本地脚本各自为政。
SSH互信配置用oracle用户,命令大致如下:
bash复制observer_host$ ssh dba@192.168.1.101 "ip addr del 192.168.1.200/24 dev ens33 2>/dev/null; exit 0"
observer_host$ ssh dba@192.168.1.102 "ip addr add 192.168.1.200/24 dev ens33 label ens33:vip; arping -I ens33 -c 3 -A 192.168.1.200; exit 0"
注意,Observer脚本里要加上失败重试和日志记录。SSH本身也可能因网络问题失败,所以要设置合理的重试次数和告警。如果SSH到旧主库失败,也可以选择直接在网络设备上做ARP静态绑定或者把旧主库对应的交换机端口手动关闭,但这些操作需要网络组的配合,不是每个DBA都能直接操作的。
如果不想引入Observer作为仲裁点,那至少要在本地脚本里把“绑定VIP前的冲突检测”做好。检测策略上面已经说了:绑定前ping VIP,如果VIP有响应但当前角色是PRIMARY,说明对端可能还持有VIP,此时记录日志并告警,不执行绑定。这个策略无法100%防止双绑,但可以大幅降低双绑风险。
5.3 切换完成后我习惯验证的几个点
不管手动切换还是自动failover,切换完成后我都会固定做一遍验证:
- 确认新主库角色:
SELECT DATABASE_ROLE, OPEN_MODE, DB_UNIQUE_NAME FROM V$DATABASE;,看到PRIMARY、READ WRITE才对; - 确认VIP在哪台机器上:
ip addr show | grep 192.168.1.200,确保VIP在新主库上; - 确认监听器状态:
lsnrctl status,重点看VIP地址是否处于监听状态,服务名是否已注册; - 从客户端测试连接:
sqlplus system@192.168.1.200/orcl,确认能顺利连上; - 检查旧主库后续状态:如果旧主库还能开机,确认它已经变成备库且VIP已经释放,避免地址残留。
这五步走完,切换过程才算真正闭环。
6. 真实环境踩坑记录与收尾清单
6.1 ARP缓存导致的客户端“假失败”
VIP漂移最容易被忽视的问题,不是数据库层,而是ARP缓存。VIP从DB01漂移到DB02之后,网络上的交换机和客户端在短时间内仍然保留着“192.168.1.200 -> DB01的MAC地址”这条ARP缓存。这段时间内客户端继续访问VIP,数据包会被交换机转发到旧主库,但旧主库已经不再提供服务,于是连接失败。
解决这个问题的最佳方式是免费ARP。VIP绑定到新主库后,立即用arping -A发送广播帧,通知同一网段内的所有设备更新ARP缓存。我脚本里已经加了这一句,arping -A 192.168.1.200,这一步执行后,交换机端口的学习基本能秒级完成。
但要注意,客户端和数据库跨三层网络(比如应用在另一个VLAN)时,免费ARP可能不会穿透三层设备,这时候需要在三层交换机或者防火墙上配置对应VIP的ARP代理,或者依赖路由器的ARP表老化。这个就要和网络组提前沟通了,不是DBA在数据库主机上就能解决的。
6.2 监听器启动顺序、SELinux、iptables这些绕不开的坑
我在多个环境里碰到过监听器启动失败的问题,根因都是系统重启后VIP还没有绑定,但监听器已经配置了VIP地址并开始启动。解决思路有三个:
- 调整启动顺序,确保脚本先绑定VIP,再启动监听器;
- 开启监听器时允许VIP地址启动失败(不同版本表现不一致,不建议依赖);
- 监听器启动后如果VIP才绑定,手动执行
lsnrctl reload。
我采用最多的是第三种思路,因为改动最小。VIP脚本在完成绑定后,主动调用一次$ORACLE_HOME/bin/lsnrctl reload,确保监听器把VIP地址纳入监听。这样即使监听器先启动导致VIP绑定失败,绑完VIP后也能恢复。
SELinux方面,如果在RHEL/CentOS 7/8上运行Oracle,通常Oracle安装文档会要求把SELinux设为disabled或者permissive。如果生产环境强制要求enforcing,那VIP绑定本身不受影响,但监听器绑定端口可能受SELinux策略限制,需要调整端口上下文。遇到监听器起不来的时候,除了看$ORACLE_HOME/network/log下的监听日志,还要看一眼/var/log/audit/audit.log里有没有AVC拒绝记录。
iptables/firewalld是另一个常见坑。VIP漂移到新主库后,新主库上的防火墙规则可能只允许了物理IP的1521端口,而VIP地址的1521端口被默认策略丢弃,客户端连不上。在脚本切换时,要确保新主库的iptables规则对VIP地址生效,或者干脆把数据库节点的业务端口放行规则统一写在物理IP和VIP都适用的CIDR上。
6.3 收尾前我建议核对一遍的清单
最后整理一份我每次部署完ADG VIP高可用后都会核对一遍的清单,你可以直接拿去用。
| 检查项 | 具体动作 | 预期结果 |
|---|---|---|
| VIP地址规划 | 确认VIP不与物理IP冲突,不在DHCP池 | 地址可用、无响应 |
| 脚本权限 | 脚本可执行、oracle可运行、日志可写 | 无权限错误 |
| systemd服务 | 服务状态active,日志无异常 | 每10秒正常检测 |
| listener.ora | 两台机器都配置了VIP监听地址 | 切换到新主 |
