Cómo crear una notificación de evento que ejecute un trabajo / procedimiento cuando refleje cambios de estado

11

Estoy haciendo esta pregunta en la secuencia de esta ¿Puedo enviar una cadena a través de TCP usando T-SQL?

Remus Rusanu expone lo que parece ser una solución óptima para mi problema, pero ... Soy demasiado inmaduro para entender y hacer todo lo que dice.

Hasta ahora, creo que lo que necesito para crear un evento de notificación para DATABASE_MIRRORING_STATE_CHANGE, ¿estoy en lo cierto?

¿Cómo puedo crear esta notificación de evento cuando se activa inserta una línea en una tabla, que almacena una marca de tiempo y una identificación que proviene de la notificación.

Hasta ahora estoy configurando una alerta por ID, cada una ejecutando un trabajo como este (este ejemplo es para 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)

Básicamente estoy creando un registro interno en esta base de datos:

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]

Pero de esta manera ... las alertas no se activan lo suficientemente rápido ... así que estoy perdiendo información ...

¿Me puede decir cómo programar este comportamiento con la creación de notificaciones de eventos para el evento de Cambio de estado de la duplicación de la base de datos ?

Atentamente

RagnaRock
fuente

Respuestas:

13

Paso 1: cree un servicio para recibir las notificaciones y una cola para ello:

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

Tenga en cuenta que estoy usando msdb, esto no es un accidente. Debido a que las notificaciones de eventos a nivel de servidor se envían desde msdbél, es mucho mejor si crea el punto final de conversación opuesto (el destino) también msdb, lo que implica que el servicio de destino y la cola también deben implementarse msdb.

Paso 2: cree el procedimiento de procesamiento de notificaciones de eventos:

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

Escribir el procedimiento de Service Broker no es su código habitual. Uno debe seguir ciertos estándares y es muy fácil desviarse en arenas movedizas. Este código muestra algunas buenas prácticas:

  • ajusta la cola del mensaje y el procesamiento en una transacción. No es obvio, obvio.
  • siempre verifique el tipo de mensaje recibido. Un buen procedimiento de intermediario de servicios debe manejar Errory EndDialogenviar mensajes adecuadamente al finalizar el diálogo desde su lado. No hacerlo da como resultado fugas en el mango ( sys.conversation_endpointscrece)
  • siempre verifique si un mensaje fue cancelado por RECEIVE. Algunas muestras comprueban @@ rowcount después RECEIVE, lo cual está perfectamente bien. Este código de muestra se basa en la verificación del nombre del mensaje (ningún mensaje implica un nombre de tipo de mensaje NULL) y maneja ese caso implícitamente.
  • crear una tabla de errores de procesamiento. La naturaleza de fondo de los procedimientos activados por SSB hace que sea realmente difícil solucionar errores si los mensajes simplemente desaparecen sin dejar rastro.

Además, este código también incluye código de buenas prácticas con respecto a la tarea en cuestión (monitoreo de DBM):

  • diferenciar entre post_time(¿ cuándo se envió la notificación? ), start_time(¿ cuándo comenzó la acción que activó la notificación? ) y processing_time(¿ cuándo se procesó la notificación? ). post_timey start_timees probable que sean idénticos o muy cercanos, pero processing_timepueden ser segundos, horas, días separados post_time. El interesante para la auditoría es generalmente post_time.
  • Dado que el post_timey el processing_timeson diferentes, debería ser obvio que una tarea de monitoreo de DBM en un procedimiento activado de notificación uniforme no tiene nada que sys.database_mirroringver . Esa vista mostrará el estado actual en el momento del procesamiento, que puede o no estar relacionado con el evento. Si el procesamiento se produce mucho tiempo después de la publicación del evento (piense en el tiempo de inactividad por mantenimiento), el problema es obvio, pero también se puede manejar en el procesamiento 'saludable' si el DBM cambia de estado muy rápido y publica dos (o más) eventos en un fila (que ocurre con frecuencia): en esta situación, el procesamiento, como en el código que publicó, audita el evento a medida que ocurre, pero registrará el estado actual final . Leer una auditoría de este tipo podría ser muy confuso más tarde.
  • audite siempre el evento XML original. De esta manera, puede consultar este XML en busca de información que no se haya "desmenuzado" en columnas en la tabla de auditoría.

Paso 3: adjunte el procedimiento a la cola:

alter queue dbm_notifications_queue
with activation (
    status=on,
    procedure_name = [dbm_notifications_procedure],
    max_queue_readers = 1,
    execute as  owner);
Remus Rusanu
fuente
Entonces debería hacer esto en ambos socios, ¿verdad? En caso de falla en el director sin testigo, ¿hay alguna forma de procesar / verificar la cola? para saber si tengo acceso a todas las situaciones de cambio de estado, o si hay algo que no se ha registrado en mi tabla de notificaciones
RagnaRock
Deberías hacerlo con ambos socios, correcto. En caso de falla en el Principal si msdbaún está en línea (es decir, la falla es una falla de la base de datos, no una falla del servidor), se producirá el procesamiento de la cola.
Remus Rusanu
Gracias por el premio. Como mínimo, ahora tiene una copia de "Pro SQL Server 2008 Mirroring" que, según he oído, es un buen libro sobre el tema.
Remus Rusanu
9

Tuve que comprar "Pro SQL Server 2008 Mirroring" después de leer el capítulo 6, descubrí que los pasos para hacerlo son:

verificar si service broker está habilitado

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

si no, corre

ALTER DATABASE DataBaseName set ENABLE_BROKER;

cree el procedimiento almacenado que queremos que se active cuando llegue el evento de notificación:

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

crear una cola, para ser algún tipo de intermediario entre el servicio y el procedimiento almacenado que queremos activar

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

crear el servicio que se asociará con el 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 una ruta

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

y luego crea la Notificación de 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
RagnaRock
fuente