• Service Broker实现发布订阅(PublishSubscribe)框架


    ervice Broker 实现一套完整的发布-订阅方案,其中author 发送Service Broker Message(又称article)到发布者(Publisher)。发布者负责分发消息给不同的订阅者(Subscriber)。每一个订阅者通过订 阅来接受特定的消息。
     
    下图描述了这一个发布-订阅方案:

    下面看看如何通过Service Broker功能来实现这一方案。
     
    定义基础架构
    实现author、发布者和不同的订阅者作为Service Broker服务。AuthorService通过与PublisherService开始一个新的会话(conversation)发送article 消息。在AuthorService发送一个实际的article消息之前,它需要事先通知PublisherService在会话中article的主 体(Subject)。
    订阅者也是Service Broker服务,它将与PublisherService进行会话。订阅者首先发送请求感兴趣的主题,随后接受所有关于这一主题的已发布的 article消息。因为这些要求,PublisherService接口需要支持如下2个contracts:
    l         author服务发送 article消息的contract
    l         订阅者订阅主题和接收已发 布article消息的contract
     
    下面定义了相关的Service Broker对象。
    消息类型Message Type
    CREATE MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/PublishMessage]
    VALIDATION = WELL_FORMED_XML;
    GO
    CREATE MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage]
    VALIDATION = NONE;
    GO
    CREATE MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage]
    VALIDATION = WELL_FORMED_XML;
    GO
     
    契约Contract
    CREATE CONTRACT [http://ssb.csharp.at/SSB_Book/c10/PublishContract]
    (
    [http://ssb.csharp.at/SSB_Book/c10/PublishMessage] SENT BY INITIATOR,
    [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage] SENT BY INITIATOR
    )
    GO
    CREATE CONTRACT [http://ssb.csharp.at/SSB_Book/c10/SubscribeContract]
    (
    [http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage] SENT BY INITIATOR,
    [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage] SENT BY TARGET
    )
    GO
     
    队列 QUEUE 服务 SERVICE
    发布者队列及其服务
    CREATE QUEUE [PublisherQueue]
    GO
    CREATE SERVICE [PublisherService] ON QUEUE [PublisherQueue]
    (
    [http://ssb.csharp.at/SSB_Book/c10/PublishContract],
    [http://ssb.csharp.at/SSB_Book/c10/SubscribeContract]
    )
    GO
     
    订阅者队列及其服务
    CREATE QUEUE SubscriberQueue1;
    GO
    CREATE SERVICE SubscriberService1 ON QUEUE SubscriberQueue1;
    GO
    CREATE QUEUE SubscriberQueue2;
    GO
    CREATE SERVICE SubscriberService2 ON QUEUE SubscriberQueue2;
    GO
     
    Author队列及其服务
    CREATE QUEUE AuthorQueue;
    GO
    CREATE SERVICE AuthorService ON QUEUE AuthorQueue;
    GO
    在创建好上述Service Broker基础架构后,下面创建2个表,分别用来存储发布和订阅记录。针对本示例,我们分别创建如下2个表:Publications和 Subscriptions。(EntLib.com译 者注:Publications 表需要分别在Author和Publisher端创建;Subscriptions表需要分别在Publisher和Subscriber端创建。)
     
    CREATE TABLE Publications
    (
    Publication UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
    Subject NVARCHAR(MAX) NOT NULL,
    OriginalXml XML NOT NULL
    )
    GO
    CREATE TABLE Subscriptions
    (
    Subscriber UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
    Subject NVARCHAR(MAX) NOT NULL,
    OriginalXml XML NOT NULL
    )
    GO
     
    通过这2个表,我们可以匹配来自AuthorService的article消息和订阅者。通过Subject列来连接这2个表,后面的“发布 者逻辑”中具体介绍。下面看看PublisherService端运行的服务程序。


    应用发布者逻辑

    在存储过程sp_PublisherService 中实现PublisherService服务的入口点(entry point)。当一有新的消息到达PublisherQueue队列是,sp_PublisherService存储过程自动激活,并开始处理消息。这一 存储过程能够处理如下消息类型:
    [http://ssb.csharp.at/SSB_Book/c10/PublishMessage]: 在存储过程开始发布article消息时,它需要从AuthorService接收该消息类型。这一消息包含有如下article消息相同的主题 (Subject)。
    [http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage]: 在存储过程准备订阅一个主题时,它需要从订阅者服务方接收该消息类型。这一消息包含有订阅者请求的主题。
    [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage]: 在存储过程发布article消息时,它会从AuthorService方接收这一消息类型。
    [http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog]: 在存储过程准备关闭PublisherService打开的会话时,存储过程会从AuthorService或订阅方服务接收这一消息类型。
    [http://schemas.microsoft.com/SQL/ServiceBroker/Error]: 在请求的主题不存在是,存储过程将从PublisherService接收这一消息类型。
     
    下面的script显示sp_PublisherService存储过程如何处理这些消息类型。
    sp_PublisherService 服务处理程序:
    CREATE PROCEDURE sp_PublisherService
    AS
    BEGIN
    DECLARE @Conversation UNIQUEIDENTIFIER;
    DECLARE @Message VARBINARY(MAX);
    DECLARE @MessageTypeName SYSNAME;
    BEGIN TRANSACTION;
    WAITFOR
    (
    RECEIVE TOP(1)
    @Conversation = conversation_handle,
    @Message = message_body,
    @MessageTypeName = message_type_name
    FROM PublisherQueue
    ), TIMEOUT 1000;
     
    WHILE (@Conversation IS NOT NULL)
    BEGIN
    IF (@MessageTypeName = 'http://ssb.csharp.at/SSB_Book/c10/PublishMessage')
    BEGIN
    EXEC sp_ProcessPublicationRequest @Conversation, @Message;
    END
    ELSE IF (@MessageTypeName ='http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage')
    BEGIN
    EXEC sp_ProcessSubscriptionRequest @Conversation, @Message;
    END
    ELSE IF (@MessageTypeName ='http://ssb.csharp.at/SSB_Book/c10/ArticleMessage')
    BEGIN
    EXEC sp_SendOnPublication @Conversation, @Message;
    END
    ELSE IF (@MessageTypeName IN (
    N'http://schemas.microsoft.com/SQL/ServiceBroker/Error',
    N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'))
    BEGIN
    END CONVERSATION @Conversation;
    IF (EXISTS (SELECT * FROM Publications WHERE Publication = @Conversation))
    BEGIN
    EXEC sp_RemovePublication @Conversation;
    END
    IF (EXISTS (SELECT * FROM Subscribers))
    BEGIN
    EXEC sp_RemoveSubscriber @Conversation;
    END
    END
    ELSE
    BEGIN
    -- Unexpected message
    RAISERROR (N'Received unexpected message type: %s', 16, 1,
    @MessageTypeName);
    ROLLBACK;
    RETURN;
    END
    COMMIT;
    SELECT @Conversation = NULL;
    BEGIN TRANSACTION;
    WAITFOR
    (
    RECEIVE TOP(1)
    @Conversation = conversation_handle,
    @Message = message_body,
    @MessageTypeName = message_type_name
    FROM PublisherQueue
    ), TIMEOUT 1000;
    END
    COMMIT;
    END
    GO
     
    在上面的存储过程中,先从PublisherQueue队列中接收一个新的消息。如下消息类型为[http://ssb.csharp.at /SSB_Book/c10/PublishMessage],就调用sp_ProcessPublicationRequest 存储过程,将接收到的发布数据记录到Publications表中。如果为[http://ssb.csharp.at/SSB_Book/c10 /SubscribeMessage] 消息类型,则调用sp_ProcessSubscriptionRequest 存储过程,负责将接收到的订阅数据记录到Subscriptions表中。最后,如果为 [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage]消息类型,则 sp_PublisherService存储过程负责调用sp_SendOnPublication 分发该消息给所有匹配的订阅者。
     
    通过sp_ProcessPublicationRequest 和sp_ProcessSubscriptionRequest 存储过程来管理发布和订阅记录。这2个存储过程分别调用其他的存储过程,将接收到的消息插入到Publications 或 Subscriptions 表中。如下显示 the sp_ProcessPublicationRequest 存储过程。因为sp_ProcessSubscriptionRequest 存储过程比较相似,这里忽略该存储过程。
     
    sp_ProcessPublicationRequest 存储过程脚本:
    CREATE PROCEDURE sp_ProcessPublicationRequest
    @Conversation UNIQUEIDENTIFIER,
    @Message VARBINARY(MAX)
    AS
    BEGIN
    DECLARE @Request XML;
    DECLARE @Subject NVARCHAR(MAX);
    SELECT @Request = CAST(@Message AS XML);
    WITH XMLNAMESPACES (DEFAULT 'http://ssb.csharp.at/SSB_Book/c10/PublishSubscribe')
    SELECT @Subject = @Request.value(N'(//Publish/Subject)[1]', N'NVARCHAR(MAX)');
    IF (@Subject IS NOT NULL)
    BEGIN
    EXEC sp_PublishPublication @Conversation, @Subject, @Message;
    END
    ELSE
    BEGIN
    END CONVERSATION @Conversation
    WITH ERROR = 1
    DESCRIPTION = N'The publication is missing a subject';
    EXEC sp_RemovePublication @Conversation;
    END
    END
    GO
     
    sp_ProcessPublicationRequest存储过程调用sp_PublishPublication,并传入 @Conversation, @Subject, @Message 三个参数。如下是sp_PublishPublication 存储过程。
    sp_PublishPublication 存储过程脚本:
    CREATE PROCEDURE sp_PublishPublication
    @Publication UNIQUEIDENTIFIER,
    @Subject NVARCHAR(MAX),
    @OriginalXml XML
    AS
    BEGIN
    INSERT INTO Publications (Publication, Subject, OriginalXml)
    VALUES
    (
    @Publication,
    @Subject,
    @OriginalXml
    )
    END
    GO
     
    Publications 表中的Publication列和Subscriptions表中的Subscription 列都存放会话ID,你需要这些会话ID来发送article消息到订阅方。最后一个存储过程是sp_SendOnPublication,在有 [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage] 消息从 AuthorService接收到后,调用才存储过程。
    sp_SendOnPublication 存储过程脚本:
    CREATE PROCEDURE sp_SendOnPublication
    @Publication UNIQUEIDENTIFIER,
    @Article VARBINARY(MAX)
    AS
    BEGIN
    DECLARE @Subscription UNIQUEIDENTIFIER;
    DECLARE @cursorSubscriptions CURSOR;
    SET @cursorSubscriptions = CURSOR LOCAL SCROLL FOR
    SELECT Subscriber
    FROM Subscriptions s
    JOIN Publications p ON s.Subject = p.Subject
    WHERE p.Publication = @Publication;
    BEGIN TRANSACTION;
    OPEN @cursorSubscriptions;
    FETCH NEXT FROM @cursorSubscriptions
    INTO @Subscription;
    WHILE (@@fetch_status = 0)
    BEGIN
     
    IF (@Article IS NOT NULL)
    BEGIN
    SEND ON CONVERSATION @Subscription
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage] (@Article);
    END
    ELSE
    BEGIN
    SEND ON CONVERSATION @Subscription
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage];
    END
    FETCH NEXT FROM @cursorSubscriptions INTO @Subscription;
    END
    CLOSE @cursorSubscriptions;
    DEALLOCATE @cursorSubscriptions;
    COMMIT;
    END
    GO
     
    sp_SendOnPublication 存储过程使用一个cusor,发送从AuthorService接收到的article消息,给匹配的订阅者。
    SEND ON CONVERSATION @Subscription
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage] (@Article);
     
    通过对Publications 和 Subscriptions 表的Subject 字段进行连接,进行匹配:
    SELECT Subscriber
    FROM Subscriptions s
    JOIN Publications p ON s.Subject = p.Subject
    WHERE p.Publication = @Publication;
     
    当接收到的是[http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog] 消息类型时(从AuthorService或从订阅者服务方),相应的发布或订阅数据则需要从Publications或Subscriptions表中 删除。分别通过sp_RemovePublication 或 sp_RemoveSubscriptions 存储过程来实现。
    sp_RemovePublication 存储过程脚本:
    CREATE PROCEDURE sp_RemovePublication
    @Publication UNIQUEIDENTIFIER
    AS
    BEGIN
    DELETE FROM Publications
    WHERE Publication = @Publication
    END
    GO

    发布信息Publishing Information

    这一节演示订阅者通过订阅从PublisherService服务接收信息,已经AuthorService如何发送新的article消息到 PublisherService服务进行分发。在订阅者可以从PublisherService接收新的article消息之前,它必须请求一个订阅。 可以通过发送一个 [http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage] 消息来实现,代码如下:
     
    订阅方请求订阅脚本:
    DECLARE @ch UNIQUEIDENTIFIER;
    BEGIN DIALOG CONVERSATION @ch
    FROM SERVICE [SubscriberService1]
    TO SERVICE 'PublisherService'
    ON CONTRACT [http://ssb.csharp.at/SSB_Book/c10/SubscribeContract]
    WITH ENCRYPTION = OFF;
    SEND ON CONVERSATION @ch
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/SubscribeMessage]
    (
    N'<?xml version="1.0"?>
    <Request xmlns="http://ssb.csharp.at/SSB_Book/c10/PublishSubscribe">
    <Subject>Subject1</Subject>
    </Request>'
    );
    GO
     
    在上述脚本中,你必须在发送的消息中指定消息主题,随后会根据主题从PublisherService 中获取发布数据。在订阅者一设置好订阅,AuthorService就可以发送article消息给PublisherService进行分发。示例代码 如下:
     
    [AuthorService] 发送PublishMessage消 息:
    DECLARE @ch UNIQUEIDENTIFIER;
    BEGIN DIALOG CONVERSATION @ch
    FROM SERVICE [AuthorService]
    TO SERVICE 'PublisherService'
    ON CONTRACT [http://ssb.csharp.at/SSB_Book/c10/PublishContract]
    WITH ENCRYPTION = OFF;
    SEND ON CONVERSATION @ch
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/PublishMessage]
    (
    N'<?xml version="1.0"?>
    <Publish xmlns="http://ssb.csharp.at/SSB_Book/c10/PublishSubscribe">
    <Subject>Subject1</Subject>
    </Publish>'
    );
     
    AuthorService必须指定随后的article消息属于哪一主题,它通过发送[http://ssb.csharp.at /SSB_Book/c10/PublishMessage] 消息类型,通知PublisherService 记录相应的发布记录。最后,AuthorService 发送属于前面指定主题的、不同的article消息。
     
    发送特定主题的article消息:
    SEND ON CONVERSATION @ch
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage]
    (
    N'This is an article on Subject1'
    );
    SEND ON CONVERSATION @ch
    MESSAGE TYPE [http://ssb.csharp.at/SSB_Book/c10/ArticleMessage]
    (
    N'And this is another article on Subject1'
    );
    GO
     
    在AuthorService 一发送article消息到PublisherService,这些消息将通过sp_PublisherService存储过程自动转发给订阅者。现在, 你可以查询订阅者服务对应的队列,⑾址⑺偷腶rticle消息成功转发过来了。
     
    备注:因为Service Broker内在的异步处理特性,在你查看SubscriberQueue1和SubscriberQueue2队列之前,你需要等待几秒钟,直到这些消 息已经处理完成。
     
  • 相关阅读:
    Linux之find命令
    Android WebView如何加载assets下的html文件
    Android 静默安装
    Android listview下拉刷新 SwipeRefreshLayout
    AndroidManifest.xml 详解
    Android 查看内存使用状况
    Android invalidate() 和 postInvalidate()的区别
    Android动画之Interpolator和AnimationSet
    实现Fragment的切换和ViewPager自动循环设置切换时间
    android 实现橡皮擦效果以及保存涂鸦的功能
  • 原文地址:https://www.cnblogs.com/goody9807/p/1754185.html
Copyright © 2020-2023  润新知