【译】RabbitMQ教程一

  • 主要通过Hello Word对RabbitMQ有初步认识

【译】RabbitMQ教程二

  • 工作队列,即一个生产者对多个消费者
  • 循环分发、消息确认、消息持久、公平分发

【译】RabbitMQ教程三

  • 如何同一个消息同时发给多个消费者
  • 开始引入RabbitMQ消息模型中的重要概念路由器Exchange以及绑定等
  • 使用了fanout类型的路由器

【译】RabbitMQ教程四

  • 如何选择性地接收消息
  • 使用了direct路由器

【译】RabbitMQ教程五

  • 如何通过多重标准接收消息
  • 使用了topic路由器,可通过灵活的路由键和绑定键的设置,
    进一步增强消息选择的灵活性

【译】RabbitMQ教程六

  • 如何使用RabbitMQ实现一个简单的RPC系统
  • 回调队列callback queue和关联标识correlation id

各教程代码


RabbitMQ 一般工作流程

生产者和RabbitMQ服务器建立连接和通道,声明路由器,同时为消息设置路由键,这样,所有的消息就会以特定的路由键发给路由器,具体路由器会发送到哪个或哪几个队列,生产者在大部分场景中都不知道。(1个路由器,但不同的消息可以有不同的路由键)。
消费者和RabbitMQ服务器建立连接和通道,然后声明队列,声明路由器,然后通过设置绑定键(或叫路由键)为队列和路由器指定绑定关系,这样,消费者就可以根据绑定键的设置来接收消息。(1个路由器,1个队列,但不同的消费者可以设置不同的绑定关系)。


主要方法

  • 声明队列(创建队列):可以生产者和消费者都声明,也可以消费者声明生产者不声明,也可以生产者声明而消费者不声明。最好是都声明。(生产者未声明,消费者声明这种情况如果生产者先启动,会出现消息丢失的情况,因为队列未创建)
  1. channel.queueDeclare(String queue, //队列的名字
  2. boolean durable, //该队列是否持久化(即是否保存到磁盘中)
  3. boolean exclusive,//该队列是否为该通道独占的,即其他通道是否可以消费该队列
  4. boolean autoDelete,//该队列不再使用的时候,是否让RabbitMQ服务器自动删除掉
  5. Map<String, Object> arguments)//其他参数
  • 声明路由器(创建路由器):生产者、消费者都要声明路由器---如果声明了队列,可以不声明路由器。
  1. channel.exchangeDeclare(String exchange,//路由器的名字
  2. String type,//路由器的类型:topic、direct、fanout、header
  3. boolean durable,//是否持久化该路由器
  4. boolean autoDelete,//是否自动删除该路由器
  5. boolean internal,//是否是内部使用的,true的话客户端不能使用该路由器
  6. Map<String, Object> arguments) //其他参数
  • 绑定队列和路由器:只用在消费者
  1. channel.queueBind(String queue, //队列
  2. String exchange, //路由器
  3. String routingKey, //路由键,即绑定键
  4. Map<String, Object> arguments) //其他绑定参数
  • 发布消息:只用在生产者
  1. channel.basicPublish(String exchange, //路由器的名字,即将消息发到哪个路由器
  2. String routingKey, //路由键,即发布消息时,该消息的路由键是什么
  3. BasicProperties props, //指定消息的基本属性
  4. byte[] body)//消息体,也就是消息的内容,是字节数组
  • BasicProperties props:指定消息的基本属性,如deliveryMode为2时表示消息持久,2以外的值表示不持久化消息
  1. //BasicProperties介绍
  2. String corrId = "";
  3. String replyQueueName = "";
  4. Integer deliveryMode = 2;
  5. String contentType = "application/json";
  6. AMQP.BasicProperties props = new AMQP.BasicProperties
  7. .Builder()
  8. .correlationId(corrId)
  9. .replyTo(replyQueueName)
  10. .deliveryMode(deliveryMode)
  11. .contentType(contentType)
  12. .build();
  • 接收消息:只用在消费者
  1. channel.basicConsume(String queue, //队列名字,即要从哪个队列中接收消息
  2. boolean autoAck, //是否自动确认,默认true
  3. Consumer callback)//消费者,即谁接收消息
  • 消费者中一般会有回调方法来消费消息
  1. Consumer consumer = new DefaultConsumer(channel) {
  2. @Override
  3. public void handleDelivery(String consumerTag, //该消费者的标签
  4. Envelope envelope,//字面意思为信封:packaging data for the message
  5. AMQP.BasicProperties properties, //message content header data
  6. byte[] body) //message body
  7. throws IOException {
  8. //获取消息示例
  9. String message = new String(body, "UTF-8");
  10. //接下来就可以根据消息处理一些事情
  11. }
  12. };

