组里最近遇到一个问题,微软的Azure Service Bus Queue是否可靠?是否会出现丢失消息的情况?

  具体缘由如下,

  由于开发的产品是SaaS产品,为防止消息丢失,跨Module消息传递使用的是微软Azure消息队列(Service Bus Queue),但是出现一个问题,一个Module向Queue里发送消息,但另一个Module没有取到该消息。因为消息发送过程中并未有异常。所以大家怀疑,是否Azure Service Bus Queue不可靠,丢失了我们的一些消息

  官方的说法是,99.5%的概率消息不会丢失。

  但我想应该没有那么凑巧,毕竟我们的消息量还在测试阶段,没有那么大,不会那么凑巧碰上。所以索性根据同事的建议,写一个测试程序来确定Service Bus Queue是否会或者容易丢失消息。

  

一. 测试程序简介

  原理:向消息队列(Queue)中发送一定量的消息,看能否全部取到。如可全部取到,则可认为消息队列基本可靠,问题出在我们自己身上。

  过程:

  首先建立一个消息队列(Queue),程序使用Azure .Net SDK实现向Queue发送和接受消息(接收到消息后会调用方法在Queue中删除此消息,删除成功,则视为接收成功)。

  主程序执行后,会启动两个线程,

  线程1负责不断向Queue中发送消息(总量一定,假定共发送10000条,由于SDK中Send方法无返回值告知是否发送成功,如果发送过程中无异常抛出,则视为成功发送)。

  线程2负责不断地从Queue中取消息,取到消息到本地后,即删除在Queue中的此消息。取到消息并成功删除视为成功取到消息,计数器+1。

  日志模块:

  使用Log4net记录日志  

二. 代码实现

  Class ServiceBusQueueHandler负责封装.Net SDK的发送,接收消息。

