前言

最近在忙一个高考项目,看着系统顺利完成了这次高考,终于可以松口气了。看到那些即将参加高考的学生,也想起当年高三的自己。

下面分享下RabbitMQ实战经验,希望对大家有所帮助:


一、生产消息

关于RabbitMQ的基础使用,这里不再介绍了,项目中使用的是Exchange中的topic模式。

先上发消息的代码

private bool MarkErrorSend(string[] lstMsg)
{
try
{
var factory = new ConnectionFactory()
{
UserName = "guest",//用户名
Password = "guest",//密码
HostName = "localhost",//ConfigurationManager.AppSettings["sHostName"],
};
//创建连接
var connection = factory.CreateConnection();
//创建通道
var channel = connection.CreateModel();
try
{
//定义一个Direct类型交换机
channel.ExchangeDeclare(
exchange: "TestTopicChange", //exchange名称
type: ExchangeType.Topic, //Topic模式,采用路由匹配
durable: true,//exchange持久化
autoDelete: false,//是否自动删除,一般设成false
arguments: null//一些结构化参数,比如:alternate-exchange
); //定义测试队列
channel.QueueDeclare(
queue: "Test_Queue", //队列名称
durable: true, //队列磁盘持久化(要和消息持久化一起使用才有效)
exclusive: false,//是否排他的,false。如果一个队列声明为排他队列,该队列首次声明它的连接可见,并在连接断开时自动删除
autoDelete: false,//是否自动删除,一般设成false
arguments: null
); //将队列绑定到交换机
string routeKey = "TestRouteKey.*";//*匹配一个单词
channel.QueueBind(
queue: "Test_Queue",
exchange: "TestTopicChange",
routingKey: routeKey,
arguments: null
); //消息磁盘持久化,把DeliveryMode设成2(要和队列持久化一起使用才有效)
IBasicProperties properties = channel.CreateBasicProperties();
properties.DeliveryMode = ; channel.ConfirmSelect();//发送确认机制
foreach (var itemMsg in lstMsg)
{
byte[] sendBytes = Encoding.UTF8.GetBytes(itemMsg);
//发布消息
channel.BasicPublish(
exchange: "TestTopicChange",
routingKey: "TestRouteKey.one",
basicProperties: properties,
body: sendBytes
);
}
bool isAllPublished = channel.WaitForConfirms();//通道(channel)里所有消息均发送才返回true
return isAllPublished;
}
catch (Exception ex)
{
//写错误日志
return false;
}
finally
{
channel.Close();
connection.Close();
}
}
catch
{
//RabbitMQ.Client.Exceptions.BrokerUnreachableException:
//When the configured hostname was not reachable.
return false;
}
}

发消息没啥特别的。关于消息持久化的介绍这里也不再介绍,不懂的可以看上篇文章。发消息需要注意的地方是,可以选择多条消息一起发送,最后才确定消息发送成功,这样效率比较高;此外,需要尽量精简每条消息的长度(楼主在这里吃过亏),不然会因消息过长从而增加发送时间。在实际项目中一次发了4万多条数据没有出现问题。


二、接收消息

接下来说下消费消息的过程,我使用的是单个连接多个channel,每个channel每次只取一条消息方法。有人会问单个TCP连接,多个channel会不会影响通信效率。这个理论上肯定会有影响的,看影响大不大而已。我开的channel数一般去到30左右,并没有觉得影响效率,有可能是因为我每个channel是拿一条消息的原因。通过单个连接多个channel的方法,可以少开了很多连接。至于我为什么选每个channel每次只取一条消息,这是外界因素限制了,具体看自己需求。

接下接收消息的过程,首先定义一个RabbitMQHelper类,里面有个全局的conn连接变量,此外还有创建连接、关闭连接和验证连接是否打开等方法。程序运行一个定时器,当

