未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合有Oracle运维基础的读者学习RAC、Data Guard、监控记录、灾备预案与模拟演练的组织方式,不是可按正文顺序直接执行的生产部署或自动切换方案。本次仅作全文结构审阅,未进行外部事实核验或环境编译、运行验证。当前代码仍缺少RAC预期节点基线,服务配置存在实例映射与负载均衡参数未落地的问题,Data Guard监控存在视图列适配依据不足、延迟口径及空值判定问题,部分过程存在对象创建顺序和SQLERRM直接写入SQL表达式的问题,应用连接监控也混用了数据库会话与连接池指标。服务管理、FAN响应和灾备执行主要记录或输出步骤,未提供真实事件接入、基础设施操作或审批校验;实际切换须按备库类型核对流程,并另行落实旧主库隔离、数据损失评估和恢复回切。银行可用性百分比是方案假设,随机响应时间及固定演练数值仅用于模拟,不构成服务水平或恢复能力的实测证明。

Oracle高可用性方案

高可用性概念与原则

Oracle高可用性是银行核心系统稳定运行的基石。在7×24小时不间断服务的金融环境中,系统停机可能造成巨大的业务损失和声誉风险。Oracle提供了多层次的高可用性解决方案,从单实例的故障转移到多地域的灾难恢复。

1. 高可用性指标

可用性等级定义

  • 99.9%:年停机时间约8.76小时
  • 99.95%:年停机时间约4.38小时
  • 99.99%:年停机时间约52.56分钟
  • 99.999%:年停机时间约5.26分钟

银行业务可用性目标示例(以下数值为方案假设,不代表统一监管要求)

  • 核心交易系统:99.99%以上
  • 客户服务系统:99.95%以上
  • 内部管理系统:99.9%以上

关键指标

  • RTO(恢复时间目标):系统故障后恢复时间
  • RPO(恢复点目标):故障后允许恢复到多早的数据时间点,通常以可容忍的数据丢失时间窗口表示
  • MTBF(平均故障间隔):系统稳定运行时间
  • MTTR(平均恢复时间):故障修复平均时间

2. 故障类型分析

硬件故障

  • 服务器硬件故障
  • 存储设备故障
  • 网络设备故障

软件故障

  • 操作系统故障
  • Oracle软件故障
  • 应用程序故障

人为错误

  • 误操作导致的数据丢失
  • 配置错误
  • 维护操作失误

灾难事件

  • 自然灾害
  • 机房故障
  • 网络中断

RAC集群架构

1. RAC集群基础架构

-- RAC集群信息查询
CREATE OR REPLACE VIEW v_rac_cluster_info AS
SELECT 
    i.instance_number,
    i.instance_name,
    i.host_name,
    i.version,
    i.status,
    i.database_status,
    i.instance_role,
    d.name as database_name,
    d.open_mode,
    d.database_role
FROM gv$instance i
CROSS JOIN v$database d
ORDER BY i.instance_number;

-- RAC节点状态监控
CREATE OR REPLACE PROCEDURE monitor_rac_cluster AS
    v_total_nodes NUMBER;
    v_active_nodes NUMBER;
    v_alert_threshold NUMBER := 0.5; -- 50%节点可用告警阈值
BEGIN
    -- 统计集群节点状态
    SELECT COUNT(*) INTO v_total_nodes
    FROM gv$instance;
    
    SELECT COUNT(*) INTO v_active_nodes
    FROM gv$instance
    WHERE status = 'OPEN' AND database_status = 'ACTIVE';
    
    DBMS_OUTPUT.PUT_LINE('RAC集群状态监控');
    DBMS_OUTPUT.PUT_LINE('================');
    DBMS_OUTPUT.PUT_LINE('总节点数: ' || v_total_nodes);
    DBMS_OUTPUT.PUT_LINE('活跃节点数: ' || v_active_nodes);
    DBMS_OUTPUT.PUT_LINE('可用率: ' || ROUND(v_active_nodes * 100 / v_total_nodes, 2) || '%');
    
    -- 详细节点状态
    DBMS_OUTPUT.PUT_LINE('');
    DBMS_OUTPUT.PUT_LINE('节点详细状态:');
    
    FOR node_rec IN (
        SELECT instance_number, instance_name, host_name, status, database_status
        FROM gv$instance
        ORDER BY instance_number
    ) LOOP
        DBMS_OUTPUT.PUT_LINE(
            '节点' || node_rec.instance_number || ' (' || node_rec.host_name || '): ' ||
            node_rec.status || ' / ' || node_rec.database_status
        );
    END LOOP;
    
    -- 告警检查
    IF v_active_nodes < v_total_nodes * v_alert_threshold THEN
        DBMS_OUTPUT.PUT_LINE('');
        DBMS_OUTPUT.PUT_LINE('⚠️  警告: 可用节点数低于阈值,请立即检查集群状态!');
    END IF;
    
END monitor_rac_cluster;
/

-- RAC服务管理
CREATE OR REPLACE PROCEDURE manage_rac_service(
    p_service_name VARCHAR2,
    p_action VARCHAR2, -- START, STOP, RELOCATE
    p_target_instance VARCHAR2 DEFAULT NULL
) AS
    v_current_status VARCHAR2(20);
    v_preferred_instances VARCHAR2(100);
