Jak utworzyć powiadomienie o zdarzeniu, które uruchamia zadanie / procedurę przy zmianach stanu kopii lustrzanej

11

Zadaję to pytanie w sekwencji tego. Czy mogę wysłać ciąg przez TCP przy użyciu T-SQL?

Remus Rusanu ujawnia to, co wydaje się optymalnym rozwiązaniem dla mojego problemu, ale ... Jestem zbyt niedojrzały, aby zrozumieć i zrobić wszystko, co mówi.

Jak dotąd myślę, czego potrzebuję, aby utworzyć zdarzenie powiadomienia dla DATABASE_MIRRORING_STATE_CHANGE, czy mam rację?

jak mogę utworzyć to powiadomienie o zdarzeniu, gdy jego wyzwalanie wstawi wiersz do tabeli, która przechowuje znacznik czasu i identyfikator pochodzący z powiadomienia.

do tej pory konfiguruję jeden alert dla każdego identyfikatora, z których każdy wykonuje takie zadanie (ten przykład dotyczy 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)

Zasadniczo tworzę dziennik wewnętrzny w tej bazie danych:

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]

Ale w ten sposób ... alerty nie są uruchamiane wystarczająco szybko ... więc tracę informacje ...

Czy możesz mi powiedzieć, jak zaprogramować to zachowanie za pomocą powiadomienia o zdarzeniu dla zdarzenia Zmieniony stan kopii lustrzanej bazy danych ?

Z poważaniem

RagnaRock
źródło

Odpowiedzi:

13

Krok 1: Utwórz usługę, aby otrzymywać powiadomienia i kolejkę dla niej:

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

Zauważ, że używam msdb, to nie jest wypadek. Ponieważ powiadomienia o zdarzeniach na poziomie serwera są wysyłane z msdbniego, znacznie lepiej jest utworzyć przeciwny punkt końcowy konwersacji (cel) również w msdb, co oznacza, że ​​usługa docelowa i kolejka również muszą zostać wdrożone msdb.

Krok 2: Utwórz procedurę przetwarzania powiadomienia o zdarzeniu:

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

Pisanie procedury brokera usług nie jest twoim zwykłym kodem. Trzeba przestrzegać pewnych standardów i bardzo łatwo jest zejść na terytorium ruchomych piasków. Ten kod pokazuje kilka dobrych praktyk:

  • zawiń komunikat dequeue i przetwarzanie w transakcji. Bez zastanowienia, oczywiste.
  • zawsze sprawdzaj typ otrzymanej wiadomości. Dobra procedura brokera usług musi odpowiednio obsługiwać Errori EndDialogwysyłać komunikaty, kończąc okno dialogowe z boku. Niezastosowanie się do tego spowoduje przeciekanie ( sys.conversation_endpointsrośnie)
  • zawsze sprawdzaj, czy komunikat został usunięty z kolejki przez RECEIVE. Niektóre próbki sprawdzają @@ rowcount później RECEIVE, co jest całkowicie OK. Ten przykładowy kod opiera się na sprawdzeniu nazwy wiadomości (żadna wiadomość nie wskazuje NULL nazwy typu wiadomości) i domyślnie obsługuje tę wielkość liter.
  • utwórz tabelę błędów przetwarzania. charakter procedur aktywowanych SSB sprawia, że ​​naprawianie błędów jest naprawdę trudne, jeśli komunikaty po prostu znikają bez śladu.

Poza tym ten kod zawiera również kod dobrej praktyki w odniesieniu do bieżącego zadania (monitorowanie DBM):

  • rozróżnić post_time( kiedy wysłano powiadomienie? ), start_time( kiedy uruchomiono powiadomienie? ) i processing_time( kiedy przetworzono powiadomienie? ). post_timei start_timeprawdopodobnie będą identyczne lub bardzo bliskie, ale processing_timemogą to być sekundy, godziny lub dni post_time. zwykle interesujący jest audyt post_time.
  • ponieważ post_timei processing_timesą różne, powinno być oczywiste, że zadanie monitorowania DBM w procedurze aktywowanej nawet powiadomieniem nie ma sys.database_mirroringwidoku biznesowego . Ten widok pokaże bieżący stan w momencie przetwarzania, który może, ale nie musi być związany ze zdarzeniem. Jeśli przetwarzanie ma miejsce długo po opublikowaniu zdarzenia (pomyśl o przestojach konserwacyjnych), problem jest oczywisty, ale można go również obsłużyć w „zdrowym” przetwarzaniu, jeśli DBM bardzo szybko zmieni stan i opublikuje dwa (lub więcej) zdarzeń w wiersz (co zdarza się często): w tej sytuacji przetwarzanie, podobnie jak w opublikowanym kodzie, kontroluje zdarzenie w momencie ich wystąpienia, ale zapisze bieżący, końcowy stan. Czytanie takiego audytu może być później bardzo mylące.
  • zawsze kontroluj oryginalne zdarzenie XML. W ten sposób możesz później zapytać XML o informacje, które nie zostały „zniszczone” w kolumnach w tabeli kontroli.

Krok 3: dołącz procedurę do kolejki:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);
Remus Rusanu
źródło
Więc powinienem to zrobić u obu partnerów, prawda? Czy w przypadku awarii Zleceniodawcy bez Świadka istnieje sposób na przetworzenie / sprawdzenie kolejki? wiedzieć, czy mam dostęp do wszystkich sytuacji zmiany stanu lub czy coś nie zostało zarejestrowane w mojej tabeli powiadomień
RagnaRock 12.01.12
Powinieneś to zrobić u obu partnerów, prawda. W przypadku awarii Zleceniodawcy, jeśli msdbjest on nadal online (tzn. Awaria jest awarią bazy danych, a nie awarią serwera), nastąpi przetwarzanie kolejki.
Remus Rusanu
Dzięki za nagrodę. Przynajmniej masz teraz kopię „Pro SQL Server 2008 Mirroring”, która według mnie jest dobrą książką na ten temat.
Remus Rusanu,
9

Musiałem kupić „Pro SQL Server 2008 Mirroring” po przeczytaniu rozdziału 6, dowiedziałem się, jak to zrobić:

sprawdź, czy broker usług jest włączony

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

jeśli nie, biegnij

ALTER DATABASE DataBaseName set ENABLE_BROKER;

utwórz procedurę składowaną, która ma zostać uruchomiona, gdy nadejdzie zdarzenie powiadomienia:

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

utwórz kolejkę, aby być pośrednikiem między usługą a procedurą składowaną, którą chcemy uruchomić

-- 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

utwórz usługę, która będzie powiązana ze zdarzeniem

-- 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

Utwórz trasę

-- 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

a następnie utwórz powiadomienie o zdarzeniu

-- 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
RagnaRock
źródło