一小时彻底搞懂RabbitMQ
windows上面安装rabbitmq-server-3.7.4.exe
首先需要安装otp_win64_20.3.exe
步骤1:安装Erlang
RabbitMQ 它依赖于Erlang,需要先安装Erlang。首先确定你的window电脑是32位还是64位,以下的安装以window 64位电脑举例。
Erlang官网:http://www.erlang.org/
rabbit安装注意事项:需要安装erlang20.3以上的版本
erlang21_win64下载地址:https://download.csdn.net/download/qq_38862234/10965869
环境配置:修改系统属性path下添加erlang的路径
安装的是不能存在中文目录,对于erl配置系统环境变量的时候,计算名称也不能存在中文
这样就表示erl环境变量安装成功了
Rabbitmq安装的时候不能存在中文,Rabbitmq安装成功之后,在windows的程序窗口会看到下面的菜单选项
1、windows安装成功之后,我们要使用Rabbitmq提供的管理控制台,我们需要开启管理控制台的插件命令,我们进入到D:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.4\sbin目目录下,使用命令rabbitmq-plugins.bat安装下,插件安装完成之后,需要对rabbitmq服务进行重启,浏览器输入http://localhost:15672/,登录的用户名和密码都是guest,这样登录成功之后,说明应该就安装安装
d:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.\sbin>rabbitmq-plugins.bat enable rabbitmq_management
接下来我们要创建用户admin 密码和12345,已经对应的vhost
rabbitmq支持6种消息模式
每一种模式我们都需要进行详细的讲解和分析
简单模式Hello World
使用ConnectionFactory创建连接,实质上,此连接就是一个Socket连接,设置host为localhost来连接本地的RabbitMQ Server,设置Port为5672(默认队列连接端口),设置用户名和密码,如果未设置用户名和密码将默认使用guest/guest口令(当然,此口令仅能在localhost本地使用)。
使用channel.queueDeclare()来定义队列,在RabbitMQ中,队列仅能够创建一次,如果发现已经存在此队列,将会忽略此方法。
功能:一个生产者P发送消息到队列Q,一个消费者C接收
生产者实现思路:
创建连接工厂ConnectionFactory,设置服务地址127.0.0.1,端口号5672,设置用户名、密码、virtual host,从连接工厂中获取连接connection,使用连接创建通道channel,使用通道channel创建队列queue,使用通道channel向队列中发送消息,关闭通道和连接。
channel.QueueDeclare(queue: "hello",
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
queue: 队列名称
durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
exclusive:是否排外的,有两个作用,一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常:com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'queue_name' in vhost '/', class-id=50, method-id=20)一般等于true的话用于一个队列只能有一个消费者来消费的场景
autoDelete:是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
arguments:
队列中的消息什么时候会自动被删除?
Message TTL(x-message-ttl):设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒, 类似于redis中的ttl,生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除, 特性Features=TTL, 单独为某条消息设置过期时间AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”);
channel.basicPublish(EXCHANGE_NAME, “”, properties.build(), message.getBytes(“UTF-8”));
Auto Expire(x-expires): 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp
Max Length(x-max-length): 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=Lim
Max Length Bytes(x-max-length-bytes): 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B
Dead letter exchange(x-dead-letter-exchange): 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX
Dead letter routing key(x-dead-letter-routing-key):将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK
Maximum priority(x-max-priority):优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费,
Lazy mode(x-queue-mode=lazy): Lazy Queues: 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中
Master locator(x-queue-master-locator)
http://www.voidcn.com/article/p-dcbgmfmb-bpw.html
参加博客:https://www.cnblogs.com/leocook/p/mq_rabbitmq_3.html
第一种
void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
throws IOException;
exchange 交换器名称
routingKey 路由键
props 有14个成员
我们来看下代码
项目的pom.xml的依赖如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.</modelVersion>
<groupId>com</groupId>
<artifactId>com.rabbitmq.test</artifactId>
<version>0.0.-SNAPSHOT</version> <dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
生产者的代码如下
package com.rabbitmq.test1;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
//
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "hello world";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘"); } }
这里最关键的就是两个地方,第一个就是channel.queueDeclare(QUEUE_NAME, false, false, false, null);
queue: 队列名称
durable: 是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
exclusive:是否排外的,有两个作用,一:当连接关闭时connection.close()该队列是否会自动删除;二:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常:com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=405, reply-text=RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'queue_name' in vhost '/', class-id=50, method-id=20)一般等于true的话用于一个队列只能有一个消费者来消费的场景
autoDelete:是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
arguments:
队列中的消息什么时候会自动被删除?
Message TTL(x-message-ttl):设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒, 类似于redis中的ttl,生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除, 特性Features=TTL, 单独为某条消息设置过期时间AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”);
channel.basicPublish(EXCHANGE_NAME, “”, properties.build(), message.getBytes(“UTF-8”));
Auto Expire(x-expires): 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp
Max Length(x-max-length): 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=Lim
Max Length Bytes(x-max-length-bytes): 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B
Dead letter exchange(x-dead-letter-exchange): 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX
Dead letter routing key(x-dead-letter-routing-key):将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK
Maximum priority(x-max-priority):优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费,
第二个就是:channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
因为我们使用一个默认exchange,我们使用(””)来标识的。QUEUE_NAME队列的名称
package com.rabbitmq.test1; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.println("消费端接收消息:" + message);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
两个消费者,消费者1的代码如下
package com.rabbitmq.test2; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端1接收消息:" + message);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
package com.rabbitmq.test1;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "hello"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
//
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "hello world";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘"); } }
我们来看下web管理控制台的展示
队列中可以看到我们创建的hello,点击hello可以看到队列中的具体指标
在创建队列的时候,可以指定队列中的消息是否持久化、队列中消息的具体参数TTL、是否过期、最大长度等
在Exchange交换页面,可以看到系统在对于的虚拟机下面会给我们创建6中默认的交换机类型
我们也可以自己创建一个Exchange
在概览页面可以查看当前消息实例的具体的信息有多少connection、channel、交换机、队列、消费这等信息
connection下面可以查看当前的connection的具体信息,以及当前connection下面存在多少个channel
在channel下面可以查看当前channel的具体流量信息
RabbitMQ学习总结 第三篇:工作队列Work Queue
生产者可以产生多条消息,但是每一条消息默认只能被一个消费者消费,c1和c2默认采用轮询的策略消费
我们模拟生产者产生20条消息
package com.rabbitmq.test2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//
for(int i = ;i < ;i++){
String message = "hello world"+i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘");
Thread.sleep(i); } channel.close();
connection.close(); } }
消费者2的代码如下
package com.rabbitmq.test2; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer2 { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端2接收消息:" + message);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
我们来看下日志的打印
生产者产生的20条消息,消费者1消费了10条,消费者2默认消费了10条,使用的是轮询的策略,默认情况下,RabbitMQ会把每个消息以此轮询发到各个消费者那,把消息平均的发到各个消费者那。这种分配管理的方式叫轮询,还可以测试多个worker的情形。
但是上面的这种work模式存在一定的问题,
你可能会注意到有的时候RabbitMQ不能像你预想中的那样分发消息。例如有两个worker,第奇数个消息对应的任务都很耗时,第偶数个消息对应的任务都很快就能执行完。这样的话其中有个worker就会一直都很繁忙,另外一个worker几乎不做任务。RabbitMQ不会去对这种现象做任何处理,依然均匀的去推送消息。
这是因为RabbitMQ在消息被生产者推送过来后就被推送到消费者端,它不会去查看未接收到消费者确认的消息数量。它只会把N个消息均与的分发到N个消费者那。
为了能解决这个问题,我们可以使用basicQos放来来设置消费者最多会同时接收多少个消息。这里设置为1,表示RabbitMQ同一时间发给消费者的消息不超过一条。这样就能保证消费者在处理完某个任务,并发送确认信息后,RabbitMQ才会向它推送新的消息,在此之间若是有新的消息话,将会被推送到其它消费者,若所有的消费者都在处理任务,那么就会等待。,这里我们来看下消费者代码的修改
我们来看下代码,注意修改消费者的代码
package com.rabbitmq.test2; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端1接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者的代码需要修改下面的三个地方
1、将消费的自动ack修改为需要人为的手动ack
channel.basicConsume(QUEUE_NAME, false, consumer);
2、增加手动ack消息的代码,ack不需要批量回复
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
3、增加代码 channel.basicQos(1);
修改之后我们来看下日志的打印
RabbitMQ学习总结 第四篇:发布/订阅 Publish/Subscribe
上篇中我们实现了Work Queue的创建,在Work Queue背后,其实是rabbitMQ把每条任务消息只发给一个消费者。本篇中我们将要研究如何把一条消息推送给多个消费者,这种模式被称为publish/subscribe(发布/订阅)。
接下来我们实现发布一条消息,多个消费者都能够接受到
生产者者可以把消息发送到exchange(消息交换机)上。exchange是一个很简单的事情,它一边接收生产者的消息,另一边再把消息推送到消息队列中。Exchange必须知道在它接收到一条消息时应该怎么去处理。应该把这条消息推送到指定的消息队列中?还是把消息推送到所有的队列中?或是把消息丢掉?这些规则都可以用exchange类型来定义。
有一些可用的exchange类型:direct, topic, headers和fanout。这里我们主要看最后一个:fanout,这里我们创建一个名字为logs、类型为fanout的exchange:
channel.exchangeDeclare("logs", "fanout");
fanout类型的exchange是很简单的。就是它把它能接收到的所有消息广播到它知道的所有队列中
我们来看下代码
发送日志消息的生产者程序和之前的程序没有太多的差别。最大的区别就是我们把消息推送到一个命名的exchange上,而不是之前未命名的默认exchange。在我们发送消息时需要提供一个routingKey,但对于fanout类型的exchange可以忽略。下边是生产者的代码EmitLog.java:
package com.rabbitmq.test3;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
//channel发送消息到交换机,采用fanout类型
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//
for(int i = ;i < ;i++){
String message = "hello world"+i;
channel.basicPublish(EXCHANGE_NAME,"", null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘");
Thread.sleep(i); } channel.close();
connection.close(); } }
改动点有下面的三处:
1、channel与交换机进行绑定,数据channel发送到Exchange中,交换机采用fanout类型,把消息发送到全部和改Exchange绑定的队列中
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
2、发送消息的时候,是发送给交换机,不再是发送给消息队列
channel.basicPublish(EXCHANGE_NAME,"", null, message.getBytes());
我们再来看消费者的代码
1、消费者1的队列是fanout1,需要将fanout1队列和生产者的Exchange进行绑定
2、消费者2的队列是fanout2,需要将fanout2队列和生产者的Exchange进行绑定
2、生产者发布了5条消息,消费者1和消费者2都能够收到这5条消息,我们来看下代码
package com.rabbitmq.test3; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout1";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, ""); Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端1接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
package com.rabbitmq.test3; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer2 { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout2";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
//设置消费者预取得消费的数量
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "");
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端2接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//这里第二个参数要设置为false,表示取消自动ack,需要手动设置ack
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
我们来看下日志的打印信息如下
RabbitMQ学习总结 第五篇:路由Routing
上面的publish模式中,Exchange会把全部的消息发送给与之绑定的队列中,下面我们可以采用路由的模式,Exchange只把消息发送到指定的队列中,使用direct模式
这里生产者需要做下面的修改,就是需要发送消息的时候,需要指定交换机依据那种bingkey进行发送,这里制定bingkey为error
package com.rabbitmq.test4;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
//channel发送消息到交换机,采用fanout类型
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
//channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//
for(int i = ;i < ;i++){
String message = "hello world"+i;
channel.basicPublish(EXCHANGE_NAME,"error", null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘");
Thread.sleep(i); } channel.close();
connection.close(); } }
生产者修改的代码如下:
1、指定交换机类型为 channel.exchangeDeclare(EXCHANGE_NAME, "direct");
2、指定交换机和队列绑定的key为error
channel.basicPublish(EXCHANGE_NAME,"error", null, message.getBytes());
消费者的代码需要修改为,当消费者队列与交换机绑定的时候,需要指定对于的bingkey
消费者1的代码为,bingkey为info, channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "info");
package com.rabbitmq.test4; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout1";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "info"); Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端1接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
消费者2的代码为,bingkey为error, channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "error");
package com.rabbitmq.test3; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer2 { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout2";
private static final String EXCHANGE_NAME = "public-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
//设置消费者预取得消费的数量
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "info");
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端2接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//这里第二个参数要设置为false,表示取消自动ack,需要手动设置ack
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
因为消费者2指定的bingkey和生产者中的bingkey一样,所以消费者2能够收到生产者发送的数据,消费者1收不到,消费者1没有任何日志的打印
我们来看下日志
RabbitMQ学习总结 第六篇:Topic类型的exchange
上面的交换机中的direct类型中,bingkey都必须是很明确的error或者info,如果bingkey要想使用通配符的模式,能否实现了,可以使用交换机的Topic模式
这些binding可以总结为:
- Q1对所有橘色的(orange)的动物感兴趣;
- Q2希望能拿到所有兔子的(rabbit)信息,还有比较懒惰的(lazy.#)动物信息。
一条以” quick.orange.rabbit”为routing key的消息将会推送到Q1和Q2两个queue上,routing key为“lazy.orange.elephant”的消息同样会被推送到Q1和Q2上。但如果routing key为”quick.orange.fox”的话,消息只会被推送到Q1上;routing key为”lazy.brown.fox”的消息会被推送到Q2上,routing key为"lazy.pink.rabbit”的消息也会被推送到Q2上,但同一条消息只会被推送到Q2上一次。
如果在发送消息时所指定的exchange和routing key在消费者端没有对应的exchange和binding key与之绑定的话,那么这条消息将会被丢弃掉。例如:"orange"和"quick.orange.male.rabbit"。但是routing为”lazy.orange.male.rabbit”的消息,将会被推到Q2上。
我们来看下代码
生产者的代码
package com.rabbitmq.test5;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class MessageProvider { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "worker";
private static final String EXCHANGE_NAME = "topic-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory(); factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01"); //获得连接:Rabbitmq中对于的connection
Connection connection = factory.newConnection();
//从connection中获得对应的channel
Channel channel = connection.createChannel();
//channel发送消息到交换机,采用fanout类型
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//
for(int i = ;i < ;i++){
String message = "hello world"+i;
channel.basicPublish(EXCHANGE_NAME,"lazy.1", null, message.getBytes());
System.out.println(" 生产消息:‘" + message + "‘");
Thread.sleep(i); } channel.close();
connection.close(); } }
生产者使用topic模式,bingkey为"lazy.1",那么消费者的bingkey为lazy.1和lazy.*都可以收到生产者发送的消息,*表示通配符全部所有的意思
我们来看消费者的全部代码为
package com.rabbitmq.test5; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout1";
private static final String EXCHANGE_NAME = "topic-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "lazy.1"); Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端1接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//true 异步接收消息
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
package com.rabbitmq.test5; import java.io.IOException; 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 com.rabbitmq.client.AMQP.BasicProperties; public class MessageConsumer2 { private static final String HOST = "127.0.0.1"; private static final int PORT = ; // rabbitmq端口是5672 private static final String USERNAME = "admin"; private static final String PASSWORD = ""; private static final String QUEUE_NAME = "fanout2";
private static final String EXCHANGE_NAME = "topic-exchange"; public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost("vhost01");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
//设置消费者预取得消费的数量
channel.basicQos();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//把队列绑定到交换机中
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME, "lazy.*");
Consumer consumer = new DefaultConsumer(channel) { @Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
//模拟业务处理需要200毫秒
try {
Thread.sleep();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String message = new String(body, "UTF-8");
System.out.println("消费端2接收消息:" + message);
//envelope.getDeliveryTag()获得当前需要收到ack的消息的编号
//false表示仅仅ack当前的消费,如果内存中给还存在多条消费没有ack,是否批量回复。false表示不批量回复
channel.basicAck(envelope.getDeliveryTag(), false);
} };
//这里第二个参数要设置为false,表示取消自动ack,需要手动设置ack
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
一小时彻底搞懂RabbitMQ的更多相关文章
- 架构师必须搞懂DNS【转】
DNS,全称Domain Name System,即域名系统,搞清楚,它不是DNF地下城与勇士. DNS是怎么来的,我们知道要访问一个服务器的资源可以通过IP的形式访问,但IP地址比较难记,也不方便读 ...
- 搞懂分布式技术12:分布式ID生成方案
搞懂分布式技术12:分布式ID生成方案 ## 转自: 58沈剑 架构师之路 2017-06-25 一.需求缘起 几乎所有的业务系统,都有生成一个唯一记录标识的需求,例如: 消息标识:message-i ...
- 搞懂分布式技术9:Nginx负载均衡原理与实践
搞懂分布式技术9:Nginx负载均衡原理与实践 本篇摘自<亿级流量网站架构核心技术>第二章 Nginx负载均衡与反向代理 部分内容. 当我们的应用单实例不能支撑用户请求时,此时就需要扩容, ...
- 搞懂分布式技术5:Zookeeper的配置与集群管理实战
搞懂分布式技术5:Zookeeper的配置与集群管理实战 4.1 配置文件 ZooKeeper安装好之后,在安装目录的conf文件夹下可以找到一个名为“zoo_sample.cfg”的文件,是ZooK ...
- 搞懂ELK并不是一件特别难的事(ELK)
本篇文章主要介绍elk的一些框架组成,原理和实践,采用的ELK本版为7.7.0版本 一.ELK介绍 1.1.ELK简介 ELK是Elasticsearch.Logstash.Kibana三大开源框架首 ...
- 一张图带你搞懂Javascript原型链关系
在某天,我听了一个老师的公开课,一张图搞懂了原型链. 老师花两天时间理解.整理的,他讲了两个小时我们当时就听懂了. 今天我把他整理出来,分享给大家.也让我自己巩固加深一下. 就是这张图: 为了更好的图 ...
- 一文搞懂Flink Window机制
Windows是处理无线数据流的核心,它将流分割成有限大小的桶(buckets),并在其上执行各种计算. 窗口化的Flink程序的结构通常如下,有分组流(keyed streams)和无分组流(non ...
- 彻底搞懂Javascript的“==”
本文转载自:@manxisuo的<通过一张简单的图,让你彻底地.永久地搞懂JS的==运算>. 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容让人犯错,从而 ...
- 完全搞懂傅里叶变换和小波(2)——三个中值定理<转载>
书接上文,本文章是该系列的第二篇,按照总纲中给出的框架,本节介绍三个中值定理,包括它们的证明及几何意义.这三个中值定理是高等数学中非常基础的部分,如果读者对于高数的内容已经非常了解,大可跳过此部分.当 ...
随机推荐
- Alink漫谈(五) : 迭代计算和Superstep
Alink漫谈(五) : 迭代计算和Superstep 目录 Alink漫谈(五) : 迭代计算和Superstep 0x00 摘要 0x01 缘由 0x02 背景概念 2.1 四层执行图 2.2 T ...
- fastjson漏洞利用备忘
预备知识: 起rmi服务 用marshalsec-0.0.3-SNAPSHOT-all.jar起一个rmi服务. java -cp marshalsec-0.0.3-SNAPSHOT-all.jar ...
- 线程池续:你必须要知道的线程池submit()实现原理之FutureTask!
前言 上一篇内容写了Java中线程池的实现原理及源码分析,说好的是实实在在的大满足,想通过一篇文章让大家对线程池有个透彻的了解,但是文章写完总觉得还缺点什么? 上篇文章只提到线程提交的execute( ...
- 2019-ICCV-PDARTS-Progressive Differentiable Architecture Search Bridging the Depth Gap Between Search and Evaluation-论文阅读
P-DARTS 2019-ICCV-Progressive Differentiable Architecture Search Bridging the Depth Gap Between Sear ...
- 【图机器学习】cs224w Lecture 15 - 网络演变
目录 Macroscopic Forest Fire Model Microscopic Temporal Network Temporal PageRank Mesoscopic 转自本人:http ...
- ActiveMQ 笔记(二)部署和DEMO(队列、主题)
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.部署操作 1. 部署在linux 上的acvtiveMQ 要可以通过前台windows 的页面访问, ...
- 使用turtle库画太极图
from turtle import * pensize(3) penup() pencolor("black") reset() speed(10) pendown() circ ...
- Java实现 蓝桥杯 算法提高 概率计算
算法提高 概率计算 时间限制:1.0s 内存限制:256.0MB 问题描述 生成n个∈[a,b]的随机整数,输出它们的和为x的概率. 输入格式 一行输入四个整数依次为n,a,b,x,用空格分隔. 输出 ...
- Java实现 LeetCode 292 Nim游戏
292. Nim 游戏 你和你的朋友,两个人一起玩 Nim 游戏:桌子上有一堆石头,每次你们轮流拿掉 1 - 3 块石头. 拿掉最后一块石头的人就是获胜者.你作为先手. 你们是聪明人,每一步都是最优解 ...
- Java实现 LeetCode 23 合并K个排序链表
23. 合并K个排序链表 合并 k 个排序链表,返回合并后的排序链表.请分析和描述算法的复杂度. 示例: 输入: [ 1->4->5, 1->3->4, 2->6 ] 输 ...