检测到连接未打开的情况下,主动创建连接处理消息。

 public class RabbitMQHelper
{
public IConnection conn = null; /// <summary>
/// 创建RabbitMQ消息中间件连接
/// </summary>
/// <returns>返回连接对象</returns>
public IConnection RabbitConnection(string sHostName, ushort nChannelMax)
{
try
{
if (conn == null)
{
var factory = new ConnectionFactory()
{
UserName = "guest",//用户名
Password = "guest",//密码
HostName = sHostName,//ConfigurationManager.AppSettings["MQIP"],
AutomaticRecoveryEnabled = false,//取消自动重连,改用定时器定时检测连接是否存在
RequestedConnectionTimeout = ,//请求超时时间设成10秒,默认的为30秒
RequestedChannelMax = nChannelMax//与开的线程数保持一致
};
//创建连接
conn = factory.CreateConnection();
Console.WriteLine("RabbitMQ连接已创建!");
} return conn;
}
catch
{
Console.WriteLine("创建连接失败,请检查RabbitMQ是否正常运行!");
return null;
}
} /// <summary>
/// 关闭RabbitMQ连接
/// </summary>
public void Close()
{
try
{
if (conn != null)
{
if (conn.IsOpen)
conn.Close();
conn = null;
Console.WriteLine("RabbitMQ连接已关闭!");
}
}
catch { }
} /// <summary>
/// 判断RabbitMQ连接是否打开
/// </summary>
/// <returns></returns>
public bool IsOpen()
{
try
{
if (conn != null)
{
if (conn.IsOpen)
return true;
}
return false;
}
catch
{
return false;
}
}
}

接下来我们看具体如何接收消息。

private static AutoResetEvent myEvent = new AutoResetEvent(false);
private RabbitMQHelper rabbit = new RabbitMQHelper();
private ushort nChannel = ;//一个连接的最大通道数和所开的线程数一致

首先初始化一个rabbit实例,然后通过RabbitConnection方法创建RabbitMQ连接。

当连接打开时候,用线程池运行接收消息的方法。注意了,这里开的线程必须和开的channel数量一致,不然会有问题(具体问题是,设了RabbitMQ连接超时时间为10秒,有时候不管用,原因未查明。RabbitMQ创建连接默认超时时间为30秒,假如在这个时间内再去调用创建的话,就有可能得到两倍的channel;)

/// <summary>
/// 单个RabbitMQ连接开多个线程,每个线程开一个channel接受消息
/// </summary>
private void CreateConnecttion()
{
try
{
rabbit.RabbitConnection("localhost", nChannel);
if (rabbit.conn != null)
{
ThreadPool.SetMinThreads(, );
ThreadPool.SetMaxThreads(, );
for (int i = ; i <= nChannel; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveMsg), "");
}
myEvent.WaitOne();//等待所有线程工作完成后,才能关闭连接
rabbit.Close();
}
}
catch (Exception ex)
{
rabbit.Close();
Console.WriteLine(ex.Message);
}
}

接着就是接收消息的方法,处理消息的过程省略了。

  /// <summary>
/// 接收并处理消息,在一个连接中创建多个通道(channel),避免创建多个连接
/// </summary>
/// <param name="con">RabbitMQ连接</param>
private void ReceiveMsg(object obj)
{
IModel channel = null;
try
{
#region 创建通道,定义中转站和队列
channel = rabbit.conn.CreateModel();
channel.ExchangeDeclare(
exchange: "TestTopicChange", //exchange名称
type: ExchangeType.Topic, //Topic模式,采用路由匹配
durable: true,//exchange持久化
autoDelete: false,//是否自动删除,一般设成false
arguments: null//一些结构化参数,比如:alternate-exchange
); //定义阅卷队列
channel.QueueDeclare(
queue: "Test_Queue", //队列名称
durable: true, //队列磁盘持久化(要和消息持久化一起使用才有效)
exclusive: false,//是否排他的,false。如果一个队列声明为排他队列,该队列首次声明它的连接可见,并在连接断开时自动删除
autoDelete: false,
arguments: null
);
#endregion
channel.BasicQos(, , false);//每次只接收一条消息 channel.QueueBind(queue: "Test_Queue",
exchange: "TestTopicChange",
routingKey: "TestRouteKey.*");
var consumer = new EventingBasicConsumer(channel); consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
var routingKey = ea.RoutingKey;
//处理消息方法
try
{
bool isMark = AutoMark(message);
if (isMark)
{
//Function.writeMarkLog(message);
//确认该消息已被消费,发消息给RabbitMQ队列
channel.BasicAck(ea.DeliveryTag, false);
}
else
{
if (MarkErrorSend(message))//把错误消息推到错误消息队列
channel.BasicReject(ea.DeliveryTag, false);
else
//消费消息失败,拒绝此消息,重回队列,让它可以继续发送到其他消费者
channel.BasicReject(ea.DeliveryTag, true);
}
}
catch (Exception ex)
{
try
{
Console.WriteLine(ex.Message);
if (channel != null && channel.IsOpen)//处理RabbitMQ停止重启而自动评阅崩溃的问题
{
//消费消息失败,拒绝此消息,重回队列,让它可以继续发送到其他消费者
channel.BasicReject(ea.DeliveryTag, true);
}
}
catch { }
}
};
//手动确认消息
channel.BasicConsume(queue: "Test_Queue",
autoAck: false,
consumer: consumer);
}
catch (Exception ex)
{
try
{
Console.WriteLine("接收消息方法出错:" + ex.Message);
if (channel != null && channel.IsOpen)//关闭通道
channel.Close();
if (rabbit.conn != null)//处理RabbitMQ突然停止的问题
rabbit.Close();
}
catch { }
}
}

