目录
RabbitMQ
MQ
常见MQ实现
RabbitMQ快速入门
- 结构介绍
- 消息模型
-
案例
-
实现思路
-
- 创建连接
-
- 创建Channel
-
- 声明队列
-
- 发送消息
-
- 关闭连接和Channel
-
-
代码实现
- publisher端实现
public class PublisherTest { @Test public void testSendMessage() throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setVirtualHost("/"); factory.setUsername("guest"); factory.setPassword("123456"); // 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(); } }
- consumer端实现
public class ConsumerTest { public static void main(String[] args) throws IOException, TimeoutException { // 1.建立连接 ConnectionFactory factory = new ConnectionFactory(); // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码 factory.setHost("127.0.0.1"); factory.setPort(5672); factory.setVirtualHost("/"); factory.setUsername("guest"); factory.setPassword("123456"); // 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.订阅消息 channel.basicConsume(queueName, true, new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { // 5.处理消息 String message = new String(body); System.out.println("接收到消息:【" + message + "】"); } }); System.out.println("等待接收消息。。。。"); } }
-
SpringAMQP
Basic Queue简单队列模型
-
实现步骤
-
- 引入依赖,配置mq连接信息
<!--AMQP依赖,包含RabbitMQ--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
spring: rabbitmq: host: 127.0.0.1 # 主机名 port: 5672 # 端口 virtual-host: / # 虚拟主机 username: guest # 用户名 password: 123456 # 密码
-
- 编写publisher端,发送消息
@SpringBootTest /*SpringRunner是JUnit 4中的一个类,用于集成Spring TestContext Framework,以便在测试中使用Spring特性(如依赖注入,自动装配等)。 * 此处用于解决RabbitTemplate无法实现自动注入的问题 * 也可使用import org.junit.jupiter.api.Test;而非import org.junit.Test;解决此问题*/ //@RunWith(SpringRunner.class) public class SpringAmqpTest { @Autowired private RabbitTemplate rabbitTemplate; /** * Basic Queue 简单队列测试 */ @Test public void testSimpleQueue() { //队列名 String queueName = "simple.queue"; //消息 String message = "hello springamqp"; //发送消息 rabbitTemplate.convertAndSend(queueName, message); } }
-
- 编写consumer端,接收消息
@Component public class SpringRabbitListener { //简单队列模型消费者 @RabbitListener(queues = "simple.queue") public void listenSimpleQueueMessage(String message){ System.out.println("消费者接收到的消息 = " + message); } }
-
Work Queue 工作队列模型
-
- 修改publisher端,模拟大量消息堆积情况
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* workQueue 工作队列测试
* 向队列中不停发送消息,模拟消息堆积。
*/
@Test
public void testWorkQueue() throws InterruptedException {
// 队列名称
String queueName = "simple.queue";
// 消息
String message = "hello, message_";
for (int i = 0; i < 50; i++) {
// 发送消息
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
-
- 修改consumer端,模拟多个消费者绑定同一个队列进行任务处理
@Component
public class SpringRabbitListener {
//工作队列模型消费者
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {
System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
Thread.sleep(200);
}
}
-
- 优化,能者多劳
此时存在问题:两个消费者接收到的消息一样多,导致处理速度下降,添加配置每次只能读取一条消息,处理完才能获取下一条消息,在消费端applicaton.yml文件中添加配置
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完才能获取下一条
发布/订阅模型
Fanout类型交换机
-
模型图
-
消息发送流程
-
1) 可以有多个队列
-
2) 每个队列都要绑定到Exchange(交换机)
-
3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
-
4) 交换机把消息发送给绑定过的所有队列
-
5) 订阅队列的消费者都能拿到消息
-
代码实现
-
- 声明交换机,队列
-
-
@Configuration
public class FanoutConfig {
/**
* 声明交换机
* @return fanout类型交换机
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("shifan.fanout");
}
/**
* 第一个队列
* @return
*/
@Bean
public Queue fanoutQueue1() {
return new Queue("fanout.queue1");
}
/**
* 绑定队列1到交换机
* @param fanoutQueue1
* @param fanoutExchange
* @return
*/
@Bean
public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
/**
* 第二个队列
* @return
*/
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
/**
* 绑定队列2到交换机
* @param fanoutQueue2
* @param fanoutExchange
* @return
*/
@Bean
public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
-
- 发送消息
@Autowired private RabbitTemplate rabbitTemplate; /** * 测试发送消息到fanout类型交换机 */ @Test public void testFanoutExchange(){ //交换机名称 String exchangeName = "shifan.fanout"; //消息 String message = "hello everyone"; //发送消息到交换机 rabbitTemplate.convertAndSend(exchangeName,"", message); }
-
- 接收消息
@Component public class SpringRabbitListener { //发布订阅模型消费者,fanout类型交换机 @RabbitListener(queues = "fanout.queue1") public void listerFanoutQueue1(String msg){ System.out.println("消费者1接收到Fanout消息:【" + msg + "】"); } @RabbitListener(queues = "fanout.queue2") public void listerFanoutQueue2(String msg){ System.out.println("消费者2接收到Fanout消息:【" + msg + "】"); } }
-
总结
-
交换机的作用
接收publisher发送的消息
将消息按照规则路由到与之绑定的队列
不能缓存消息,路由失败,消息丢失
FanoutExchange的会将消息路由到每个绑定的队列
-
Direct类型交换机
- 模型图
-
流程
> 队列与交换机的绑定,不能是任意绑定了,而是要指定一个`RoutingKey`(路由key) > > 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 `RoutingKey`。 > > Exchange不再把消息交给每一个绑定的队列,而是根据消息的`Routing Key`进行判断,只有队列的`Routingkey`与消息的 `Routing key`完全一致,才会接收到消息
-
代码实现
-
- 基于注解声明交换机,队列
@Component public class SpringRabbitListener { //发布订阅模型消费者,direct类型交换机 //基于注解的方式声明队列和交换机 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1") ,exchange = @Exchange(name = "shifan.direct",type = ExchangeTypes.DIRECT) ,key = {"red","yellow"})) public void listenDirectQueue1(String msg){ System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue2") ,exchange = @Exchange(name = "shifan.direct",type = ExchangeTypes.DIRECT) ,key = {"red","blue"})) public void listenDirectQueue2(String msg){ System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】"); } }
-
- 发送消息
@Autowired private RabbitTemplate rabbitTemplate; /** * 测试发送消息到direct类型交换机 */ @Test public void testDirectExchange(){ //交换机名 String exchangeName = "shifan.direct"; //消息 String message = "道阻且长,行则将至"; //发送消息到direct交换机 rabbitTemplate.convertAndSend(exchangeName, "yellow",message); }
-
-
总结
Direct交换机与Fanout交换机的差异
Fanout交换机将消息路由给每一个与之绑定的队列
Direct交换机根据RoutingKey判断路由给哪个队列
如果多个队列具有相同的RoutingKey,则与Fanout功能类似
Topic类型交换机
- 模型图
-
- 与Direct的区别
- Topic`类型的`Exchange`与`Direct`相比,都是可以根据`RoutingKey`把消息路由到不同的队列。只不过`Topic`类型`Exchange`可以让队列在绑定`Routing key` 的时候使用通配符!
- `Routingkey` 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: `item.insert`
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
举例:
item.#
:能够匹配item.spu.insert
或者 item.spu
item.*
:只能匹配item.spu
-
代码实现
-
- 基于注解声明交换机,队列
@Component public class SpringRabbitListener { //发布订阅模型消费者,topic类型交换机 @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue1") ,exchange = @Exchange(name = "shifan.topic",type = ExchangeTypes.TOPIC) ,key = "china.#")) public void listenTopicQueue1(String msg){ System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】"); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue2") ,exchange = @Exchange(name = "shifan.topic",type = ExchangeTypes.TOPIC) ,key = "#.news")) public void listenTopicQueue2(String msg){ System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】"); } }
-
- 发送消息
@Autowired private RabbitTemplate rabbitTemplate; /** * 测试发送消息到topic类型交换机 */ @Test public void testTopicExchange(){ //交换机名 String exchangeName = "shifan.topic"; //消息 String message = "道阻且长,行则将至"; //发送消息到direct交换机 rabbitTemplate.convertAndSend(exchangeName, "china.weather",message); }
-
消息转换器
-
SpringAMQP默认使用的消息序列化与反序列化方式
-
JDK序列化
-
缺点
数据体积过大
有安全漏洞
可读性差
-
-
改用JSON方式实现序列化与反序列化
-
代码实现
-
- 引入依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.9.10</version> </dependency>
-
- 将消息转换器加入到容器中
-
-
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}