BEGIN
    -- 查询服务当前状态
    SELECT LISTAGG(inst_id, ',') WITHIN GROUP (ORDER BY inst_id)
    INTO v_preferred_instances
    FROM gv$active_services
    WHERE name = p_service_name;
    
    DBMS_OUTPUT.PUT_LINE('管理RAC服务: ' || p_service_name);
    DBMS_OUTPUT.PUT_LINE('当前运行实例: ' || NVL(v_preferred_instances, '无'));
    DBMS_OUTPUT.PUT_LINE('执行操作: ' || p_action);
    
    CASE p_action
        WHEN 'START' THEN
            -- 启动服务
            DBMS_OUTPUT.PUT_LINE('启动服务 ' || p_service_name);
            -- 实际环境中使用 srvctl start service 命令
            
        WHEN 'STOP' THEN
            -- 停止服务
            DBMS_OUTPUT.PUT_LINE('停止服务 ' || p_service_name);
            -- 实际环境中使用 srvctl stop service 命令
            
        WHEN 'RELOCATE' THEN
            -- 迁移服务
            IF p_target_instance IS NOT NULL THEN
                DBMS_OUTPUT.PUT_LINE('迁移服务 ' || p_service_name || ' 到实例 ' || p_target_instance);
                -- 实际环境中使用 srvctl relocate service 命令
            ELSE
                DBMS_OUTPUT.PUT_LINE('错误: 迁移操作需要指定目标实例');
            END IF;
            
        ELSE
            DBMS_OUTPUT.PUT_LINE('错误: 不支持的操作 ' || p_action);
    END CASE;
    
END manage_rac_service;
/

2. RAC负载均衡配置

-- RAC服务配置表
CREATE TABLE rac_service_config (
    service_id NUMBER PRIMARY KEY,
    service_name VARCHAR2(50) UNIQUE,
    preferred_instances VARCHAR2(100), -- 首选实例列表
    available_instances VARCHAR2(100), -- 可用实例列表
    load_balancing_method VARCHAR2(20), -- ROUND_ROBIN, LEAST_CONNECTIONS, THROUGHPUT
    failover_type VARCHAR2(20), -- SELECT, SESSION, TRANSACTION
    failover_method VARCHAR2(20), -- BASIC, PRECONNECT
    connection_pool_min NUMBER DEFAULT 5,
    connection_pool_max NUMBER DEFAULT 100,
    business_priority VARCHAR2(20), -- HIGH, MEDIUM, LOW
    status CHAR(1) DEFAULT 'A',
    create_time DATE DEFAULT SYSDATE
);

-- 银行业务服务配置
INSERT INTO rac_service_config VALUES (
    1, 'BANK_CORE_SVC', '1,2', '1,2,3,4', 'THROUGHPUT', 'SESSION', 'BASIC',
    20, 200, 'HIGH', 'A', SYSDATE
);

INSERT INTO rac_service_config VALUES (
    2, 'BANK_QUERY_SVC', '3,4', '1,2,3,4', 'ROUND_ROBIN', 'SELECT', 'PRECONNECT',
    10, 150, 'MEDIUM', 'A', SYSDATE
);

INSERT INTO rac_service_config VALUES (
    3, 'BANK_BATCH_SVC', '4', '3,4', 'LEAST_CONNECTIONS', 'SESSION', 'BASIC',
    5, 50, 'LOW', 'A', SYSDATE
);

-- 生成RAC服务创建脚本
CREATE OR REPLACE PROCEDURE generate_rac_service_script AS
    v_script CLOB;
BEGIN
    v_script := '#!/bin/bash' || CHR(10) ||
                '# Oracle RAC服务配置脚本' || CHR(10) ||
                '# 生成时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') || CHR(10) ||
                CHR(10);
    
    FOR svc_rec IN (
        SELECT * FROM rac_service_config WHERE status = 'A' ORDER BY service_id
    ) LOOP
        v_script := v_script || 
                   '# 创建服务: ' || svc_rec.service_name || CHR(10) ||
                   '# HIGH/MEDIUM/LOW仅为业务优先级元数据,不作为srvctl -P参数传入' || CHR(10) ||
                   'srvctl add service -d BANKPROD -s ' || svc_rec.service_name || ' \' || CHR(10) ||
                   '  -r "' || svc_rec.preferred_instances || '" \' || CHR(10) ||
                   '  -a "' || svc_rec.available_instances || '" \' || CHR(10) ||
                   '  -e ' || svc_rec.failover_type || ' \' || CHR(10) ||
                   '  -m ' || svc_rec.failover_method || ' \' || CHR(10) ||
                   '  -z 180 -w 5' || CHR(10) || CHR(10) ||
                   
                   '# 启动服务: ' || svc_rec.service_name || CHR(10) ||
                   'srvctl start service -d BANKPROD -s ' || svc_rec.service_name || CHR(10) ||
                   CHR(10);
    END LOOP;
    
    v_script := v_script || 
               '# 检查服务状态' || CHR(10) ||
               'srvctl status service -d BANKPROD' || CHR(10);
    
    DBMS_OUTPUT.PUT_LINE(v_script);
END generate_rac_service_script;
/

Data Guard配置

1. Data Guard架构设计

-- Data Guard配置信息表
CREATE TABLE dg_config_info (
    config_id NUMBER PRIMARY KEY,
    database_role VARCHAR2(20), -- PRIMARY, STANDBY
    database_name VARCHAR2(30),
    host_name VARCHAR2(100),
    db_unique_name VARCHAR2(30),
    protection_mode VARCHAR2(30), -- MAXIMUM_AVAILABILITY, MAXIMUM_PERFORMANCE, MAXIMUM_PROTECTION
    transport_mode VARCHAR2(20), -- SYNC, ASYNC
    apply_mode VARCHAR2(20), -- REAL_TIME, MANUAL
    compression_enabled CHAR(1) DEFAULT 'Y',
    encryption_enabled CHAR(1) DEFAULT 'Y',
    network_timeout NUMBER DEFAULT 30,
    status CHAR(1) DEFAULT 'A',
    create_time DATE DEFAULT SYSDATE
);

-- 银行Data Guard配置
INSERT INTO dg_config_info VALUES (
    1, 'PRIMARY', 'BANKPROD', 'bank-db-01.local', 'BANKPROD',
    'MAXIMUM_AVAILABILITY', 'SYNC', 'REAL_TIME', 'Y', 'Y', 30, 'A', SYSDATE
);

INSERT INTO dg_config_info VALUES (
    2, 'STANDBY', 'BANKSTBY', 'bank-db-dr.local', 'BANKSTBY',
    'MAXIMUM_AVAILABILITY', 'SYNC', 'REAL_TIME', 'Y', 'Y', 30, 'A', SYSDATE
);

