วิธีสร้างการแจ้งเตือนเหตุการณ์ที่เรียกใช้งาน / ขั้นตอนเมื่อทำมิเรอร์การเปลี่ยนแปลงสถานะ


11

ฉันถามคำถามตามลำดับของอันนี้ฉันสามารถส่งสตริงผ่าน TCP โดยใช้ T-SQL ได้ไหม?

Remus Rusanu แสดงให้เห็นว่ามันเป็นทางออกที่ดีที่สุดสำหรับปัญหาของฉัน แต่ ... ฉันยังเด็กเกินไปที่จะเข้าใจและทำทุกสิ่งที่เขาพูด

จนถึงตอนนี้ฉันคิดว่าสิ่งที่ฉันต้องสร้างเหตุการณ์การแจ้งเตือนสำหรับ DATABASE_MIRRORING_STATE_CHANGE ฉันถูกต้องหรือไม่

ฉันจะสร้างการแจ้งเตือนเหตุการณ์นี้ได้อย่างไรเมื่อมีการทริกเกอร์แทรกบรรทัดในตารางที่จัดเก็บการประทับเวลาและ ID ที่มาจากการแจ้งเตือน

จนถึงตอนนี้ฉันกำลังตั้งค่าการแจ้งเตือนหนึ่งรายการต่อ ID แต่ละคนกำลังทำงานเช่นนี้ (ตัวอย่างนี้สำหรับ 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)

โดยพื้นฐานฉันกำลังสร้างบันทึกภายในในฐานข้อมูลนี้:

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]

แต่ด้วยวิธีนี้ ... การแจ้งเตือนไม่ได้ถูกเรียกให้เร็วพอ ... ดังนั้นฉันจึงสูญเสียข้อมูล ...

คุณสามารถบอกวิธีการเขียนโปรแกรมพฤติกรรมนี้ด้วยการสร้างการแจ้งเตือนเหตุการณ์สำหรับฐานข้อมูลการเปลี่ยนแปลงสถานะมิเรอร์ฐานข้อมูลได้หรือไม่

ขอแสดงความนับถืออย่างสูง

คำตอบ:


13

ขั้นตอนที่ 1: สร้างบริการเพื่อรับการแจ้งเตือนและคิว:

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

โปรดทราบว่าฉันกำลังใช้msdbนี่ไม่ใช่อุบัติเหตุ เนื่องจากการแจ้งเตือนเหตุการณ์ระดับเซิร์ฟเวอร์msdbนั้นดีกว่ามากถ้าคุณสร้างจุดสิ้นสุดการสนทนาตรงข้าม (เป้าหมาย) ด้วยmsdbซึ่งหมายความว่าบริการปลายทางและคิวต้องถูกปรับใช้msdbด้วย

ขั้นตอนที่ 2: สร้างโพรซีเดอร์การประมวลผลการแจ้งเตือนเหตุการณ์:

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

ขั้นตอนนายหน้าบริการการเขียนไม่ใช่รหัสที่คุณใช้ หนึ่งจะต้องปฏิบัติตามมาตรฐานบางอย่างและเป็นเรื่องง่ายมากที่จะหลงทางในดินแดนทรายดูด รหัสนี้แสดงแนวทางปฏิบัติที่ดีบางประการ:

  • ห่อข้อความและการประมวลผลในธุรกรรม ไม่มีเกมง่ายๆชัดเจน
  • ตรวจสอบประเภทข้อความที่ได้รับเสมอ ขั้นตอนนายหน้าบริการที่ดีจะต้องจัดการErrorและEndDialogส่งข้อความอย่างเหมาะสมโดยสิ้นสุดการโต้ตอบจากด้านข้าง ไม่ทำเช่นนั้นส่งผลให้เกิดการรั่วไหล ( sys.conversation_endpointsขึ้น)
  • ตรวจสอบเสมอว่าข้อความถูก dequeued โดยรับ ตัวอย่างบางส่วนตรวจสอบ @@ rowcount หลังจากRECEIVEที่ตกลงอย่างสมบูรณ์ โค้ดตัวอย่างนี้อาศัยการตรวจสอบชื่อข้อความ (ไม่มีข้อความที่บอกเป็นนัยถึงชื่อประเภทข้อความ NULL) และจัดการกับกรณีนั้นโดยปริยาย
  • สร้างตารางข้อผิดพลาดในการประมวลผล ลักษณะพื้นหลังของโพรซีเดอร์ที่เปิดใช้งาน SSB ทำให้ยากต่อการแก้ไขข้อผิดพลาดหากข้อความหายไปโดยไม่มีการติดตาม

