Come creare una notifica di eventi che esegue un processo / procedura quando si rispecchia il cambiamento di stato


11

Sto ponendo questa domanda nella sequenza di questa. Posso inviare una stringa su TCP usando T-SQL?

Remus Rusanu espone quella che sembra essere una soluzione ottimale per il mio problema, ma ... Sono troppo immaturo per capire e rendere tutto ciò che dice.

Finora penso di cosa ho bisogno per creare un evento di notifica per DATABASE_MIRRORING_STATE_CHANGE, ho ragione?

come posso creare questa notifica di evento quando il suo trigger inserisce una riga in una tabella, che memorizza un timestamp e un ID che proviene dalla notifica.

finora sto impostando un avviso per ID, ognuno in esecuzione un lavoro come questo (questo esempio è per ID = 1):

    DECLARE @state AS varchar(50);
    SELECT @state = mirroring_state_desc FROM SYS.database_mirroring WHERE mirroring_guid IS NOT NULL;
    IF (@state IS null) SET @state = ' ';
    INSERT INTO MirroringAlerts (DateTime, alertID, alertDesc, Sync, alertCreator) values (SYSDATETIME(), 1, 'Principal synchronized with W ', @state, @@SERVERNAME)

Fondamentalmente sto creando un registro interno in questo database:

CREATE TABLE [dbo].[MirroringAlerts](
    [DateTime] [datetime] NOT NULL,
    [alertID] [smallint] NOT NULL,
    [alertDesc] [nchar](50) NOT NULL,
    [Sync] [nchar](12) NOT NULL,
    [alertCreator] [nchar](128) NULL
) ON [PRIMARY]

Ma in questo modo ... gli avvisi non vengono attivati ​​abbastanza velocemente ... quindi sto perdendo informazioni ...

Potete dirmi come programmare questo comportamento con la creazione di una notifica evento per l' evento Cambiato stato mirroring del database ?

I migliori saluti

Risposte:


13

Passaggio 1: creare un servizio per ricevere le notifiche e una coda per esso:

use msdb;
go

create queue dbm_notifications_queue;
create service dbm_notification_service
    on queue dbm_notifications_queue
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
go

create event notification dbm_notifications
    on server   
    for database_mirroring_state_change
    to service N'dbm_notification_service', N'current database';
go

Nota che sto usando msdb, questo non è un incidente. Poiché le notifiche degli eventi a livello di server vengono inviate da msdbesso è molto meglio se si crea anche l'endpoint di conversazione opposto (la destinazione) msdb, il che implica che anche il servizio di destinazione e la coda devono essere distribuiti msdb.

Passaggio 2: creare la procedura di elaborazione della notifica dell'evento:

use msdb;
go

create table dbm_notifications_errors (
    incident_time datetime not null,
    session_id int not null,
    has_rolled_back bit not null,
    [error_number] int not null,
    [error_message] nvarchar(4000) not null,
    [message_body] varbinary(max));
create clustered index cdx_dbm_notifications_errors 
    on dbm_notifications_errors  (incident_time);
go

create table mirroring_alerts (
    alert_time datetime not null,
    start_time datetime not null,
    processing_time datetime not null,
    database_id smallint not null,
    database_name sysname not null,
    [state] tinyint not null,
    [text_data] nvarchar(max),
    event_data xml not null);
create clustered index cdx_mirroring_alerts
    on mirroring_alerts (alert_time);   
go      

