在Service Bus Queue 和Topics/Subscriptions中,发送的消息内容都需要封装成BrokeredMessage对象,接收方收到的消息也是BrokeredMessage对象实例。所以,充分理解BrokeredMessage对象的使用是深入Service Bus brokered messaging服务的前提。这篇随笔将从BrokeredMessage的结构、如何创建、属性以及方法的使用全面介绍一下BrokeredMessage。
BrokeredMessage结构
如图所示,一个消息包含消息头和消息体,整个消息的容量上限是256K。
消息头
消息头包含了一些固定的属性,比如MessageId,SessionId等,以及一个键值对用来管理自定义的属性。消息头的大小不能超过64K。
消息体
消息体包含了需要传输的序列化的数据,可以是一个业务对象,或者是数据流,比如FileStream,也可以为空。
创建BrokeredMessage对象
构造方法
Name | Description |
BrokeredMessage() | 创建BrokeredMessage的新实例 |
BrokeredMessage(Object) | 创建BrokeredMessage的新实例,传入对象默认使用DataContractSerializer 与XmlDictionaryWriter binary编码进行序列化(这种序列化方式为WCF标准的序列化方式,Service Bus其实与WCF有着千丝万缕的联系,并且整个Windows Azure的实现中WCF都无处不在,以后我们会看到这一点),序列化后的数据流作为消息体存储并传输。 |
BrokeredMessage(Stream, Boolean) | 创建BrokeredMessage的新实例,传入参数Stream作为消息体进行传输。 |
BrokeredMessage(Object, XmlObjectSerializer) | 创建BrokeredMessage的新实例,并使用提供的序列化器对传入对象进行序列化。 |
举两个例子:
使用对象构造消息:
1: IssueData issueData = new IssueData()
2: {
3: IssueID = 1,
4: IssueTitle = "Title"
5: };
6: BrokeredMessage message = new BrokeredMessage(issueData);
接收消息:
1: BrokeredMessage message = myQueueClient.Receive(TimeSpan.FromSeconds(1));
2: if (message != null)
3: {
4: IssueData issueData = message.GetBody<IssueData>();
5: message.Complete();
6: }
使用Stream构造消息:
1: FileStream photo = new FileStream(@"E:\image027.png", FileMode.Open);
2: BrokeredMessage msg = new BrokeredMessage(photo, true);
3: myQueueClient.Send(msg);
接收消息:
1: BrokeredMessage message = myQueueClient.Receive(TimeSpan.FromSeconds(1));
2: if (message != null)
3: {
4: try
5: {
6: Stream fs = message.GetBody<Stream>();
7: message.Complete();
8: }
9: catch
10: {
11: message.Abandon();
12: }
13: }
BrokeredMessage常用方法
Name | Description |
Abandon | 当消息接收模式是PeekLock时,调用此方法将解锁消息,解锁后消息重新回到Queue或Subscription中,其他客户端可以接收该消息。 |
Complete | 当消息被接收方成功处理以后,调用此方法将消息从队列中删除,其他客户端无法获取该消息。 |
DeadLetter | 调用DeadLetter方法将消息放置到DeadLetterQueue中。在使用PeekLock模式接收消息,并且消息没有被Abandon或是Complete,此方法可调用。后期将有关于DeadLetter的具体介绍,请关注我的随笔。 |
Defer | 当消息接收模式是PeekLock时,调用此方法将终止消息的处理,并且将其“挂起”,随后通过消息的SequenceNumber再将defered的消息取回并进行处理。通过Defer方法可以调整消息处理的优先级,比如餐厅,对于外卖服务和送餐服务,一般情况下外卖比送餐具有更高的优先级,这会可以只处理外卖服务消息,同时将收到的送餐消息Defer,Defer之前需要将消息的SequenceNumber存储,等外卖服务消息处理完成以后,再根据SequenceNumber将之前Defered的送餐服务消息取回,并进行处理,保证了外卖服务的优先处理。 |
GetBody<T> | 调用此泛型方法将获得消息体,消息体反序列化成T类型对象输出。 |
BrokeredMessage常用属性
BrokeredMessage类提供了一系列的属性用于在应用中实现特定的消息处理逻辑。所有的属性存储在消息头部,并且不需要经过序列化。如果数据量小的话,我们可以考虑将数据存储在属性键值对中(BrokeredMessage.Properties),因不需要序列化,这么做可以改善性能。
MessageId
MessageId用来唯一标识一个Message,当创建BrokeredMessage时,MessageId将自动获得一个Guid值,我们可以在消息发送前根据需求修改MessageId的值。
MessageId通常用在检测重复消息机制中,比如,消息内容为订单信息,如果要实现业务规则规定订单号一致的消息不能重复发送,
首先需要在创建Queue的时候将RequiresDuplicateDetection属性设为true(请注意一定是在创建的时候,不支持创建完成后再修改属性)
1: QueueDescription myQueue = null;
2: myQueue = namespaceClient.CreateQueue(new QueueDescription(queueName) { RequiresDuplicateDetection = true });
然后使用订单号作为BrokeredMessage的MessageId,这时同样订单号的消息虽然能发送成功,假设发送了3条消息拥有同样的订单号,但是在接收方只能接收到一条(实际上Queue中也是只包含一条信息,虽然调用QueueClient得Send方法成功,但是实际并没有发送到Queue中),避免了消息的重复处理。
Label
Label属性值为字符串类型,发送方和接收方可以用来实现一些应用层特定的业务逻辑。我们可以理解为Label相当于一个标记,接收方可以根据这个标记来判断消息的特性,从而确定该如何处理消息。
1: // Create a brokered message based on the order.
2: BrokeredMessage orderInMsg = new BrokeredMessage(orderIn);
3: // Use the labelproperty to indicate that this is a test message.
4: orderInMsg.Label = "TestMessage";
Properties
Properties值类型是IDictionary<string, Object>,可以往里头存储一一对应的键值对。在Service Bus Topics/Subscriptions入门这篇文章中,提到了使用Properties对发送至Subscription的消息进行过滤。另外,如果使用简单业务对象作为消息内容,且数据量较小(消息头最大64K),我们也可以通过Properties来存储消息内容,避免对象序列化造成的性能损失。
1: // Create a message from the order.
2: BrokeredMessage orderMsg = new BrokeredMessage(order);
3:
4: // Set message properties from values in the order.
5: orderMsg.Properties.Add("loyalty", order.HasLoyltyCard);
6: orderMsg.Properties.Add("items", order.Items);
7: orderMsg.Properties.Add("value", order.Value);
8: orderMsg.Properties.Add("region", order.Region);
CorrelationId
CorrelationId属性类型为字符串,其同样可以作为Subscription的过滤条件,使得Subscription只接收CorrelationID为某一特定值的消息。
1: // Create a message from the order.
2: BrokeredMessage orderMsg = new BrokeredMessage(order);
3:
4: // Set the CorrelationId to the region.
5: orderMsg.CorrelationId = order.Region
1: string correlationId = "China";
2: CorrelationFilter filter = new CorrelationFilter(correlationId);
3: SubscriptionDescription subscriptionDescription = namespaceClient.CreateSubscription(topicName, subscriptionName, filter);
TimeToLive
TimeToLive属性定义了消息在云端的过期时间,默认消息永不过期,即消息发送到了Queue中,只要没有被删除,总是能被接收方接收。我们可以通过这个属性自定义消息的过期时间。
1: // Create a message from the order.
2: BrokeredMessage orderMsg = new BrokeredMessage(order);
3:
4: // Set the message time to live to 30 days.
5: orderMsg.TimeToLive = TimeSpan.FromDays(30);
SessionId
SessionId属性类型为字符串,当我们需要相同的接收方接收一批消息的时候,可以使用SessionId。
假设有这么一种情况,消息接收方在进行消息处理时并不是一条一条处理的,而是需要10条消息作为一个整体来处理,则如果仍旧每次接收一条消息,则有可能在接收到第5条时,下一条数据被其他接收方获取了,那就破坏了整体性。基于这种情况,我们可以这10条消息赋值相同的SessionId,表明这10条消息属于同一会话,是一个不可分割的整体。接收方通过AcceptMessageSession获取这10条消息的会话(MessageSession),此时会话无法被其他接收方获取,最后接收方通过MessageSession将这10条消息获取下来,并作出统一处理。(请注意如需使用SessionId,需要在创建Queue的时候将RequiresSession属性设置为true。后续将有针对MessageSession更详细介绍文章,欢迎关注)
1: List<PizzaOrder> Orders = OrderManager.GetPendingOrders();
2:
3: string orderBatchId = Guid.NewGuid().ToString();
4:
5: foreach (PizzaOrder order in Orders)
6: {
7: // Create a brokered message based on the order.
8: BrokeredMessage orderMsg = new BrokeredMessage(orderIn);
9:
10: // Set the SessionId.
11: orderMsg.SessionId = orderBatchId;
12:
13: // Send the message.
14: queueClient.Send(orderMsg);
15: }
ScheduledEnqueueTimeUtc
ScheduledEnqueueTimeUtc设置了消息的可见时间,消息发送以后在哪个时刻能够被接收方接收。我想大概的应用场景可以是这样,对于客户的提前预定,比如预定了周日上午十点的金融服务,我们可以开发一个提醒服务,应用程序在收到这个预定时,将提醒消息发送出去,并且告知Service Bus在周日上午九点半才让这个提醒消息可见,这样的话接收方可以提前半小时获得这个提醒,然后为客户的到来做准备。
1: // Create a new pizza order.
2: PizzaOrder orderIn = new PizzaOrder()
3: {
4: Name = "Alan",
5: Pizza = "Hawaiian",
6: Quantity = 1
7: };
8:
9: // Create a brokered message based on the order.
10: BrokeredMessage orderInMsg = new BrokeredMessage(orderIn);
11:
12: // Schedule the order.
13: orderInMsg.ScheduledEnqueueTimeUtc = DateTime.Parse("2013-01-27 09:30");
14:
15: // Send the message to the queue.
16: queueClient.Send(orderInMsg);
Size
Size是一个只读属性,提供的消息的大小,单位是byte。在使用Size的时候,有一点需要特别注意,即在消息发送前,通过Size获取到的仅仅是消息体的大小,而在消息发送之后,通过Size获取到的才是消息头+消息体的大小,即消息的真实大小。