นอกจากนี้รหัสนี้ยังทำโค้ดแนวปฏิบัติที่เหมาะสมเกี่ยวกับงานที่กำลังทำอยู่ (การตรวจสอบ DBM):

  • แยกความแตกต่างระหว่างpost_time( เมื่อมีการส่งการแจ้งเตือน ), start_time( เมื่อใดที่การกระทำที่เริ่มการแจ้งเตือนเริ่มขึ้น ) และprocessing_time( เมื่อการแจ้งเตือนถูกประมวลผลเมื่อใด ) post_timeและstart_timeมีแนวโน้มที่จะเหมือนกันหรือใกล้มาก แต่สามารถวินาทีชั่วโมงวันนอกเหนือจากprocessing_time หนึ่งที่น่าสนใจสำหรับการตรวจสอบเป็นเรื่องปกติpost_timepost_time
  • ตั้งแต่post_timeและprocessing_timeจะแตกต่างกันก็ควรจะชัดเจนว่า DBM การตรวจสอบงานในขั้นตอนการเปิดใช้งานการแจ้งเตือนแม้มีธุรกิจไม่มองไปที่sys.database_mirroringมุมมอง มุมมองนั้นจะแสดงสถานะปัจจุบันในขณะที่ทำการประมวลผลซึ่งอาจหรืออาจจะไม่เกี่ยวข้องกับเหตุการณ์ หากการประมวลผลเกิดขึ้นเป็นเวลานานหลังจากเหตุการณ์ถูกโพสต์ (คิดว่าการหยุดทำงานของการบำรุงรักษา) เกินกว่าปัญหาจะเห็นได้ชัด แต่สามารถจัดการกับการประมวลผลที่ 'ดีต่อสุขภาพ' ได้ถ้า DBM เปลี่ยนสถานะอย่างรวดเร็วและโพสต์สองเหตุการณ์ แถว (ซึ่งเกิดขึ้นบ่อยครั้ง): ในสถานการณ์นี้การประมวลผลเช่นเดียวกับในรหัสที่คุณโพสต์ให้ตรวจสอบเหตุการณ์ที่เกิดขึ้น แต่จะบันทึกสถานะปัจจุบันขั้นสุดท้ายสถานะ การอ่านการตรวจสอบดังกล่าวอาจทำให้สับสนในภายหลัง
  • ตรวจสอบเหตุการณ์ XML ดั้งเดิมเสมอ วิธีนี้คุณสามารถสืบค้น XML นี้ในภายหลังสำหรับข้อมูลใด ๆ ที่ไม่ได้ 'ฉีก' เป็นคอลัมน์ในตารางการตรวจสอบ

ขั้นตอนที่ 3: แนบขั้นตอนกับคิว:

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

ดังนั้นฉันควรทำสิ่งนี้กับทั้งคู่ใช่มั้ย ในกรณีที่ความล้มเหลวในอาจารย์ใหญ่ที่ไม่มีพยานมีวิธีการดำเนินการ / ตรวจสอบคิวหรือไม่? เพื่อทราบว่าฉันสามารถเข้าถึงสถานการณ์การเปลี่ยนแปลงทั้งหมดหรือหากมีบางสิ่งที่ไม่ได้ถูกบันทึกไว้ในตารางการแจ้งเตือนของฉัน
RagnaRock

คุณควรทำทั้งคู่ใช่มั้ย ในกรณีของความล้มเหลวในหลักการหากmsdbยังคงออนไลน์ (นั่นคือความล้มเหลวคือความล้มเหลวของฐานข้อมูลไม่ใช่ความล้มเหลวของเซิร์ฟเวอร์) แล้วการประมวลผลคิวจะเกิดขึ้น
Remus Rusanu

ขอบคุณสำหรับรางวัล อย่างน้อยที่สุดตอนนี้คุณมีสำเนาของ "Pro SQL Server 2008 Mirroring" ซึ่งฉันได้ยินว่าเป็นหนังสือที่ดีในหัวข้อ
Remus Rusanu

9

ฉันต้องซื้อ "Pro SQL Server 2008 Mirroring" หลังจากอ่านบทที่ 6 ฉันพบขั้นตอนการทำเช่นนี้คือ:

ตรวจสอบว่านายหน้าบริการเปิดใช้งานอยู่หรือไม่

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

ถ้าไม่ให้เรียกใช้

ALTER DATABASE DataBaseName set ENABLE_BROKER;

สร้างกระบวนงานที่เก็บไว้ที่เราต้องการที่จะถูกเรียกเมื่อเหตุการณ์การแจ้งเตือนมาถึง:

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

สร้างคิวเพื่อเป็นคนกลางบางประเภทระหว่างบริการและขั้นตอนการจัดเก็บที่เราต้องการเรียกใช้

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

สร้างบริการที่จะเชื่อมโยงกับเหตุการณ์

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

สร้างเส้นทาง

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

จากนั้นสร้างการแจ้งเตือนเหตุการณ์

-- 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.