路由器类型

  • fanout:会忽视绑定键,每个消费者都可以接受到所有的消息(前提是每个消费者都要有各自单独的队列,而不是共有同一队列)。
  • direct:只有绑定键和路由键完全匹配时,才可以接受到消息。
  • topic:可以设置多个关键词作为路由键,在绑定键中可以使用*#来匹配
  • headers:(可以忽视它的存在)

教程一 HelloWorld

看主要代码

  1. //生产者
  2. channel.queueDeclare(QUEUE_NAME, false, false, false, null); ----①
  3. String message = "Hello World!";
  4. channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
  5. //消费者
  6. channel.queueDeclare(QUEUE_NAME, false, false, false, null);
  7. channel.basicConsume(QUEUE_NAME, true, consumer);

这里,生产者和消费者都没有声明路由器,而是声明了同名的队列。生产者发布消息时,使用了默认的无名路由器(""),并以队列的名字作为了路由键。消费者在消费时,由于没有声明路由器,这并不表示没有路由器的存在,消费者此时使用的是默认的路由器,即Default exchange,该路由器和所有的队列都进行绑定,并且使用队列的名字作为了路由键进行绑定。所以,生产者使用默认路由器以队列的名字作为了绑定键进行了消息发布,而消费者也使用了默认的路由器,并以队列的名字作为绑定键进行了绑定。而默认路由器是direct类型,路由键和绑定键完全匹配时,消费者才能接受到消息,所以教程1中的消费者可以接收到消息。(为了认证这一点,可以将代码①去掉,然后先运行消费者,让它等待监听,然后启动生产者,发送消息,消费者同样会收到消息。这里的生产者声明队列,只是让RabbitMQ服务器先创建这个队列,以免发送的消息因为找不到队列而丢失。)


教程二 Work Queues

看主要代码

  1. //生产者
  2. channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
  3. String message = "1.";
  4. channel.basicPublish("", TASK_QUEUE_NAME,
  5. MessageProperties.PERSISTENT_TEXT_PLAIN,
  6. message.getBytes("UTF-8"));
  7. //消费者
  8. channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
  9. channel.basicQos(1);---①
  10. ...channel.basicAck(envelope.getDeliveryTag(), false);...---③
  11. boolean autoAck = false;---②
  12. channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer);

这里也使用了默认的direct路由器。假如启动多个工作者(消费者),按道理这些工作者应该可以接收到所有的消息啊,但是不要忘了这几个工作者都是从同一个队列中取消息,消息取出一个,队列中就少一个,所以每个工作者都只是收到的消息的一部分。既然这几个工作者都从同一个队列中取消息,那每个工作者应该怎么取呢?

如果没有代码①,并且②设置为true,即自动确认收到消息,RabbitMQ只要发出消息就认为消费者收到了,此时RabbitMQ采取的是循环分发的策略,在这几个工作者中循环轮流分发消息。每个工作者接受到的消息数量都是相同的。
如果有代码①,并且②设置为false,则RabbitMQ会采取公平分发策略,即将消息发给空闲的工作者(空闲,工作者将消息处理完毕,执行了代码③;不空闲,即工作者还在处理消息,还没有给RabbitMQ发回确认信息,即还没有执行代码③)。
代码①中的参数1:(prefetchCount)maximum number of messages that the server will deliver

为了防止队列丢失,在声明队列的时候指定了durabletrue。为了防止消息丢失,设置了消息属性BasicPropertiesMessageProperties.PERSISTENT_TEXT_PLAIN,让我们看看值是什么:

 
MessageProperties.PERSISTENT_TEXT_PLAIN

可以看出里面包含了deliveryMode=2。从这张图也可以看到BasicProperties属性的全貌。

