介绍

RabbitMQ是一个由erlang开发的基于AMQP(Advanced Message Queue)协议的开源实现。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面都非常的优秀。是当前最主流的消息中间件之一。

RabbitMQ的官方

  • 概念:

    • Brocker:消息队列服务器实体。
    • Exchange:消息交换机,指定消息按什么规则,路由到哪个队列。
    • Queue:消息队列,每个消息都会被投入到一个或者多个队列里。
    • Binding:绑定,它的作用是把exchange和queue按照路由规则binding起来。
    • Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
    • Vhost:虚拟主机,一个broker里可以开设多个vhost,用作不用用户的权限分离。
    • Producer:消息生产者,就是投递消息的程序。
    • Consumer:消息消费者,就是接受消息的程序。
    • Channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
  • 消息队列的使用过程大概如下:
    • 消息接收

      • 客户端连接到消息队列服务器,打开一个channel。
      • 客户端声明一个exchange,并设置相关属性。
      • 客户端声明一个queue,并设置相关属性。
      • 客户端使用routing key,在exchange和queue之间建立好绑定关系。
    • 消息发布
      • 客户端投递消息到exchange。
      • exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。
  • AMQP 里主要要说两个组件:
    • Exchange 和 Queue
    • 绿色的 X 就是 Exchange ,红色的是 Queue ,这两者都在 Server 端,又称作 Broker
    • 这部分是 RabbitMQ 实现的,而蓝色的则是客户端,通常有 Producer 和 Consumer 两种类型。
  • Exchange通常分为四种:
    • fanout:该类型路由规则非常简单,会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,相当于广播功能
    • direct:该类型路由规则会将消息路由到binding key与routing key完全匹配的Queue中
    • topic:与direct类型相似,只是规则没有那么严格,可以模糊匹配和多条件匹配
    • headers:该类型不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配
  • 使用场景

下载与安装


管理工具

操作起来很简单,只需要在DOS下面,进入安装目录(安装路径\RabbitMQ Server\rabbitmq_server-3.2.2\sbin)执行如下命令就可以成功安装。

rabbitmq-plugins enable rabbitmq_management

可以通过访问:http://localhost:15672进行测试,默认的登陆账号为:guest,密码为:guest。

其他配置

1. 安装完以后erlang需要手动设置ERLANG_HOME 的系统变量。

set ERLANG_HOME=F:\Program Files\erl9.0
#环境变量`path`里加入:%ERLANG_HOME%\bin
#环境变量`path`里加入: 安装路径\RabbitMQ Server\rabbitmq_server-3.6.10\sbin

2.激活Rabbit MQ’s Management Plugin

使用Rabbit MQ 管理插件,可以更好的可视化方式查看Rabbit MQ 服务器实例的状态,你可以在命令行中使用下面的命令激活。

rabbitmq-plugins.bat  enable  rabbitmq_management

3.创建管理用户

rabbitmqctl.bat add_user sa 123456

4. 设置管理员

rabbitmqctl.bat set_user_tags sa administrator

5.设置权限

rabbitmqctl.bat set_permissions -p / sa ".*" ".*" ".*"

6. 其他命令

#查询用户:
rabbitmqctl.bat list_users
#查询vhosts:
rabbitmqctl.bat list_vhosts
#启动RabbitMQ服务:
net stop RabbitMQ && net start RabbitMQ

以上这些,账号、vhost、权限、作用域等基本就设置完了。


基于.net使用

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)

要使用主题发布,只需使用带有主题的重载的Publish方法:

var bus = RabbitHutch.CreateBus(...);
bus.Publish(message, "X.A");

订阅者可以通过指定要匹配的主题来过滤邮件。

  • 这些可以包括通配符:

    • *=>匹配一个字。
    • #=>匹配到零个或多个单词。

所以发布的主题为“X.A.2”的消息将匹配“#”,“X.#”,“* .A.*”,而不是“X.B. *”或“A”。

警告,Publish只顾发送消息到队列,但是不管有没有消费端订阅,所以,发布之后,如果没有消费者,该消息将不会被消费甚至丢失。

  • 消息订阅(Subscribe)

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管理后台添加TestQueueVHost,并且分配用户权限,然后到RabbitMQHelper.BusBuilder类里配置RabbitMQ连接服务的相关信息 host=127.0.0.1:5672;virtualHost=TestQueue;username=sa;password=123456,(根据配置的内容和用户修改)


参考资料(鸣谢):


附:Demo源码GitHub地址


欢迎到原文地址关注和交流

