RabbitMQ是一个消息中间件,在一些需要异步处理、发布/订阅等场景的时候,使用RabbitMQ可以完成我们的需求。 下面是我在学习java语言实现RabbitMQ(自RabbitMQ官网的Tutorials)的一些记录。

首先有三个名称了解一下(以下图片来自rabbitMQ官网)

  • producer是用户应用负责发送消息

  • queue是存储消息的缓冲(buffer)

  • consumer是用户应用负责接收消息

下面是我使用rabbitMQ原生的jar包做的测试方法

maven pom.xml 加入

<dependency>

<groupId>com.rabbitmq</groupId>

<artifactId>amqp-client</artifactId>

<version>3.5.6</version>

</dependency>

方法实现示意图

发送消息方法(Send.java)

 import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class Send { private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.7");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
// "" 表示默认exchange
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'"); channel.close();
connection.close();
} }

10~16行 是获取rabbitmq.client.Channel, rabbitMQ的API操作基本都是通过channel来完成的。

18行 channel.queueDeclare(QUEUE_NAME, false, false, false, null),这里channel声明了一个名字叫“hello”的queue,声明queue的操作是幂等的,也就是说只有不存在相同名称的queue的情况下才会创建一个新的queue。

21行 channel.basicPublish("", QUEUE_NAME, null, message.getBytes()),chaneel在这个queue里发布了消息(字节数组)。

24~25行 则是链接的关闭,注意关闭顺序就好了。

接受消息方法 (Recv.java)

 import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; import java.io.IOException; public class Recv { private final static String QUEUE_NAME = "hello"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.7");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}

16~22行 和Send类中一样,也是获取同一个rabbitMQ服务的channel,这也是能接受到消息的基础。

24行 同样声明了一个和Send类中发布的queue相同的queue。

27~35行 DefaultConsumer类实现了Consumer接口,由于推送消息是异步的,因此在这里提供了一个callback来缓冲接受到的消息。

先运行Recv 然后再运行Send,就可以看到消息被接受输出到控制台了,如果多启动几个Recv,会发现消息被每个消费者按顺序分别消费了,

这也就是rabbitMQ默认采用Round-robin dispatching(轮询分发机制)。

Work queues

上面简单的实现了rabbitMQ消息的发送和接受,但是无论Send类中的queueDeclare 、basicPublish方法还有Recv类中的basicConsume方法都有很多的参数,

下面我们分析一下几个重要的参数。

(一)Message acknowledgment 消息答复

上面Recv.java的第35行中,channel.basicConsume(QUEUE_NAME, true, consumer),

在Channel接口中定义为 String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException;

这个autoAck我们当前实现为true,表示服务器会自动确认ack,一旦RabbitMQ将一个消息传递到consumer,它马上会被标记为删除状态。

这样如果consumer在正常执行任务过程中,一旦consumer服务挂了,那么我们就永远的失去了这个consumer正在处理的所有消息。

为了防止这种情况,rabbitMQ支持Message acknowledgment,当消息被一个consumer接受并处理完成后,consumer发送给rabbitMQ一个回执,然后rabbitMQ才会删除这个消息。

当一个消息挂了,rabbitMQ会给另外可用的consumer继续发送上个consumer因为挂了而没有处理成功的消息。

因此我们可以设置autoAck=false,来显示的让服务端做消息成功执行的确认。

(二)Message durability 消息持久化

Message acknowledgment 确保了consumer挂了的情况下,消息还可以被其他consumer接受处理,但是如果rabbitMQ挂了呢?

在声明队列的方法中,Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException;

durable=true 意味着该队列将在服务器重启后继续存在。Send和Recv两个类中声明队列的方法都要设置durable=true。

现在,我们需要将消息标记为持久性——通过将MessageProperties(它实现BasicProperties)设置为PERSISTENT_TEXT_PLAIN

(三)Fair dispatch 公平分发

rabbitMQ默认是轮询分发,这样对多个consumer而言,可能就会出现负载不均衡的问题,无论是任务本身难易度,还是consumer处理能力的不同,都是导致这种问题。

为了处理这种情况我们可以使用basicQos方法来设置prefetchCount = 1。 这告诉rabbitMQ一次只给consumer一条消息,换句话来说,就是直到consumer发回ack,然后再向这个consumer发送下一条消息。

int prefetchCount = 1;
channel.basicQos(prefetchCount);

正是因为Fair dispatch是基于ack的,所有它最好和Message acknowledgment同时使用,否则在autoAck=true的情况下,单独设置Fair dispatch并没有效果。

下面是本人测试以上三种情况的测试代码,可以直接使用。

import java.util.Scanner;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties; public class NewTask { private static final String QUEUE_NAME = "task_queue"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.7");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); boolean durable = true; //消息持久化
channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
// 多个消息使用空格分隔
Scanner sc = new Scanner(System.in);
String[] splits = sc.nextLine().split(" ");
for (int i = 0; i < splits.length; i++) {
channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, splits[i].getBytes());
System.out.println(" [x] Sent '" + splits[i] + "'");
} channel.close();
connection.close();
}
}
import com.rabbitmq.client.*;

