RabbitMQ入门与使用篇
pmnz1078
7年前
<h2>介绍</h2> <p>RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面都非常的优秀。是当前最主流的消息中间件之一。</p> <p><a href="/misc/goto?guid=4958839324027820493" rel="nofollow,noindex">RabbitMQ的官方</a></p> <p><img src="https://simg.open-open.com/show/ddbe02647d9170f810861f77f76513f1.png"></p> <ul> <li>概念: <ul> <li>Brocker:消息队列服务器实体。</li> <li>Exchange:消息交换机,指定消息按什么规则,路由到哪个队列。</li> <li>Queue:消息队列,每个消息都会被投入到一个或者多个队列里。</li> <li>Binding:绑定,它的作用是把exchange和queue按照路由规则binding起来。</li> <li>Routing Key:路由关键字,exchange根据这个关键字进行消息投递。</li> <li>Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不用用户的权限分离。</li> <li>Producer:消息生产者,就是投递消息的程序。</li> <li>Consumer:消息消费者,就是接受消息的程序。</li> <li>Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。</li> </ul> </li> <li>消息队列的使用过程大概如下: <ul> <li>消息接收 <ul> <li>客户端连接到消息队列服务器,打开一个channel。</li> <li>客户端声明一个exchange,并设置相关属性。</li> <li>客户端声明一个queue,并设置相关属性。</li> <li>客户端使用routing key,在exchange和queue之间建立好绑定关系。</li> </ul> </li> <li>消息发布 <ul> <li>客户端投递消息到exchange。</li> <li>exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。</li> </ul> </li> </ul> </li> <li>AMQP 里主要要说两个组件: <ul> <li>Exchange 和 Queue</li> <li>绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker</li> <li>这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型。</li> </ul> </li> <li>Exchange通常分为四种: <ul> <li>fanout:该类型路由规则非常简单,会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,相当于广播功能</li> <li>direct:该类型路由规则会将消息路由到binding key与routing key完全匹配的Queue中</li> <li>topic:与direct类型相似,只是规则没有那么严格,可以模糊匹配和多条件匹配</li> <li>headers:该类型不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配</li> </ul> </li> <li>使用场景 <ul> <li><a href="/misc/goto?guid=4959749733070136208" rel="nofollow,noindex">官方介绍</a></li> </ul> </li> </ul> <h2>下载与安装</h2> <ul> <li>下载 <ul> <li><a href="/misc/goto?guid=4958188626551291166" rel="nofollow,noindex">rabbitmq</a></li> <li><a href="/misc/goto?guid=4959624602593319176" rel="nofollow,noindex">erlang</a></li> </ul> </li> <li>安装 <ul> <li>先安装erlang</li> <li>然后再安装rabbitmq</li> </ul> </li> </ul> <h2>管理工具</h2> <ul> <li>参考 <a href="/misc/goto?guid=4959623343518350803" rel="nofollow,noindex">官方文档</a></li> </ul> <p>操作起来很简单,只需要在DOS下面,进入安装目录( 安装路径\RabbitMQ Server\rabbitmq_server-3.2.2\sbin )执行如下命令就可以成功安装。</p> <pre> <code class="language-java">rabbitmq-plugins enable rabbitmq_management</code></pre> <p>可以通过访问: http://localhost:15672 进行测试,默认的登陆账号为:guest,密码为:guest。</p> <h2><img src="https://simg.open-open.com/show/ed43acaed23d5cb0a6abd0cb8f1046c8.png"></h2> <h2>其他配置</h2> <p>1. 安装完以后erlang需要手动设置ERLANG_HOME 的系统变量。</p> <pre> <code class="language-java">set ERLANG_HOME=F:\Program Files\erl9.0 #环境变量`path`里加入:%ERLANG_HOME%\bin #环境变量`path`里加入: 安装路径\RabbitMQ Server\rabbitmq_server-3.6.10\sbin</code></pre> <p>2.激活Rabbit MQ’s Management Plugin</p> <p>使用Rabbit MQ 管理插件,可以更好的可视化方式查看Rabbit MQ 服务器实例的状态,你可以在命令行中使用下面的命令激活。</p> <pre> <code class="language-java">rabbitmq-plugins.bat enable rabbitmq_management</code></pre> <p>3.创建管理用户</p> <pre> <code class="language-java">rabbitmqctl.bat add_user sa 123456</code></pre> <p>4. 设置管理员</p> <pre> <code class="language-java">rabbitmqctl.bat set_user_tags sa administrator</code></pre> <p>5.设置权限</p> <pre> <code class="language-java">rabbitmqctl.bat set_permissions -p / sa ".*" ".*" ".*"</code></pre> <p>6. 其他命令</p> <pre> <code class="language-java">#查询用户: rabbitmqctl.bat list_users #查询vhosts: rabbitmqctl.bat list_vhosts #启动RabbitMQ服务: net stop RabbitMQ && net start RabbitMQ</code></pre> <p>以上这些,账号、vhost、权限、作用域等基本就设置完了。</p> <h2>基于.net使用</h2> <p><a href="/misc/goto?guid=4958190094529547792" rel="nofollow,noindex">RabbitMQ.Client</a> 是RabbiMQ 官方提供的的客户端</p> <p><a href="/misc/goto?guid=4959543577174193674" rel="nofollow,noindex">EasyNetQ</a> 是基于RabbitMQ.Client 基础上封装的开源客户端,使用非常方便</p> <p>以下操作RabbitMQ的代码例子,都是基于EasyNetQ的使用和再封装,在文章底部有 <strong>demo例子的源码下载地址</strong></p> <p>创建 IBus</p> <pre> <code class="language-java">/// <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); } }</code></pre> <p>Fanout Exchange</p> <p><img src="https://simg.open-open.com/show/e68688daa8cf0d972e1844ec30224457.png"></p> <p>所有发送到Fanout Exchange的消息都会被转发到与该Exchange 绑定(Binding)的所有Queue上。</p> <p>Fanout Exchange 不需要处理RouteKey 。只需要简单的将队列绑定到exchange 上。这样发送到exchange的消息都会被转发到与该交换机绑定的所有队列上。类似子网广播,每台子网内的主机都获得了一份复制的消息。 所以,Fanout Exchange 转发消息是最快的。</p> <pre> <code class="language-java">/// <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; } }</code></pre> <p><img src="https://simg.open-open.com/show/0b45528082530216784418a2cbf97104.png"></p> <p>所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue。</p> <p>Direct模式,可以使用RabbitMQ自带的Exchange:default Exchange 。所以不需要将Exchange进行任何绑定(binding)操作 。消息传递时,RouteKey必须完全匹配,才会被队列接收,否则该消息会被抛弃。</p> <pre> <code class="language-java">/// <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; }</code></pre> <p>Topic Exchange</p> <p><img src="https://simg.open-open.com/show/b8533753976c55e8cb36878913489808.png"></p> <ul> <li>消息发布(Publish)</li> </ul> <p>要使用主题发布,只需使用带有主题的重载的Publish方法:</p> <pre> <code class="language-java">var bus = RabbitHutch.CreateBus(...); bus.Publish(message, "X.A");</code></pre> <p>订阅者可以通过指定要匹配的主题来过滤邮件。</p> <ul> <li>这些可以包括通配符: <ul> <li>*=>匹配一个字。</li> <li>#=>匹配到零个或多个单词。</li> </ul> </li> </ul> <p>所以发布的主题为“X.A.2”的消息将匹配“#”,“X.#”,“* .A.*”,而不是“X.B. *”或“A”。</p> <p>警告,Publish只顾发送消息到队列,但是不管有没有消费端订阅,所以,发布之后,如果没有消费者,该消息将不会被消费甚至丢失。</p> <ul> <li>消息订阅(Subscribe)</li> </ul> <p>EasyNetQ提供了消息订阅,当调用Subscribe方法时候,EasyNetQ会创建一个用于接收消息的队列,不过与消息发布不同的是,消息订阅增加了一个参数,subscribe_id.代码如下:</p> <pre> <code class="language-java">bus.Subscribe("my_id", handler, x => x.WithTopic("X.*"));</code></pre> <p>警告: 具有相同订阅者但不同主题字符串的两个单独订阅可能不会产生您期望的效果。 subscriberId有效地标识个体AMQP队列。 具有相同subscriptionId的两个订阅者将连接到相同的队列,并且两者都将添加自己的主题绑定。 所以,例如,如果你这样做:</p> <pre> <code class="language-java">bus.Subscribe("my_id", handlerOfXDotStar, x => x.WithTopic("X.*")); bus.Subscribe("my_id", handlerOfStarDotB, x => x.WithTopic("*.B"));</code></pre> <p>匹配“x. <em>”或“</em> .B”的所有消息将被传递到“XXX_my_id”队列。 然后,RabbitMQ将向两个消费者传递消息,其中handlerOfXDotStar和handlerOfStarDotB轮流获取每条消息。</p> <p>现在,如果你想要匹配多个主题(“X. <em>”OR“</em> .B”),你可以使用另一个重载的订阅方法,它采用多个主题,如下所示:</p> <pre> <code class="language-java">bus.Subscribe("my_id", handler, x => x.WithTopic("X.*").WithTopic("*.B"));</code></pre> <pre> <code class="language-java">/// <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); }); }); }</code></pre> <p>具体发布/订阅消息的Demo和相关测试看源码Demo</p> <h2><img src="https://simg.open-open.com/show/9bc8f0452d4724239cb2018262053bc7.jpg"></h2> <h2>注意</h2> <p>当在创建订阅者去消费队列的时候</p> <pre> <code class="language-java">/// <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())); } }</code></pre> <p>using里的对象在执行完成后被回收了,导致刚连接上去就又断开了(刚开始写的时候,习惯性加using,排查了好久才发现,欲哭无泪)</p> <p>源码项目运行前的准备与确认:</p> <p>到RabbitMQ管理后台添加 TestQueue VHost,并且分配用户权限,然后到 RabbitMQHelper.BusBuilder 类里配置RabbitMQ连接服务的相关信息 host=127.0.0.1:5672;virtualHost=TestQueue;username=sa;password=123456 ,(根据配置的内容和用户修改)</p> <p><img src="https://simg.open-open.com/show/c7115124d08d6accd17470db388b5e37.png"></p> <h2>参考资料(鸣谢):</h2> <ul> <li><a href="/misc/goto?guid=4959751196465335651" rel="nofollow,noindex">EasyNetQ-基于Topic的路由</a></li> <li><a href="/misc/goto?guid=4959751196563798826" rel="nofollow,noindex">.NET操作RabbitMQ组件EasyNetQ使用中文简版文档。</a></li> <li><a href="/misc/goto?guid=4959751196652959264" rel="nofollow,noindex">RabbitMQ入门指南</a></li> </ul> <p>附: <a href="/misc/goto?guid=4959751196741623440" rel="nofollow,noindex">Demo源码</a> GitHub地址,有兴趣的童鞋可以来一波关注!</p> <p>有任何想说的请留言哦</p> <p>转载请申明原文地址,谢谢合作</p> <p>技术分享,技术问答,资源分享,欢迎到:NoBB开发者社区,Let Go!</p> <p>老铁们关注我的《大话WEB开发》微信公众号,跟踪我的原创博文</p> <p>并不定时推荐WEB开发相关技术博文,共勉与学习交流!二维码↓</p> <p><img src="https://simg.open-open.com/show/215e8b67e6ddaa9a99c2ceefee007662.jpg"></p> <p> </p> <p>来自:https://blog.thankbabe.com/2017/08/03/rabbitmq-demo/</p> <p> </p>