RabbitMQ入门与使用篇的更多相关文章

  1. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  2. RabbitMQ入门之Hello World

    RabbitMQ简介   在介绍RabbitMQ之前,我们需要了解一些最基础的概念,相信使用过或者听说过RabbitMQ的人都不会陌生,但笔者还是不厌其烦地在这里讲述,因为笔者的理念是self con ...

  3. RabbitMQ学习总结 第二篇:快速入门HelloWorld

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  4. 消息中间件 RabbitMQ 入门篇

    消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天   作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...

  5. RabbitMQ入门看这一篇就够了

    一文搞懂 RabbitMQ 的重要概念以及安装 一 RabbitMQ 介绍 这部分参考了 <RabbitMQ实战指南>这本书的第 1 章和第 2 章. 1.1 RabbitMQ 简介 Ra ...

  6. RabbitMQ学习总结 第一篇:理论篇

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  7. 2.RABBITMQ 入门 - WINDOWS - 生产和消费消息 一个完整案例

    关于安装和配置,见上一篇 1.RABBITMQ 入门 - WINDOWS - 获取,安装,配置 公司有需求,要求使用winform开发这个东西(消息中间件),另外还要求开发一个日志中间件,但是也是要求 ...

  8. RabbitMQ入门-从HelloWorld开始

    从读者的反馈谈RabbitMQ 昨天发完<RabbitMQ入门-初识RabbitMQ>,我陆陆续续收到一些反馈.鉴于部分读者希望结合实例来讲 期待下篇详细,最好结合案例.谢谢! 哪都好,唯 ...

  9. RabbitMQ 入门指南——安装

    RabbitMQ好文 Rabbitmq Java Client Api详解 tohxyblog-博客园-rabbitMQ教程系列 robertohuang-CSDN-rabbitMQ教程系列 Rabb ...

随机推荐

  1. ASP.NET Core Web API 最小化项目

    ASP.NET Core中默认的ASP.NET Core 模板中有Web API 模板可以创建Web API项目. 有时,只需要创建一个API,不需要关心Razor,本地化或XML序列化.通过删除无用 ...

  2. MyBatis源码解析【3】生命周期

    经过之前的项目构建,我们已经得到了一个可以使用的最基本的项目. 其中已经包括整个执行的过程.但是我们在完成之后也遇到了很多问题,我们就要慢慢的一步步解决这些问题. 讲道理,今天我们其实应该直接开始看源 ...

  3. linux 常用 掌握要点

    1.查看正在执行的进程(Process) ps命令 Process Status 进程状态 语法: ps  [option]  [--help] -A  列出所有的行程 -w  显示加宽可以显示较多的 ...

  4. Vue.js 基本功能了解

    一.写在前面 隔了这么久才来出Vue的第二篇文章,真是堕落了,自己先惩罚下/(ㄒoㄒ)/~~ 回过头看自己第一篇相关文章<初试 Vue.js>(http://www.cnblogs.com ...

  5. java 数据库编程 学习笔记 不断更新

    最近开始学习java,感觉java的数据库编程需要发个随笔记录一下,话不多说 切入正题. 一.数据库访问技术的简介 应用程序  →  执行SQL语句 →数据库 → 检索数据结果 → 应用程序   ( ...

  6. Java Jpa 规范

    Jpa最早是EJB3.0里面的内容,JSR 220: Enterprise JavaBeansTM 3.0 https://www.jcp.org/en/jsr/detail?id=220 后来大约在 ...

  7. Oracle查询多行数据合并成一行数据

    例如: select base_id, translate (ltrim (text1, '/'), '*/', '*,') xmmc,translate (ltrim (text2, '/'), ' ...

  8. requireJS 源码(三) data-main 的加载实现

    (一)入口 通过 data-main 去加载 JS 模块,是通过  req(cfg) 入口去进行处理的. 为了跟踪,你可以在此 加断点 进行调试跟踪. (二)  req({ })执行时,functio ...

  9. Spring Boot 系列(四)静态资源处理

    在web开发中,静态资源的访问是必不可少的,如:图片.js.css 等资源的访问. spring Boot 对静态资源访问提供了很好的支持,基本使用默认配置就能满足开发需求. 一.默认静态资源映射 S ...

  10. JavaScript中异步编程

    一 关于事件的异步 事件是JavaScript中最重要的一个特征,nodejs就是利用js这一异步而设计出来的.所以这里讲一下事件机制. 在一个js文件中,如果要运行某一个函数,有2中手段,一个就是直 ...