本文为学习RabbitMQ后的学习总结记录,大致包含包含以下部分:
这里选择在Centos7虚拟机中使用Docker来安装。
进行在线拉取:
docker pull rabbitmq:3-management
为了防止自己以后看不懂,这里稍微解释一下参数:
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
xxx(挖个坑,下次来做)
RabbitMQ共有七种消息模型,这里只介绍最常使用的五种消息模型。首先这五种消息模型可以大致分为使用交换机(Exchage)与未使用交换机。
1. 未使用交换机。 消息发布者直接把消息发送给消息队列(Queue),再由消息队列将消息分发给对应的消费者。
基本消息队列(BasicQueue)
publisher 将消息发给指定的队列,consumer通过监听队列消息,来获取publisher发布的数据。一个队列只有一个消费者。
工作消息队列(WorkQueue)
工作队列,在基本消息队列的基础上,增加消费者个数,提高消息的处理速度,避免消息队列中的消息堆积。一个消息只能交给某一个消费者进行处理。这里消费者消费消息默认采取预取措施,即轮流向队列中拿取数据进行消费,不考虑具体的消费速度。
举个实际的例子,假如队列中一共有50条数据,由两个消费者进行消费。消费者1的消费速度较消费者2更快,但是最终的效果是两个消费者每个消费25条数据。这里可以设置预取消息的上限:
spring.rabbitmq.listener.simple.prefetch= 1
2. 使用交换机。 这里称为发布订阅模式。该种方式能够将同一消息发送给多个消费者。实现方式是加入exchange(交换机)。
常见的exchange类型包括:广播(Fanout)、路由(Direct)和主题(Topic)。
**注意:** exchange只负责消息路由,不提供存储功能,路由失败则消息丢失。
广播(Fanout Exchange)
Fanout Exchange 会将接收到的消息路由到每一个与其绑定的queue。
路由(Direct Exchange)
Direct Exchange 会将接收到的消息根据规则路由到指定的Queue。
这里简述此路由规则:Queue 与 Exchange 进行绑定时,会设置一个 bindingkey
,在publisher发布消息时,会为消息设置一个routeKey
,Exchange将消息路由到bindingkey 与 routeKey一致的队列。
主题(Topic Exchange)
TopicExchange 与 DirectExchange 类似,区别在于TopicExchange为多个单词的列表,用 .
进行分隔,DirectExchange 只能指定一个单词。
同时Queue与Exchange 通过BindingKey绑定时,可以使用通配符。通配符有两种类型:
a) #:代指0个或多个单词。如:China.#
b) *:代指一个单词。如:※.news
AMQP:全称 Advanced Message Queue Protocol,是一个在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关。
Spring AMQP:这是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
未使用spring-amqp 实现发送数据与消费数据
这里仅使用接收简单队列模型原生的代码编写来引出spring-amqp,下面则全部以spring-amqp为基础,来实现消息的发送与消费。
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.220.137");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();
// 2.创建通道Channel
Channel channel = connection.createChannel();
// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);
// 4.发送消息
String message = "hello, rabbitmq!";
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("发送消息成功:【" + message + "】");
// 5.关闭通道和连接
channel.close();
connection.close();
导入AMQP依赖。publiser 与 consumer 服务都需要amqp 依赖。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
amqp 相关的环境配置
在application.yml中进行相关的配置:
spring:
rabbitmq:
host: 192.168.220.137 # rabbitmq的IP 地址
port: 5672 # 端口
username: itcast # 登录用户名
password: 123321 # 登录密码
virtual-host: / # 虚拟主机目录
spring-amqp 封装RabbitTemplate 来实现数据的发送与消费。
@Autowired
private RabbitTemplate rabbitTemplate;
简单队列模型发送数据与消费数据代码实现:
发送数据:
@Test
public void testSendMessage2SimpleQueue() {
// 指定队列名称
String queueName = "simple.queue";
// 具体发送的消息
String msg = "hello.SpringAmqp";
rabbitTemplate.convertAndSend(queueName,msg);
}
接收数据:
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg){
System.out.println("Consumer 1 consume get message from queue【" + msg+"】");
}
工作队列模型发送数据与消费数据代码实现:
发送数据:
@Test
public void testSendMessageWorkQueue() throws InterruptedException {
// 指定队列名称
String queueName = "work.queue";
String msg = "hello.SpringAmqp";
// 在一秒钟发送50 条数据
for(int i=1;i<=50;i++){
rabbitTemplate.convertAndSend(queueName,msg + i);
Thread.sleep(20);
}
}
接收数据:
@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("Consumer 1 consume get message from work.queue:[" + msg+"]" + LocalDate.now());
// 模拟不同消费者的不同消费速度
Thread.sleep(20);
}
@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("Consumer 2 consume get message from work.queue:【" + msg+"】" + LocalDate.now());
// 模拟不同消费者的不同消费速度
Thread.sleep(50);
}
Fanout Exchange 模型发送数据与消费数据代码实现:
发送数据:
@Test
public void testSendFanoutExchange() throws InterruptedException {
String exchangeName = "itcast.fanout";
String msg = "hello,every one!";
// 此处,设置routeKey 为空
rabbitTemplate.convertAndSend(exchangeName,"",msg);
}
接收数据:
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) throws InterruptedException {
System.err.println("Consumer consume get message from fanout.queue1: [" + msg+"]");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) throws InterruptedException {
System.err.println("Consumer consume get message from fanout.queue2: [" + msg+"]");
}
Direct Exchange 模型发送数据与消费数据代码实现:
发送数据:
@Test
public void testSendDirectExchange() throws InterruptedException {
String exchangeName = "itcast.direct";
String msg = "hello,red!";
// 设置routeKey 为red
rabbitTemplate.convertAndSend(exchangeName,"red",msg);
}
接收数据:
@RabbitListener(
bindings = @QueueBinding(
// 指定队列名称
value = @Queue(name = "direct.queue1"),
// 指定交换机名称以及类型
exchange = @Exchange(value = "itcast.direct",type = ExchangeTypes.DIRECT),
// 指定队列与交换机关联的 bindingKey
key = {"red","blue"}
))
public void listenDirectQueue1(String msg){
System.err.println("Consumer consume get message from direct.queue1: [" + msg+"]");
}
@RabbitListener(
bindings = @QueueBinding(
value = @Queue("direct.queue2"),
exchange = @Exchange(value = "itcast.direct",type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
)
)
public void listenDirectQueue2(String msg){
System.err.println("Consumer consume get message from direct.queue2: [" + msg+"]");
}
Topic Exchange 模型发送数据与消费数据代码实现:
发送数据:
@Test
public void testSendTopicExchange() throws InterruptedException {
String exchangeName = "itcast.topic";
String msg = "hello";
// 设置多个routeKey,以'.' 分隔
String routeKey = "china.news.weather";
rabbitTemplate.convertAndSend(exchangeName,routeKey,msg);
}
接收数据:
@RabbitListener(bindings = @QueueBinding(
// 指定队列名称
value = @Queue("topic.queue1"),
// 指定交换机名称以及类型
exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC),
// 指定队列与交换机关联的 bindingKey。可使用通配符 '#' 或者 '*'
key = "china.#"
))
public void listenTopicQueue1(String msg){
System.err.println("Consumer consume get message from topic.queue1: [" + msg+"]");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("topic.queue2"),
exchange = @Exchange(value = "itcast.topic",type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg){
System.err.println("Consumer consume get message from topic.queue2: [" + msg+"]");
}
除了使用注解来定义队列、交换机及其两种进行绑定的方式外,还可以自定义组件来实现该功能。
@Configuration
public class FanoutConfig {
@Bean // 声明FanoutExchange
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
@Bean // 声明队列
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
@Bean // 声明交换机与队列的绑定关系
public Binding fanoutBinding1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
@Bean
public Queue objectQueue(){
return new Queue("object.queue");
}
SpringAMQP-消息转换器
在SpringAMQP的发送数据的方法中,发送数据的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会在底层帮我们先序列化为字节后再发送。
Sring中对消息对象是由org.springframework.amqp.suppoer.converter.MessageConverter
来处理的。默认实现为SimpleMessageConverter
,这是基于JDK的ObjectOutputStream
完成序列化的。该方法序列化后的字节可读性非常差。
如果要修改,只需定义一个MessageConverter类型的Bean即可。这里可以使用JSON方式的序列化。
导入坐标
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
dependency>
定义MessageConverter类型的Bean
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
以上就为本篇文章的全部内容啦!
如果本篇内容对您有帮助的话,请多多点赞支持一下呗!