create procedure dbm_notifications_procedure
as
begin
    declare @dh uniqueidentifier, @mt sysname, @raw_body varbinary(max), @xml_body xml; 

    begin transaction;
    begin try;
        receive top(1)
            @dh = conversation_handle,
            @mt = message_type_name,
            @raw_body = message_body
        from dbm_notifications_queue;
        if N'http://schemas.microsoft.com/SQL/Notifications/EventNotification' = @mt
        begin
            set @xml_body = cast(@raw_body as xml);
             -- shred the XML and process it accordingly
             -- IMPORTANT! IMPORTANT!
             -- DO NOT LOOK AT sys.database_mirroring
             -- The view represents the **CURRENT** state
             -- This message reffers to an **EVENT** that had occured
             -- the current state may or may no be relevant for this **PAST** event
            declare @alert_time datetime
                , @start_time datetime
                , @processing_time datetime = getutcdate()
                , @database_id smallint 
                , @database_name sysname
                , @state tinyint
                , @text_data nvarchar(max);

            set @alert_time = @xml_body.value (N'(//EVENT_INSTANCE/PostTime)[1]', 'DATETIME');
            set @start_time = @xml_body.value (N'(//EVENT_INSTANCE/StartTime)[1]', 'DATETIME');
            set @database_id = @xml_body.value (N'(//EVENT_INSTANCE/DatabaseID)[1]', 'SMALLINT');
            set @database_name = @xml_body.value (N'(//EVENT_INSTANCE/DatabaseName)[1]', 'SYSNAME');
            set @state = @xml_body.value (N'(//EVENT_INSTANCE/State)[1]', 'TINYINT');
            set @text_data = @xml_body.value (N'(//EVENT_INSTANCE/TextData)[1]', 'NVARCHAR(MAX)');

            insert into mirroring_alerts (
                alert_time, 
                start_time,
                processing_time,
                database_id,
                database_name,
                [state],
                text_data,
                event_data)
            values (
                @alert_time, 
                @start_time,
                @processing_time,
                @database_id,
                @database_name,
                @state,
                @text_data,
                @xml_body);
        end
        else if N'http://schemas.microsoft.com/SQL/ServiceBroker/Error' = @mt
        begin
        set @xml_body = cast(@raw_body as xml);
        DECLARE @error INT
                , @description NVARCHAR(4000);
        WITH XMLNAMESPACES ('http://schemas.microsoft.com/SQL/ServiceBroker/Error' AS ssb)
        SELECT @error = CAST(@xml_body AS XML).value('(//ssb:Error/ssb:Code)[1]', 'INT'),
            @description = CAST(@xml_body AS XML).value('(//ssb:Error/ssb:Description)[1]', 'NVARCHAR(4000)');          

        insert into dbm_notifications_errors(
            incident_time,
            session_id, 
            has_rolled_back,
            [error_number],
            [error_message],
            [message_body])
        values (
            getutcdate(),
            @@spid,
            0,
            @error,
            @description,
            @raw_body);
            end conversation @dh;
        end
        else if N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog' = @mt
        begin
            end conversation @dh;
        end
        commit;
    end try
    begin catch
        declare @xact_state int = xact_state(), 
            @error_number int = error_number(), 
            @error_message nvarchar(4000) = error_message(),
            @has_rolled_back bit = 0;
        if @xact_state = -1
        begin
            -- Doomed transaction, it must rollback
            rollback;
            set @has_rolled_back = 1;
        end
        else if @xact_state = 0
        begin
            -- transaction was already rolled back (deadlock?)
            set @has_rolled_back = 1;
        end
        insert into dbm_notifications_errors(
            incident_time,
            session_id, 
            has_rolled_back,
            [error_number],
            [error_message],
            [message_body])
        values (
            getutcdate(),
            @@spid,
            @has_rolled_back,
            @error_number,
            @error_message,
            @raw_body);
        if (@has_rolled_back = 0)
        begin
            commit;
        end
    end catch
end
go

La procedura di broker del servizio di scrittura non è il codice run-of-the-mill. Bisogna seguire determinati standard ed è molto facile allontanarsi nel territorio delle sabbie mobili. Questo codice mostra alcune buone pratiche:

  • avvolgere il messaggio dequeue e l'elaborazione in una transazione. Nessun problema, ovvio.
  • controlla sempre il tipo di messaggio ricevuto. Una buona procedura di broker di servizi deve gestire Errore EndDialogmessaggi in modo appropriato chiudendo la finestra di dialogo dal suo lato. Non farlo si traduce in perdite di handle ( sys.conversation_endpointscresce)
  • controlla sempre se un messaggio è stato rimesso in coda da RECEIVE. Alcuni esempi controllano @@ rowcount dopo RECEIVE, il che è perfettamente OK. Questo codice di esempio si basa sulla verifica del nome del messaggio (nessun messaggio implica il nome del tipo di messaggio NULL) e gestisce implicitamente quel caso.
  • creare una tabella degli errori di elaborazione. la natura di base delle procedure attivate da SSB rende davvero difficile la risoluzione degli errori se i messaggi svaniscono semplicemente senza traccia.

Inoltre, questo codice fa anche un codice di buone pratiche per quanto riguarda l'attività a portata di mano (monitoraggio DBM):

  • distinguere tra post_time( quando è stata inviata la notifica? ), start_time( quando è iniziata l'azione che ha attivato la notifica? ) e processing_time( quando è stata elaborata la notifica? ). post_timee start_timesarà probabilmente identico o molto vicino, ma a distanza di processing_timesecondi, ore, giorni post_time. quello interessante per l'audit è di solito post_time.
  • dal momento che l' post_timee processing_timesono diversi, dovrebbe essere evidente che un DBM monitoraggio compito in una notifica anche attivato procedura non ha commercio guardando sys.database_mirroringvista . Tale vista mostrerà lo stato corrente al momento dell'elaborazione, che può essere o meno correlato all'evento. Se l'elaborazione si verifica molto tempo dopo la pubblicazione dell'evento (si ritiene che i tempi di inattività della manutenzione) siano evidenti, il problema è evidente, ma può gestire anche l'elaborazione "integra" se il DBM cambia lo stato molto velocemente e registra due (o più) eventi in un riga (che accade di frequente): in questa situazione l'elaborazione, come nel codice che hai pubblicato, controlla l'evento nel momento in cui si verifica, ma registra lo stato corrente, finale ,. Leggere un simile audit potrebbe essere molto confuso in seguito.
  • controlla sempre l'evento XML originale. In questo modo è possibile in seguito interrogare questo XML per qualsiasi informazione che non sia stata "distrutta" in colonne nella tabella di controllo.

Passaggio 3: allegare la procedura alla coda:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);

