HAVIT Knowledge Base

Vývoj webových aplikací, .NET, SQL, návrh
Welcome to HAVIT Knowledge Base Sign in | Join | Help
-
Home Články Forums Obrázky Soubory

SQL

Microsoft SQL Server, Transact-SQL, Business Intelligence, CLR, ...

Service Broker: Notifikace .NET aplikace z SQL serveru (events, queries, messages)

SQL Server 2005 přichází s novou funkčností tzv. Service Brokera - komplexní message-based komunikační platformy, s frontami a dalšími prvky potřebnými pro stavbu robustních SOA aplikací (Service Oriented Architecture).

Podrobněji o Service Brokeru viz SQL Books Online, pro nás je pro tuto chvíli podstatné, že prostřednictvím Service Brokera lze implementovat rozumnou podobu notifikací z SQL serveru do .NET aplikací, kdy jsou .NET aplikace SQL serverem informovány o určitých událostech (např. různých CREATE, DROP, ALTER DDL příkazech), o změnách dat (tzv. query notifications) či jakýchkoliv jiných zprávách, které si sami iniciujeme (např. z trigerů, atp.). Jak dále uvidíme, dotnetovské SqlCacheDependency či SqlDependency nejsou nic jiného, než konkrétním využitím Service Brokera.

Základní principy

Podívejme se nejprve na základní principy práce Service Brokera:

  1. Komunikace prostřednictvím Service Brokera je založena na zprávách (MESSAGE). Každá jednotlivá zpráva představuje příslušnou událost, příkaz, notifikaci, prostě atomickou komunikační jednotku.
  2. Zprávy jsou uchovávány ve frontách (QUEUE). Fronta je určitým bufferem mezi odesílatelem a příjemcem zprávy.
  3. Formální požadavky, které musí zpráva splňovat (např. XML schema), určuje typ zprávy (MESSAGE TYPE).
  4. Zprávy se posílají v konverzaci (DIALOG CONVERSATION) mezi dvěma endpointy, které představují služby (SERVICE). Cesty Mezi různými instancemi SQL serveru lze zprávy předávat pomocí routů (ROUTE).
  5. Formální požadavky na konverzaci/dialog určuje CONTRACT, který mj. určuje typ zpráv vyměňovaných mezi službami a směr, kterým se posílají.
  6. Zpráva může mimo explicitní konverzace vzniknout např. i událostí (EVENT) na straně SQL Serveru (např. DDL události CREATE, ALTER, DROP, nebo trace události, jak je známe z Profileru), nebo prostřednictvím sledování aktualizací dat (Query Notification), kdy SQL Server sleduje výsledky určitého SQL dotazu a oznámí jejich změnu (klasicky využíváno pro expiraci datové cache ASP.NET).
  7. Podstatné je, že Service Broker sám neinicializuje komunikaci mimo SQL Server, do .NET aplikace, naopak .NET aplikace si musí zprávu sama vyzvednout z fronty (RECEIVE), resp. může využít konstrukce WAITFOR, čímž v případě prázdné fronty pasivně vyčkává, než se zpráva objeví.

Výše uvedené není ani zdaleka vyčerpávajícím popisem fungování Service Brokeru, pro naše účely však postačuje jako úvod do problematiky.

Pro dále popisovanou funkčnost je potřeba mít Service Brokera na databázi zapnutého:

ALTER DATABASE AdventureWorks SET ENABLE_BROKER

Query Notifications - SqlNotificationRequest, SqlDependency, SqlCacheDependency

Nejběžnějším způsobem využití Service Brokera v .NET aplikaci jsou Query Notification Services - novinka SQL Serveru 2005 spočívají v možnosti vyžádat (subscribe) sledování výsledku určitého SQL dotazu (query). V případě změny pak dojde k vytvoření příslušné zprávy (message), jejímu uložení do fronty (queue), odkud si ji z naší .NET aplikace vyzvedneme a o aktualizaci na straně SQL Serveru se tak dozvíme (a může tak například dojít k vyřazení určitých dat z cache atp.).

Základní principy Query Notifications
  1. Query Notifications nelze aktivovat z T-SQL, ani CLR kódu hostovaného v SQL Serveru. Query Notifications lze aktivovat pouze prostřednictvím klientské aplikace, v případě .NET Frameworku prostřednictvím třídy SqlNotificationRequest (vlastnost SqlCommand.Notification).
  2. SqlNotificationRequest je svázán s příkazem (SqlCommand), kterému je nastaven a teprve vykonáním tohoto příkazu se provede příslušná subscription QN na straně SQL serveru.
  3. SQL Server sleduje pouze první změnu výsledku dotazu, tím QN končí a případný další notification request je potřeba iniciovat novým vykonáním příkazu.
  4. V žádném případě se nejedná o komunikaci iniciovanou SQL Serverem, nýbrž si musíme z příslušné fronty sami zprávu o aktualizaci vyzvednout, resp. si obsah fronty průběžně hlídat.
  5. V případě SqlDependency a SqlCacheDependency za nás vyzvednutí zprávy z příslušné fronty Service Brokera zajišťuje samotný .NET Framework prostřednictvím opakovaného volání příkazu WAITFOR (RECEIVE ...) TIMEOUT v kontinuálně otevřené samostatné SqlConnection ze samostatného threadu naší aplikace. Přesvědčit se o tom můžete v SQL Profileru.