class ServiceBusQueueHandler
{
public static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public ServiceBusQueueHandler()
{
/* For most scenarios, it is recommended that you keep Mode to Auto.
* This indicates that your application will attempt to use TCP to connect to the Windows Azure Service Bus,
* but will use HTTP if unable to do so. In general, this allows your connection to be more efficient.
* However, if TCP is always unavailable to your application,
* you can save some time on your connection if you globally set the mode to HTTP.*/
ServiceBusEnvironment.SystemConnectivity.Mode = ConnectivityMode.AutoDetect;
} //Send Message
public bool SendMessage(string strMessageBody, QueueClient client, int idelayTime = )
{
//log.Debug("=>SendMessage");
bool bRet = false; try
{
BrokeredMessage message = new BrokeredMessage(strMessageBody); DateTime utcEnqueueTime = DateTime.UtcNow.AddSeconds(idelayTime);
//log.Debug(string.Format("DateTime.UtcNow = {0}", DateTime.UtcNow.ToString()));
//log.Debug(string.Format("EnqueuedTimeUtc = {0}", utcEnqueueTime.ToString())); //set the time when this message will be visiable
message.ScheduledEnqueueTimeUtc = utcEnqueueTime; //http://msdn.microsoft.com/en-us/library/microsoft.servicebus.messaging.queueclient.send.aspx
client.Send(message); log.Debug(string.Format("Success send! Send Time = {0}, Body = {1}", DateTime.UtcNow.ToString(), message.GetBody<string>()));
bRet = true;
}
catch (TimeoutException e)
{
//Thrown when operation times out. Timeout period is initialized through the MessagingFactorySettings may need to increase the value of OperationTimeout to avoid this exception if timeout value is relatively low.
log.Debug(string.Format("TimeoutException: {0}", e.Message));
return bRet;
}
catch (ArgumentException e)
{
//Thrown when the BrokeredMessage is null.
log.Debug(string.Format("ArgumentException: {0}", e.Message));
return bRet;
}
catch (InvalidOperationException e)
{
//Thrown if the message has already been sent by a QueueClient or MessageSender once already.
log.Debug(string.Format("InvalidOperationException: {0}", e.Message));
return bRet;
}
catch (OperationCanceledException e)
{
//Thrown if the client entity has been closed or aborted.
log.Debug(string.Format("OperationCanceledException: {0}", e.Message));
return bRet;
}
catch (UnauthorizedAccessException e)
{
//Thrown if there is an I/O or security error.
log.Debug(string.Format("UnauthorizedAccessException: {0}", e.Message));
return bRet;
}
catch (SerializationException e)
{
//Thrown when an error occurs during serialization or deserialization.
log.Debug(string.Format("SerializationException: {0}", e.Message));
return bRet;
}
catch (MessagingEntityNotFoundException e)
{
//Thrown if the queue does not exist.
log.Debug(string.Format("MessagingEntityNotFoundException: {0}", e.Message));
return bRet;
}
catch (MessagingException e)
{
log.Debug(string.Format("MessagingException: {0}", e.Message));
if (e.IsTransient)
{
//e.IsTransient: Gets a value indicating whether the exception is transient. Check this property to determine if the operation should be retried.
HandleTransientErrors(e);
}
return bRet;
}
catch (Exception e)
{
log.Debug(string.Format("Exception: {0}", e.Message));
return bRet;
} //log.Debug("<=SendMessage");
return bRet;
} //SendMessages, the maximum size of the batch is the same as the maximum size of a single message (currently 256 Kb).
public bool SendMessages(List<string> arrayMessages, QueueClient client, int idelayTime = )
{
//log.Debug("=>SendMessage");
bool bRet = false; int i = ; //prepare data
List<BrokeredMessage> arrayBrokedMessages = new List<BrokeredMessage>(); DateTime utcEnqueueTime = DateTime.UtcNow.AddSeconds(idelayTime);
log.Debug(string.Format("DateTime.UtcNow = {0}", DateTime.UtcNow.ToString()));
log.Debug(string.Format("EnqueuedTimeUtc = {0}", utcEnqueueTime.ToString())); foreach (string strMessageBody in arrayMessages)
{
BrokeredMessage message = new BrokeredMessage(strMessageBody); // The Id of message must be assigned
message.MessageId = "Message_" + (++i).ToString();
message.ScheduledEnqueueTimeUtc = utcEnqueueTime; arrayBrokedMessages.Add(message);
} //send messages
try
{
client.SendBatch(arrayBrokedMessages);
log.Debug(string.Format("Success send batch messages!"));
bRet = true;
}
catch (TimeoutException e)
{
//Thrown when operation times out. Timeout period is initialized through the MessagingFactorySettings may need to increase the value of OperationTimeout to avoid this exception if timeout value is relatively low.
log.Debug(string.Format("TimeoutException: {0}", e.Message));
return bRet;
}
catch (ArgumentException e)
{
//Thrown when the BrokeredMessage is null.
log.Debug(string.Format("ArgumentException: {0}", e.Message));
return bRet;
}
catch (InvalidOperationException e)
{
//Thrown if the message has already been sent by a QueueClient or MessageSender once already.
log.Debug(string.Format("InvalidOperationException: {0}", e.Message));
return bRet;
}
catch (OperationCanceledException e)
{
//Thrown if the client entity has been closed or aborted.
log.Debug(string.Format("OperationCanceledException: {0}", e.Message));
return bRet;
}
catch (UnauthorizedAccessException e)
{
//Thrown if there is an I/O or security error.
log.Debug(string.Format("UnauthorizedAccessException: {0}", e.Message));
return bRet;
}
catch (SerializationException e)
{
//Thrown when an error occurs during serialization or deserialization.
log.Debug(string.Format("SerializationException: {0}", e.Message));
return bRet;
}
catch (MessagingEntityNotFoundException e)
{
//Thrown if the queue does not exist.
log.Debug(string.Format("MessagingEntityNotFoundException: {0}", e.Message));
return bRet;
}
catch (MessagingException e)
{
log.Debug(string.Format("MessagingException: {0}", e.Message));
if (e.IsTransient)
{
//e.IsTransient: Gets a value indicating whether the exception is transient. Check this property to determine if the operation should be retried.
HandleTransientErrors(e);
}
return bRet;
}
catch (Exception e)
{
log.Debug(string.Format("Exception: {0}", e.Message));
return bRet;
} log.Debug("<=SendMessage");
return bRet;
} //get messages from a queue
//iWaitTimeout: The time span that the server will wait for the message batch to arrive before it times out.
public List<BrokeredMessage> GetMessages(int iMaxNumMsg, int iWaitTimeout, QueueClient client)
{
//log.Debug("=>ReceiveMessages"); List<BrokeredMessage> list = new List<BrokeredMessage>(); try
{
//receive messages from Agent Subscription
list = client.ReceiveBatch(iMaxNumMsg, TimeSpan.FromSeconds(iWaitTimeout)).ToList<BrokeredMessage>();
}
catch (MessagingException e)
{
log.Debug(string.Format("ReceiveMessages, MessagingException: {0}", e.Message));
if (e.IsTransient)
{
//e.IsTransient: Gets a value indicating whether the exception is transient. Check this property to determine if the operation should be retried.
HandleTransientErrors(e);
}
return null;
}
catch (Exception e)
{
log.Debug(string.Format("ReceiveMessages, Exception: {0}", e.Message));
return null;
} //subClient.Close();
//log.Debug("<=ReceiveMessages");
return list;
} public bool DeleteMessage(BrokeredMessage message)
{
//log.Debug("=>DeleteMessage");
bool bRet = false; try
{
message.Complete();
bRet = true;
log.Debug(string.Format("Delete Message Successfully"));
}
catch (Exception e)
{
log.Debug(e.Message);
return bRet;
} //log.Debug("<=DeleteMessage");
return bRet;
} private void HandleTransientErrors(MessagingException e)
{
//If transient error/exception, let's back-off for 2 seconds and retry
log.Debug(e.Message);
log.Debug("Transient error happened! Will retry in 2 seconds");
Thread.Sleep();
}
}

  Main方法以及线程1,线程2的实现。