三、处理错误消息

把处理失败的消息放到“错误队列”,然后把原队列的消息删除(这里主要解决问题是,存在多个处理失败或处理不了的消息时,如果把这些消息都放回原队列,它们会继续分发到其他线程的channel,但结果还是处理不了,就会造成一个死循环,导致后面的消息无法处理)。把第一次处理不了的消息放到“错误队列”后,重新再开一个新的连接去处理“错误队列”的消息。

/// <summary>
/// 把处理错误的消息发送到“错误消息队列”
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
private bool MarkErrorSend(string msg)
{
RabbitMQHelper MQ = new RabbitMQHelper();
MQ.RabbitConnection("localhost",);
//创建通道
var channel = MQ.conn.CreateModel();
try
{
//定义一个Direct类型交换机
channel.ExchangeDeclare(
exchange: "ErrorTopicChange", //exchange名称
type: ExchangeType.Topic, //Topic模式,采用路由匹配
durable: true,//exchange持久化
autoDelete: false,//是否自动删除,一般设成false
arguments: null//一些结构化参数,比如:alternate-exchange
); //定义阅卷队列
channel.QueueDeclare(
queue: "Error_Queue", //队列名称
durable: true, //队列磁盘持久化(要和消息持久化一起使用才有效)
exclusive: false,//是否排他的,false。如果一个队列声明为排他队列,该队列首次声明它的连接可见,并在连接断开时自动删除
autoDelete: false,//是否自动删除,一般设成false
arguments: null
); //将队列绑定到交换机
string routeKey = "ErrorRouteKey.*";//*匹配一个单词
channel.QueueBind(
queue: "Error_Queue",
exchange: "ErrorTopicChange",
routingKey: routeKey,
arguments: null
); //消息磁盘持久化,把DeliveryMode设成2(要和队列持久化一起使用才有效)
IBasicProperties properties = channel.CreateBasicProperties();
properties.DeliveryMode = ; channel.ConfirmSelect();//发送确认机制
byte[] sendBytes = Encoding.UTF8.GetBytes(msg);
//发布消息
channel.BasicPublish(
exchange: "ErrorTopicChange",
routingKey: "ErrorRouteKey.one",
basicProperties: properties,
body: sendBytes
); bool isAllPublished = channel.WaitForConfirms();//通道(channel)里所有消息均发送才返回true
return isAllPublished;
}
catch (Exception ex)
{
//写错误日志
return false;
}
finally
{
channel.Close();
MQ.conn.Close();
}
}

总结:RabbitMQ本身已经很稳定了,而且性能也很好,所有不稳定的因素都在我们处理消息的过程,所以可以放心使用。

Demo源码地址:https://github.com/Bingjian-Zhu/RabbitMQHelper