如果想让多个消费者共同消费某些消息,只要让他们共用同一队列即可(当然前提是你得保证消息可以都进到这个队列中来,如本例中使用direct路由器,消息的路由键和队列的绑定键设为一致,当然也可以使用fanout路由器,路由键和绑定键随意设置,不一致也能收到,因为fanout路由器会忽略路由键的设置)。


教程三 Publish/Subscribe

看主要代码

  1. //生产者
  2. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
  3. channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
  4. //消费者
  5. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
  6. String queueName = channel.queueDeclare().getQueue();---①
  7. channel.queueBind(queueName, EXCHANGE_NAME, "");
  8. channel.basicConsume(queueName, true, consumer);

教程三才引出路由器的概念。生产者和消费者声明了同样的路由,并指明路由类型为fanout,该路由器会忽视路由键,将消息发布到所有绑定的队列中(仍需要绑定,只是绑定时绑定键任意就行了)。
假如启动多个消费者,因为代码①中调用无参的声明去恶劣方法channel.queueDeclare(),就会创建了一个非持久、独特的、自动删除的队列,并返回一个自动生成的名字。所以多个消费者取消息时使用的是各自的队列,不会存在多个消费者从同一个队列取消息的情况。
这样多个消费者就可以接收到同一消息。

如果想实现多个消费者都可以接收到所有的消息,只要让他们各自使用单独的队列即可(当然前提是保证路由键和绑定键的设置可以让消息都进入到队列,如本例中使用fanout路由器,无需考虑绑定键和路由键)。


教程4 Routing

看主要代码:

  1. //生产者
  2. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
  3. channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
  4. //消费者
  5. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
  6. String queueName = channel.queueDeclare().getQueue();
  7. String[] severities = {"info", "warning", "error"};
  8. for (String severity : severities) {
  9. channel.queueBind(queueName, EXCHANGE_NAME, severity);
  10. }
  11. channel.basicConsume(queueName, true, consumer);

可以看出,教程3使用了direct路由器,该路由器的特点是可以设定路由键和绑定键,消费者只能从队列中取出两者匹配的消息。
在生产者发消息时,为消息设置不同的路由键(如例子中severity可以设为infowarnerror)。
消费者在通过为队列设置多个绑定关系,来选择想要接收的消息。
这里有一个概念叫做多重绑定,即多个队列以相同的绑定键binding key绑定到同一个路由器上,此时direct路由器就会像fanout路由器一样,将消息广播给所有符合路由规则的队列。


教程5 Topics

看主要代码:

  1. //生产者
  2. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
  3. String routingKey = "";
  4. String message = "msg...";
  5. channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
  6. //消费者
  7. channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
  8. String queueName = channel.queueDeclare().getQueue();
  9. String bingingKeys[] = {""};
  10. for (String bindingKey : bingingKeys) {
  11. channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
  12. }
  13. channel.basicConsume(queueName, true, consumer);

这里使用了topic路由器,它与direct路由器类似,不同在于,topic路由器可以为路由键设置多重标准。一个消息有一个路由键,direct路由器只能为路由键指定一个关键字,但是topic路由器可以在路由键中通过点号分割多个单词来组成路由键,消费者在绑定的时候,可以设置多重标准来选择接受。
举个例子:假如日志根据严重级别infowarnerror,也可以根据来源分为cronkernauth。某个日志消息设置路由键为kern.info,表示来自kerninfo级别的日志。想要选择接收消息的时候,direct路由器就办不到,它要么可以根据严重级别来筛选,要么根据来源来筛选,而topic路由器则可以轻松应对,只要将绑定键设置为kern.info就可以精准获取该类型的日志。


教程6 Remote Procedure Call

教程6属于RabbitMQ在RPC领域的应用,与上面几个教程的内容没有多大衔接,可以直接阅读原文。

作者:maxwellyue
链接:https://www.jianshu.com/p/a6460b4b155f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

