组里最近遇到一个问题,微软的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. kuangbin专题七 HDU1166 敌兵布阵 (线段树或树状数组)

    C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于 ...

  2. 解决dubbo注册zookepper服务IP乱入问题的三种方式

    最近做一个项目引入了dubbo+zookepper的分布式服务治理框架.在应用的发布的时候出现一个怪问题.zookepper服务是起在开发服务器192.168.23.180上.本机起应用服务提供者注册 ...

  3. vue项目中禁止移动端双击放大,双手拉大放大的方法

    在vue打包后生成的dist目录文件下下面有index.html 打开里面 把原来的这个 <meta name=viewport content="width=device-width ...

  4. Apache 403 错误。。

    两个方面.. 一: httpd.conf  是否有 <directory '/www'></directory> 是否有  Deny from all 或者 Require l ...

  5. Luogu P1503 鬼子进村 set

    还是拿set搞... 用set记录每个被摧毁的位置,Q的时候二分一下,在和上一个摧毁的位置减一下,即可求出能到的房子数 #include<iostream> #include<cst ...

  6. 洛谷1026(字符串dp)

    常规dp.看到数据很小就直接暴力了,没有预处理.kmp好像过分了-- #include <cstdio> #include <cstring> #include <ios ...

  7. Ansible故障

    常见问题一: [root@m01 ~]# ansible  -k 172.16.1.51 -m ping SSH password: [WARNING]: No hosts matched, noth ...

  8. MySQL SQL_MODE详解

    http://blog.itpub.net/29773961/viewspace-1813501/

  9. 单元测试mock框架——jmockit实战

    JMockit是google code上面的一个java单元测试mock项目,她很方便地让你对单元测试中的final类,静态方法,构造方法进行mock,功能强大.项目地址在:http://jmocki ...

  10. ios中页面底部输入框,position:fixed元素的问题

    在安卓上点击页面底部的输入框,软键盘弹出,页面移动上移.ios上,软件盘弹出,输入框看不到了.让他弹出时让滚动条在最低部 var u = navigator.userAgent, app = navi ...