Quindi dovrei farlo in entrambi i partner, giusto? In caso di fallimento nel Preside senza Testimone, c'è un modo per elaborare / controllare la coda? per sapere se ho accesso a tutte le situazioni di cambiamento dello stato o se c'è qualcosa che non è stato registrato nella mia tabella delle notifiche
RagnaRock,

Dovresti farlo su entrambi i partner, giusto. In caso di errore sul principale se msdbè ancora online (ad es. L'errore è un errore DB, non un errore del server), si verificherà l'elaborazione della coda.
Remus Rusanu,

Grazie per il premio. Per lo meno, ora hai una copia di "Pro SQL Server 2008 Mirroring" che ho sentito dire che è un buon libro sull'argomento.
Remus Rusanu,

9

Ho dovuto acquistare "Pro SQL Server 2008 Mirroring" dopo aver letto il capitolo 6, ho scoperto che i passaggi per farlo sono:

controlla se il broker di servizi è abilitato

SELECT CASE is_broker_enabled
WHEN 1 Then 'Enabled'
ELSE 'Disabled'
END
FROM sys.databases
WHERE name = 'DataBaseName'

se no, corri

ALTER DATABASE DataBaseName set ENABLE_BROKER;

creare la procedura memorizzata che vogliamo che venga attivata quando arriva l'evento di notifica:

CREATE PROCEDURE dbo.dba_MirroringStateChanged
AS
DECLARE @Message XML,
        @DBName sysname,
        @MirrorStateChange INT,
        @ServerName sysname,
        @PostTime datetime,
        @SPID INT,
        @TextData NVARCHAR(500),
        @DatabaseID INT,
        @TransactionsID INT,
        @StartTime datetime;
SET NOCOUNT ON;
-- Receive first unread message in service broker queue
RECEIVE TOP (1)
@Message = CAST(message_body AS XML)
FROM DBMirrorQueue;

BEGIN TRY
    -- Parse state change and database affected
    -- 7 or 8 = database failing over,
    --11 = synchronizing,
    --1 or 2 = synchronized
    SET @MirrorStateChange =
    @Message.value('(/EVENT_INSTANCE/State)[1]', 'int');
    SET @DBName =
    @Message.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'sysname');
    SET @ServerName =
    @Message.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname');
    SET @PostTime =
    @Message.value('(/EVENT_INSTANCE/PostTime)[1]', 'datetime');
    SET @SPID = @Message.value('(/EVENT_INSTANCE/SPID)[1]', 'int');
    SET @TextData =
    @Message.value('(/EVENT_INSTANCE/TextData)[1]', 'nvarchar(500)');
    SET @DatabaseID =
    @Message.value('(/EVENT_INSTANCE/DatabaseID)[1]', 'int');
    SET @TransactionsID =
    @Message.value('(/EVENT_INSTANCE/TransactionsID)[1]', 'int');
    SET @StartTime =
    @Message.value('(/EVENT_INSTANCE/StartTime)[1]', 'datetime');
END TRY
    BEGIN CATCH
        PRINT 'Parse of mirroring state change message failed.';
    END CATCH

IF (@MirrorStateChange IN (1,2,3,4,5,6,7,8,9,10,11,12,13))
BEGIN

    DECLARE @state AS varchar(50);
    SELECT @state = mirroring_state_desc FROM SYS.database_mirroring WHERE mirroring_guid IS NOT NULL;
    IF (@state IS null) SET @state = ' ';
    INSERT INTO MirroringAlerts (DateTime, alertID, alertDesc, Sync, alertCreator) values (SYSDATETIME(), @MirrorStateChange , @TextData , @state, @SERVERNAME);

END

creare una coda, per essere una sorta di intermediario tra il servizio e la procedura memorizzata che vogliamo attivare

-- Create Queue if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.service_queues
    WHERE name = 'DBMirrorQueue')
BEGIN
    CREATE QUEUE DBMirrorQueue
    WITH ACTIVATION (
    PROCEDURE_NAME = dbo.dba_MirroringStateChanged,
    MAX_QUEUE_READERS = 1,
    EXECUTE AS OWNER);
END

creare il servizio che sarà associato all'evento

-- Create Service if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.services
    WHERE name = 'DBMirrorService')
BEGIN
    CREATE SERVICE DBMirrorService
    ON QUEUE DBMirrorQueue ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
END

Crea un percorso

-- Create Route if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.routes
    WHERE name = 'DBMirrorRoute')
BEGIN
    CREATE ROUTE DBMirrorRoute
    WITH SERVICE_NAME = 'DBMirrorService',
    ADDRESS = 'Local';
END

e quindi creare la notifica dell'evento

-- Create Event Notification if not exists
IF NOT EXISTS (SELECT 1
    FROM sys.server_event_notifications
    WHERE name = 'DBMirrorStateChange')
BEGIN
    CREATE EVENT NOTIFICATION DBMirrorStateChange
    ON SERVER
    FOR DATABASE_MIRRORING_STATE_CHANGE
    TO SERVICE 'DBMirrorService', 'current database';
END
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.