-- Data Guard状态监控
CREATE OR REPLACE PROCEDURE monitor_dataguard_status AS
    v_db_role VARCHAR2(30);
    v_protection_mode VARCHAR2(30);
    v_switchover_status VARCHAR2(30);
    v_log_mode VARCHAR2(20);
    v_force_logging VARCHAR2(10);
    v_gap_count NUMBER;
BEGIN
    -- 获取当前数据库角色和状态
    SELECT database_role, protection_mode, switchover_status, 
           log_mode, force_logging
    INTO v_db_role, v_protection_mode, v_switchover_status,
         v_log_mode, v_force_logging
    FROM v$database;
    
    DBMS_OUTPUT.PUT_LINE('Data Guard状态监控');
    DBMS_OUTPUT.PUT_LINE('==================');
    DBMS_OUTPUT.PUT_LINE('数据库角色: ' || v_db_role);
    DBMS_OUTPUT.PUT_LINE('保护模式: ' || v_protection_mode);
    DBMS_OUTPUT.PUT_LINE('切换状态: ' || v_switchover_status);
    DBMS_OUTPUT.PUT_LINE('日志模式: ' || v_log_mode);
    DBMS_OUTPUT.PUT_LINE('强制日志: ' || v_force_logging);
    
    -- 如果是主库,检查备库状态
    IF v_db_role = 'PRIMARY' THEN
        DBMS_OUTPUT.PUT_LINE('');
        DBMS_OUTPUT.PUT_LINE('备库状态:');
        
        FOR dest_rec IN (
            SELECT dest_id, destination, status, error,
                   applied_scn, last_applied_time
            FROM v$archive_dest_status
            WHERE status IN ('VALID', 'DEFERRED', 'ERROR', 'INACTIVE')
            AND destination IS NOT NULL
            ORDER BY dest_id
        ) LOOP
            DBMS_OUTPUT.PUT_LINE(
                '目标' || dest_rec.dest_id || ' (' || dest_rec.destination || '): ' ||
                dest_rec.status || 
                CASE WHEN dest_rec.error IS NOT NULL THEN ' - ' || dest_rec.error ELSE '' END
            );
        END LOOP;
        
        -- 检查日志传输延迟
        BEGIN
            SELECT COUNT(*) INTO v_gap_count
            FROM v$archive_gap;
            
            IF v_gap_count > 0 THEN
                DBMS_OUTPUT.PUT_LINE('');
                DBMS_OUTPUT.PUT_LINE('⚠️  发现归档日志间隙: ' || v_gap_count || ' 个');
            END IF;
        EXCEPTION
            WHEN OTHERS THEN
                NULL; -- 忽略错误,可能是没有配置Data Guard
        END;
    END IF;
    
    -- 如果是备库,检查应用状态
    IF v_db_role IN ('PHYSICAL STANDBY', 'LOGICAL STANDBY') THEN
        DBMS_OUTPUT.PUT_LINE('');
        DBMS_OUTPUT.PUT_LINE('日志应用状态:');
        
        FOR apply_rec IN (
            SELECT process, status, client_process, sequence#,
                   block#, blocks, delay_mins
            FROM v$managed_standby
            WHERE process IN ('MRP0', 'LSP0', 'RFS')
            ORDER BY process
        ) LOOP
            DBMS_OUTPUT.PUT_LINE(
                apply_rec.process || ': ' || apply_rec.status ||
                CASE WHEN apply_rec.sequence# IS NOT NULL 
                     THEN ' (序列号: ' || apply_rec.sequence# || ')' 
                     ELSE '' END
            );
        END LOOP;
    END IF;
    
END monitor_dataguard_status;
/

-- Data Guard切换操作
CREATE OR REPLACE PROCEDURE perform_dataguard_switchover(
    p_operation VARCHAR2, -- SWITCHOVER, FAILOVER
    p_target_standby VARCHAR2 DEFAULT NULL,
    p_force CHAR(1) DEFAULT 'N'
) AS
    v_current_role VARCHAR2(30);
    v_switchover_status VARCHAR2(30);
    v_can_switchover BOOLEAN := FALSE;
BEGIN
    -- 获取当前状态
    SELECT database_role, switchover_status
    INTO v_current_role, v_switchover_status
    FROM v$database;
    
    DBMS_OUTPUT.PUT_LINE('Data Guard切换操作');
    DBMS_OUTPUT.PUT_LINE('==================');
    DBMS_OUTPUT.PUT_LINE('当前角色: ' || v_current_role);
    DBMS_OUTPUT.PUT_LINE('切换状态: ' || v_switchover_status);
    DBMS_OUTPUT.PUT_LINE('操作类型: ' || p_operation);
    
    -- 检查切换条件
    IF p_operation = 'SWITCHOVER' THEN
        IF v_current_role = 'PRIMARY' AND v_switchover_status = 'TO STANDBY' THEN
            v_can_switchover := TRUE;
        ELSIF v_current_role LIKE '%STANDBY' AND v_switchover_status = 'TO PRIMARY' THEN
            v_can_switchover := TRUE;
        END IF;
        
        IF v_can_switchover THEN
            DBMS_OUTPUT.PUT_LINE('');
            DBMS_OUTPUT.PUT_LINE('✓ 当前角色与切换状态通过示例检查;实际切换仍需核对备库类型、完整前置条件与操作流程');
            DBMS_OUTPUT.PUT_LINE('请在主库执行以下命令:');
            
            IF v_current_role = 'PRIMARY' THEN
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE COMMIT TO SWITCHOVER TO STANDBY WITH SESSION SHUTDOWN;');
                DBMS_OUTPUT.PUT_LINE('SHUTDOWN IMMEDIATE;');
                DBMS_OUTPUT.PUT_LINE('STARTUP MOUNT;');
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE OPEN READ ONLY;');
            ELSE
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE COMMIT TO SWITCHOVER TO PRIMARY WITH SESSION SHUTDOWN;');
                DBMS_OUTPUT.PUT_LINE('SHUTDOWN IMMEDIATE;');
                DBMS_OUTPUT.PUT_LINE('STARTUP;');
            END IF;
        ELSE
            DBMS_OUTPUT.PUT_LINE('');
            DBMS_OUTPUT.PUT_LINE('✗ 不满足切换条件,当前状态: ' || v_switchover_status);
        END IF;
        
    ELSIF p_operation = 'FAILOVER' THEN
        IF v_current_role LIKE '%STANDBY' THEN
            DBMS_OUTPUT.PUT_LINE('');
            DBMS_OUTPUT.PUT_LINE('⚠️  故障转移操作,请确认主库确实不可用');
            DBMS_OUTPUT.PUT_LINE('请在备库执行以下命令:');
            
            IF p_force = 'Y' THEN
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE ACTIVATE STANDBY DATABASE;');
            ELSE
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE RECOVER MANAGED STANDBY DATABASE FINISH;');
                DBMS_OUTPUT.PUT_LINE('ALTER DATABASE ACTIVATE STANDBY DATABASE;');
            END IF;
            
            DBMS_OUTPUT.PUT_LINE('SHUTDOWN IMMEDIATE;');
            DBMS_OUTPUT.PUT_LINE('STARTUP;');
        ELSE
            DBMS_OUTPUT.PUT_LINE('');
            DBMS_OUTPUT.PUT_LINE('✗ 故障转移只能在备库上执行');
        END IF;
    END IF;
    
