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. 初始Windows程序

    1.属性 窗体标题 Name  窗体的图标 Icon  背景图片 BackgroundImage 背景颜色 BackColor  最大化按钮 MaxIMonBox  最小化按钮 Minimun 窗体边 ...

  2. React demo:express、react-redux、react-router、react-roter-redux、redux-thunk(一)

    近期终于把之前留下的坑填上了(说了好久的要网站重写,总算是写完了),不过最后的在线添加文章,功能虽然做了,后台没把接口加上,实在是没精力去折腾了,公司又有事要忙,现在把从0开始到完成的一个思路来写一下 ...

  3. 【IntelliJ IDEA】使用idea解决新建jsp文件而找不到jsp文件模版的新建选项

    使用idea解决新建jsp文件而找不到jsp文件模版的新建选项,这样每次创建一个新的jsp文件岂不是很耗时间? 解决办法: 就是要让idea知道你需要在这个目录下创建jsp文件 左上角,file中点击 ...

  4. Alpha冲刺No.1

    冲刺Day1 一.站立式会议计划 全体成员先安装好Android Studio,mysql,以及navicat for MySQL 将上述软件调试至可运行状态 自主把玩安卓虚拟机,mysql 通过一些 ...

  5. 那些在django开发中遇到的坑

    1. 关于csrf错误 CSRF(Cross-site request forgery)跨站请求伪造,也被称为“one click attack”或者session riding,通常缩写为CSRF或 ...

  6. Python基于共现提取《釜山行》人物关系

    Python基于共现提取<釜山行>人物关系 一.课程介绍 1. 内容简介 <釜山行>是一部丧尸灾难片,其人物少.关系简单,非常适合我们学习文本处理.这个项目将介绍共现在关系中的 ...

  7. iOS 简易无限滚动的图片轮播器-SDCycleScrollView

    @interface ViewController () <</span>SDCycleScrollViewDelegate> @end @implementation Vie ...

  8. nyoj 还是回文

    还是回文 时间限制:2000 ms | 内存限制:65535 KB 难度:3 描述 判断回文串很简单,把字符串变成回文串也不难.现在我们增加点难度,给出一串字符(全部是小写字母),添加或删除一个字符, ...

  9. 基本数据类型 Symbol

    ES6 规范之前, JavaScript 一共有六种数据类型,分别是五种基本数据类型: string . number , boolean , null , undefined ,和一种引用数据类型: ...

  10. Python format 格式化函数

    str.format() 格式化字符串的函数 str.format(),它增强了字符串格式化的功能. 基本语法是通过 {} 和 : 来代替以前的 % format 函数可以接受不限个参数,位置可以不按 ...