RabbitMQ实战经验分享的更多相关文章

  1. 第9期Unity User Group Beijing图文报道:《Unity实战经验分享》

    时间来到了金秋九月,北京UUG活动也来到了第九期.本次活动的主题为<Unity实战经验分享>,为此我们邀请了3位资深的行业大神.这次我们仍然在北京市海淀区丹棱街5号微软大厦举行活动,在这里 ...

  2. Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)

    Visual Studio 2015开发Qt项目实战经验分享(附项目示例源码)    转 https://blog.csdn.net/lhl1124281072/article/details/800 ...

  3. ASP.NET Core & Docker 实战经验分享

    一.前言 最近一直在研究和实践ASP.NET Core.Docker.持续集成.在ASP.NET Core 和 Dcoker结合下遇到了一些坑,在此记录和分享,希望对大家有一些帮助. 二.中间镜像 我 ...

  4. Hystrix 实战经验分享

    一.背景 Hystrix是Netlifx开源的一款容错框架,防雪崩利器,具备服务降级,服务熔断,依赖隔离,监控(Hystrix Dashboard)等功能. 尽管说Hystrix官方已不再维护,且有A ...

  5. 想入职阿里的Java开发者必看,阿里巴巴面试官实战经验分享!

    最近社区Java技术进阶群的小伙伴总是会问,如何面试阿里Java技术岗,需要什么条件,做哪些准备:小编就这些问题找到了阿里技术团队中在一线真正带Java开发团队并直接参与技术面试的专家,分享了自身在筛 ...

  6. 干货: 可视化项目实战经验分享,轻松玩转 Bokeh (建议收藏)

    作者 | Will Koehrsen 翻译 | Lemon 译文出品 | Python数据之道 (ID:PyDataRoad) 本文通过一个项目案例,详细的介绍了如何从 Bokeh 基础到构建 Bok ...

  7. 从零开始 Code Review,两年实战经验分享!

    作者:wenhx http://www.cnblogs.com/wenhx/p/5641766.html 前几天看了<Code Review 程序员的寄望与哀伤>,想到我们团队开展 Cod ...

  8. YOLO_Online 将深度学习最火的目标检测做成在线服务实战经验分享

    YOLO_Online 将深度学习最火的目标检测做成在线服务 第一次接触 YOLO 这个目标检测项目的时候,我就在想,怎么样能够封装一下让普通人也能够体验深度学习最火的目标检测项目,不需要关注技术细节 ...

  9. 实战经验分享之C#对象XML序列化

    .Net Framework提供了对应的System.Xml.Seriazliation.XmlSerializer负责把对象序列化到XML,和从XML中反序列化为对象.Serializer的使用比较 ...

随机推荐

  1. mvc 模式和mtc 模式的区别

    首先说说Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起,模型负责业务对象与数据库的映射( ...

  2. JS实现的ajax和同源策略

    一.回顾jQuery实现的ajax 首先说一下ajax的优缺点 优点: AJAX使用Javascript技术向服务器发送异步请求: AJAX无须刷新整个页面: 因为服务器响应内容不再是整个页面,而是页 ...

  3. yum安装软件内容

    linux  yum源改为阿里yum源 1.备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.back ...

  4. Python关于类的实操

    实操一:总结 1.什么是绑定到对象的方法,如何定义,如何调用,给谁用?有什么特性? 2.什么是绑定到类的方法,如何定义,如何调用,给谁用?有什么特性? 3.什么是解除绑定的函数,如何定义,如何调用,给 ...

  5. Solver Of Caffe

    本文旨在解决如何编写solver文件. Solver的流程: 1.     设计好需要优化的对象,以及用于学习的训练网络和用于评估的测试网络.(通过调用另外一个配置文件prototxt来进行) 2.  ...

  6. 网络扫描信息收集基于(Windows)

    1.首先说明一下一款网络扫描工具,在之前的博客中我曾简要的写过关于Advance IP Scanner使用方法,最近要写网络扫描的工具,所以对这款工具做一个详细的功能细节上的介绍. 如下图  在输入框 ...

  7. CF1121C 模拟

    恶心场恶心题,,round千万不能用库函数的.. /*枚举时间轴t,r是当前完成比例, 记录每个测试的开始时间si,如果有t-si等于r,那么这个测试就标记一下 优先队列存储每个测试,按照si+ai的 ...

  8. yii2 Menu组件的使用

    1.首先引入类 use yii\widgets\Menu; 2.配置组件 <?php echo Menu::widget([ //ul的样式以及相应的属性 'options' => ['c ...

  9. HTTP协议请求头信息和响应头信息

    阅读目录 http的请求部分 常用请头信息 常用响应头信息 http的请求部分 基本结构 请求行 GET  /test/hello.html HTTP/1.1 消息头(并不是每一次请求都一样) 空行 ...

  10. Git 从 master 分支拉新分支开发

    一. 切换到被copy的分支(master),并且从远端拉取最新版本 $git checkout master $git pull 二.从当前分支拉copy开发分支 $git checkout -b ...