END perform_dataguard_switchover;
/

2. 自动故障检测与切换

-- Data Guard自动监控和告警
CREATE TABLE dg_monitoring_log (
    log_id NUMBER PRIMARY KEY,
    check_time DATE DEFAULT SYSDATE,
    check_type VARCHAR2(50), -- LAG_CHECK, CONNECTIVITY_CHECK, APPLY_CHECK
    status VARCHAR2(20), -- OK, WARNING, CRITICAL
    message VARCHAR2(4000),
    auto_action_taken VARCHAR2(100),
    alert_sent CHAR(1) DEFAULT 'N'
);

CREATE SEQUENCE dg_monitor_seq START WITH 1 INCREMENT BY 1;

-- Data Guard健康检查
CREATE OR REPLACE PROCEDURE check_dataguard_health AS
    v_log_id NUMBER;
    v_db_role VARCHAR2(30);
    v_current_scn NUMBER;
    v_applied_scn NUMBER;
    v_lag_seconds NUMBER;
    v_transport_lag NUMBER;
    v_apply_lag NUMBER;
    v_alert_message VARCHAR2(4000);
BEGIN
    -- 获取数据库角色
    SELECT database_role INTO v_db_role FROM v$database;
    
    IF v_db_role = 'PRIMARY' THEN
        -- 主库检查备库延迟
        FOR dest_rec IN (
            SELECT dest_id, destination, status, error,
                   (SYSDATE - last_archived_time) * 24 * 3600 as transport_lag_sec
            FROM v$archive_dest_status
            WHERE status = 'VALID'
            AND destination IS NOT NULL
        ) LOOP
            v_log_id := dg_monitor_seq.NEXTVAL;
            
            IF dest_rec.transport_lag_sec > 300 THEN -- 5分钟
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'TRANSPORT_LAG', 'CRITICAL',
                    '目标' || dest_rec.dest_id || '传输延迟' || 
                    ROUND(dest_rec.transport_lag_sec, 2) || '秒'
                );
            ELSIF dest_rec.transport_lag_sec > 60 THEN -- 1分钟
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'TRANSPORT_LAG', 'WARNING',
                    '目标' || dest_rec.dest_id || '传输延迟' || 
                    ROUND(dest_rec.transport_lag_sec, 2) || '秒'
                );
            ELSE
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'TRANSPORT_LAG', 'OK',
                    '目标' || dest_rec.dest_id || '传输正常'
                );
            END IF;
        END LOOP;
        
    ELSIF v_db_role LIKE '%STANDBY' THEN
        -- 备库检查应用延迟
        BEGIN
            SELECT MAX(sequence#) INTO v_current_scn
            FROM v$archived_log
            WHERE dest_id = 1 AND applied = 'YES'
            AND TRUNC(completion_time) >= TRUNC(SYSDATE);
            
            SELECT (SYSDATE - MAX(completion_time)) * 24 * 3600
            INTO v_apply_lag
            FROM v$archived_log
            WHERE dest_id = 1 AND applied = 'YES';
            
            v_log_id := dg_monitor_seq.NEXTVAL;
            
            IF v_apply_lag > 600 THEN -- 10分钟
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'APPLY_LAG', 'CRITICAL',
                    '日志应用延迟' || ROUND(v_apply_lag, 2) || '秒'
                );
            ELSIF v_apply_lag > 180 THEN -- 3分钟
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'APPLY_LAG', 'WARNING',
                    '日志应用延迟' || ROUND(v_apply_lag, 2) || '秒'
                );
            ELSE
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message
                ) VALUES (
                    v_log_id, 'APPLY_LAG', 'OK',
                    '日志应用正常'
                );
            END IF;
            
        EXCEPTION
            WHEN OTHERS THEN
                v_log_id := dg_monitor_seq.NEXTVAL;
                INSERT INTO dg_monitoring_log (
                    log_id, check_type, status, message, alert_sent
                ) VALUES (
                    v_log_id, 'APPLY_CHECK', 'CRITICAL',
                    '无法检查日志应用状态: ' || SQLERRM, 'Y'
                );
        END;
    END IF;
    
    COMMIT;
    
    -- 发送告警(Critical级别)
    FOR alert_rec IN (
        SELECT * FROM dg_monitoring_log
        WHERE check_time >= SYSDATE - 1/24 -- 最近1小时
        AND status = 'CRITICAL'
        AND alert_sent = 'N'
    ) LOOP
        DBMS_OUTPUT.PUT_LINE('CRITICAL: ' || alert_rec.message);
        
        UPDATE dg_monitoring_log 
        SET alert_sent = 'Y'
        WHERE log_id = alert_rec.log_id;
    END LOOP;
    
    COMMIT;
    
