RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面都很是的优秀。是当前最主流的消息中间件之一。html
RabbitMQ的官方 git
操做起来很简单,只须要在DOS下面,进入安装目录(安装路径RabbitMQ Server abbitmq_server-3.2.2sbin
)执行以下命令就能够成功安装。 github
rabbitmq-plugins enable rabbitmq_management
能够经过访问:http://localhost:15672
进行测试,默认的登录帐号为:guest,密码为:guest。bash
1. 安装完之后erlang须要手动设置ERLANG_HOME 的系统变量。服务器
set ERLANG_HOME=F:Program Fileserl9.0 #环境变量`path`里加入:%ERLANG_HOME%in #环境变量`path`里加入: 安装路径RabbitMQ Server abbitmq_server-3.6.10sbin
2.激活Rabbit MQ’s Management Plugin分布式
使用Rabbit MQ 管理插件,能够更好的可视化方式查看Rabbit MQ 服务器实例的状态,你能够在命令行中使用下面的命令激活。ide
rabbitmq-plugins.bat enable rabbitmq_management
3.建立管理用户工具
rabbitmqctl.bat add_user sa 123456
4. 设置管理员测试
rabbitmqctl.bat set_user_tags sa administrator
5.设置权限ui
rabbitmqctl.bat set_permissions -p / sa ".*" ".*" ".*"
6. 其余命令
#查询用户: rabbitmqctl.bat list_users #查询vhosts: rabbitmqctl.bat list_vhosts #启动RabbitMQ服务: net stop RabbitMQ && net start RabbitMQ
以上这些,帐号、vhost、权限、做用域等基本就设置完了。
RabbitMQ.Client 是RabbiMQ 官方提供的的客户端
EasyNetQ 是基于RabbitMQ.Client 基础上封装的开源客户端,使用很是方便
如下操做RabbitMQ的代码例子,都是基于EasyNetQ的使用和再封装,在文章底部有demo例子的×××地址
建立 IBus
/// <summary> /// 消息服务器链接器 /// </summary> public class BusBuilder { public static IBus CreateMessageBus() { // 消息服务器链接字符串 // var connectionString = ConfigurationManager.ConnectionStrings["RabbitMQ"]; string connString = "host=127.0.0.1:5672;virtualHost=TestQueue;username=sa;password=123456"; if (connString == null || connString == string.Empty) throw new Exception("messageserver connection string is missing or empty"); return RabbitHutch.CreateBus(connString); } }
Fanout Exchange
全部发送到Fanout Exchange的消息都会被转发到与该Exchange 绑定(Binding)的全部Queue上。
Fanout Exchange 不须要处理RouteKey 。只须要简单的将队列绑定到exchange 上。这样发送到exchange的消息都会被转发到与该交换机绑定的全部队列上。相似子网广播,每台子网内的主机都得到了一份复制的消息。
因此,Fanout Exchange 转发消息是最快的。
/// <summary> /// 消息消耗(fanout) /// </summary> /// <typeparam name="T">消息类型</typeparam> /// <param name="handler">回调</param> /// <param name="exChangeName">交换器名</param> /// <param name="queueName">队列名</param> /// <param name="routingKey">路由名</param> public static void FanoutConsume<T>(Action<T> handler, string exChangeName = "fanout_mq", string queueName = "fanout_queue_default", string routingKey = "") where T : class { var bus = BusBuilder.CreateMessageBus(); var adbus = bus.Advanced; var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Fanout); var queue = CreateQueue(adbus, queueName); adbus.Bind(exchange, queue, routingKey); adbus.Consume(queue, registration => { registration.Add<T>((message, info) => { handler(message.Body); }); }); } /// <summary> /// 消息上报(fanout) /// </summary> /// <typeparam name="T">消息类型</typeparam> /// <param name="topic">主题名</param> /// <param name="t">消息命名</param> /// <param name="msg">错误信息</param> /// <returns></returns> public static bool FanoutPush<T>(T t, out string msg, string exChangeName = "fanout_mq", string routingKey = "") where T : class { msg = string.Empty; try { using (var bus = BusBuilder.CreateMessageBus()) { var adbus = bus.Advanced; var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Fanout); adbus.Publish(exchange, routingKey, false, new Message<T>(t)); return true; } } catch (Exception ex) { msg = ex.ToString(); return false; } }
全部发送到Direct Exchange的消息被转发到RouteKey中指定的Queue。
Direct模式,可使用RabbitMQ自带的Exchange:default Exchange 。因此不须要将Exchange进行任何绑定(binding)操做 。消息传递时,RouteKey必须彻底匹配,才会被队列接收,不然该消息会被抛弃。
/// <summary> /// 消息发送(direct) /// </summary> /// <typeparam name="T">消息类型</typeparam> /// <param name="queue">发送到的队列</param> /// <param name="message">发送内容</param> public static void DirectSend<T>(string queue, T message) where T : class { using (var bus = BusBuilder.CreateMessageBus()) { bus.Send(queue, message); } } /// <summary> /// 消息接收(direct) /// </summary> /// <typeparam name="T">消息类型</typeparam> /// <param name="queue">接收的队列</param> /// <param name="callback">回调操做</param> /// <param name="msg">错误信息</param> /// <returns></returns> public static bool DirectReceive<T>(string queue, Action<T> callback, out string msg) where T : class { msg = string.Empty; try { var bus = BusBuilder.CreateMessageBus(); bus.Receive<T>(queue, callback); } catch (Exception ex) { msg = ex.ToString(); return false; } return true; } /// <summary> /// 消息发送 /// <![CDATA[(direct EasyNetQ高级API)]]> /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <param name="msg"></param> /// <param name="exChangeName"></param> /// <param name="routingKey"></param> /// <returns></returns> public static bool DirectPush<T>(T t, out string msg, string exChangeName = "direct_mq", string routingKey = "direct_rout_default") where T : class { msg = string.Empty; try { using (var bus = BusBuilder.CreateMessageBus()) { var adbus = bus.Advanced; var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Direct); adbus.Publish(exchange, routingKey, false, new Message<T>(t)); return true; } } catch (Exception ex) { msg = ex.ToString(); return false; } } /// <summary> /// 消息接收 /// <![CDATA[(direct EasyNetQ高级API)]]> /// </summary> /// <typeparam name="T">消息类型</typeparam> /// <param name="handler">回调</param> /// <param name="exChangeName">交换器名</param> /// <param name="queueName">队列名</param> /// <param name="routingKey">路由名</param> public static bool DirectConsume<T>(Action<T> handler, out string msg, string exChangeName = "direct_mq", string queueName = "direct_queue_default", string routingKey = "direct_rout_default") where T : class { msg = string.Empty; try { var bus = BusBuilder.CreateMessageBus(); var adbus = bus.Advanced; var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Direct); var queue = CreateQueue(adbus, queueName); adbus.Bind(exchange, queue, routingKey); adbus.Consume(queue, registration => { registration.Add<T>((message, info) => { handler(message.Body); }); }); } catch (Exception ex) { msg = ex.ToString(); return false; } return true; }
Topic Exchange
要使用主题发布,只需使用带有主题的重载的Publish方法:
var bus = RabbitHutch.CreateBus(...); bus.Publish(message, "X.A");
订阅者能够经过指定要匹配的主题来过滤邮件。
因此发布的主题为“X.A.2”的消息将匹配“#”,“X.#”,“ .A.”,而不是“X.B. *”或“A”。
警告,Publish只顾发送消息到队列,可是无论有没有消费端订阅,因此,发布以后,若是没有消费者,该消息将不会被消费甚至丢失。
EasyNetQ提供了消息订阅,当调用Subscribe方法时候,EasyNetQ会建立一个用于接收消息的队列,不过与消息发布不一样的是,消息订阅增长了一个参数,subscribe_id.代码以下:
bus.Subscribe("my_id", handler, x => x.WithTopic("X.*"));
警告: 具备相同订阅者但不一样主题字符串的两个单独订阅可能不会产生您指望的效果。 subscriberId有效地标识个体AMQP队列。 具备相同subscriptionId的两个订阅者将链接到相同的队列,而且二者都将添加本身的主题绑定。 因此,例如,若是你这样作:
bus.Subscribe("my_id", handlerOfXDotStar, x => x.WithTopic("X.*")); bus.Subscribe("my_id", handlerOfStarDotB, x => x.WithTopic("*.B"));
匹配“x.”或“ .B”的全部消息将被传递到“XXX_my_id”队列。 而后,RabbitMQ将向两个消费者传递消息,其中handlerOfXDotStar和handlerOfStarDotB轮流获取每条消息。
如今,若是你想要匹配多个主题(“X. ”OR“ .B”),你可使用另外一个重载的订阅方法,它采用多个主题,以下所示:
bus.Subscribe("my_id", handler, x => x.WithTopic("X.*").WithTopic("*.B"));
/// <summary> /// 获取主题 /// </summary> /// <typeparam name="T">主题内容类型</typeparam> /// <param name="subscriptionId">订阅者ID</param> /// <param name="callback">消息接收响应回调</param> /// <param name="topics">订阅主题集合</param> public static void TopicSubscribe<T>(string subscriptionId, Action<T> callback, params string[] topics) where T : class { var bus = BusBuilder.CreateMessageBus(); bus.Subscribe(subscriptionId, callback, (config) => { foreach (var item in topics) config.WithTopic(item); }); } /// <summary> /// 发布主题 /// </summary> /// <typeparam name="T">主题内容类型</typeparam> /// <param name="topic">主题名称</param> /// <param name="message">主题内容</param> /// <param name="msg">错误信息</param> /// <returns></returns> public static bool TopicPublish<T>(string topic, T message, out string msg) where T : class { msg = string.Empty; try { using (var bus = BusBuilder.CreateMessageBus()) { bus.Publish(message, topic); return true; } } catch (Exception ex) { msg = ex.ToString(); return false; } } /// <summary> /// 发布主题 /// </summary> /// <![CDATA[(topic EasyNetQ高级API)]]> /// <typeparam name="T">消息类型</typeparam> /// <param name="t">消息内容</param> /// <param name="topic">主题名</param> /// <param name="msg">错误信息</param> /// <param name="exChangeName">交换器名</param> /// <returns></returns> public static bool TopicSub<T>(T t, string topic, out string msg, string exChangeName = "topic_mq") where T : class { msg = string.Empty; try { if (string.IsNullOrWhiteSpace(topic)) throw new Exception("推送主题不能为空"); using (var bus = BusBuilder.CreateMessageBus()) { var adbus = bus.Advanced; //var queue = adbus.QueueDeclare("user.notice.zhangsan"); var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Topic); adbus.Publish(exchange, topic, false, new Message<T>(t)); return true; } } catch (Exception ex) { msg = ex.ToString(); return false; } } /// <summary> /// 获取主题 /// </summary> /// <![CDATA[(topic EasyNetQ高级API)]]> /// <typeparam name="T">消息类型</typeparam> /// <param name="subscriptionId">订阅者ID</param> /// <param name="callback">回调</param> /// <param name="exChangeName">交换器名</param> /// <param name="topics">主题名</param> public static void TopicConsume<T>(Action<T> callback, string exChangeName = "topic_mq",string subscriptionId = "topic_subid", params string[] topics) where T : class { var bus = BusBuilder.CreateMessageBus(); var adbus = bus.Advanced; var exchange = adbus.ExchangeDeclare(exChangeName, ExchangeType.Topic); var queue = adbus.QueueDeclare(subscriptionId); foreach (var item in topics) adbus.Bind(exchange, queue, item); adbus.Consume(queue, registration => { registration.Add<T>((message, info) => { callback(message.Body); }); }); }
具体发布/订阅消息的Demo和相关测试看源码Demo
当在建立订阅者去消费队列的时候
/// <summary> /// 获取主题 /// </summary> /// <param name="topic"></param> public static void GetSub<T>(T topic, Action<T> callback) where T : class { using (var bus = BusBuilder.CreateMessageBus()) { bus.Subscribe<T>(topic.ToString(), callback, x => x.WithTopic(topic.ToString())); } }
using里的对象在执行完成后被回收了,致使刚链接上去就又断开了(刚开始写的时候,习惯性加using,排查了很久才发现,欲哭无泪)
源码项目运行前的准备与确认:
到RabbitMQ管理后台添加TestQueue
VHost,而且分配用户权限,而后到RabbitMQHelper.BusBuilder
类里配置RabbitMQ链接服务的相关信息host=127.0.0.1:5672;virtualHost=TestQueue;username=sa;password=123456
,(根据配置的内容和用户修改)
参考资料(鸣谢):
附:Demo源码GitHub地址