import java.io.IOException;

public class Worker {

  private final static String TASK_QUEUE_NAME = "task_queue";

  public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.7");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); // basicQos方法来设置prefetchCount = 1。 这告诉RabbitMQy一次只给worker一条消息,换句话来说,就是直到worker发回ack,然后再向这个worker发送下一条消息。
channel.basicQos(1); final Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8"); System.out.println(" [x] Received '" + message + "'");
try {
doWork(message);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println(" [x] Done");
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
// 当consumer确认收到某个消息,并且已经处理完成,RabbitMQ可以删除它时,consumer会向RabbitMQ发送一个ack(nowledgement)。
boolean autoAck = true;
channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer);
} protected static void doWork(String message) throws InterruptedException {
for (char ch: message.toCharArray()) {
if (ch == '.') Thread.sleep(1000);
}
}
}

发布/订阅(Publish/Subscribe)

一个完整的rabbitMQ消息模型是会有Exchange的。

rabbitMQ的消息模型的核心思想是producer永远不会直接发送任何消息到queue中,实际上,在很多情况下producer根本不知道一条消息是否被发送到了哪个queue中。

在rabbitMQ中,producer仅仅将消息发送到一个exchange中。要理解exchange也非常简单,它一边负责接收producer发送的消息, 另一边将消息推送到queue中。

exchange必须清楚的知道在收到消息之后该如何进行下一步的处理,比如是否应该将这条消息发送到某个queue中? 还是应该发送到多个queue中?还是应该直接丢弃这条消息等。

exchange模型如下:

exchange类型也有好几种:directtopicheaders以及fanout。

Fanout exchange

下面我们来创建一个fanout类型的exchange,顾名思义,fanout会向所有的queue广播所有收到的消息。

 import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import rabbitMQ.RabbitMQTestUtil; public class EmitLog { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] argv) throws IOException, TimeoutException { ConnectionFactory factory = RabbitMQTestUtil.getConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout"); // 多个消息使用空格分隔
Scanner sc = new Scanner(System.in);
String[] splits = sc.nextLine().split(" ");
for (int i = 0; i < splits.length; i++) {
channel.basicPublish(EXCHANGE_NAME, "", null, splits[i].getBytes());
System.out.println(" [x] Sent '" + splits[i] + "'");
} channel.close();
connection.close();
}
}
 import java.io.IOException;

 import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; import rabbitMQ.RabbitMQTestUtil; public class ReceiveLogs { private static final String EXCHANGE_NAME = "logs"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = RabbitMQTestUtil.getConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, ""); Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}

Direct exchange

在fanout的exchange类型中,消息的发布已经队列的绑定方法中,routingKey参数都是默认空值,因为fanout类型会直接忽略这个值,

但是在其他exchange类型中它拥有很重要的意义,

rabbitMQ支持以上两种绑定,消息在发布的时候,会指定一个routing key,而图一中exchange会把routing key为orange发送的消息将会被路由到queue Q1中,使用routing key为black或者green的将会被路由到Q2中。

将多个queue使用相同的binding key进行绑定也是可行的。可以在X和Q1中间增加一个routing key black。 它会向所有匹配的queue进行广播,使用routing key为black发送的消息将会同时被Q1Q2接收。

下面是我测试debug和error两种routing key发布消息并接受处理消息的代码:

import java.util.Scanner;
import java.util.concurrent.TimeoutException; import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; import rabbitMQ.RabbitMQTestUtil; public class EmitLog { private static final String EXCHANGE_NAME = "direct_logs"; public static void main(String[] argv)
throws java.io.IOException, TimeoutException { ConnectionFactory factory = RabbitMQTestUtil.getConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "direct"); // 多个消息使用空格分隔
Scanner sc = new Scanner(System.in);
String[] splits = sc.nextLine().split(" ");
for (int i = 0; i < splits.length; i++) {
channel.basicPublish(EXCHANGE_NAME, splits[i], null, splits[i].getBytes());
System.out.println(" [x] Sent '" + splits[i] + "'");
} channel.close();
connection.close();
}
}
import java.io.IOException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; import rabbitMQ.RabbitMQTestUtil; public class ReceiveLogsDebug { private static final String EXCHANGE_NAME = "direct_logs"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = RabbitMQTestUtil.getConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "debug"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}
import java.io.IOException;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; import rabbitMQ.RabbitMQTestUtil; public class ReceiveLogsError { private static final String EXCHANGE_NAME = "direct_logs"; public static void main(String[] argv) throws Exception {
ConnectionFactory factory = RabbitMQTestUtil.getConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel(); channel.exchangeDeclare(EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "error"); System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}

发送输入:

debug接受:

error接受:

Topic exchange

发送到topic exchange中的消息不能有一个任意的routing_key——它必须是一个使用点分隔的单词列表。单词可以是任意的。一些有效的routing key例子:”stock.usd.nyse”,”nyse.vmw”,”quick.orange.rabbit”。

routing key的长度限制为255个字节数。

binding key也必须是相同的形式。topic exchange背后的逻辑类似于direct——一条使用特定的routing key发送的消息将会被传递至所有使用与该routing key相同的binding key进行绑定的队列中。 然而,对binding key来说有两种特殊的情况:

  1. *(star)可以代替任意一个单词
  2. #(hash)可以代替0个或多个单词

和Direct exchange差不多,代码就不copy了,有兴趣的直接看看教程http://www.rabbitmq.com/tutorials/tutorial-five-java.html

Push vs Pull

An initial question we considered is whether consumers should pull data from brokers or brokers should push data to the consumer.

传统大多数的消息系统(比如Kafka)都是采用的pull data from brokers,数据被从生产者推送到brokers,并被消费者从brokers那里拉出(Pull),

然而无论是推模式还是拉模式,都各有利弊,

基于推模式的系统可以最快速的将消息推送给消息者,但是当消费者消费速率低时,消费者会拒绝(a denial of service attack),

而且很难与多样化的消费者打交道,因为brokers控制着数据传输的速率。

基于拉模式的系统更关注消费者的消费能力,有助于对发送给使用者的数据进行积极的批处理,但是如果brokers没有数据,

那么消费者可能会在紧密的循环中进行轮询,解决方案是允许消费者请求阻塞在“长时间轮询”中,直到数据到达为止。

RabbitMQ支持两种方式

  • Have messages delivered to them ("push API")
  • Fetch messages as needed ("pull API")






RabbitMQ基础入门的更多相关文章

  1. ASP.NET Core消息队列RabbitMQ基础入门实战演练

    一.课程介绍 人生苦短,我用.NET Core!消息队列RabbitMQ大家相比都不陌生,本次分享课程阿笨将给大家分享一下在一般项目中99%都会用到的消息队列MQ的一个实战业务运用场景.本次分享课程不 ...

  2. C# 消息队列之 RabbitMQ 基础入门

    Ø  简介 C# 实现消息队列的方式有很多种,比如:MSMQ.RabbitMQ.EQueue 等,本文主要介绍使用 RabbitMQ 实现消息队列的基础入门.包括如下内容: 1.   什么是消息队列? ...

  3. RabbitMQ基础入门篇

    下载安装 Erlang RabbitMQ 启动RabbitMQ管理平台插件 DOS下进入到安装目录\sbin,执行以下命令 rabbitmq-plugins enable rabbitmq_manag ...

  4. 大话RabbitMQ 基础入门

    ----------写在前面---------- 近些年微服务越来越火,让我也忍不住想去一窥微服务究竟,讲到微服务,就离不开分布式,而分布式,也离不开消息队列,在消息队列中,RabbitMQ可以说是比 ...

  5. rabbitmq(一)-基础入门

    原文地址:https://www.jianshu.com/p/e186a7fce8cc 在学东西之前,我们先有一个方法论,知道如何学习.学习一个东西一般都遵循以下几个环节: xxx是什么,诞生的原因, ...

  6. RabbitMQ,Apache的ActiveMQ,阿里RocketMQ,Kafka,ZeroMQ,MetaMQ,Redis也可实现消息队列,RabbitMQ的应用场景以及基本原理介绍,RabbitMQ基础知识详解,RabbitMQ布曙

    消息队列及常见消息队列介绍 2017-10-10 09:35操作系统/客户端/人脸识别 一.消息队列(MQ)概述 消息队列(Message Queue),是分布式系统中重要的组件,其通用的使用场景可以 ...

  7. Rabbit MQ 基础入门

    Rabbit MQ 学习(一)基础入门 简介 RabbitMQ 简介 为什么选择 RabbitMQ RabbitMQ 的模型架构是什么? AMQP 协议是什么? AMQP 常用命令 概念 生产者和消费 ...

  8. SpringCloudStream学习(一)RabbitMQ基础

    应公司大佬要求,学习一下SpringCloudStream,作为技术储备.这几天也看了这方面的资料,现在写一篇笔记,以做总结.文章会从RabbitMQ基础讲起,到SpringCloudStream结束 ...

  9. RabbitMQ由浅入深入门全总结(一)

    写在最前面 距离上一次发文章已经很久了,其实这段时间一直也没有停笔,只不过在忙着找工作还有学校结课的事情,重新弄了一下博客,后面也会陆陆续续会把文章最近更新出来~ 这篇文章有点长,就分了两篇Q PS: ...

随机推荐

  1. PHP-CGI,FASTcgi,php-fpm,之间的关系?

    刚开始对这个问题我也挺纠结的,看了<HTTP权威指南>后,感觉清晰了不少.首先,CGI是干嘛的?CGI是为了保证web server传递过来的数据是标准格式的,方便CGI程序的编写者.   ...

  2. DataTables ajax + bootstrap 分页/搜索/排序/常见问题

    最近学校的网站建设需要,尝试使用了下Jquery dataTables控件,接触过C#的人都知道,C#中也含有一个DataTable,但它和我们今天讨论的东西无关 我使用的是官网最新的DataTabl ...

  3. 听翁恺老师mooc笔记(4)--指针的应用场景

    指针应用场景一:交换两个变量的值 在学习函数时,交换两个数的值,做一个swap函数,传递值进去,也可以将两个值交换过来,没问题,可是离开swap就没有用了,为什么?因为传进去的是两个值. #inclu ...

  4. C语言博客-指针

    一.PTA实验作业(5分) 题目1:6-1 两个4位正整数的后两位互换 1. 本题PTA提交列表 2. 设计思路 3.代码截图 4.本题调试过程碰到问题及PTA提交列表情况说明. 无 题目2:6-3 ...

  5. 2018年3月份的PTA(一)

    写程序证明p++等价于(p)++还是等价于(p++)? 由程序说明p++等价于(p)++,因为(p++)在程序中是没有地址的,而输出中p++和(p)++的地址不同是由于在线C语言开发环境地址是动态的 ...

  6. PTA題目的處理(三)

    题目7-1 高速公路超速處罰 1.實驗代碼 #include <stdio.h> //#include <stdlib.h> int main() { int csp,lsp; ...

  7. 关于安装win7系统时出现0x0000007b电脑蓝屏代码的问题

    问题解析: 0X0000007B 这个错误网上都说是sata硬盘的什么引导模式的原因引起. 在网上查找了很久,大概引起错误的原因就是:sata和ide两种模式不同,前者可以装win7系统,后者是xp系 ...

  8. python性能分析--cProfile

    Python标准库中提供了三种用来分析程序性能的模块,分别是cProfile, profile和hotshot,另外还有一个辅助模块stats.这些模块提供了对Python程序的确定性分析功能,同时也 ...

  9. php的开发的apache的配置及伪静态的应用

    1.Apache之所以能够解析php代码是游览器首先发送数据到模版页面,然后模版页提交数据到php页面,然后php代码经过Apache解析过后生成结果的,所以是 在Apache的配置文件中是可以看到开 ...

  10. 解决IE下a标签点击有虚线边框的问题

    解决IE下a标签点击有虚线边框的问题 关键词:IE去除虚线边框.IE解决a标签虚线问题 先看看IE下,a标签出现的虚线边框问题: (上面中,红线包裹的就是一个翻页的按钮,按钮实际是hml的a标签做的, ...