转自:https://blog.thankbabe.com/2017/08/03/rabbitmq-demo/?from=cnblogs

介绍

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 为了方便使用,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. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  2. RabbitMq应用二

    在应用一中,基本的消息队列使用已经完成了,在实际项目中,一定会出现各种各样的需求和问题,rabbitmq内置的很多强大机制和功能会帮助我们解决很多的问题,下面就一个一个的一起学习一下. 消息响应机制 ...

  3. 如何优雅的使用RabbitMQ

    RabbitMQ无疑是目前最流行的消息队列之一,对各种语言环境的支持也很丰富,作为一个.NET developer有必要学习和了解这一工具.消息队列的使用场景大概有3种: 1.系统集成,分布式系统的设 ...

  4. RabbitMq应用一的补充(RabbitMQ的应用场景)

    直接进入正题. 一.异步处理 场景:发送手机验证码,邮件 传统古老处理方式如下图 这个流程,全部在主线程完成,注册->入库->发送邮件->发送短信,由于都在主线程,所以要等待每一步完 ...

  5. RabbitMq应用一

    RabbitMq应用一 RabbitMQ的具体概念,百度百科一下,我这里说一下我的理解,如果有少或者不对的地方,欢迎纠正和补充. 一个项目架构,小的时候,一般都是传统的单一网站系统,或者项目,三层架构 ...

  6. 缓存、队列(Memcached、redis、RabbitMQ)

    本章内容: Memcached 简介.安装.使用 Python 操作 Memcached 天生支持集群 redis 简介.安装.使用.实例 Python 操作 Redis String.Hash.Li ...

  7. 消息队列性能对比——ActiveMQ、RabbitMQ与ZeroMQ(译文)

    Dissecting Message Queues 概述: 我花了一些时间解剖各种库执行分布式消息.在这个分析中,我看了几个不同的方面,包括API特性,易于部署和维护,以及性能质量..消息队列已经被分 ...

  8. windows下 安装 rabbitMQ 及操作常用命令

    rabbitMQ是一个在AMQP协议标准基础上完整的,可服用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...

  9. RabbitMQ + PHP (三)案例演示

    今天用一个简单的案例来实现 RabbitMQ + PHP 这个消息队列的运行机制. 主要分为两个部分: 第一:发送者(publisher) 第二:消费者(consumer) (一)生产者 (创建一个r ...

  10. RabbitMQ + PHP (二)AMQP拓展安装

    上篇说到了 RabbitMQ 的安装. 这次要在讲案例之前,需要安装PHP的AMQP扩展.不然可能会报以下两个错误. 1.Fatal error: Class 'AMQPConnection' not ...

随机推荐

  1. log4j2 扩展日志级别,支持将系统日志与业务处理日志拆分

    项目中,有时候需要对系统中已处理的一些业务数据日志进行提取分析,通常log4j默认提供的日志级别可能不够用,这时候我们就需要对日志级别进行扩展,以满足我们的需求. 本文就简单介绍一下log4j2的日志 ...

  2. Appium+python自动化8-Appium Python API【转载】

    前言: Appium Python API全集,不知道哪个大神整理的,这里贴出来分享给大家. 1.contexts contexts(self): Returns the contexts withi ...

  3. codeforces-505B

    题目连接:http://codeforces.com/contest/505/problem/B B. Mr. Kitayuta's Colorful Graph time limit per tes ...

  4. 集群/分布式/微服务/SOA 转

    https://www.cnblogs.com/Java3y/p/9479410.html 二.集群/分布式/微服务/SOA是什么? 像我这种技术小白,看到这些词(集群/分布式/微服务/SOA)的时候 ...

  5. java trim start end space

    Java program that trims starts and ends public class Program { public static String trimEnd(String v ...

  6. kotlin扩展函数

    kotlin 扩展函数 https://www.kotlincn.net/docs/reference/extensions.html 扩展函数在android中的应用 fun AppCompatAc ...

  7. [POI2014]Salad Bar

    题目大意: 一个长度为$n(n\leq10^6)$的字符串,每一位只会是$p$或$j$.你需要取出一个子串$S$(从左到右或从右到左一个一个取出),使得不管是从左往右还是从右往左取,都保证每时每刻已取 ...

  8. 四. Java继承和多态10. Java Object类

    Object 类位于 java.lang 包中,是所有 Java 类的祖先,Java 中的每个类都由它扩展而来. 定义Java类时如果没有显示的指明父类,那么就默认继承了 Object 类.例如: p ...

  9. 1,java的跨平台原理

    简单讲一下java的跨平台原理: (1)为什么跨平台: 由于各OS支持的指令集各不相同,就需要程序在不同的平台执行不同的代码 (2)JAVA是如何实现的: ava开发了适合不同的OS及不同位数的jav ...

  10. 如何避免CSS :before、:after 中文乱码

    问题: 在进行页面开发时,经常会使用:before, :after伪元素创建一些小tips,但是在:before或:after的content属性使用中文的话,会导致某些浏览器上出现乱码. 解决方案: ...