如何编写消耗MSMQ的事务性多线程WCF服务

时间:2009-05-30 04:37:07

标签: wcf msmq

我有一个WCF服务,它将消息发布到私有的非事务性MSMQ队列。我有另一个WCF服务(多线程),它处理MSMQ消息并将它们插入数据库。

我的问题在于测序。我希望消息按特定顺序排列。例如,MSG-A需要在插入MSG-B之前转到数据库。因此,从数据库角度来看,我目前的解决方案非常粗糙和昂贵。

我正在阅读该消息,如果它的MSG-B并且数据库中没有MSG-A,我会将其丢回消息队列,并且我一直这样做,直到MSG-A插入到数据库中。但这是一个非常昂贵的操作,因为它涉及表扫描(SELECT stmt)。

消息始终按顺序发布到队列中。

没有使我的WCF队列处理服务单线程(通过将服务行为属性InstanceContextMode设置为Single),有人可以提出更好的解决方案吗?

由于

2 个答案:

答案 0 :(得分:1)

在将消息从队列中取出后,不要立即将消息推送到数据库,而是在内存中保留待处理消息的列表。当您获得A或B时,请检查匹配的是否在列表中。如果是这样,请将它们(按正确的顺序)提交给数据库,并从列表中删除匹配的一个。否则,只需将新消息添加到该列表即可。

如果检查匹配是一个太昂贵的序列化任务 - 我认为你是多线程的原因 - 你可以让另一个线程处理列表。读取现有的多个线程,立即将大多数消息提交给DB,但将As和Bs放在(threadsafe)列表中。后台线程通过该列表查找匹配的As和Bs,当它找到它们时,它以正确的顺序提交它们(并从列表中删除它们)。

底线是 - 由于您从具有多个线程的队列中删除项目,因此您必须在某处进行序列化,以确保排序。诀窍是尽量减少锁定在串行代码中的次数和时间长度。

在检测到这种情况时,您可以在数据库级别使用触发器或其他东西来重新排序条目。我担心我对DB编程的了解不足以帮助那里。

更新:假设消息包含一些id,允许您将消息“A”与正确的关联消息“B”相关联,以下代码将确保A在B之前进入数据库。请注意,它不能确定它们是数据库中的相邻记录 - 在A和B之间可能还有其他消息。另外,如果由于某种原因你得到A或B而没有收到其他类型的匹配消息,这个代码会泄漏内存,因为它挂起来永远无法比拟的消息。

(你可以将这两个'锁定的块'提取到一个子程序中,但为了清楚起见,我将它留下来,就A和B而言。)

static private object dictionaryLock = new object();
static private Dictionary<int, MyMessage> receivedA = 
    new Dictionary<int, MyMessage>();
static private Dictionary<int, MyMessage> receivedB = 
    new Dictionary<int, MyMessage>();

public void MessageHandler(MyMessage message)
{
    MyMessage matchingMessage = null;
    if (IsA(message))
    {
        InsertIntoDB(message);
        lock (dictionaryLock)
        {
            if (receivedB.TryGetValue(message.id, out matchingMessage))
            {
                receivedB.Remove(message.id);
            }
            else
            {
                receivedA.Add(message.id, message);
            }
        }
        if (matchingMessage != null)
        {
            InsertIntoDB(matchingMessage);
        }
    }
    else if (IsB(message))
    {
        lock (dictionaryLock)
        {
            if (receivedA.TryGetValue(message.id, out matchingMessage))
            {
                receivedA.Remove(message.id);
            }
            else
            {
                receivedB.Add(message.id, message);
            }
        }
        if (matchingMessage != null)
        {
            InsertIntoDB(message);
        }
    }
    else
    {
        // not A or B, do whatever
    }
}

答案 1 :(得分:1)

如果您是这些队列的唯一客户端,您可以非常轻松地将时间戳添加为邮件头(请参阅IDesign示例)并将数据库中的已发送字段(有点像Outlook消息)保存在数据库中同样。您可以按发送顺序处理它们(基本上您在消费时移动排序逻辑)。

希望这有帮助, 阿德里安