一、MQ概述
1、什么是MQ
1、MQ(message queue)消息队列:从字面意思看,本质是一个队列,FIFO(先进先出),只不过队列中存放的内容是message,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ是一种非常常见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用MQ之后,消息发送上游只需要依赖MQ,不用依赖其他服务。
2、消息队列作用:
2、应用解耦
1、场景带入:假设应用中有订单系统、库存系统。用户创建订单后,订单系统直接调用库存系统的接口,假如库存系统无法访问,则订单扣减库存会失败,从而导致订单失败,订单系统与库存系统耦合。引入消息队列后,用户下单之后,订单系统完成持久化处理,将消息写入消息队列中,返回用户下单成功;库存系统订阅下单系统的消息,获取下单信息,进行库存操作。
3、流量消峰
1、场景带入:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,使用消息队列做缓冲。用户的请求,服务器接收后,首先写入消息队列,假如消息队列长度超过最大数量,则直接抛弃用户请求或转到错误页面。秒杀业务根据消息队列中的请求信息,再进行后续处理。
4、异步处理
1、场景带入:用户注册后,需要发送邮件和注册短信。传统方式有两种:串行和并行
2、串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个步骤全部完成后,返回给客户端。
3、并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信,以上三个任务完成后,返回给客户端。与串行的区别是:并行的方式可以提高处理的时间。假设三个节点都是50ms,不考虑网络等其他开销,则串行的方式是150ms,并行方式是100ms。
4、引入消息队列,将不是必须的业务逻辑,异步处理。用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍
5、常见消息队列中间件的特点
1、ActiveMQ:
优点:单机吞吐量万级,时效性ms级,可用性高,基于主从架构实现高可用性,消息可靠性较低的概率丢失数据。 缺点:官方社区现在对ActiveMQ 5.x维护越来越少,高吞吐量场景较少使用。
2、Kafka:
优点:性能卓越,单机写入TPS约在百万条/秒,高吞吐,时效性ms级可用性高,是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用,消费者采用Pull方式获取消息,消息有序, 通过控制能够保证所有消息被消费且仅被消费一次。 缺点:Kafka单机超过64个队列/分区,Load会发生明显的飙高现象,队列越多,load越高,发送消息响应时间变长,使用短轮询方式,实时性取决于轮询间隔时间,消费失败不支持重试;支持消息顺序, 但是一台代理宕机后,就会产生消息乱序,社区更新较慢。
3、RocketMQ:
优点:单机吞吐量十万级,可用性非常高,分布式架构,消息可以做到0丢失,MQ功能较为完善,还是分布式的,扩展性好,支持10亿级别的消息堆积,不会因为堆积导致性能下降。 缺点:支持的客户端语言不多,目前是java及c++
4、RabbitMQ:
优点:由于erlang语言的高并发特性,性能较好;吞吐量到万级,MQ功能比较完备,健壮、稳定、易 用、跨平台、支持多种语言。 缺点:学习成本高,商业版需要收费
二、RabbitMQ概述及安装配置
1、概述
1、RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统,遵循Monzilla Public License开源协议
2、AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
3、主要特征:
保证可靠性:使用一些机制来保证可靠性,如持久化、传输确认、发布确认。 灵活的路由功能 可伸缩性:支持消息集群,多台RabbitMQ服务器可以组成一个集群。 高可用性:RabbitMQ集群中的某个节点出现问题时,队列依然可用。 支持多种协议。 支持多语言客户端。 提供良好的管理界面 提供跟踪机制:如果消息出现异常,可以通过跟踪机制分析异常原因。 提供插件机制:可通过插件进行多方面扩展。
2、核心概念
1、Message:
消息,消息是不具名的,它是由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括路由键(Routing Key)、优先权(Priority)、指出该消息可能需要持久性存储(delivery-mode)等。
2、Publisher:
消息生产者,也是一个向交换机发布消息的客户端应用程序。
3、Exchange:
交换机一方面它接收来自生产者的消息,另一方面它将消息推送到队列中(Queue),交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是多个队列,亦或是把消息丢弃,这个得由交换机类型决定。 消息到达Broker的第一站,根据分发规则,匹配查询表中的路由键(Routing Key),分发消息到Queue中。常用的类型有:direct(point-to-point)、topic(publish-subscribe)、fanout(multicast)、headers。
4、Queue:
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
5、Consumer:
消息消费者,表示一个从消息队列中取得消息的客户端应用程序,同一个应用程序既可以是生产者又是可以是消费者。
6、Connection:
Publisher/Consumer和Broker之间的TCP连接。
7、Channel:
信道,如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel是在Connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的Channel进行通讯,AMQP method包含了Channel Id帮助客户端和Message Broker识别Channel,所以Channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP Connection的开销。
8、Virtual Host:
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个VHost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制。VHost是AMQP概念的基础,必须在连接时指定, RabbitMQ默认的VHost是/
9、Broker:
接收和分发消息的应用,RabbitMQ Server就是 Message Broker
10、Binding:
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。 Exchange和Queue的绑定可以是多对多的关系。
3、安装说明
1、说明:由于RabbitMQ是基于Erlang语言的,因此先安装Erlang
2、下载地址:
yum remove erlang*
yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC unixODBC-devel httpd python-simplejson
4、安装Erlang
tar -zxvf otp_src_20.1.tar.gz
cd otp_src_20.1
./configure --prefix= /usr/local/RabbitMQ/erlang --enable-smp-support --enable-threads --enable-sctp --enable-kernel-poll --enable-hipe --with-ssl --without-javac
make && make install
vim /etc/profile
export PATH = $PATH :/usr/local/RbbitMQ/erlang/bin
source /etc/profile
erl
halt( ) .
1、注意:如果执行第3步提示如下错误的解决方案
configure: error: No curses library functions found
configure: error: /bin/sh ‘/usr/local/rabbitMQ/otp_src_20.1/erts/configure’ failed for erts
yum install ncurses-devel
2、任意目录输入erl进入界面表示环境变量配置成功
5、安装RabbitMQ
xz -d rabbitmq-server-generic-unix-3.7.0.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.7.0.tar
echo 'export PATH=$PATH:/usr/local/RabbitMQ/rabbitmq_server-3.7.0/sbin' >> /etc/profile
source /etc/profile
6、RabbitMQ相关操作命令
rabbitmq-server -detached
rabbitmqctl stop
rabbitmqctl status
ps aux| grep rabbit
rabbitmq-plugins enable rabbitmq_management
7、访问管理界面
1、管理界面端口(15672
)
2、rabbitmq的默认端口是(5672
)
3、IP地址:15672
三、RabbitMQ用户管理
1、用户级别
1、超级管理员administrator,可以登录控制台,查看所有信息,可以对用户和策略进行操作
2、监控者monitoring,可以登录控制台,可以查看节点的相关信息,比如进程数,内存磁盘使用情况
3、策略制定者policymaker,可以登录控制台,制定策略,但是无法查看节点信息
4、普通管理员management仅能登录控制台
5、其他,无法登录控制台,一般指的是提供者和消费者
2、添加用户(命令方式)
./rabbitmqctl add_user 用户名 密码
./rabbitmqctl set_user_tags 用户名 用户级别
./rabbitmqctl set_permissions -p "VHost_Name" 用户名 ".*" ".*" ".*"
./rabbitmqctl change_password 用户名 新密码
./rabbitmqctl delete_user 用户名
./rabbitmqctl set_vhost_limits -p 虚拟主机名 '{"max-connections": 数量}'
3、添加用户(Web方式)
1、浏览器进入管理界面,默认用户和密码都是 guest
,guest具有最高权限,只能在本机登录,所以先用命令模式创建用户并登录。
2、添加用户并为用户分配可以访问的虚拟主机
3、为用户分配访问的虚拟主机,默认情况下是没有任何可以访问的,可以添加一个主机,然后分配权限
4、给指定用户分配虚拟主机
5、设置完成后,回到用户界面确认
4、交换机和队列创建
1、创建队列
2、创建交换机
3、交换机绑定队列,点击需要绑定队列的交换机名称后,进入绑定页面
四、交换器、路由键、绑定
1、概念
1、Exchange(交换器):RabbitMQ消息传递模型的核心思想是生产者生产的消息从不会直接发送到队列
。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。 相反,生产者只能将消息发送到交换机(exchange)
,交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将消息路由到一个或多个队列中。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列,还是说把他们到许多队列中,还是说应该丢弃它们。这就的由交换机的类型来决定。
2、RoutingKey(路由键):生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则,而这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能生效
。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时,通过指定路由键来决定消息流向哪里。
3、Binding(绑定):RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到具体队列了。
说明:生产者将消息发送给交换器时,会指定一个RoutingKey,当BindingKey与RoutingKey相匹配时,消息会路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的BindingKey。BindingKey并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视BindingKey,而是将消息路由到所有绑定到该交换器的队列中
。
2、交换器类型
1、RabbitMQ常用的交换器类型有direct(直接)、fanout(扇出)、topic(主题)、headers(标题)
这四种
2、fanout exchange:
它会把所有发送到该交换器的消息路由到所有与该交换机绑定的队列中。它不处理路由键, 只是简单的将队列绑定到交换器上。类似广播一样(发布订阅模式),它是转发消息是最快的。
3、direct exchange:
消息中的路由键(RoutingKey)如果和Binding中的BindingKey完全匹配, 交换器就将消息发到对应的队列中。
4、topic exchange:
与direct类型的交换器相似,也是将消息路由到BindingKey与RoutingKey相匹配的队列中,但是它是通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。
RoutingKey与BindingKey为一个点号“.”分隔的字符串(被点号分隔开的每一段独立的字符串称为一个单词),比如:java.util.concurrent 同样会识别两个特殊字符串“*”和“#”,用做模糊匹配,“#”匹配0个或多个单词,“*”匹配一个单词
5、headers exchange:
不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的Headers属性进行匹配。在绑定队列和交换器时制定一组键值对,当消息发送到交换器时,RabbitMQ会获取到该消息的Headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
3、环境准备
1、创建一个Maven项目
2、父工程依赖
<?xml version="1.0" encoding="UTF-8"?>
< 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.0</ modelVersion>
< groupId> com.itan</ groupId>
< artifactId> Learning</ artifactId>
< version> 1.0-SNAPSHOT</ version>
< packaging> pom</ packaging>
< properties>
< java.version> 1.8</ java.version>
< project.build.sourceEncoding> UTF-8</ project.build.sourceEncoding>
< spring.boot.version> 2.3.1.RELEASE</ spring.boot.version>
< maven-resources-plugin.version> 3.1.0</ maven-resources-plugin.version>
< maven-compiler-plugin.version> 3.8.1</ maven-compiler-plugin.version>
< maven-surefire-plugin.version> 2.22.2</ maven-surefire-plugin.version>
</ properties>
< dependencyManagement>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-dependencies</ artifactId>
< version> ${spring.boot.version}</ version>
< type> pom</ type>
< scope> import</ scope>
</ dependency>
</ dependencies>
</ dependencyManagement>
< build>
< plugins>
< plugin>
< groupId> org.apache.maven.plugins</ groupId>
< artifactId> maven-compiler-plugin</ artifactId>
< version> ${maven-compiler-plugin.version}</ version>
< configuration>
< compilerVersion> ${java.version}</ compilerVersion>
< encoding> ${project.build.sourceEncoding}</ encoding>
</ configuration>
</ plugin>
< plugin>
< groupId> org.apache.maven.plugins</ groupId>
< artifactId> maven-surefire-plugin</ artifactId>
< version> ${maven-surefire-plugin.version}</ version>
< configuration>
< skipTests> true</ skipTests>
</ configuration>
</ plugin>
</ plugins>
< resources>
< resource>
< directory> src/main/webapp</ directory>
</ resource>
< resource>
< directory> src/main/resources</ directory>
</ resource>
< resource>
< directory> src/main/java</ directory>
< includes>
< include> **/*.xml</ include>
</ includes>
</ resource>
</ resources>
</ build>
</ project>
3、选中项目创建子工程rabbitmq_web,是一个springboot项目
<?xml version="1.0" encoding="UTF-8"?>
< 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 https://maven.apache.org/xsd/maven-4.0.0.xsd" >
< modelVersion> 4.0.0</ modelVersion>
< parent>
< groupId> com.itan</ groupId>
< artifactId> Learning</ artifactId>
< version> 1.0-SNAPSHOT</ version>
</ parent>
< groupId> com.itan</ groupId>
< artifactId> rabbitmq-web</ artifactId>
< version> 0.0.1-SNAPSHOT</ version>
< name> rabbitmq-web</ name>
< description> rabbitmq-web</ description>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
< exclusions>
< exclusion>
< groupId> org.junit.vintage</ groupId>
< artifactId> junit-vintage-engine</ artifactId>
</ exclusion>
</ exclusions>
</ dependency>
< dependency>
< groupId> org.projectlombok</ groupId>
< artifactId> lombok</ artifactId>
< version> 1.18.12</ version>
</ dependency>
< dependency>
< groupId> com.alibaba</ groupId>
< artifactId> fastjson</ artifactId>
< version> 1.2.47</ version>
</ dependency>
< dependency>
< groupId> commons-lang</ groupId>
< artifactId> commons-lang3</ artifactId>
< version> 3.9</ version>
</ dependency>
< dependency>
< groupId> com.rabbitmq</ groupId>
< artifactId> amqp-client</ artifactId>
< version> 5.8.0</ version>
</ dependency>
< dependency>
< groupId> commons-io</ groupId>
< artifactId> commons-io</ artifactId>
< version> 2.6</ version>
</ dependency>
</ dependencies>
< build>
< plugins>
< plugin>
< groupId> org.apache.maven.plugins</ groupId>
< artifactId> maven-resources-plugin</ artifactId>
< version> ${maven-resources-plugin.version}</ version>
< configuration>
< encoding> UTF-8</ encoding>
</ configuration>
</ plugin>
< plugin>
< groupId> org.apache.maven.plugins</ groupId>
< artifactId> maven-compiler-plugin</ artifactId>
< version> ${maven-compiler-plugin.version}</ version>
< configuration>
< source> ${java.version}</ source>
< target> ${java.version}</ target>
< encoding> UTF-8</ encoding>
</ configuration>
</ plugin>
< plugin>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-maven-plugin</ artifactId>
< version> 2.3.1.RELEASE</ version>
< configuration>
< finalName> ${project.artifactId}</ finalName>
< mainClass> com.itan.nioweb.NioWebApplication</ mainClass>
< fork> true</ fork>
</ configuration>
</ plugin>
</ plugins>
</ build>
</ project>
4、创建RabbitMQUtils连接工具类
import com. rabbitmq. client. Connection ;
import com. rabbitmq. client. ConnectionFactory ;
import java. io. IOException ;
import java. util. concurrent. TimeoutException ;
public class RabbitMQUtils {
public static Connection getConnection ( ) throws IOException , TimeoutException {
ConnectionFactory factory = new ConnectionFactory ( ) ;
factory. setHost ( "101.26.156.147" ) ;
factory. setPort ( 5672 ) ;
factory. setVirtualHost ( "test_host" ) ;
factory. setUsername ( "test" ) ;
factory. setPassword ( "123456" ) ;
Connection connection = factory. newConnection ( ) ;
return connection;
}
}
4、RabbitMQ简单模式
1、简单模式:一个队列只有一个消费者,生产者将消息发送到队列,消费者从队列中取出数据
public class ProducerTest1 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
String msg = "Hello World" ;
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
channel. queueDeclare ( "queue1" , false , false , false , null ) ;
channel. basicPublish ( "" , "queue1" , null , msg. getBytes ( ) ) ;
System . out. println ( "发送消息:" + msg) ;
channel. close ( ) ;
connection. close ( ) ;
}
}
运行程序之后,发现新创建了一个队列,队列中有一条消息,没有消息消费者,此消息会一直存在
public class ConsumerTest1 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
Consumer consumer = new DefaultConsumer ( channel) {
@Override
public void handleDelivery ( String consumerTag, Envelope envelope,
AMQP. BasicProperties properties, byte [ ] body) throws IOException {
String msg = new String ( body) ;
System . out. println ( "消费消息:" + msg) ;
}
} ;
channel. basicConsume ( "queue1" , true , consumer) ;
}
}
运行之后,消息已经被消费,队列中的消息数为0
5、RabbitMQ工作模式
1、工作模式:多个消费者监听同一个队列,RabbitMQ采用轮询的方式将消息平均分发给消费者
但多个消费者中最终只有一个消费者能成功消费消息(消费者之间是竞争关系,一个消息只能被处理一次,轮询分发
)。
public class ProducerTest2 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
System . out. println ( "请输入消息:" ) ;
Scanner scanner = new Scanner ( System . in) ;
String msg = null ;
while ( ! "exit" . equals ( msg = scanner. nextLine ( ) ) ) {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
channel. basicPublish ( "" , "queue1" , null , msg. getBytes ( ) ) ;
System . out. println ( "发送消息:" + msg) ;
channel. close ( ) ;
connection. close ( ) ;
}
}
}
public class ConsumerTest2 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
Consumer consumer = new DefaultConsumer ( channel) {
@Override
public void handleDelivery ( String consumerTag, Envelope envelope,
AMQP. BasicProperties properties, byte [ ] body) throws IOException {
String msg = new String ( body) ;
System . out. println ( "消费消息:" + msg) ;
}
} ;
System . out. println ( "消费者1启动等待消费......" ) ;
channel. basicConsume ( "queue1" , true , consumer) ;
}
}
启动两个消费者,可以通过Allow multiple instances
允许多个实例执行,再启动生产者发送10个消息,通过结果可以看到两个消费者都有5条消息,并且是按照有序的一个接收一次消息。
6、RabbitMQ订阅模式
1、订阅模式:一条消息可以被多个消费者同时获取,生产者将消息发送到交换机 exchange
,消费者将自己对应的队列注册到交换机,当发送消息后所有注册的队列的消费者都可以收到消息。
2、新建一个fanout模式的交换器,并给交换器绑定两个队列
public class ProducerTest3 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
System . out. println ( "请输入消息:" ) ;
Scanner scanner = new Scanner ( System . in) ;
String msg = null ;
while ( ! "exit" . equals ( msg = scanner. nextLine ( ) ) ) {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
channel. basicPublish ( "test_exchange" , "" , null , msg. getBytes ( ) ) ;
System . out. println ( "发送消息:" + msg) ;
channel. close ( ) ;
connection. close ( ) ;
}
}
}
1、消费者:将ConsumerTest2代码作如下修改,并设置Allow multiple instances
,启动两次
2、运行结果,消费者1与消费者2都同时收到两条消息
7、RabbitMQ路由模式之redirect交换器
1、路由模式:一个交换机绑定多个消息队列,每个消息队列都有自己唯一的 key
,每个消息队列有一个消费者监听。
2、新建一个direct模式的交换器(消息中的路由键(routing key)如果和Binding中的binding key一致,交换器才将消息发到对应的队列中
)并绑定两个新的队列,并分别设置路由键a,b
public class ProducerTest4 {
public static void main ( String [ ] args) throws IOException , TimeoutException {
System . out. println ( "请输入消息:" ) ;
Scanner scanner = new Scanner ( System . in) ;
String msg = null ;
while ( ! "exit" . equals ( msg = scanner. nextLine ( ) ) ) {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
if ( msg. contains ( "a" ) ) {
channel. basicPublish ( "direct_exchange" , "a" , null , msg. getBytes ( ) ) ;
} else if ( msg. contains ( "b" ) ) {
channel. basicPublish ( "direct_exchange" , "b" , null , msg. getBytes ( ) ) ;
}
System . out. println ( "发送消息:" + msg) ;
channel. close ( ) ;
connection. close ( ) ;
}
}
}
1、消费者:将ConsumerTest2代码作如下修改,并设置Allow multiple instances
,启动两次
2、运行结果,消费者a只接收到路由键为a的消息,消费者b只接收到路由键为b的消息
8、RabbitMQ路由模式之topic交换器
1、创建一个topic类型的交换器,并创建队列,绑定路由键
public class TopicProducer {
public static void main ( String [ ] args) throws IOException , TimeoutException {
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
Map < String , String > bindingKeyMap = new HashMap < > ( ) ;
bindingKeyMap. put ( "com.itan.client" , "消息发送到topic_queue1与topic_queue2" ) ;
bindingKeyMap. put ( "com.java.client" , "消息发送到topic_queue2" ) ;
bindingKeyMap. put ( "com.demo" , "消息发送到topic_queue2" ) ;
bindingKeyMap. put ( "java.itan.demo" , "消息发送到topic_queue1" ) ;
bindingKeyMap. put ( "java.util.demo" , "没有接收队列" ) ;
for ( Map. Entry < String , String > entry : bindingKeyMap. entrySet ( ) ) {
channel. basicPublish ( "topic_exchange" , entry. getKey ( ) , null , entry. getValue ( ) . getBytes ( ) ) ;
System . out. println ( "路由键为:" + entry. getKey ( ) + " 消息内容:" + entry. getValue ( ) ) ;
}
channel. close ( ) ;
connection. close ( ) ;
}
}
public class TopicConsumer1 {
public static void main ( String [ ] args) throws IOException , TimeoutException , InterruptedException {
System . out. println ( "消费者1等待消费......" ) ;
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
DeliverCallback deliverCallback = ( consumerTag, message) -> {
byte [ ] body = message. getBody ( ) ;
String msg = new String ( body) ;
System . out. println ( "绑定键为:" + message. getEnvelope ( ) . getRoutingKey ( ) + " 消息内容为:" + msg) ;
} ;
CancelCallback cancelCallback = consumerTag -> {
System . out. println ( "消息消费被取消:" + consumerTag) ;
} ;
channel. basicConsume ( "topic_queue1" , true , deliverCallback, cancelCallback) ;
}
}
public class TopicConsumer2 {
public static void main ( String [ ] args) throws IOException , TimeoutException , InterruptedException {
System . out. println ( "消费者2等待消费......" ) ;
Connection connection = RabbitMQUtils . getConnection ( ) ;
Channel channel = connection. createChannel ( ) ;
DeliverCallback deliverCallback = ( consumerTag, message) -> {
byte [ ] body = message. getBody ( ) ;
String msg = new String ( body) ;
System . out. println ( "绑定键为:" + message. getEnvelope ( ) . getRoutingKey ( ) + " 消息内容为:" + msg) ;
} ;
CancelCallback cancelCallback = consumerTag -> {
System . out. println ( "消息消费被取消:" + consumerTag) ;
} ;
channel. basicConsume ( "topic_queue2" , true , deliverCallback, cancelCallback) ;
}
}