//this function is used to send a number of messages to a queue
public static void SendMessageToQueue()
{
int sendMessageNum = ; log.Debug(string.Format("=> SendMessageToQueue, send message number = {0}", sendMessageNum)); //prepare the handler, client
ServiceBusQueueHandler handler = new ServiceBusQueueHandler();
QueueClient client = QueueClient.CreateFromConnectionString(connectionString, queueName); //the message num which is sent successfully
int count = ; for (int i = ; i < sendMessageNum; i++)
{
//send a message
string strMessageBody = System.Guid.NewGuid().ToString(); bool bRet = handler.SendMessage(strMessageBody, client, ); if (bRet)
{
count++;
} //wait 2s, then send next message
Thread.Sleep();
} log.Debug(string.Format("<= SendMessageToQueue, success sent message number = {0}", count));
} public static void ReceiveMessageFromQueue()
{
log.Debug("=> ReceiveMessageFromQueue"); //prepare the handler, client
ServiceBusQueueHandler handler = new ServiceBusQueueHandler();
QueueClient client = QueueClient.CreateFromConnectionString(connectionString, queueName); //the message num which is received successfully
int count = ; //if we can't get message in 1 hour(60 * 60 = 30 * 120), we think there are no more messages in the queue
int failCount = ; while (failCount < )
{
List<BrokeredMessage> list = handler.GetMessages(, , client); if (list.Count > )
{
foreach (BrokeredMessage e in list)
{
log.Debug(string.Format("Received 1 Message, Time = {0}, Message Body = {1}", DateTime.UtcNow.ToString(), e.GetBody<string>())); //delete message
bool bRet = handler.DeleteMessage(e); if (bRet)
{
count++;
}
} log.Debug(string.Format("Current Count Number = {0}", count));
}
else
{
failCount++;
log.Debug(string.Format("Didn't Receive any Message this time, fail count number = {0}", failCount));
} //wait 10s, then send next message
Thread.Sleep();
} log.Debug(string.Format("<= ReceiveMessageFromQueue, success received message number = {0}", count));
} static void Main(string[] args)
{
log4net.GlobalContext.Properties["LogName"] = "TestServiceBus.log";
log4net.Config.XmlConfigurator.Configure(); Console.WriteLine("Start"); Thread threadSendMessage = new Thread(SendMessageToQueue);
Thread threadReceMessage = new Thread(ReceiveMessageFromQueue); threadSendMessage.Start();
threadReceMessage.Start(); //Console.WriteLine("Stop");
Console.ReadLine();
}

  当然,这里有一个小地方,因为线程1只会发送10000条消息,线程2一直在接收,但当一个小时内没有接收到消息时,则可认为队列中不会再有消息,则停止接收。

三. 测试结果

  从Log来看,程序跑了将近8个小时,最后结果如下:

  成功发送10000条消息

-- ::, [] DEBUG TestServiceBus.Program <= SendMessageToQueue, success sent message number = 

  成功接收10000条消息

-- ::, [] DEBUG TestServiceBus.Program Current Count Number = 

  所以仅从此次测试结果来看,Service Bus Queue并未丢失消息。所以组里遇到消息的问题,建议还是从自己代码入手检查问题,是否我们自己出了问题,而非Service Bus Queue。

---------------------------------------------------------------

2015年5月5日更新:最终找到Service Bus丢失消息的原因,问题果然出在我们自己这边,发消息时,message id有重复的可能,导致可能会丢信。message id应唯一。

  抛砖引玉,谢谢:-)

  Kevin Song

  2015年5月2日

  