RabbitMQ教程总结的更多相关文章

  1. RabbitMQ教程(一)——安装配置

    RabbitMQ教程(一)——安装配置 一.前言 由于最近在学习RabbitMQ消息队列,但是鉴于网上对于官网介绍的教程比较少或者由于时间长长期未更新,因此决定将对官网的RabbitMQ入门教程进行翻 ...

  2. [译]RabbitMQ教程C#版 - “Hello World”

    [译]RabbitMQ教程C#版 - “Hello World”   先决条件本教程假定RabbitMQ已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需 ...

  3. 【详细】【转】C#中理解委托和事件 事件的本质其实就是委托 RabbitMQ英汉互翼(一),RabbitMQ, RabbitMQ教程, RabbitMQ入门

    [详细][转]C#中理解委托和事件   文章是很基础,但很实用,看了这篇文章,让我一下回到了2016年刚刚学委托的时候,故转之! 1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托 ...

  4. PHP RabbitMQ 教程(三)

    发布/订阅 我们在上一节创建了一个工作队列,并假定队列对应的任务传送给了某个客户端.在这一章节我们会做一些完全不一样的东西–我们会发送一条消息到多个消费者,也称之为"发布/订阅"模 ...

  5. 保姆级别的RabbitMQ教程!一看就懂!(有安装教程,送安装需要的依赖包,送Java、Golang两种客户端教学Case)

    保姆级别的RabbitMQ教程!一看就懂!(有安装教程,送安装需要的依赖包,送Java.Golang两种客户端教学Case)   目录 什么是AMQP 和 JMS? 常见的MQ产品 安装RabbitM ...

  6. RabbitMQ教程C#版 - Hello World

    先决条件 本教程假定RabbitMQ已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难,可以 ...

  7. [译]RabbitMQ教程C#版 - 工作队列

    先决条件 本教程假定RabbitMQ已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难,可以 ...

  8. [译]RabbitMQ教程C#版 - 远程过程调用(RPC)

    先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...

  9. [译]RabbitMQ教程C#版 - 主题

    先决条件 本教程假定 RabbitMQ 已经安装,并运行在localhost标准端口(5672).如果你使用不同的主机.端口或证书,则需要调整连接设置. 从哪里获得帮助 如果您在阅读本教程时遇到困难, ...

随机推荐

  1. javascript; JS版HtmlEncode方法,结果与C#中HttpUtility.HtmlEncode方法一样。

    <script type="text/javascript"> function HTMLEncode(html) { var temp = document.crea ...

  2. des加密——补齐

    下面这个网址(英文)介绍的比较全面. http://www.di-mgt.com.au/cryptopad.html

  3. 020_自己编写的wordcount程序在hadoop上面运行,不使用插件hadoop-eclipse-plugin-1.2.1.jar

    1.Eclipse中无插件运行MP程序 1)在Eclipse中编写MapReduce程序 2)打包成jar包 3)使用FTP工具,上传jar到hadoop 集群环境 4)运行 2.具体步骤 说明:该程 ...

  4. PHP汉子转拼音

    <?php /** +------------------------------------------------------ * PHP 汉字转拼音 +------------------ ...

  5. 【Flask】ORM关系以及一对多

    ### ORM关系以及一对多:mysql级别的外键,还不够ORM,必须拿到一个表的外键,然后通过这个外键再去另外一张表中查找,这样太麻烦了.SQLAlchemy提供了一个`relationship`, ...

  6. Windos Server 2008 FTP 服务安装

    安装服务:FTP 系统环境:Windos 2008 R2 x64 安装FTP服务 管理-->角色-->添加角色-->Web 服务器 IIS 测试

  7. debian下使用dynamic printk分析usb转串口驱动执行流程

    看了一篇文章<debug by printing>,文中提到了多种通过printk来调试驱动的方法,其中最有用的就是"Dynamic debugging". “Dyna ...

  8. Kubernetes pod网络解析

    在Kubernetes中,会为每一个pod分配一个IP地址,pod内的所有容器都共享这个pod的network namespace,彼此之间使用localhost通信. 那么pod内所有容器间的网络是 ...

  9. C++中随机数的生成

    1.随机数由生成器和分布器结合产生 生成器generator:能够产生离散的等可能分布数值 分布器distributions: 能够把generator产生的均匀分布值映射到其他常见分布,如均匀分布u ...

  10. ubuntu 忘记root密码

    Ubuntu14.04系统中,因为误操作导致管理员密码丢失或无效,并且忘记root密码,此时无法进行任何root/sudo权限操作.可以通过GRUB重新设置root密码,并恢复管理员账户到正常状态. ...