Ilustrační příklad SqlDependency:

    class Program
    {
        static void Main(string[] args)
        {
            const string connString = "Server=localhost;Database=AdventureWorks;Integrated Security=true;";
            using (SqlConnection conn = new SqlConnection(connString))
            {
                conn.Open();

                SqlCommand cmd = new SqlCommand("SELECT Bonus FROM Sales.SalesPerson");
                cmd.Connection = conn;

                SqlDependency dependency = new SqlDependency(cmd);
                dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
                SqlDependency.Start(connString);

                cmd.ExecuteNonQuery();

                Console.ReadLine();
            }

        }

        static void dependency_OnChange(object sender, SqlNotificationEventArgs e)
        {
            Console.WriteLine("Bonus changed...");
        }
    }

SqlNotificationRequest

Pokud nám nestačí vyšší programátoská abstrakce SqlDependency či SqlCacheDependency a potřebujeme řídit Query Notifications podrobněji, můžeme využít třídu SqlNotificationRequest, resp. instanční vlastnost SqlCommand.Notification, kam instanci třídy SqlNotificationRequest přiřadíme a zajistíme tak subscribování daného příkazu do Query Notifications Service. Na rozdíl od SqlDependency pak ale musíme sami zajistit vyzvedávání zpráv z příslušné fronty a jejich zpracování (viz níže).

Požadavky na sledovaný SQL dotaz

Dotaz, ke kterému chceme QueryNotification aktivovat, musí splňovat určité nemalé restrikce:

  • jména tabulek musí být uváděna včetně schématu, tedy Sales.SalesPerson, dbo.MojeTabulka, atp.
  • nelze použít SELECT *, vždy musíme udělat výčet sloupců
  • nelze použít agregační funkce
  • nelze používat subqueries, outer-joins, self-joins

Podrobný výčet omezení viz např. http://msdn2.microsoft.com/en-US/library/ms181122.aspx.

Event Notifications

Dalším typem zpráv, které můžeme od SQL Serveru prostřednictvím Service Brokera odebírat, jsou tzv. Event Notifications. V zásadě se jedná o DDL a trace události na straně serveru, ať už na úrovni databáze nebo serveru jako celku. Např. CREATE_DATABASE, ALTER_PROCEDURE, DROP_ASSEMBLY, Audit_Login, SP_Recompile, atp. atp. Události jsou hiearchicky uspořádány a lze se přihlásit i k odběru celé skupiny najednou.

Na rozdíl od Query Notifications, kdy se nám příslušné prvky Service Brokera vytvářely převážně automaticky, zde již musíme provést základní inicializaci ručně. Na rozdíl od Query Notifications se Event Notifications celé řídí prostřednictvím T-SQL příkazů. Jednoduchý příklad by mohl vypadat nějak takto:

CREATE QUEUE MyNotifyQueue;

CREATE SERVICE MyNotifyService
    ON QUEUE MyNotifyQueue
    (
    [http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]
    )


CREATE ROUTE MyNotifyRoute WITH SERVICE_NAME = 'MyNotifyService', ADDRESS = 'LOCAL';

CREATE EVENT NOTIFICATION Notify_AlterProcedure
    ON DATABASE
    FOR ALTER_PROCEDURE
    TO SERVICE 'MyNotifyService', 'current database'
MESSAGE TYPE pro události je již v systému pod názvem PostEventNotification připraven, vytvoříme tedy jen frontu (CREATE QUEUE), službu (CREATE SERVICE), routu (CREATE ROUTE) a pak už samotnou event-notifikace (CREATE EVENT NOTIFICATION). Tím je na straně SQL Serveru vše připraveno a nyní už můžeme jen vyzvedávat zprávy na straně .NET aplikace:

    class Program
    {
        static bool done = false;

        static void Main(string[] args)
        {
            Thread t = new Thread(ReceiveEvent);
            t.Start();
            
            while (!done)
            {
                Thread.Sleep(1000);
                Console.Write(".");
            }

            Console.ReadLine();
        }

        static void ReceiveEvent()
        {
            const string connString = "Server=localhost;Database=AdventureWorks;Integrated Security=true;";

            using (SqlConnection conn = new SqlConnection(connString))
            {
                conn.Open();

                SqlCommand cmd = new SqlCommand();
                cmd.Connection = conn;
                cmd.CommandText = "WAITFOR (RECEIVE TOP(1) * FROM MyNotifyQueue), TIMEOUT @Timeout";
                cmd.Parameters.AddWithValue("@Timeout", 60000); // 60s

                do
                {
                    SqlDataReader reader = cmd.ExecuteReader();
                    if (reader.Read())
                    {
                        OnEvent();
                    }
                    done = true;
                }
                while (!done);
            }
        }

        static void OnEvent()
        {
            Console.WriteLine("Event received...");
        }
    }