END check_dataguard_health;
/

自动故障转移

1. Fast Application Notification (FAN)

-- FAN事件监控表
CREATE TABLE fan_event_log (
    event_id NUMBER PRIMARY KEY,
    event_time DATE DEFAULT SYSDATE,
    event_type VARCHAR2(50), -- NODE_DOWN, NODE_UP, SERVICE_DOWN, SERVICE_UP
    instance_name VARCHAR2(30),
    service_name VARCHAR2(50),
    event_details VARCHAR2(4000),
    action_taken VARCHAR2(100),
    notification_sent CHAR(1) DEFAULT 'N'
);

CREATE SEQUENCE fan_event_seq START WITH 1 INCREMENT BY 1;

-- FAN事件处理器
CREATE OR REPLACE PROCEDURE handle_fan_event(
    p_event_type VARCHAR2,
    p_instance_name VARCHAR2,
    p_service_name VARCHAR2 DEFAULT NULL,
    p_event_details VARCHAR2 DEFAULT NULL
) AS
    v_event_id NUMBER;
    v_action_taken VARCHAR2(100);
BEGIN
    v_event_id := fan_event_seq.NEXTVAL;
    
    CASE p_event_type
        WHEN 'NODE_DOWN' THEN
            v_action_taken := '记录服务自动转移预案;本示例未执行迁移';
            
            -- 记录事件
            INSERT INTO fan_event_log (
                event_id, event_type, instance_name, event_details, action_taken
            ) VALUES (
                v_event_id, p_event_type, p_instance_name, p_event_details, v_action_taken
            );
            
            -- 执行故障转移逻辑
            DBMS_OUTPUT.PUT_LINE('节点故障检测: ' || p_instance_name);
            DBMS_OUTPUT.PUT_LINE('生成自动故障转移提示;本示例未执行故障转移');
            
            -- 迁移关键服务到其他节点
            FOR svc_rec IN (
                SELECT service_name, preferred_instances, available_instances
                FROM rac_service_config
                WHERE ',' || preferred_instances || ',' LIKE '%,' || SUBSTR(p_instance_name, -1) || ',%'
                AND status = 'A'
            ) LOOP
                DBMS_OUTPUT.PUT_LINE('迁移服务: ' || svc_rec.service_name);
                -- 实际环境中调用 srvctl relocate service 命令
            END LOOP;
            
        WHEN 'SERVICE_DOWN' THEN
            v_action_taken := '尝试重启服务';
            
            INSERT INTO fan_event_log (
                event_id, event_type, instance_name, service_name, 
                event_details, action_taken
            ) VALUES (
                v_event_id, p_event_type, p_instance_name, p_service_name,
                p_event_details, v_action_taken
            );
            
            DBMS_OUTPUT.PUT_LINE('服务故障检测: ' || p_service_name || ' @ ' || p_instance_name);
            
            -- 尝试在其他实例上启动服务
            FOR inst_rec IN (
                SELECT instance_name
                FROM gv$instance
                WHERE status = 'OPEN'
                AND instance_name != p_instance_name
                ORDER BY instance_number
                FETCH FIRST 1 ROWS ONLY
            ) LOOP
                DBMS_OUTPUT.PUT_LINE('在实例 ' || inst_rec.instance_name || ' 上启动服务');
                -- 实际环境中调用服务启动命令
            END LOOP;
            
        WHEN 'NODE_UP' THEN
            v_action_taken := '节点恢复,重新平衡服务';
            
            INSERT INTO fan_event_log (
                event_id, event_type, instance_name, event_details, action_taken
            ) VALUES (
                v_event_id, p_event_type, p_instance_name, p_event_details, v_action_taken
            );
            
            DBMS_OUTPUT.PUT_LINE('节点恢复: ' || p_instance_name);
            DBMS_OUTPUT.PUT_LINE('重新分布服务负载...');
            
        ELSE
            v_action_taken := '记录事件,无特定操作';
            
            INSERT INTO fan_event_log (
                event_id, event_type, instance_name, service_name,
                event_details, action_taken
            ) VALUES (
                v_event_id, p_event_type, p_instance_name, p_service_name,
                p_event_details, v_action_taken
            );
    END CASE;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE('FAN事件处理完成,事件ID: ' || v_event_id);
    
END handle_fan_event;
/

2. 应用层故障转移

-- 应用连接池监控
CREATE TABLE app_connection_monitor (
    monitor_id NUMBER PRIMARY KEY,
    check_time DATE DEFAULT SYSDATE,
    instance_name VARCHAR2(30),
    service_name VARCHAR2(50),
    active_connections NUMBER,
    idle_connections NUMBER,
    failed_connections NUMBER,
    avg_response_time NUMBER, -- 毫秒
    status VARCHAR2(20), -- HEALTHY, WARNING, CRITICAL
    auto_action VARCHAR2(100)
);

CREATE SEQUENCE app_monitor_seq START WITH 1 INCREMENT BY 1;

-- 连接健康检查
CREATE OR REPLACE PROCEDURE check_connection_health AS
    v_monitor_id NUMBER;
    v_total_sessions NUMBER;
    v_active_sessions NUMBER;
    v_blocked_sessions NUMBER;
    v_avg_response_time NUMBER;
    v_status VARCHAR2(20);
    v_action VARCHAR2(100);
