消息队列【RocketMq|RabbitMq】

- RabbitMq、ActiveMq、RocketMq、kafaka

  1. 应用解耦
    在这里插入图片描述
    订单系统写入消息队列,然后返回用户下单成功,
    库存系统去订阅消息,进行入库

  2. 流量销峰
    在这里插入图片描述
    应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

    用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到 错误页面
    秒杀业务根据消息队列中的请求信息,再做后续处理
    通过消息队列缓解服务器压力.

  3. 异步处理
    在这里插入图片描述

在这里插入图片描述在这里插入图片描述

RocketMq:

通信协议【TCP/HTTP】从SDK中找到

架构图:

在实际的部署过程中,Broker是实际存储消息的数据节点,Nameserver则是服务发现节点,Producer发送消息到某一个Topic,并给到某个Consumer用于消费的过程中,需要先请求Nameserver拿到这个Topic的路由信息,即Topic在哪些Broker上有,每个Broker上有哪些队列,拿到这些请求后再把消息发送到Broker中;相对的,Consumer在消费的时候,也会经历这个流程。
https://www.jianshu.com/p/95ab928960b3
https://www.cnblogs.com/linlinismine/p/9184073.html
在这里插入图片描述
topic下默认为4个队列:
在这里插入图片描述 轮询监控队列

  • NameServer是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。

  • Broker部署相对复杂,Broker分为Master与Slave,一个Master可以对应多个Slave,但是一个Slave只能对应一个Master,Master与Slave的对应关系通过指定相同的BrokerName,不同的BrokerId来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。每个Broker与NameServer集群中的所有节点建立长连接,定时注册Topic信息到所有NameServer。

  • Producer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master建立长连接,且定时向Master发送心跳。Producer完全无状态,可集群部署,天然支持集群。

  • Consumer与NameServer集群中的其中一个节点(随机选择)建立长连接,定期从NameServer取Topic路由信息,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。Consumer既可以从Master订阅消息,也可以从Slave订阅消息,订阅规则由Broker配置决定,天然支持集群。

- 消息读写:从文件到客户端
在这里插入图片描述

消息存储:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
刷盘机制:
同步【在分布式系统中,所有的Broker消息都刷盘完,才通知发送消息已经成功】 、异步

在这里插入图片描述

RocketMq : 如何保证消息不被丢失(消息可靠性)

三处丢失

Producer : 同步发送、异步回调、重试机制。
Borker:刷盘策略,同步刷盘、异步刷盘。集群化部署
Consumer: 手动确认机制。

分布式事务:事务消息
在这里插入图片描述
在这里插入图片描述
在本地事务中睡眠一定时间,消息回查会1min回查一次。上图可以看到最后一次回查成功了。

集群模式、广播模式消费:

指定一个类型就OK 了。

顺序消费:
将消息发往同一个Broker下的同一个Topic下的同一个队列中,因为顺序消费只能保证局部顺序,不能保证全局消费。
在这里插入图片描述
在这里插入图片描述
消费者不变:
在这里插入图片描述

消息堆积问题:

推拉方式:
一般是push模式。
在这里插入图片描述

消息确认机制:
在这里插入图片描述

RabbitMQ:

     1.rabbitMQ监控系统的端口:15672
     2.rabbitMQ服务调用端口:5672

1.0 如何保证消息队列的顺序执行
场景:
在这里插入图片描述
由于拿到每个数据后,消费每个数据的速度不一样,导致了入数据库的数据顺序有问题。

解决方案:(RabbitmQ)
1、多个队列将消息往一个队列发
在这里插入图片描述
2、消费者内部采用内存队列来消费
在这里插入图片描述
解决方案(ActiveMq的):
有消息组
https://blog.csdn.net/YAOQINGGG/article/details/82563304

2.0如何保证消息不丢失?

三处消息丢失:
生产者:confirm机制。
rabbitmq:开启持久化 2处 元数据 和 队列数据。
消费者:手动ack告诉rabbitmq 手动确认机制。

一:rabbitmq的事务,阻塞的,回滚,然后重试;confirm机制,消息队列回传你ack确认(针对提供者断线了导致的数据丢失),如果没能收到会回调你nack接口告诉你失败了
二:持久化数据开启(针对ActiveMq挂了)
三:消费者采取手动确认机制 消费结束的时候手动ack 到消息队列。

参考:https://www.cnblogs.com/756623607-zhang/p/10507267.html

3.0如何处理消费者消费失败?

加入一个死信队列,后台启动线程重新消费

4.0 如何保证消息队列消息不被重复消费

答案:一般消息队列都有自己的确认机制(正常情况下)。如果网络原因导致消费端没有确认到消息队列,也就是队列没有收到消息,那就导致了重复消费。

如何解决?

这个问题针对业务场景来答,分以下三种情况:
(1)比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
(3)如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis.那消费者开始消费前,先去redis中查询有没有消费记录即可。

消息队列模式:
1.简单模式
在这里插入图片描述
工作原理:
当客户端(生产者)将消息写入消息队列中时,消息队列中信息的数量加1.
消费者实时监听消息队列,当消息队列中有消息时,则获取消息,之后执行业务逻辑.同时消息队列的数量减一
实现:

<!-- 消息队列 -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>3.5.1</version>
		</dependency>

消息生产者

public class Test_1_simple_provider {
	
	@Test
	public void provider() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		//4,发送消息,routingKey必须与queue一致
		String msg="msg1";
		channel.basicPublish("", "orderQueue", null, msg.getBytes());
		//5,关闭
		channel.close();
		connection.close();
		System.out.println("发送数据成功");
	   
	}

}

消息消费者:

public class Test_1_simple_consumer {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者取到:"+mString);
		}
	   
	}

}

2.工作模式
在这里插入图片描述
说明:
由一个生产者负责消息写入队列,但是如果有一个消费者负责消费,可能会造成消息的积压.所以准备多个消费者共同消费一个队列中的消息.
实现:
生产者:

public class Test_2_work_provider {

	@Test
	public void provider() throws Exception {
		// 1,建立连接
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection = factory.newConnection();
		// 2,建立通道
		Channel channel = connection.createChannel();
		// 3,定义队列
		// durable true 持久化,重启服务器后,数据还有
		// exclusive true,只能通过当前连接消费 false
		// autoDelete true 队列中消息处理完后,自动删除队列
		// arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		// 4,发送消息
		for (int i = 3; i < 10; i++) {
			String msg = "msg" + i;
			channel.basicPublish("", "orderQueue", null, msg.getBytes());
		}
		// 5,关闭
		channel.close();
		connection.close();
		System.out.println("发送数据成功");

	}

}

消费者1

public class Test_2_work_consumer1 {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		System.out.println("消费者1启动");

		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者1取到:"+mString);
		}
	   
	}

}

消费者2

public class Test_2_work_consumer2 {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		System.out.println("消费者2启动");

		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者2取到:"+mString);
		}
	   
	}

}

说明:
消息被两个消费者消费

3.发布订阅模式
在这里插入图片描述
特点:如果生产者发送消息,那么订阅的全部消费者都会执行消息.
后期添加的消费者得不到以前的消息。需要先启动消费者

生产者:

public class Test_3_publish_p {
	// 定义生产者
	@Test
	public void provider() throws IOException {
		System.out.println("开始发布消息");
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection = factory.newConnection();
		// 定义通道
		Channel channel = connection.createChannel();
		// 定义交换机名称
		String exchange_name = "E1";
		// fanout是定义发布订阅模式  direct是 路由模式 topic是主题模式
		channel.exchangeDeclare(exchange_name, "fanout");

		String msg = "order1" ;
		channel.basicPublish(exchange_name, "", null, msg.getBytes());

		channel.close();
		connection.close();
	}

	
}

消费者1:
public class Test_3_publish_c1 {

@Test
public void consumer1() throws Exception{
	ConnectionFactory factory = new ConnectionFactory();
	factory.setHost("192.168.216.202");
	factory.setPort(5672);
	factory.setUsername("jtadmin");
	factory.setPassword("jtadmin");
	factory.setVirtualHost("/jt");
	Connection	connection = factory.newConnection();
	
	Channel channel = connection.createChannel();
	String exchange_name = "E1";
	//定义交换机模式
	channel.exchangeDeclare(exchange_name, "fanout");
	
	String queue_name = UUID.randomUUID().toString();
	System.out.println("队列名称"+queue_name);
	//定义队列
	channel.queueDeclare(queue_name, true, false, false, null);
	//将队列和交换机绑定   key:表示接收数据标识
	channel.queueBind(queue_name, exchange_name, "");
	//定义消费数量 
	channel.basicQos(1);
	//定义消费者
	QueueingConsumer consumer = new QueueingConsumer(channel);
	
	//将消费者和队列绑定,并且需要手动返回
	channel.basicConsume(queue_name, false, consumer);
	System.out.println("消费者1");

	while(true){
		QueueingConsumer.Delivery delivery = consumer.nextDelivery();
		String msg = new String(delivery.getBody());
		System.out.println(msg+"入库");
		//false表示一个一个返回
		channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
	}
}

}
消费者2:

@Test
	public void consumer1() throws Exception{
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection	connection = factory.newConnection();
		
		Channel channel = connection.createChannel();
		String exchange_name = "E1";
		//定义交换机模式
		channel.exchangeDeclare(exchange_name, "fanout");
		
		String queue_name = UUID.randomUUID().toString();
		System.out.println("队列名称"+queue_name);
		//定义队列
		channel.queueDeclare(queue_name, true, false, false, null);
		//将队列和交换机绑定   key:表示接收数据标识
		channel.queueBind(queue_name, exchange_name, "");
		//定义消费数量 
		channel.basicQos(1);
		//定义消费者
		QueueingConsumer consumer = new QueueingConsumer(channel);
		
		//将消费者和队列绑定,并且需要手动返回
		channel.basicConsume(queue_name, false, consumer);
		System.out.println("消费者2");

		while(true){
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String msg = new String(delivery.getBody());
			System.out.println(msg+"给用户发短信");
			//false表示一个一个返回
			channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
		}
	}

}

说明:
两个消费者都能消费消息

4.路由模式
在这里插入图片描述
说明:路由模式是发布订阅模式的升级,通过定义不同的路由key使得程序将消息发送到不同的队列中.

5.主题模式
在这里插入图片描述
说明:
可以通过路由key将消息发送到一类相同的key中 使用通配符实现
符号说明:
#号:表示任意字符(任意个.)
*号:任意单个字符或者词组(单个.)

ActiveMq:

1、点对点模式:
只有一个消费者可以接收到消息
不能重复消费
生产者:
$queneName = "/queue/userReg";
消费者:
$stomp->subscribe('/queue/userReg');

2、发布/订阅模型特点:
多个消费者都可以收到消息
能重复消费
生产者:
$queneName = "/topic/userReg";
消费者:
$stomp->subscribe('/topic/userReg');

几种消息队列的比较:

这篇文章对比的不错可以看下

例如:
面试:公司是怎么处理消息被消费者消费失败的问题的
在这里插入图片描述
引入了死信队列,将失败的消息放到死信队列中,重新消费。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值