Sql Server Service Broker:如何为简单的队列场景构建对话?
我是一名 Sql Server Service Broker 新手,我正在尝试掌握为(看似)简单的用例设置 Service Broker 的最佳方法:我想创建一个简单的工作队列,其中一个应用程序将工作项放入队列,单独的应用程序从该队列中选取工作项并处理它们。 第一个应用程序不需要从第二个应用程序获取状态消息。 我希望队列位于单个 Sql Server 实例中。
最让我困惑的是对话/对话与这种情况有何关系。 我知道您只能在对话/对话的上下文中发送/接收消息,但由于两个应用程序之间没有来回的聊天,我感觉不知道什么时候是创建新对话的正确时间。 两种极端的选择似乎是:
- 每次我将一个工作项目加入队列时,我都会开始一个新的对话。 因此,每次对话最终都会包含一条消息。
- 在部署时,我手动创建一个无限生命周期的对话。 当需要将工作项目加入队列时,我总是将其作为单个对话的一部分发送。
走这两条路的后果是什么?
另外,在第一种情况下,我似乎需要做一些结束对话,以便 Sql Server 能够在内部清理资源。 对于何时放置这些内容的正确位置有任何指导吗? (或者依靠对话最终超时可能会更好吗?)
I'm a Sql Server Service Broker novice and I'm trying to grasp the best way to set Service Broker up for a (seemingly) simple use case: I want to create a simple work queue, where one application drops work items into the queue, and separate application picks up work items from that queue and processes them. There is no need for the first application to get status messages back from the second. I want the queue to live in a single Sql Server instance.
What most confuses me is how conversations/dialogs relate to this situation. I know you can only send/receive messages in the context of a conversation/dialog, but because there is no back-and-forth chatter between the two applications, I feel lost about when is the correct time to create a new conversation. The two extreme alternatives seem to be:
- Each time I enqueue a work item, I begin a new conversation. So each conversation ends up having exactly one message in it.
- At deployment time, I manually create a single infinite-lifespan conversation. When it's time to enqueue a work item, I always send it as part of that single conversation.
What would the consequences of going either of these routes be?
Also, in the first case, it seems like I need to do some END CONVERSATIONs, in order for Sql Server to be able to clean up resources internally. Is there any guidance for when would be the correct place to put these in? (Or might it potentially be better to rely on the conversations timing out eventually?)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您应该从每个工作项目各自的对话开始。 生产者(发起者)开始对话并发送描述工作项的消息,然后提交。 使用者(目标)接收消息(或被激活),检查有效负载以了解工作项详细信息,执行工作,然后结束对话并提交。 生成的 EndDialog 消息被发送回发起者服务队列,发起者队列上的激活过程通过结束发起者端的对话来响应它。
这是最简单的部署,启动并运行它将确保您拥有坚实的基础。 当生产者将工作项排入队列时,不要偷工减料并在发起者端结束对话,这是 即发即弃,有几个缺点。
如果您有很高的性能要求(每秒超过 200 个请求),那么您必须开始更明确地管理对话。 我有一篇关于出于性能原因重用对话的博客文章。 在接收端,我建议阅读编写 Service Broker 过程。
我还有一个博客条目,几乎可以满足您的需要,尽管它没有安排工作项目,而是启动自定义过程: 异步过程执行。
如果您决定使用激活上下文中的工作项,从而利用激活的良好自我平衡功能,那么您需要 了解发生激活的 EXECUTE AS 上下文。
You should start with each work item on its own conversation. The producer (initiator) begins a dialog and send the message describing the work item, then commits. The consumer (target) receives the message (or gets activated), inspects the payload to understand the work item details, executes the work, then ends the dialog and commit. The resulting EndDialog message gets sent back to the initiator service queue, and an activated procedure on the initiator queue responds to it by ending the dialog on the initiator side.
This is the simplest deployment and getting it up and running will ensure you have a sound foundation to build upon. Don't cut corners and end the dialog on the initiator side from when the producer enqueues the work item, this is fire-and-forget and has several draw backs.
If you have high performance requirements (over 200 requests per second) then you'll have to start managing the conversations more explicitly. I have a blog entry on reusing conversations for performance reasons. On the receive side I recommend reading Writing Service Broker Procedures.
I also have a blog entry that pretty much does what you need, albeit it does not schedule work items but instead launches a custom procedure: Asynchronous procedure execution.
If you decide to consume the work items from an activated context, thus leveraging the nice self balancing capabilities of activation, then you need to understand the EXECUTE AS context under which activation occurs.
我真的很喜欢 Remus 的回答,尽管它并没有特别涉及为什么您可能更喜欢为每个工作项启动单独的对话,而不是将所有工作项放在一个对话中。 与此相关的两个注意事项:
首先,如果您有多个线程/进程处理工作项,则将所有工作项放入单个对话中可能会导致并发问题。 Service Broker 工作进程往往看起来像这样(伪代码):(
通过在工作项成功处理之前不提交,您可以确保,例如,如果您的进程终止,那么它已接收但尚未处理的工作项将不会)
会出现并发问题,因为服务代理程序被编程为每个 RECEIVE 命令都会获取队列中与所接收的消息共享相同会话(或会话组)的所有消息的独占读锁。 该锁将一直保持到事务提交为止。 (请参阅对话组锁定。)因此,如果队列中的所有工作项处于单个对话中,那么当一个工作进程处于“处理工作项”步骤时,没有其他工作进程可以执行任何工作。
将大量项目放入单个对话中的第二个问题是,它会增加您可能丢失或在某些错误情况下必须重新处理的工作项目的数量。 为了正确地描述这一点,我尊重雷姆斯; 请参阅他的回收对话,尤其是“重用单个对话框来发送所有消息 [...] 就像把所有鸡蛋放在一个篮子里。” 您也许能够从其中一些错误情况中恢复,但这可能会给您的代码带来更多复杂性。
可能还有一些反对对所有工作项目使用单一对话的论点,但我对它们不太熟悉。
这并不是说正确的解决方案始终是为每个工作项目启动单独的对话。 不过,在读完莱姆斯的帖子后,他的建议似乎是正确的。 每个对话从一个工作项目开始,然后根据需要增加复杂性。 (但在任何情况下,您都不应该走极端,将所有消息放入一个对话中。)
I really like Remus' answer, though it doesn't especially touch on why you might prefer starting a separate conversation per work item, rather than putting all work items in a single conversation. Two notes related to this:
First, putting all work items into a single conversation will probably cause concurrency problems if you have multiple threads/processes processing work items. Service broker worker processes tend to look like this (in pseudocode):
(By not committing until work items are successfully processed, you ensure, for example, that if your process dies then the work items it has received but not yet processed won't get dropped from the queue.)
The concurrency problem would arise because service broker is programmed such that each RECEIVE command acquires an exclusive read lock on all messages in the queue that share the same conversation (or conversation group) as the ones that were RECEIVEd. That lock is held until the transaction is committed. (See Conversation Group Locks.) So if all work items in the queue are in a single conversation, then while one worker process is in the "process work items" step, no other worker processes can be doing any work.
A second issue with putting lots of items into a single conversation is that it increases the amount of work items you might lose or have to reprocess in certain error conditions. To describe this properly I defer to Remus; see his Recycling Conversations, especially the part that says "reusing a single dialog to send all your messages [...] is like putting all your eggs in one basket." You might be able to recover from some of these error situations, but it will probably introduce more complexity to your code.
There are probably a few more arguments to be made against using a single conversation for all work items, but I'm not as familiar with with them.
This is not to say the correct solution is always to start a separate conversation for each and every work item. After having read through Remus' posts, though, his advice seems sound; start with one work item per conversation, and then add complexity if required. (But probably in no case should you go to the extreme of putting all messages in a single conversation.)