BEGIN
    FOR inst_rec IN (
        SELECT instance_number, instance_name
        FROM gv$instance
        WHERE status = 'OPEN'
        ORDER BY instance_number
    ) LOOP
        v_monitor_id := app_monitor_seq.NEXTVAL;
        
        -- 获取会话统计信息
        SELECT COUNT(*) INTO v_total_sessions
        FROM gv$session
        WHERE inst_id = inst_rec.instance_number
        AND type = 'USER';
        
        SELECT COUNT(*) INTO v_active_sessions
        FROM gv$session
        WHERE inst_id = inst_rec.instance_number
        AND type = 'USER'
        AND status = 'ACTIVE';
        
        SELECT COUNT(*) INTO v_blocked_sessions
        FROM gv$session
        WHERE inst_id = inst_rec.instance_number
        AND blocking_session IS NOT NULL;
        
        -- 计算平均响应时间(简化模拟)
        v_avg_response_time := ROUND(DBMS_RANDOM.VALUE(10, 500), 2);
        
        -- 判断健康状态
        IF v_blocked_sessions > 10 OR v_avg_response_time > 1000 THEN
            v_status := 'CRITICAL';
            v_action := '需要立即处理阻塞会话';
        ELSIF v_blocked_sessions > 5 OR v_avg_response_time > 500 THEN
            v_status := 'WARNING';
            v_action := '监控会话性能';
        ELSE
            v_status := 'HEALTHY';
            v_action := '正常运行';
        END IF;
        
        -- 记录监控数据
        INSERT INTO app_connection_monitor (
            monitor_id, instance_name, active_connections, idle_connections,
            failed_connections, avg_response_time, status, auto_action
        ) VALUES (
            v_monitor_id, inst_rec.instance_name, v_active_sessions,
            v_total_sessions - v_active_sessions, v_blocked_sessions,
            v_avg_response_time, v_status, v_action
        );
        
        -- 如果状态为CRITICAL,执行自动操作
        IF v_status = 'CRITICAL' THEN
            DBMS_OUTPUT.PUT_LINE('实例 ' || inst_rec.instance_name || ' 状态严重');
            DBMS_OUTPUT.PUT_LINE('阻塞会话数: ' || v_blocked_sessions);
            DBMS_OUTPUT.PUT_LINE('平均响应时间: ' || v_avg_response_time || 'ms');
            
            -- 自动处理阻塞会话
            FOR blocked_rec IN (
                SELECT sid, serial#, blocking_session
                FROM gv$session
                WHERE inst_id = inst_rec.instance_number
                AND blocking_session IS NOT NULL
                AND last_call_et > 300 -- 超过5分钟的阻塞
            ) LOOP
                DBMS_OUTPUT.PUT_LINE('终止阻塞会话: ' || blocked_rec.sid || ',' || blocked_rec.serial#);
                -- 实际环境中执行 ALTER SYSTEM KILL SESSION
            END LOOP;
        END IF;
    END LOOP;
    
    COMMIT;
    
END check_connection_health;
/

灾难恢复方案

1. 异地灾备架构

-- 灾备站点配置
CREATE TABLE dr_site_config (
    site_id NUMBER PRIMARY KEY,
    site_name VARCHAR2(50),
    site_type VARCHAR2(20), -- PRIMARY, DR, BACKUP
    location VARCHAR2(100),
    network_latency_ms NUMBER, -- 网络延迟
    bandwidth_mbps NUMBER,     -- 带宽
    rto_minutes NUMBER,        -- 恢复时间目标
    rpo_minutes NUMBER,        -- 恢复点目标
    auto_failover_enabled CHAR(1) DEFAULT 'N',
    last_sync_time DATE,
    sync_status VARCHAR2(20),  -- SYNC, LAG, FAILED
    status CHAR(1) DEFAULT 'A',
    create_time DATE DEFAULT SYSDATE
);

-- 银行灾备站点配置
INSERT INTO dr_site_config VALUES (
    1, '主数据中心', 'PRIMARY', '北京亦庄', 0, 10000, 0, 0, 'N', 
    SYSDATE, 'SYNC', 'A', SYSDATE
);

INSERT INTO dr_site_config VALUES (
    2, '同城灾备中心', 'DR', '北京昌平', 2, 1000, 30, 5, 'Y',
    SYSDATE, 'SYNC', 'A', SYSDATE
);

INSERT INTO dr_site_config VALUES (
    3, '异地灾备中心', 'DR', '上海张江', 50, 100, 120, 60, 'N',
    SYSDATE, 'LAG', 'A', SYSDATE
);

-- 灾备切换预案
CREATE TABLE dr_procedure_config (
    procedure_id NUMBER PRIMARY KEY,
    disaster_type VARCHAR2(50),  -- DATACENTER_FAILURE, NETWORK_OUTAGE, SYSTEM_CRASH
    severity_level VARCHAR2(20), -- LOW, MEDIUM, HIGH, CRITICAL
    target_site_id NUMBER,
    estimated_rto_minutes NUMBER,
    estimated_rpo_minutes NUMBER,
    approval_required CHAR(1) DEFAULT 'Y',
    procedure_steps CLOB,
    rollback_steps CLOB,
    last_tested_date DATE,
    test_result VARCHAR2(20),
    status CHAR(1) DEFAULT 'A',
    CONSTRAINT fk_dr_target_site FOREIGN KEY (target_site_id) 
        REFERENCES dr_site_config(site_id)
);

-- 灾备预案配置
INSERT INTO dr_procedure_config VALUES (
    1, '主数据中心完全故障', 'CRITICAL', 2, 30, 5, 'Y',
    '1. 确认主数据中心完全不可用
     2. 启动同城灾备中心
     3. 激活Data Guard standby数据库
     4. 切换网络路由到灾备中心
     5. 启动应用服务
     6. 验证核心业务功能
     7. 通知相关业务部门',
    '1. 确认主数据中心恢复
     2. 同步数据到主数据中心
     3. 执行switchover回到主数据中心
     4. 切换网络路由
     5. 验证业务正常运行',
    SYSDATE - 30, 'SUCCESS', 'A'
);

-- 灾备切换执行
CREATE OR REPLACE PROCEDURE execute_dr_procedure(
    p_disaster_type VARCHAR2,
    p_manual_override CHAR(1) DEFAULT 'N'
) AS
    v_procedure_rec dr_procedure_config%ROWTYPE;
    v_target_site dr_site_config%ROWTYPE;
    v_can_execute BOOLEAN := FALSE;
    v_execution_log CLOB;
BEGIN
    -- 获取匹配的灾备预案
    SELECT * INTO v_procedure_rec
    FROM dr_procedure_config
    WHERE disaster_type = p_disaster_type
    AND status = 'A'
    ORDER BY severity_level DESC
    FETCH FIRST 1 ROWS ONLY;
    
    -- 获取目标站点信息
    SELECT * INTO v_target_site
    FROM dr_site_config
    WHERE site_id = v_procedure_rec.target_site_id;
    
    v_execution_log := '灾备切换执行日志' || CHR(10) ||
                      '执行时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS') || CHR(10) ||
                      '灾难类型: ' || p_disaster_type || CHR(10) ||
                      '目标站点: ' || v_target_site.site_name || CHR(10) ||
                      '预计RTO: ' || v_procedure_rec.estimated_rto_minutes || ' 分钟' || CHR(10) ||
                      '预计RPO: ' || v_procedure_rec.estimated_rpo_minutes || ' 分钟' || CHR(10) ||
                      RPAD('=', 60, '=') || CHR(10) || CHR(10);
    
    -- 检查执行条件
    IF v_procedure_rec.approval_required = 'Y' AND p_manual_override = 'N' THEN
        v_execution_log := v_execution_log || '✗ 该预案标记为需要批准;本示例仅检查输入标志,不校验管理员身份或真实审批记录' || CHR(10);
    ELSIF v_target_site.status != 'A' THEN
        v_execution_log := v_execution_log || '✗ 目标站点状态异常,无法执行切换' || CHR(10);
    ELSIF v_target_site.sync_status NOT IN ('SYNC', 'LAG') THEN
        v_execution_log := v_execution_log || '✗ 目标站点同步状态异常: ' || v_target_site.sync_status || CHR(10);
    ELSE
        v_can_execute := TRUE;
    END IF;
    
    IF v_can_execute THEN
        v_execution_log := v_execution_log || '✓ 满足示例标志与站点状态检查,开始生成灾备切换步骤;未执行真实审批或切换' || CHR(10) || CHR(10);
        v_execution_log := v_execution_log || '执行步骤:' || CHR(10);
        v_execution_log := v_execution_log || v_procedure_rec.procedure_steps || CHR(10) || CHR(10);
        
        -- 实际环境中在这里执行具体的切换操作
        -- 1. Data Guard switchover/failover
        -- 2. 网络路由切换
        -- 3. 应用服务启动
        -- 4. 负载均衡配置更新
        
        v_execution_log := v_execution_log || '灾备切换步骤已生成;本示例未启动实际切换操作' || CHR(10);
        v_execution_log := v_execution_log || '切换完成后请执行业务验证测试' || CHR(10);
        
    ELSE
        v_execution_log := v_execution_log || '灾备切换执行被阻止,请检查上述问题' || CHR(10);
    END IF;
    
    DBMS_OUTPUT.PUT_LINE(v_execution_log);
    
    -- 记录执行日志
    INSERT INTO dr_execution_log (
        log_id, disaster_type, target_site_id, execution_time,
        can_execute, execution_log, manual_override
    ) VALUES (
        dr_exec_log_seq.NEXTVAL, p_disaster_type, v_target_site.site_id, SYSDATE,
        CASE WHEN v_can_execute THEN 'Y' ELSE 'N' END, v_execution_log, p_manual_override
    );
    
    COMMIT;
    
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('错误: 未找到匹配的灾备预案 - ' || p_disaster_type);
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('执行灾备切换时发生错误: ' || SQLERRM);
        ROLLBACK;
