MQ :
- rabbit还有些麻烦。 还要讲一些原理。
- rabbitmq的运作流程是需要了解的,不然的话会对使用有很大的阻碍;
- 我就不再写运行原理了,这篇文章写得不错(如果第一次用rabbitmq一定要看,5分钟就能懂): https://www.cnblogs.com/dwlsxj/p/RabbitMQ.html
随着不断的深入: 后面会发现rabbitmq的路由类型中topic可以代替其他direct、fanout这两种模式;
header模式目前还不清楚怎么回事。
topic: 因为topic路由有 * 和 # 的规则,所以可以代替fanout订阅模式;
direct: 可以直接由topic的固定写法代替(就是不使用topic的#、*的规则)
初学的可能开始看不懂,不过没关系,都试一遍就知道原因了。
- 想了解更多可以看看(也可以不看):
- rabbitmqJava客户端API指南: https://www.rabbitmq.com/api-guide.html
- rabbitmq整合springboot文档 : https://spring.io/projects/spring-amqp
在rabbitmq中,也可以按大方向分为: 生产消费模式、订阅模式
因为rabbitmq的运行原理与activemq不同,所以理解也就不同;
<p>
在rabbitmq中,有几个概念:Message(要传输的数据)、Queue(装消息的队列名称)、Exchange(路由器)、routing Key(可以进行数据传输的队列匹配规则)、binding key (需要接收数据的队列)
例如:
1. 要传输的数据为(Message):"我要使用rabbitmq"
2. 将上面要传输的数据放在3个Queue中, 并分别为Queue自定义取名: spring.rabbitmq.one 、 springboot.rabbitmq.two 、 springcould.rabbitmq.three
3. 定义一个路由器(Exchange)【明白topic类型其实就足够了】[当然路由器有4种类型,不知道的自己去看看5分钟就懂了] ,将上面3个Queue 绑定到路由器(Exchange)上面
4. 路由器设置routing Key (就是一个匹配的规则,跟他匹配上的上面自定义的Queue才能把消息发送到路由器上(Exchange) )
5. binding key (需要接收数据的队列) 也就是在接收端的监听注解上面加上要接收的Queue名称(也就是上面自定的3个中的Queue,可以都有,可以1个或者其他,也可以都没有[不过就不能接收消息了])
下面这段话,一定要看;可以学会了之后看。
在rabbitmq里,有各种默认行为:
如果我们不指定exchange,会有个默认的direct类型的exchange
如果不指定队列和交换器的绑定关系,默认就按消息的key绑定对应的queue。
此时发一个消息,消息的key是什么,就会被默认交换器送给对应的queue。
所以就存在为什么有些代码写的简单,还是能用的原因,如果知道原理后,用起来将会得心应手。
开始整合springboot:
消费端:
1. 引入依赖
<!-- rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 添加配置文件
spring:
rabbitmq: ############################################### rabbitmq配置 ##############################################
password: admin # 密码
username: admin # 用户名
port: 5673 # 端口号 5672用于常规连接(我这里是linux改成了5672), 5671用于使用TLS的连接
host: 47.96.100.61 # ip
publisher-confirms: true # 消息发送确认机制,防止消息丢失
publisher-returns: true # 如果要进行消息回调,则这里必须要设置为true
connection-timeout: 10s # 超时时间
dynamic: true # 默认创建一个AmqpAdmin的Bean 默认为true
requested-heartbeat: 2s # 请求的心跳超时时间
3. 配置数据 (根据自己情况配置,这里面自定义了 topic/fanout/direct)
package com.toad.config.rabbitmq;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author: wangqinmin
* @date: 2019/3/29 16:54
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
@Configuration
public class RabbitConfig {
/**rabbitmq的路由类型有4种(常用生产消费模式:direct就可以了,topic其实也差不多就是有通配符显得更高级点):
*
* fanout 把所有发送到该Exchange的消息路由到所有与它绑定的Queue中(广播模式)
* direct Routing Key==Binding Key
* topic 我这里自己总结的简称模糊匹配
* headers Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
*/
/**
* =================== fanout 模式 ====================
* fanout属于广播模式,只要跟它绑定的队列都会通知并且接受到消息。
*/
@Bean
public Queue fanoutA() {
return new Queue("fanout.a");
}
@Bean
public Queue fanoutB() {
return new Queue("fanout.b");
}
@Bean
public Queue fanoutC() {
return new Queue("fanout.c");
}
/**
* =================== topic 模式 ====================
* routing key为一个句点号"." 分隔的字符串 (如"stock.usd.nyse"、"nyse.vmw"、"quick.orange.rabbit")
* binding key与routing key一样也是句点号"."分隔的字符串
* binding key中可以存在两种特殊字符"*"与"#",用于做模糊匹配: (其中"*"用于匹配一个单词,"#"用于匹配多个单词(可以是零个))
*/
@Bean
public Queue topiocA() {
return new Queue("topic.a");
}
@Bean
public Queue topicB() {
return new Queue("topic.b");
}
@Bean
public Queue topicC() {
return new Queue("topic.c");
}
/**
* =================== direct 模式 ====================
* direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中
*/
@Bean
public Queue directA() {
return new Queue("directA");
}
@Bean
public Queue directB() {
return new Queue("directB");
}
/**
* 自定义交换器
* =================== 自定义一个fanout交换器 ===================
* =================== 自定义一个topic交换器 ====================
* =================== 自定义一个direct交换器 ====================
*/
@Bean
FanoutExchange fanoutExchange() {
// 定义一个名为fanoutExchange的fanout交换器
return new FanoutExchange("fanoutExchange");
}
@Bean
TopicExchange topicExchange() {
// 定义一个名为topicExchange的topic交换器
return new TopicExchange("topicExchange");
}
@Bean
DirectExchange directExchange() {
// 定义一个名为directExchange的direct交换器
return new DirectExchange("directExchange");
}
@Bean
HeadersExchange headersExchange() {
// 定义一个名为headersExchange的headers交换器
return new HeadersExchange("headersExchange");
}
/**
* 将队列与交换机绑定
* =================== fanout 队列与交换机绑定 ====================
*/
@Bean
public Binding bindingExchangeWithA() {
return BindingBuilder.bind(fanoutA()).to(fanoutExchange());
}
@Bean
public Binding bindingExchangeWithB() {
return BindingBuilder.bind(fanoutB()).to(fanoutExchange());
}
@Bean
public Binding bindingExchangeWithC() {
return BindingBuilder.bind(fanoutC()).to(fanoutExchange());
}
/**
* 将队列与交换机绑定
* =================== topic 队列与交换机绑定 ====================
* 注意: 这里的写法; (这里的routing key :其实就是让写路由规则)
*/
@Bean
public Binding bindingTopicExchangeWithA() {
return BindingBuilder.bind(topiocA()).to(topicExchange()).with("topic.msg");
}
@Bean
public Binding bindingTopicExchangeWithB() {
return BindingBuilder.bind(topicB()).to(topicExchange()).with("topic.#");
}
@Bean
public Binding bindingTopicExchangeWithC() {
return BindingBuilder.bind(topicC()).to(topicExchange()).with("topic.*.z");
}
/**
* 将队列与交换机绑定
* =================== direct 队列与交换机绑定 ====================
*/
@Bean
public Binding bindingDirectExchangeWithA() {
return BindingBuilder.bind(directA()).to(directExchange()).with("directA");
}
@Bean
public Binding bindingDirectExchangeWithB() {
return BindingBuilder.bind(directB()).to(directExchange()).with("directB");
}
}
4. 使用mq ,发送数据 例子:
package com.toad.swan.web.controller;
import com.alibaba.fastjson.JSON;
import com.toad.common.base.BaseController;
import com.toad.common.baseclass.ApiResult;
import com.toad.swan.entity.UUser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
/**
* @author: wangqinmin
* @date: 2019/4/2 09:28
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
*/
@RestController
@RequestMapping("/RabbitMq")
@Slf4j
@Api("RabbitMq")
public class RabbitMqController extends BaseController {
@Autowired
private AmqpTemplate rabbitTemplate;
/**
* 在rabbitmq中,也可以按大方向分为: 生产消费模式、订阅模式
* 因为rabbitmq的运行原理与activemq不同,所以理解也就不同;
* <p>
* 在rabbitmq中,有几个概念:Message(要传输的数据)、Queue(装消息的队列名称)、Exchange(路由器)、routing Key(可以进行数据传输的队列)、binding key (需要接收数据的队列)
* 例如:
* 1. 要传输的数据为(Message):"我要使用rabbitmq"
* 2. 将上面要传输的数据放在3个Queue中, 并分别为Queue自定义取名: spring.rabbitmq.one 、 springboot.rabbitmq.two 、 springcould.rabbitmq.three
* 3. 定义一个路由器(Exchange)[当然路由器有4种类型,不知道的自己去看看5分钟就懂了] ,将上面3个Queue 绑定到路由器(Exchange)上面
* 4. 路由器设置routing Key (就是一个匹配的规则,跟他匹配上的上面自定义的Queue才能把消息发送到路由器上(Exchange) )
* 5. binding key (需要接收数据的队列) 也就是在接收端的监听注解上面加上要接收的Queue名称(也就是上面自定的3个中的Queue,可以都有,可以1个或者其他,也可以都没有[不过就不能接收消息了])
*/
/**
* fanout属于广播模式,只要跟它绑定的队列都会通知并且接受到消息。
* this.rabbitTemplate.convertAndSend(ExchangeType, routingKey, dateString);
*
* @param uUser
* @return
* @throws Exception
*/
@PostMapping("/fanout")
@ApiOperation(value = "rabbitmq异步添加用户", notes = "生产消费者模式", response = ApiResult.class)
public Object addSysUser(@Valid @RequestBody UUser uUser) throws Exception {
// 广播模式 routingKey 为空串就可以了。
for(int i=0;i<10000;i++){
this.rabbitTemplate.convertAndSend("fanoutExchange", "", JSON.toJSONString(uUser));
}
return success(true);
}
/**
* topic模式 (如果你懂的话,就知道topic其实是一种分组模式)
* this.rabbitTemplate.convertAndSend(ExchangeType, binding key, dateString);
*
* @param uUser
* @return
* @throws Exception
*/
@PostMapping("/topic")
@ApiOperation(value = "rabbit异步添加用户", notes = "发布、订阅模式", response = ApiResult.class)
public Object addUser(@Valid @RequestBody UUser uUser) throws Exception {
// 下面这种写法,就是所有topic类型路由器绑定Queue都能发送消息到rabbitmq中【跟topic模式识别规则有关】;
// 注意: 这里的topic.a 已经不是routing key(topic模式,routing key 配置在配置文件中的) ,这里可以理解为binding key。
this.rabbitTemplate.convertAndSend("topicExchange", "topic.a", JSON.toJSONString(uUser));
return success(true);
}
/**
* direct模式
* this.rabbitTemplate.convertAndSend(ExchangeType, routingKey, dateString);
*
* @param uUser
* @return
* @throws Exception
*/
@PostMapping("/direct")
@ApiOperation(value = "rabbit异步添加用户", notes = "发布、订阅模式", response = ApiResult.class)
public Object addUserInfo(@Valid @RequestBody UUser uUser) throws Exception {
// 只有directA的队列(Queue)才能传输数据
this.rabbitTemplate.convertAndSend("directExchange", "directA", JSON.toJSONString(uUser));
return success(true);
}
}
最后一步,就是接受rabbitmq的数据了,通常是另新建一个项目了。。
1. 引入依赖,跟上面一样。
2. application.yml的配置文件 ( 这里专门修改端口,方便使用测试 )
server:
port: 8081 # 配置项目访问端口
spring:
rabbitmq: ############################################### rabbitmq配置 ##############################################
password: admin # 密码
username: admin # 用户名
port: 5673 # 端口号 5672用于常规连接(我这里是linux改成了5672), 5671用于使用TLS的连接
host: 47.96.100.61 # ip
publisher-confirms: true # 消息发送确认机制,防止消息丢失
publisher-returns: true # 如果要进行消息回调,则这里必须要设置为true
connection-timeout: 10s # 超时时间
dynamic: true # 默认创建一个AmqpAdmin的Bean 默认为true
requested-heartbeat: 2s # 请求的心跳超时时间
3. 消费数据了:
唯一需要根据情况修改的地方:@RabbitListener(queues = {"自定义的queue"})
package com.rabbitmq.mq.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author: wangqinmin
* @date: 2019/3/29 17:28
* @description: 仰天大笑出门去,我辈岂是蓬蒿人
* @description fanout模式消费者
*/
@Component
// 消费端,不同的Queue这里的监听不同
@RabbitListener(queues = {"fanout.a"})
public class FanoutA2Consumer {
/**
* 消息消费
*
* @RabbitHandler 代表此方法为接受到消息后的处理方法
*/
@RabbitHandler
public void recieved(String msg) {
System.out.println("FanoutA2Consumer [fanout.a]:" + msg);
}
}
完工 !
源码也是有的,有兴趣的再找我要吧。