一、概述
(一)异步通信案例
(二)JMS
JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。JMS是一种与厂商无关的 API,用来访问收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API,而 JMS 则提供同样与厂商无关的访问方法,以访问消息收发服务。
80年代的后期,各大厂商都开始研发自己的消息队列。众多的MQ产品使用不同的API、底层使用不同的消息协议,统一MQ的使用日渐迫切。为了解决统一使用MQ的问题,在2001年JMS诞生了。JMS试图通过提供公共Java API的方式,隐藏单独MQ产品提供商的实际接口,从而解决了各种MQ使用互通的问题。
(三)AMQP
JMS 非常棒,微软也开发了NMS(.NET 消息传递服务)来支持他们的平台和编程语言,它的效果也还不错,但使用两套不同标准的应用之间该怎么异步消息通信呢,因此,需要一个异步消息的通用标准。JMS、NMS都没有标准的底层协议,并且API是与编程语言绑定的。为了解决这个问题,AMQP诞生了。
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
(四)AMQP和JMS之间的关系
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式,JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。JMS规定了两种消息模型;而AMQP的消息模型更加丰富。
(五)消息队列MQ
消息队列(MQ)是一种应用程序对应用程序的通信方法。MQ是消息通信的模型,并不是具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。
MQ的消费-生产者模型的一个典型的代表,一端往消息队列中不断的写入消息,而另一端则可以读取或者订阅队列中的消息。MQ和JMS类似,但不同的是JMS是SUN JAVA消息中间件服务的一个标准和API定义,而MQ则是遵循了AMQP协议的具体实现和产品。
(六)常见的MQ产品
ZeroMQ、RabbitMQ、ActiveMQ、Redis、RocketMQ、Kafka
二、RabbitMQ安装
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的。
官网:http://www.rabbitmq.com/
参考文档:https://www.rabbitmq.com/getstarted.html
下载地址:https://www.rabbitmq.com/download.html
(一)安装erlang依赖的库
1、yum install ncurses ncurses-base ncurses-devel ncurses-libs ncurses-static ncurses-term ocaml-curses ocaml-curses-devel -y
2、yum install openssl-devel zlib-devel –y
3、yum -y install make ncurses-devel gcc gcc-c++ unixODBC unixODBC-devel openssl openssl-devel
(二)安装erlang
1、先安装wget命令
yum install wget
2、切换到/usr/local目录下,使用wget命令下载erlang的安装包
wget http://erlang.org/download/otp_src_20.0.tar.gz
注意:
RabbitMQ的版本和erlang的版本之间有对应关系,具体可以参考:
https://www.rabbitmq.com/which-erlang.html
3、解压之前下载好的安装包
4、切换到解压后的安装包的根目录中去
5、使用configure完成erlang的安装,prefix即为安装目录
./configure --prefix=/usr/local/erlang --with-ssl -enable-threads -enable-smmp-support -enable-kernel-poll --enable-hipe --without-javac
操作完毕后出现以下界面:
6、执行make&&make install命令,这步操作约十分钟
(三)配置并测试erlang环境变量
1、在/etc/profile中添加erlang的环境变量
ERLANG_HOME=/usr/local/erlang
PATH=
E
R
L
A
N
G
H
O
M
E
/
b
i
n
:
ERLANG_HOME/bin:
ERLANGHOME/bin:PATH
export ERLANG_HOME
export PATH
2、使用source /etc/profile刷新环境变量的配置
3、控制台中输入erl,如果出现以下界面,说明erlang安装完毕
注意:erlang控制台的退出命令是 halt().
(四)安装RabbitMQ
1、切换到/usr/local/目录中
2、下载RabbitMQ压缩包并解压
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.10/rabbitmq-server-generic-unix-3.6.10.tar.xz
yum -y install xz
xz -d rabbitmq-server-generic-unix-3.6.10.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.6.10.tar
(五)启动RabbitMQ
1。切换到RabbitMQ的安装目录中
2、./rabbitmq-server启动
注意:上述方式为前台启动,会占据Linux客户端,影响其它的操作,可以采用后端启动方式,RabbitMQ的常用命令如下:
直接启动 ./rabbitmq-server
后台启动 ./rabbitmq-server -detached
开启插件管理页面 ./rabbitmq-plugins enable rabbitmq_management
关闭服务 ./rabbitmqctl stop
(六)访问RabbitMQ后台管理界面
http://虚拟机IP:15672/
RabbitMQ后台管理界面1默认有个登录用户guest,也可以自己创建,然后登录
./rabbitmqctl add_user admin 123456
./rabbitmqctl set_user_tags admin administrator
./rabbitmqctl set_permissions -p / admin “." ".” “.*”
使用admin/123456作为用户名密码来登录,如果出现以下界面则登录成功,后面MQ中的具体信息可以通过后台管理界面进行查看。
三、RabbitMQ消息模型介绍
(一)消息传递的常用术语
1、生产者:发送消息的程序
2、队列:队列是位于 RabbitMQ 内的邮箱的名称。尽管消息流经 RabbitMQ 和应用程序,但它们只能存储在队列中。队列只受主机的内存和磁盘限制,它本质上是一个大的消息缓冲区。许多生产者可以向一个队列发送消息,许多消费者可以尝试从一个队列接收数据。
3、消费者:等待接收消息的程序
4、交换器:交换器必须确切地知道如何处理接收到的消息。它应该附加到特定的队列吗?它应该附加到许多队列中吗?或者它应该被丢弃。
(二)交换器类型
1、fanout:所有发送到该交换器的消息全部发送到其对应的队列中
2、direct:把消息路由到那些binding key和routing key一致的队列中
3、topic:与direct类型的Exchage相似,也是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:
routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key与routing key一样也是句点号“. ”分隔的字符串
binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
4、headers:不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。 在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
(三)AMQP消息协议通信模型
(四)RabbitMQ的常见队列模型
1、simple模式:即简单的点对点消息模型。开启mq服务,开启进程P 生产者向mq 写消息,进程C消费者监听mq,消费消息。
2、work模式:一个生产者P,对应了多个消费者C。这些多个C,消费的消息各自不同,C1和C2 消费的消息,构成所有消息的一个全集。可开启C的消费竞争 channel.basicQos(1);C1和C2 能者多劳。
3、订阅模式
(1)普通订阅模式fanout
(2)路由模式direct
(3)通配符模式topic
4、RPC:MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行。
5、消息确认 Message acknowledgment
在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,除非它的RabbitMQ连接断开。 这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug——Queue中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻辑…。pub message是没有ack的。
消息一旦被消费者接收,队列中的消息就会被删除。RabbitMQ怎么知道消息被接收了呢?
如果消费者领取消息后,还没执行操作就挂掉了呢?或者抛出了异常?消息消费失败,但是RabbitMQ无从得知,这样消息就丢失了!
因此,RabbitMQ有一个ACK机制。当消费者获取消息后,会向RabbitMQ发送回执ACK,告知消息已经被接收。不过这种回执ACK分两种情况:
自动ACK:消息一旦被接收,消费者自动发送ACK
手动ACK:消息接收后,不会发送ACK,需要手动调用
如果消息不太重要,丢失也没有影响,那么自动ACK会比较方便
如果消息非常重要,不容丢失。那么最好在消费完成后手动ACK,否则接收消息后就自动ACK,RabbitMQ就会把消息从队列中删除。如果此时消费者宕机,那么消息就丢失了。
6、流量控制Prefetch count
前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
7、Rabbitmq消费消息的模式
rabbitmq的消费模式分为两种: 推(Push)模式和拉(Pull)模式。推模式采用Basic.Consume进行消费,而拉模式则是调用Basic.Get模式
(1)Push模式
mq主动将消息推送给消费者(消费者需提供一个消费接口)
mq属于主动方,消费者属于一种被动消费,一旦有消息到达mq,会触发mq推送机制,将消息推送给消费者,不管消费者处于何种状态。
优点:
A、消费者代码较少:对于消费者来说,只需提供一个消费接口给mq即可;mq将接收到的消息,随即推送到指定的消费接口
B、消息实时性比较高:对于消费者来说,消息一旦到达mq,mq会立即推送给消费者
缺点:
A、消费者属于被动方,消息量比较大时,对消费者性能要求比较高;若消费者机器资源有限,可能会导致压力过载,引发宕机的情况。
B、对消费者可用性要求比较高:当消费者不可用时,会导致很push失败,在mq方需要考虑至少推送成功一次。
(2)Pull模式
消息消费的过程:
A、消费端采用轮询的方式,从mq服务中拉取消息进行消费
B、消费完成通知mq删除已消费成功的消息
C、继续拉取消息消费
对于消费者来说,是主动方,可以采用线程池的方式,根据机器的性能来增加或缩小线程池的大小,控制拉取消息的速度,可以很好的控制自身的压力。
优点:
A、消费者可以根据自己的性能主动控制消息拉去的速度,控制自己的压力,不至于把自己弄跨
B、实时性相对于push方式会低一些
C、消费者属于主动方,控制权更大一些
缺点:
A、消费方需要实现消息拉取的代码
B、消费速度较慢时,可能导致mq中消息积压,消息消费延迟等
(3)最佳实践
消费者性能较好,对实时性要求比较高的,可以采用push的方式
消费者性能有限,建议采用pull的方式
整体上来说,主要在于消费者的性能,机器的性能如果没有问题,push和pull都是可以的
四、Java操作RabbitMQ
构建Maven项目,添加依赖
com.rabbitmq
amqp-client
5.7.3
编写RabbitMQ工具类,用于获取连接对象
public class RabbitMQConnectionUtil {
private static ConnectionFactory factory;
private RabbitMQConnectionUtil() {
}
static {
//定义连接工厂
factory = new ConnectionFactory();
//设置服务地址
factory.setHost("192.168.174.101");
//端口(可以在后台管理界面查看,5672是默认值,也可以修改)
factory.setPort(5672);
//设置账号信息,用户名、密码、vhost
factory.setVirtualHost("/");
factory.setUsername("admin");
factory.setPassword("123456");
}
/**
* 创建RabbitMQ的连接
*/
public static Connection getConnection() {
Connection connection = null;
try {
connection = factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
}
(一)simple模式:
生产者
public class Producer {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = RabbitMQConnectionUtil.getConnection();
// 从连接中创建通道,使用通道才能完成消息相关的操作
Channel channel = connection.createChannel();
/**
* 声明(创建)队列,该操作会生成一个消息队列
* 第一个参数:队列名称
* 第二个参数:是否是持久化队列
* 第三个参数:是否是独占队列
* 第四个参数:是否自动删除(消费者和生产者都设置为true,此时消费者消费完毕完毕并完毕就能删除消息队列,防止重复消费)
* 第五个参数:队列中的消息什么时候会被自动删除
*/
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 消息内容
String message = "Hello java!";
// 向指定的队列中发送消息,发送的消息可以在后台系统中看到
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("Sent '" + message + "'");
//关闭通道和连接
channel.close();
connection.close();
}
}
消费者
public class Consumer1 {
private final static String QUEUE_NAME = "simple_queue";
public static void main(String[] argv) throws Exception {
// 获取到连接
Connection connection = RabbitMQConnectionUtil.getConnection();
// 创建通道
Channel channel = connection.createChannel();
// 声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 定义队列的消费者
DefaultConsumer consumer = new DefaultConsumer(channel) {
// 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
// body 即消息体
String msg = new String(body);
System.out.println("received: " + msg + "!");
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
(二)work模式(能者多劳模式)
生产者
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
Connection connection= RabbitMQConnectionUtil.getConnection();
Channel channel=connection.createChannel();
channel.queueDeclare("work_queue", false,false, true,null);
for(int i=1;i<=10;i++){
channel.basicPublish("", "work_queue", null,("这是第"+i+"条消息").getBytes());
Thread.sleep(500);
}
channel.close();
connection.close();
}
消费者:此处的两个消费者都通过channel.basicQos()设置了一次性处理的个数,并且才用了不同的消息确认机制。
消费者1
public static void main(String[] args) throws IOException, TimeoutException {
Connection con = RabbitMQConnectionUtil.getConnection();
Channel channel = con.createChannel();
channel.queueDeclare("work_queue", false, false, true, null);
//设定work模式,一次最多处理一个
channel.basicQos(1);
//创建消费者对象
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费方1:" + new String(body));
}
};
//消费消息,这里的第二个参数为true表示自动确认消息
channel.basicConsume("work_queue", true, consumer);
}
消费者2
public static void main(String[] args) throws IOException, TimeoutException {
Connection con = RabbitMQConnectionUtil.getConnection();
final Channel channel = con.createChannel();
channel.queueDeclare("work_queue", false, false, true, null);
//设定work模式,一次最多处理一个
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费方2:" + new String(body));
//消费者2使用手动确认的方式来确认消息,重要消息可选择手动确认
//System.out.println(envelope.getDeliveryTag());
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//消费消息,这里的第二个参数为false表示不自动确认消息
//如果此处不设置自动确认又没有手动确认,则最后消费者2只能得到一条消息
//其余消息全部由消费者1进行消费
channel.basicConsume("work_queue", false, consumer);
}
(三)发布订阅模式之-fanout模式
首先通过后台管理工具创建交换机,也可以通过channel.exchangeDeclare方法创建
下面几种交换机创建都不在代码中创建,方便阅读理解,实际开发中可以由代码统一创建维护
生产者
public static void main(String[] args) throws IOException, TimeoutException {
Connection con = RabbitMQConnectionUtil.getConnection();
Channel channel = con.createChannel();
//向交换机发送消息
channel.basicPublish("fanout_exchange", "", null, "这是一条fanout消息".getBytes());
channel.close();
con.close();
}
消费者1、消费者2(代码几乎一样,只放一个了)
public static void main(String[] args) throws IOException {
Connection con = RabbitMQConnectionUtil.getConnection();
Channel channel = con.createChannel();
//声明队列,下面会将队列和交换机绑定
channel.queueDeclare("fanout_queue_1", false, false, true, null);
//将队列和交换机绑定
channel.queueBind("fanout_queue_1", "fanout_exchange", "");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("消费者1:" + new String(body));
}
};
// 监听队列,自动返回完成
channel.basicConsume("fanout_queue_1", true, consumer);
}
(四)发布订阅模式之-direct模式
创建交换机direct_change
生产者
public static void main(String[] args) throws IOException, TimeoutException {
Connection con = RabbitMQConnectionUtil.getConnection();
Channel channel = con.createChannel();
channel.basicPublish("direct_exchange", "update", null, "一条direct信息".getBytes());
channel.close();
con.close();
}
消费者1和消费者2负责处理不同的路由键,这里就写一个
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("insert_delete_queue", false, false, true, null);
channel.queueBind("insert_delete_queue", "direct_exchange", "insert");
channel.queueBind("insert_delete_queue", "direct_exchange", "delete");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("insert/delete:" + new String(body) + "!");
}
};
channel.basicConsume("insert_delete_queue", true, consumer);
}
(五)发布订阅模式之-topic模式
创建交换机topic_exchange
routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
binding key与routing key一样也是句点号“. ”分隔的字符串
binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)
生产者
public static void main(String[] args) throws IOException, TimeoutException {
Connection con= RabbitMQConnectionUtil.getConnection();
Channel channel=con.createChannel();
//第二个参数是路由键
channel.basicPublish("topic_exchange", "item.delete.product", null,"topic类型的消息".getBytes() );
channel.close();
con.close();
}
消费者
public static void main(String[] args) throws IOException {
Connection con = RabbitMQConnectionUtil.getConnection();
Channel channel = con.createChannel();
channel.queueDeclare("topic_queue", false, false, true, null);
channel.queueBind("topic_queue", "topic_exchange", "item.#");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("received:" + new String(body) + "!");
}
};
channel.basicConsume("topic_queue", true, consumer);
}
(六)消费者的push、pull模式
之前案例中的就是push模式,由监听器程序监听推送的消息自行处理
pull模式需要手动实现
//生产者以之前简单模式下的生产者为例
public static void main(String[] args) throws IOException, TimeoutException {
Connection con= RabbitMQConnectionUtil.getConnection();
Channel channel=con.createChannel();
GetResponse response = channel.basicGet("simple_queue", true);
System.out.println(new String(response.getBody()));
channel.close();
con.close();
}
五、Spring AMQP
对AMQP的封装,可简化企业级消息服务的开发过程
(一)创建SpringBoot项目并添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
(二)application.properties中添加RabbitMQ的配置信息
spring.rabbitmq.host=192.168.174.101
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/
(三)监听器
@Component
public class Listener {
//simple
@RabbitListener(queuesToDeclare = @Queue(name = "simple_queue", durable = "false", autoDelete = "true"))
public void simple(String msg) {
System.out.println("simple接收到消息:" + msg);
}
//work
@RabbitListener(queuesToDeclare = @Queue(name = "work_queue", durable = "false", autoDelete = "true"), concurrency = "1")
public void work1(String msg) {
System.out.println("work1接收到消息:" + msg);
}
@RabbitListener(queuesToDeclare = @Queue(name = "work_queue", durable = "false", autoDelete = "true"), concurrency = "1")
public void work2(String msg) {
System.out.println("work2接收到消息:" + msg);
}
//fanout
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "fanout1_queue", durable = "false", autoDelete = "true"),
exchange = @Exchange(name = "fanout_exchange", type = ExchangeTypes.FANOUT, durable = "false")
))
public void fanout1(String msg) {
System.out.println("fanout1接收到消息:" + msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "fanout2_queue", durable = "false", autoDelete = "true"),
exchange = @Exchange(name = "fanout_exchange", type = ExchangeTypes.FANOUT, durable = "false")
))
public void fanout2(String msg) {
System.out.println("fanout2接收到消息:" + msg);
}
//direct
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct_queue", durable = "false", autoDelete = "true"),
exchange = @Exchange(name = "direct_exchange", type = ExchangeTypes.DIRECT, durable = "false"),
key = "update"
))
public void direct(String msg) {
System.out.println("direct收到的消息:" + msg);
}
//topic
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic_queue", durable = "false", autoDelete = "true"),
exchange = @Exchange(name = "topic_exchange", type = ExchangeTypes.TOPIC, durable = "false"),
key = "item.*"
))
public void topic(String msg) {
System.out.println("topic收到的消息:" + msg);
}
}
(四)测试类
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitMQApplication.class)
public class SyeduRabbitmqApplicationTests {
@Autowired
private AmqpTemplate amqpTemplate;
@Test
public void testSend() throws InterruptedException {
//simple模式
amqpTemplate.convertAndSend("","simple_queue", "simple模式的消息");
//work模式
for(int i=1;i<=10;i++){
amqpTemplate.convertAndSend("", "work_queue","work模式的第"+i+"条消息");
Thread.sleep(500);
}
//fanout
amqpTemplate.convertAndSend("fanout_exchange", "", "fanout的消息");
//direct
amqpTemplate.convertAndSend("direct_exchange", "update", "direct消息");
//topic
amqpTemplate.convertAndSend("topic_exchange", "item.delete", "topic消息");
}
}
六、消息队列的常见使用场景
总之就是异步处理
1、注册成功后异步向用户发送邮件或短信
2、秒杀操作中异步对数据库进行操作(秒杀的案例在下方,jemeter用来模拟高并发环境)