END execute_dr_procedure;
/

-- 创建必要的表和序列
CREATE TABLE dr_execution_log (
    log_id NUMBER PRIMARY KEY,
    disaster_type VARCHAR2(50),
    target_site_id NUMBER,
    execution_time DATE DEFAULT SYSDATE,
    can_execute CHAR(1),
    execution_log CLOB,
    manual_override CHAR(1) DEFAULT 'N',
    execution_result VARCHAR2(20)
);

CREATE SEQUENCE dr_exec_log_seq START WITH 1 INCREMENT BY 1;

2. 灾备演练管理

-- 灾备演练计划表
CREATE TABLE dr_drill_schedule (
    drill_id NUMBER PRIMARY KEY,
    drill_name VARCHAR2(100),
    drill_type VARCHAR2(30), -- PLANNED, SURPRISE, COMPLIANCE
    disaster_scenario VARCHAR2(100),
    scheduled_date DATE,
    duration_hours NUMBER,
    participants VARCHAR2(500),
    success_criteria CLOB,
    drill_status VARCHAR2(20), -- PLANNED, IN_PROGRESS, COMPLETED, CANCELLED
    actual_start_time DATE,
    actual_end_time DATE,
    drill_result VARCHAR2(20), -- SUCCESS, PARTIAL_SUCCESS, FAILED
    lessons_learned CLOB,
    next_drill_date DATE,
    create_time DATE DEFAULT SYSDATE
);

CREATE SEQUENCE dr_drill_seq START WITH 1 INCREMENT BY 1;

-- 创建银行灾备演练计划
INSERT INTO dr_drill_schedule VALUES (
    dr_drill_seq.NEXTVAL, '2024年第一季度灾备演练', 'PLANNED', 
    '主数据中心网络中断', DATE '2024-03-15', 4,
    'DBA团队,运维团队,业务测试团队,风险管理部',
    '1. 30分钟内完成数据库切换
     2. 核心业务功能正常运行
     3. 数据零丢失
     4. 应用响应时间不超过平时1.5倍',
    'PLANNED', NULL, NULL, NULL, NULL, DATE '2024-06-15', SYSDATE
);

-- 灾备演练执行和评估
CREATE OR REPLACE PROCEDURE execute_dr_drill(p_drill_id NUMBER) AS
    v_drill_rec dr_drill_schedule%ROWTYPE;
    v_start_time DATE;
    v_test_results CLOB;
    v_success_count NUMBER := 0;
    v_total_tests NUMBER := 0;