Obdobně jako interní implementace SqlDependency zde v samostatném threadu provádíme průběžný polling WAITFOR(RECEIVE ...) s příslušným timeoutem, zatímco v hlavním vlákně běží aplikace dál (obsluha UI, atp.). Receiving-vláknu bychom také měli nastavit vlastnost IsBackground na true, aby při skončení hlavního vlákna došlo k ukončení tohoto pollingu.

Vlastní zprávy odesíláné z SQL Serveru (např. z triggerů)

Dalším zdrojem zpráv předávaných prostřednictvím Service Brokera mohou být i zprávy, které sami vytvoříme, jejichž odeslání sami inicializujeme - např. z různých triggerů (DML i DDL), nebo uložených procedur.

Pro tuto chvíli přesahuje podrobný popis zamýšlený rozsah tohoto článku, nicméně jak je už z výše uvedeného zřejmé, nejde opět o nic jiného než o inicializaci jednotlivých prvků Service Brokera (vše T-SQL) a dále posílání zpráv prostřednictvím T-SQL příkazů BEGIN DIALOG a SEND. Samotná .NET aplikace by pak byla stejný princip, jak u Event Notifikacions, jen formát zpráv se bude lišit podle toho, jak si ho zadefinujeme (zprávy mohou být i prázdné, nebo plain-text).

Mnohé příklady odesílání zpráv přes Service Brokera viz SQL Books Online. Možná tento článek rozšířím někdy později.

Published 4. prosince 2006 22:47 by Robert Haken
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Mifko said:

Velmi pekny clanok, asi bude stat za to pozriet sa trochu na Service Broker. Dalsi clanok by isto potesil aj inych...

prosince 5, 2006 10:34
 

Martin said:

Zamysleme se nad tím, jestli to není příliš složité. Vždyť by stačilo do své aplikace přidat tabulku do ní sloupce s názvem hlídané tabulky a nějaký timestamp. V příslušných triggrech pak měnit (nejlépe pomocí SP) tuto tabulku no a v aplikaci v theadu hlídat hodnotu v timestamp. Ne, oni místo toho rozšíří syntaxi a funkcionalitu takovýmto způsobem.

Je jasné, že SQL nemůže samo "obvolávat" klienty a říkat jim, že se něco změnilo a tak to pořád zůstane na klientovi aby se paralelně dotazoval.

Když se tato funkcionalita měla objevit, čekal jsem, že MS upraví komunikační protokol tak, že server bude registrovat požadavky na notifikaci u jednotlivých spojení a v případě, že událost nastane je automaticky posílal klientovi. Tedy že zaimplementuje něco co je ještě "níže, hlouběji" než triggery.

prosince 8, 2006 7:09
 

Robert Haken said:

Ono když pomineme všechny ostatní výhody Service Brokera, tak přinejmenším ta konstrukce WAITFOR (RECEIVE ...) má klíčový význam. Místo abychom se totiž z našeho hlídacího threadu stále dokola dotazovali na sledovací tabulku, generovali nové a nové SqlCommandy, zatěžovali spojení i SQL Server, atd., tak tenhle WAITFOR nám umožní blokovat sledovací thread do okamžiku, než se nějaký message objeví.

prosince 8, 2006 8:46
 

Radek Hornof said:

Ahoj, vyzkousel jsem SqlDependency presne dle kodu v clanku //dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);//

bohužel se mě dependency_OnChange vyvolá ihned po spuštění kódu aniž by došlo ke změně v tabulce.

V Event logu aplikace, systému ani SQL není žádná chyba.

v SQL Activity Monitoru je záznam pro SqlQueryNotificationServise - status suspend, Command Delete.

V pohledu sys.dm_broker_queue_monitors je záznam 15 1701581100 RECEIVES_OCCURRING 2007-03-13 13:33:21.547 2007-03-13 13:30:21.450 1

Pokud máte někdo nápad prosím o radu

Děkuji

Radek Hornof

FW2.0

MSSQL2005 edice developer,express

března 13, 2007 14:52
 

David Kopelent said:

Jen malickost. Pro zapnutí brokera použijte např. ALTER DATABASE AdventureWorks SET ENABLE_BROKER . V textu je překlep, což nikterak neovlivní velmi dobrý článek.

srpna 31, 2007 12:25
 

Robert Haken said:

Opraveno, díky za upozornění. Bylo tam ENEBLE_BROKER.

srpna 31, 2007 12:32

What do you think?

(required) 
(optional)
(required) 
Enter the code you see below

Submit