【Microsoft Azure学习之旅】测试消息队列(Service Bus Queue)是否会丢消息的更多相关文章

  1. 【Microsoft Azure学习之旅】Azure Java SDK - Service Bus的认证问题

    [2014年12月12日增加备注:12月10日,Microsoft Azure Java SDK team发布了v0.7.0版本,增加对Service Bus SAS的支持,已解决这个问题:-)] 最 ...

  2. 【Microsoft Azure学习之旅】消息服务Service Bus的学习笔记及Demo示例

    今年项目组做的是Cloud产品,有幸接触到了云计算的知识,也了解并使用了当今流行的云计算平台Amazon AWS与Microsoft Azure.我们的产品最初只部署在AWS平台上,现在产品决定同时支 ...

  3. 如何在ASP.NET Core中使用Azure Service Bus Queue

    原文:USING AZURE SERVICE BUS QUEUES WITH ASP.NET CORE SERVICES 作者:damienbod 译文:如何在ASP.NET Core中使用Azure ...

  4. 微软云消息队列 Azure service bus queue

    前言 第一次使用消息队列,遇到了一些问题:同一个消息有多次出列.是一个消息只入列一次,还是多次?还是因为出列问题,出列了多次? Microsoft Azure service bus queue Az ...

  5. 消息队列(Message Queue)简介及其使用

    消息队列(Message Queue)简介及其使用 摘要:利用 MSMQ(Microsoft Message Queue),应用程序开发人员可以通过发送和接收消息方便地与应用程序进行快速可靠的通信.消 ...

  6. Windows Azure Service Bus (3) 队列(Queue) 使用VS2013开发Service Bus Queue

    <Windows Azure Platform 系列文章目录> 在之前的Azure Service Bus中,我们已经介绍了Service Bus 队列(Queue)的基本概念. 在本章中 ...

  7. 消息队列(Message Queue)基本概念(转)

    背景 之前做日志收集模块时,用到flume.另外也有的方案,集成kafaka来提升系统可扩展性,其中涉及到消息队列当时自己并不清楚为什么要使用消息队列.而在我自己提出的原始日志采集方案中不适用消息队列 ...

  8. c/c++ linux 进程间通信系列6,使用消息队列(message queue)

    linux 进程间通信系列6,使用消息队列(message queue) 概念:消息排队,先进先出(FIFO),消息一旦出队,就从队列里消失了. 1,创建消息队列(message queue) 2,写 ...

  9. 三.RabbitMQ之异步消息队列(Work Queue)

    上一篇文章简要介绍了RabbitMQ的基本知识点,并且写了一个简单的发送和接收消息的demo.这一篇文章继续介绍关于Work Queue(工作队列)方面的知识点,用于实现多个工作进程的分发式任务. 一 ...

随机推荐

  1. springcloud系列三 搭建服务模块

    搭建服务模块为了模拟正式开发环境,只是少写了service层直接在controller里面直接引用,直接上图和代码:更为方便: 创建完成之后加入配置: pom.xml文件: <?xml vers ...

  2. Qt 学习之路 2(16):深入 Qt5 信号槽新语法

    Qt 学习之路 2(16):深入 Qt5 信号槽新语法  豆子  2012年9月19日  Qt 学习之路 2  53条评论 在前面的章节(信号槽和自定义信号槽)中,我们详细介绍了有关 Qt 5 的信号 ...

  3. vs 部署SharePoint项目时, package丢失

    bug描述:vs部署sharepoint项目时报错:重启iis应用池失败,未将对象设置引用到实例. 解决方案:查看项目文件(包括隐藏文件),发现package文件不见了,在回收站内能找到被删除的pac ...

  4. 关于c++中const的基本用法

    c++中的const 有点类似于c里的宏定义#define,但是似乎是在宏定义基础上的代码优化,具体我解释不清,下面主要提到的是 const 在c++中的3中基本用法: 1.指向常量的指针 例如:co ...

  5. 数位dp 简单入门

    推荐博客 推荐博客 数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数.所谓数位dp,字面意思就是在数位上进行dp咯.数位还算是比较好听的名字,数位的含义:一个数有 ...

  6. POJ 2441 Arrange the Bulls(状态压缩DP)

    题意很简单,n头牛,m个位置,每头牛有各自喜欢的位置,问安排这n头牛使得每头牛都在各自喜欢的位置有几种安排方法. 2000MS代码: #include <cstdio> #include ...

  7. dropzone手动上传

    html: <div class="field"> <div id="file" class="dropzone"> ...

  8. Spring Cloud 监控相关

    因为最近客户提出想监控Spring Cloud运行状况的需求,所以稍稍做了调研.目前了解的方法如下: Eureka Server启动后可以在根目录路径看到所有注册的Eureka Client状况 各个 ...

  9. 使用nrm解决npm下载包慢的问题!

    nrm的安装使用 作用:提供了一些最常用的NPM包镜像地址,能够让我们快速的切换安装包时候的服务器地址: 什么是镜像:原来包刚一开始是只存在于国外的NPM服务器,但是由于网络原因,经常访问不到,这时候 ...

  10. ctrip-apollo

    云端多网卡问题: 参考:https://blog.csdn.net/buyaore_wo/article/details/79847404