BEGIN
    -- 获取演练计划
    SELECT * INTO v_drill_rec
    FROM dr_drill_schedule
    WHERE drill_id = p_drill_id;
    
    v_start_time := SYSDATE;
    
    -- 更新演练状态为进行中
    UPDATE dr_drill_schedule
    SET drill_status = 'IN_PROGRESS',
        actual_start_time = v_start_time
    WHERE drill_id = p_drill_id;
    
    COMMIT;
    
    v_test_results := '灾备演练执行报告' || CHR(10) ||
                     '演练名称: ' || v_drill_rec.drill_name || CHR(10) ||
                     '开始时间: ' || TO_CHAR(v_start_time, 'YYYY-MM-DD HH24:MI:SS') || CHR(10) ||
                     RPAD('=', 60, '=') || CHR(10) || CHR(10);
    
    -- 测试1: 数据库切换时间
    v_total_tests := v_total_tests + 1;
    v_test_results := v_test_results || '测试1: 数据库切换时间' || CHR(10);
    
    -- 模拟切换操作
    DBMS_LOCK.SLEEP(2); -- 模拟切换时间
    
    DECLARE
        v_switch_time NUMBER := 25; -- 模拟25分钟切换时间
    BEGIN
        IF v_switch_time <= 30 THEN
            v_test_results := v_test_results || '✓ 切换时间: ' || v_switch_time || ' 分钟 (≤30分钟) - 通过' || CHR(10);
            v_success_count := v_success_count + 1;
        ELSE
            v_test_results := v_test_results || '✗ 切换时间: ' || v_switch_time || ' 分钟 (>30分钟) - 失败' || CHR(10);
        END IF;
    END;
    
    -- 测试2: 数据完整性验证
    v_total_tests := v_total_tests + 1;
    v_test_results := v_test_results || CHR(10) || '测试2: 数据完整性验证' || CHR(10);
    
    DECLARE
        v_data_loss NUMBER := 0; -- 模拟数据丢失量
    BEGIN
        IF v_data_loss = 0 THEN
            v_test_results := v_test_results || '✓ 数据零丢失 - 通过' || CHR(10);
            v_success_count := v_success_count + 1;
        ELSE
            v_test_results := v_test_results || '✗ 数据丢失: ' || v_data_loss || ' 条记录 - 失败' || CHR(10);
        END IF;
    END;
    
    -- 测试3: 业务功能验证
    v_total_tests := v_total_tests + 1;
    v_test_results := v_test_results || CHR(10) || '测试3: 核心业务功能验证' || CHR(10);
    
    DECLARE
        v_function_tests NUMBER := 8; -- 通过的功能测试数
        v_total_functions NUMBER := 10;
    BEGIN
        IF v_function_tests >= v_total_functions * 0.9 THEN
            v_test_results := v_test_results || '✓ 业务功能: ' || v_function_tests || '/' || 
                            v_total_functions || ' 通过 (≥90%) - 通过' || CHR(10);
            v_success_count := v_success_count + 1;
        ELSE
            v_test_results := v_test_results || '✗ 业务功能: ' || v_function_tests || '/' || 
                            v_total_functions || ' 通过 (<90%) - 失败' || CHR(10);
        END IF;
    END;
    
    -- 测试4: 性能验证
    v_total_tests := v_total_tests + 1;
    v_test_results := v_test_results || CHR(10) || '测试4: 应用响应时间验证' || CHR(10);
    
    DECLARE
        v_response_ratio NUMBER := 1.3; -- 模拟响应时间比例
    BEGIN
        IF v_response_ratio <= 1.5 THEN
            v_test_results := v_test_results || '✓ 响应时间比例: ' || v_response_ratio || 
                            ' (≤1.5倍) - 通过' || CHR(10);
            v_success_count := v_success_count + 1;
        ELSE
            v_test_results := v_test_results || '✗ 响应时间比例: ' || v_response_ratio || 
                            ' (>1.5倍) - 失败' || CHR(10);
        END IF;
    END;
    
    -- 演练总结
    v_test_results := v_test_results || CHR(10) || RPAD('=', 60, '=') || CHR(10);
    v_test_results := v_test_results || '演练总结:' || CHR(10);
    v_test_results := v_test_results || '通过测试: ' || v_success_count || '/' || v_total_tests || CHR(10);
    v_test_results := v_test_results || '成功率: ' || ROUND(v_success_count * 100 / v_total_tests, 2) || '%' || CHR(10);
    
    DECLARE
        v_drill_result VARCHAR2(20);
    BEGIN
        IF v_success_count = v_total_tests THEN
            v_drill_result := 'SUCCESS';
            v_test_results := v_test_results || '演练结果: 成功' || CHR(10);
        ELSIF v_success_count >= v_total_tests * 0.75 THEN
            v_drill_result := 'PARTIAL_SUCCESS';
            v_test_results := v_test_results || '演练结果: 部分成功' || CHR(10);
        ELSE
            v_drill_result := 'FAILED';
            v_test_results := v_test_results || '演练结果: 失败' || CHR(10);
        END IF;
        
        -- 更新演练记录
        UPDATE dr_drill_schedule
        SET drill_status = 'COMPLETED',
            actual_end_time = SYSDATE,
            drill_result = v_drill_result,
            lessons_learned = v_test_results
        WHERE drill_id = p_drill_id;
    END;
    
    COMMIT;
    
    DBMS_OUTPUT.PUT_LINE(v_test_results);
    
EXCEPTION
    WHEN OTHERS THEN
        -- 更新演练状态为失败
        UPDATE dr_drill_schedule
        SET drill_status = 'COMPLETED',
            actual_end_time = SYSDATE,
            drill_result = 'FAILED',
            lessons_learned = '演练执行过程中发生异常: ' || SQLERRM
        WHERE drill_id = p_drill_id;
        
        COMMIT;
        RAISE;
END execute_dr_drill;
/

Oracle高可用性方案是银行信息系统稳定运营的重要保障。综合运用RAC集群、Data Guard、自动故障转移和灾难恢复等技术,可将99.99%以上的系统可用性作为方案设计目标;能否达到该目标取决于端到端架构、故障覆盖、运维流程与实际测量,本文的模拟代码不构成达标证明。在实际部署时,需要根据业务重要性、成本预算和监管要求,选择合适的高可用性架构和策略。