小小MQ,知识点竟然这么多???(一)

小小MQ,知识点竟然这么多???

一、MQ的基本概念

1.MQ概述

MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。
常见的服务通信:
在这里插入图片描述
加入MQ后:
在这里插入图片描述

二、MQ的优势

1.应用解耦

服务与服务之间不再约定协议而对接接口,而是通过生产者/消费者的模式让中间件的MQ来对接两边的数据通信实现解耦合(扩展性更强)。
常见的服务通信:
(img-6bqQbeN2-1630732260464)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20210904002931751.png)]加入MQ后:
在这里插入图片描述

2.异步提速

常见的服务通信:
需要阻塞成功获取到响应状态在写入数据到数据库,阻塞同步往下执行。
在这里插入图片描述加入MQ后:
在这里插入图片描述

3.削峰填谷

常见的服务通信:当一瞬间有5000个请求给到服务器时,服务器最大能处理1000请求,受不了了当场去世。
在这里插入图片描述

加入MQ后:
先把请求丢到队列中等待处理,系统每秒从mq中拉取1000个请求进行处理(刚好卡在能处理的1000个,真实压榨案例),这样就变成了从1秒钟处理5000个请求的高峰期,拆分成了 5秒钟,每秒钟处理1000请求的平缓处理期,哦不,是满载处理期。
在这里插入图片描述

根据下面的图所示,确实高峰期被削掉了
在这里插入图片描述

三、MQ的劣势

系统可用性降低

系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务产生影响。如何保证MQ的高可用?

系统复杂度提高

MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行的异步调用。如何保证消息不背丢失等等。

四、常见的MQ产品

MQ是一种抽象的概念,衍生了各种基于其思想的实现,如常见的:RabbitMQ,RocketMQ,Kafka。
在这里插入图片描述

五、RabbitMQ 介绍

建议直接看总结,下面的知识可以慢慢品(以下引用Guide哥的)

1.RabbitMQ 简介

RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。

RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点:

  • 可靠性: RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
  • 灵活的路由: 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。
  • 扩展性: 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
  • 高可用性: 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
  • 支持多种协议: RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
  • 多语言客户端: RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。
  • 易用的管理界面: RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。
  • 插件机制: RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。

RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。

下面再来看看图—— RabbitMQ 的整体模型架构。
图1-RabbitMQ 的整体模型架构

1.1 Producer(生产者) 和 Consumer(消费者)
  • Producer(生产者) :生产消息的一方(邮件投递者)
  • Consumer(消费者) :消费消息的一方(邮件收件人)

消息一般由 2 部分组成:消息头(或者说是标签 Label)和 消息体。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。

1.2 Exchange(交换器)

在 RabbitMQ 中,消息并不是直接被投递到 Queue(消息队列) 中的,中间还必须经过 Exchange(交换器) 这一层,Exchange(交换器) 会把我们的消息分配到对应的 Queue(消息队列) 中。

Exchange(交换器) 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 Producer(生产者) ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。

RabbitMQ 的 Exchange(交换器) 有4种类型,不同的类型对应着不同的路由策略direct(默认)fanout, topic, 和 headers,不同类型的Exchange转发消息的策略有所区别。这个会在介绍 Exchange Types(交换器类型) 的时候介绍到。

Exchange(交换器) 示意图如下:
Exchange(交换器) 示意图
生产者将消息发给交换器的时候,一般会指定一个 RoutingKey(路由键),用来指定这个消息的路由规则,而这个 RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效

RabbitMQ 中通过 Binding(绑定)Exchange(交换器)Queue(消息队列) 关联起来,在绑定的时候一般会指定一个 BindingKey(绑定建) ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。

Binding(绑定) 示意图:
Binding(绑定) 示意图
生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。

1.3 Queue(消息队列)

Queue(消息队列) 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

RabbitMQ 中消息只能存储在 队列 中,这一点和 Kafka 这种消息中间件相反。Kafka 将消息存储在 topic(主题) 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。

多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。

RabbitMQ 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。

1.4 Broker(消息中间件的服务节点)

对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。

下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。
消息队列的运转过程
这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 Exchange Types(交换器类型)

1.5 Exchange Types(交换器类型)

RabbitMQ 常用的 Exchange Type 有 fanoutdirecttopicheaders 这四种(AMQP规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。

① fanout

fanout 类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。

② direct

direct 类型的Exchange路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。
direct 类型交换器
以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。
direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。

③ topic

前面讲到direct类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:

  • RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
  • BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
  • BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
    topic 类型交换器
    以上图为例:
  • 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queuel 和 Queue2;
  • 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中;
  • 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中;
  • 路由键为 “java.rabbitmq.demo” 的消息只会路由到Queuel中;
  • 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。
④ headers(不推荐)

headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式)'对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。

2.总结

用大白话说,RabbitMQ整个架构就是:客户端、交换机、队列,这三个角色因为交换机不同的模式(直连交换机、扇形交换机、主体交换机、首部交换机)以及不同的组装形成了RabbitMQ使用的各种模式:简单模式()、工作队列模式、发布/订阅模式、路由模式、通配符模式等,其最核心的莫过于队列,提供者及对应入队,消费后对应出队。

3.Windows本地环境安装RabbitMQ

3.1 下载Erlang

RabbitMQ是基于Erlang环境开发的,先下载个Erlang(24),https://erlang.org/download/otp_win64_24.0.exe,下载直接一键点安装

3.2 安装RabbitMQ(3.9.5)

https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.9.5/rabbitmq-server-3.9.5.exe,·1下载直接一键点安装

3.3启动

这个应该不用我说按啥启动了吧

在这里插入图片描述

启动成功,如下

在这里插入图片描述

3.4安装管理插件

右键快捷方式打开文件夹所在目录
在这里插入图片描述
执行:
rabbitmq-plugins enable rabbitmq_management

5.访问后台

后台管理网址:
http://localhost:15672/
安装后的默认初始
账号:guest
密码:guset
然后就得到了黑化版的RabbitMQ?
在这里插入图片描述

4.从一个简单的例子认识RabbitMQ(Java)

1.示例

依赖:

		 <dependency>
	        <groupId>org.springframework.boot</groupId>
			 <artifactId>spring-boot-starter-amqp</artifactId>
	        <version>2.4.1</version>
	    </dependency>

生产者:

package boot.spring.test;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @description:
 * @author:lx
 * @date: 2021/09/04 下午 3:12
 * @Copyright: lx
 */
public class Provider {

	/**
	 * 声明的队列名
	 */
	private final static String QUEUE_NAME = "test_queue";

	public static void main(String[] args) throws IOException, TimeoutException {

		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("127.0.0.1");
		// 默认端口号
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("guest");
		connectionFactory.setPassword("guest");
		connectionFactory.setVirtualHost("/");

		// 获取TCP长连接
		Connection conn = connectionFactory.newConnection();

		// 创建通信“通道”,相当于TCP中的虚拟连接
		Channel channel = conn.createChannel();
		// 开启RabbitMQ事务,当没有接收到MQ反馈时抛出异常并回滚
		channel.txSelect();

		// 创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
		// 第一个参数:队列名称
		// 第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
		// 第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有一次则拥有它的消费者才能一直使用,其它消费者不让访问
		// 第四个参数:是否自动删除,false代表连接停掉后不自动删除这个队列
		// 其它额外参数:null
		channel.queueDeclare(QUEUE_NAME, true, false, false, null);
		String message = "hello world!";

		try {
			// 第一个参数:交换机,这里时简单demo版本,没有用到交换机
			// 第二个参数:队列名称
			// 第三个参数:额外的设置属性
			// 第四个参数:要传递的消息字节数组
			channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
		} catch (Exception e) {
			// 发生异常的回滚
			channel.txRollback();
		}

		// 正常流程提交事务
		channel.txCommit();
		channel.close();
		conn.close();
		System.out.println("发送成功!");
	}
}

消费者:

package boot.spring.test;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @description:
 * @author:lx
 * @date: 2021/09/04 下午 3:33
 * @Copyright: lx
 */
public class Consumer {

	private final static String QUEUE_NAME = "test_queue";

	public static void main(String[] args) throws IOException, TimeoutException {

		ConnectionFactory connectionFactory = new ConnectionFactory();
		connectionFactory.setHost("127.0.0.1");
		connectionFactory.setPort(5672);
		connectionFactory.setUsername("guest");
		connectionFactory.setPassword("guest");
		connectionFactory.setVirtualHost("/");

		// 获取TCP长连接
		Connection conn = connectionFactory.newConnection();

		// 创建通信“通道”,相当于TCP中的虚拟连接
		Channel channel = conn.createChannel();

		// 创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
		// 第一个参数:队列名称
		// 第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
		// 第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有一次则拥有它的消费者才能一直使用,其它消费者不让访问
		// 第四个参数:是否自动删除,false代表连接停掉后不自动删除这个队列
		// 其它额外参数:null
		channel.queueDeclare(QUEUE_NAME, true, false, false, null);

		// 创建一个消息消费者
		// 第一个参数:队列名称
		// 第二个参数:第二个参数表示是否自动确认收到消息,false代表手动编程确认消息
		// 第三个参数:传入DefaultConsumer的实现类
		channel.basicConsume(QUEUE_NAME, false, new Receiver(channel));

	}
}

/**
 * @description:
 * @author:lx
 * @date: 2021/09/04 下午 3:37
 * @Copyright: lx
 */
class Receiver extends DefaultConsumer {

	private Channel channel;

	/**
	 * 重写构造函数,channel通道对象需要从外层传入,在handleDelivery中要用到
	 *
	 * @param channel
	 */
	public Receiver(Channel channel) {
		super(channel);
		this.channel = channel;
	}

	@Override
	public void handleDelivery(String consumerTag,
	                           Envelope envelope,
	                           AMQP.BasicProperties properties,
	                           byte[] body)
			throws IOException {
		String message = new String(body);
		System.out.println("消费者接收到的消息:" + message);
		System.out.println("消息的TagID:" + envelope.getDeliveryTag());
//		int i = 1 / 0;
		// false只签收当前的消息,设置为true的时候代表签收该消费者所有未签收的消息
		channel.basicAck(envelope.getDeliveryTag(), false);
	}
}
2.重复消费

启动生产者,来到管理页面,可以看到消息已经入队,进入准备被消费的状态。
在这里插入图片描述
Debug启动消费者后,在管理页面可以看到,队内消息状态由准备-转变到了待回应状态,total总数还是存在的,此时消息已被接收到,但未被响应。
在这里插入图片描述
由于断点导致MQ超时未收到响应,状态回滚到ready,消息仍然在队中,但事实上消费者已经消费过一次了,这里引出一个问题-重复消费。
在这里插入图片描述
还是很多场景导致你发生异常回滚的情况还有很多,比如:
程序发送异常导致,顺带一提,程序异常会导致消费者程序崩溃,MQ也会一直阻塞在等待响应的阶段
消费者进程突然GG
在这里插入图片描述
使用代理将捕获转发,模拟丢包的情况

解决方案

既然重复消费这种情况是难以避免的,那么我们如何去处理这种情况呢?
消费端处理消息的业务逻辑保持幂等性
幂等性,任意多次执行所产生的影响均与一次执行的影响相同。就是因为上面所说的重复消费导致消费者多次消费,在多次消费过程中保证多次消费结果与单次消费影响一致。(例如:下单进行扣款操作,如果因为重复消费导致下发了第二次扣款请求时,应该由程序去控制校验是否已存在扣款记录然后在进行消费扣款,否则则会产生重复扣款的操作)

1.当拿到消费者数据在mysql进行插入操作时,先判断唯一约束的字段是否重复,重复则不操作(此种操作在数据量大的情况下极其耗费性能,可以考虑给字段加索引优化性能),或者在进行增改操作前,将唯一约束字段放入缓存,在执行操作前查询缓存是否存在该id,在执行之后的操作。
2.当拿到这个消息做redis的set的操作,set操作幂等。

3.消息丢失

消息在网络传输中丢失,MQ宕机丢失消息

4.消息积压

程序异常会导致消费者程序崩溃,MQ也会一直阻塞在等待响应的阶段,导致消息一直堆积

5.MQ高可用

https://blog.csdn.net/yygEwing/article/details/116329666?utm_source=app&app_version=4.14.1

六、源码分析

从简单的源码分析更深入的认知RabbitMQ
在这里插入图片描述

Broker: 接收和分发消息的应用,RabbitMQ Server就是Message Broker
Virtual Host: 处于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace概念。当多个不同的用户使用同一个RabbitMQserver提供的服务时,可以划分出多个vhost,每个用户在自己的vhost创建exchange/queue等
Connection: publisher/consumer和broker之间的TCP连接
Channel: 如何每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也低。channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQPmethod包含了channelId帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection的开销。

1.回顾设计模式

为了提高阅读体验,二十三种简要叙述设计模式篇迁移至另一篇文章,建议先简要了解设计模式篇后,再继续往下阅读。
设计模式篇: 小傅哥-重学设计模式读后篇!!!
内容结构:

1.二十三种设计模式
	1.1 工厂方法模式
	1.2 抽象工厂模式
	1.3 建造者模式
	1.4 原型模式
	1.5 单例模式
	1.6 适配器模式
	1.7 桥接模式
	1.8 组合模式(没细品)
	1.9 装饰器模式(没细品)
	1.10 外观模式(没细品)
	1.11 享元模式
	1.12 代理模式(没有细品)
	1.13 责任链
	1.14 命令模式
	1.15 迭代器模式(没有细品)
	1.16 中介者模式(没有细品)
	1.17 备忘录模式(没有细品)
	1.18 观察者模式
	1.19 状态模式
	1.20 策略模式
	1.21 模板模式(没有细品)
	1.22 访问者模式(没有细品)
	1.23 解释器模式
2.设计模式的本质?主要是想解决什么样的问题?
3.如何不滥用设计模式?
4.思想的演变过程

2.理解RabbitMQ与客户端的数据交互

2.1 网络基础

下面的图表试图显示不同的TCP/IP和其他的协议在最初OSI模型中的位置:

7应用层例如HTTPSMTPSNMPFTPTelnetSIPSSHNFSRTSPXMPPWhoisENRPAMQP
6表示层例如XDRASN.1SMBAFPNCP
5会话层例如ASAPTLSSSH、ISO 8327 / CCITT X.225、RPCNetBIOSASPWinsockBSD sockets
4传输层例如TCPUDPRTPSCTPSPXATPIL
3网络层例如IPICMPIGMPIPXBGPOSPFRIPIGRPEIGRPARPRARPX.25
2数据链路层例如以太网令牌环HDLC帧中继ISDNATMIEEE 802.11FDDIPPP
1物理层例如线路无线电光纤信鸽

下面引入一部分TCP/IP一书中的部分知识

2.1.1 引言

通过路由器连接的两个网络
通过路由器连接的两个网络

在TCP/IP协议族中,网络层IP提供的是一种不可靠的服务。也就是说,它只是尽可能快地把分组从源结点送到目的结点,但是并不提供任何可靠性保证。而另一方面,TCP在不可靠的IP层上提供了一个可靠的运输层。为了提供这种可靠的服务,TCP采用了超时重传、发送和接收端到端的确认分组等机制。由此可见,运输层和网络层分别负责不同的功能。

TCP/IP协议族中不同层次的协议

TCP和UDP是两种最为著名的运输层协议,二者都使用IP作为网络层协议。

虽然TCP使用不可靠的IP服务,但它却提供一种可靠的运输层服务(TCP使用了三次握手、包序号确保包不丢失)。

UDP为应用程序发送和接收数据报。一个数据报是指从发送方传输到接收方的一个信息单元(例如,发送方指定的一定字节数的信息)。但是与TCP不同的是,UDP是不可靠的,它不能保证数据报能安全无误地到达最终目的。(UDP协议就像是去邮局发邮件,它只管发出去,而不会理会接收者是否有收到

IP是网络层上的主要协议,同时被TCP和UDP使用。TCP和UDP的每组数据都通过端系统和每个中间路由器中的IP层在互联网中进行传输。

2.1.2 包封装

当应用程序用TCP传送数据时,数据被送入协议栈中,然后逐个通过每一层直到被当作一串比特流送入网络。其中每一层对收到的数据都要增加一些首部信息(有时还要增加尾部信息),该过程下图所示。TCP传给IP的数据单元称作TCP报文段或简称为TCP段(TCP segment)。IP传给网络接口层的数据单元称作IP数据报(IP datagram)。通过以太网传输的比特流称作帧(Frame)

下图中帧头和帧尾下面所标注的数字是典型以太网帧首部的字节长度。以太网数据帧的物理特性是其长度必须在46~1500字节之间。

下图中IP和网络接口层之间传送的数据单元应该是分组(packet)。分组既可以是一个IP数据报,也可以是IP数据报的一个片(fragment)。

第1章 概述_TCP/IP详解卷1 协议_即时通讯网(52im.net)

运输层协议首部长度: UDP数据与TCP数据基本一致。唯一的不同是UDP传给IP的信息单元称作UDP数据报(UDP datagram),而且UDP的首部长为8字节。

IP首部指定运输层协议: 由于TCP、UDP、ICMP和IGMP都要向IP传送数据,因此IP必须在生成的IP首部中加入某种标识,以表明数据属于哪一层。为此,IP在首部中存入一个长度为8bit的数值,称作协议域。1表示为ICMP协议,2表示为IGMP协议,6表示为TCP协议,17表示为UDP协议。

传输层占用端口号: 类似地,许多应用程序都可以使用TCP或UDP来传送数据。运输层协议在生成报文首部时要存入一个应用程序的标识符。TCP和UDP都用一个16bit的端口号来表示不同的应用程序。TCP和UDP把源端口号和目的端口号分别存入报文首部中。

头部指定网络层协议: 网络接口分别要发送和接收IP、ARP和RARP数据,因此也必须在以太网的帧首部中加入某种形式的标识,以指明生成数据的网络层协议。为此,以太网的帧首部也有一个16 bit的帧类型域

2.1.3 端口号

服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,**FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。**任何TCP/IP实现所提供的服务都用知名的1~1023之间的端口号。这些知名端口号由Internet号分配机构(Internet Assigned Numbers Authority, IANA)来管理。

到1992年为止,知名端口号介于1~255之间。256~1023之间的端口号通常都是由Unix系统占用,以提供一些特定的Unix服务—也就是说,提供一些只有Unix系统才有的、而其他操作系统可能不提供的服务。现在IANA管理1~1023之间所有的端口号。

Internet扩展服务与Unix特定服务之间的一个差别就是Telnet和Rlogin。它们二者都允许通过计算机网络登录到其他主机上。Telnet是采用端口号为23的TCP/IP标准且几乎可以在所有操作系统上进行实现。相反,Rlogin最开始时只是为Unix系统设计的(尽管许多非Unix系统现在也提供该服务),因此在80年代初,它的有名端口号为513。

2.1.4 TCP三次握手

在TCP协议中,通信双方将通过三次TCP报文实现对以上信息的了解,并在此基础上建立一个TCP连接,而通信双方的三次TCP报文段的交换过程,也就是通常所说的TCP连接建立实现的三次握手(Three-Way Handshake)过程。
在这里插入图片描述

2.1.5 过程

在这里插入图片描述

  • 第一次
    第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
  • 第二次
    第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
  • 第三次
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
  • 完成三次握手,客户端与服务器开始传送数据
2.1.6 AMQP协议

百度百科说,AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现有RabbitMQ等。
简而言之,AMQP是一种应用层协议,由之前的网络小知识可以知道,应用层协议最终填充到TCP(传输层协议)的报文中,再通过IP协议进行网络通信。IP-TCP-AMQP-应用程序

2.1.7 AMQP 0.9.1模型

AMQP 0-9-1(可以定义为AMQP协议的一种约定版本)的工作过程如下图:消息(message)被发布者(publisher)发送给交换机(exchange),交换机常常被比喻成邮局或者邮箱。然后交换机将收到的消息根据路由规则分发给绑定的队列(queue)。最后AMQP代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。

enter image description here

发布者(publisher)发布消息时可以给消息指定各种消息属性(message meta-data)。有些属性有可能会被消息代理(brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。

从安全角度考虑,网络是不可靠的,接收消息的应用也有可能在处理消息的时候失败。基于此原因,AMQP模块包含了一个消息确认(message acknowledgements)的概念:当一个消息从队列中投递给消费者后(consumer),消费者会通知一下消息代理(broker),这个可以是自动的也可以由处理消息的应用的开发者执行。当“消息确认”被启用的时候,消息代理不会完全将消息从队列中删除,直到它收到来自消费者的确认回执(acknowledgement)。

在某些情况下,例如当一个消息无法被成功路由时,消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。

队列,交换机和绑定统称为AMQP实体(AMQP entities)。

2.1.8 总结

服务进程在进行网络通信前需要绑定一个IP地址+端口号,这两者确定一个唯一。

进程间通过可以通过发网络数据包在以太网中进行通信,一个数据包携带IP协议包(网络层进行传输而约定的协议,其中携带了ip地址,用于告诉互联网这个包发给谁,也约定了传输层的协议:是使用TCP还是UDP协议),IP包中携带了传输层协议包,传输层协议包之中携带了绑定的端口号,确定了网络中一个唯一在运行的进程,携带的用户数据就是你进程要传输的数据。

TCP代表传输控制协议。TCP用于在预先建立的连接上发送和接收数据流。TCP协议本身负责建立连接,并确保正确接收所有传输的数据。TCP协议使用三次握手来保证网络通信中不丢包。

在了解了上述一系列知识点后,我们在之后的学习会更加容易。

2.2 使用wireShark进行网络抓包分析
2.2.1 准备

捕获选项
在这里插入图片描述
过滤设置
在这里插入图片描述

2.2.2 随便抓个包来分析下

创建个远程登录的账户,设置过滤规则:ip.addr eq 172.20.10.2,执行生产者Provider。
还记得Frame吗,上面说过的以太网传输之中的一帧,可以理解一个数据包
在这里插入图片描述
这个包里面带有的参数,也是上面说过的噢
在这里插入图片描述
详解:

建立通信前开始建立TCP连接,客户端启动随即绑定一个端口60941访问mq服务端口5672(本地环境,ip都是一样的)
这一开始的三个包,对应着三次握手的理论;客户端发送syn给服务器,服务器这边做了两件事,一是接收刚刚客户
端发的syn,然后再发一个ack包给客户端,告诉客户端我接受到你发的包了,seq为客户端发送时的编码,ack为此
编码+1,顺带一提,回复客户端时,用的seq是一个新的值y,客户端收到后将服务器发来的y+1作为ack值返回给服
务器,告诉它,我刚刚收到你发的包了。
25	2.750249	172.20.10.2	172.20.10.2	TCP	56	609415672 [SYN] Seq=0 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
26	2.750309	172.20.10.2	172.20.10.2	TCP	56	567260941 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM=1
27	2.750360	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=1 Ack=1 Win=2619648 Len=0

回到一开始看过的表格图片,TCP是传输层协议还记得吗,像平时浏览器访问百度,它是通过应用程序(浏览器)
---应用层(HTTP)---传输层(TCP)---网络层(IP)
在建立了TCP长连接后,才有了后面的AMQP(应用层)协议通信,这里的0-9-1是上面提到的协议版本
28	2.766998	172.20.10.2	172.20.10.2	AMQP	52	Protocol-Header 0-9-1

发完一个包后,服务器TCP回复确认收到了这个包
29	2.767044	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=1 Ack=9 Win=2619648 Len=0

30	2.769218	172.20.10.2	172.20.10.2	AMQP	558	Connection.Start 
31	2.769256	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=9 Ack=515 Win=2619136 Len=0
35	2.832482	172.20.10.2	172.20.10.2	AMQP	443	Connection.Start-Ok 
36	2.832522	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=515 Ack=408 Win=2619392 Len=0
37	2.832823	172.20.10.2	172.20.10.2	AMQP	64	Connection.Tune 
38	2.832856	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=408 Ack=535 Win=2619136 Len=0
39	2.841530	172.20.10.2	172.20.10.2	AMQP	64	Connection.Tune-Ok 
40	2.841574	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=535 Ack=428 Win=2619136 Len=0
41	2.842382	172.20.10.2	172.20.10.2	AMQP	65	Connection.Open vhost=/admin 
42	2.842423	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=535 Ack=449 Win=2619136 Len=0
43	2.842588	172.20.10.2	172.20.10.2	AMQP	57	Connection.Open-Ok 
44	2.842619	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=449 Ack=548 Win=2619136 Len=0
45	2.864370	172.20.10.2	172.20.10.2	AMQP	57	Channel.Open 
46	2.864417	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=548 Ack=462 Win=2619136 Len=0
47	2.866522	172.20.10.2	172.20.10.2	AMQP	60	Channel.Open-Ok 
48	2.866556	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=462 Ack=564 Win=2619136 Len=0
49	2.869172	172.20.10.2	172.20.10.2	AMQP	56	Tx.Select 
50	2.869202	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=564 Ack=474 Win=2619136 Len=0
51	2.869354	172.20.10.2	172.20.10.2	AMQP	56	Tx.Select-Ok 
52	2.869379	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=474 Ack=576 Win=2619136 Len=0
53	2.870161	172.20.10.2	172.20.10.2	AMQP	74	Queue.Declare q=test_queue 
54	2.870190	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=576 Ack=504 Win=2619136 Len=0
55	2.870457	172.20.10.2	172.20.10.2	AMQP	75	Queue.Declare-Ok q=test_queue 
56	2.870489	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=504 Ack=607 Win=2619136 Len=0
57	2.872716	172.20.10.2	172.20.10.2	AMQP	113	Basic.Publish x= rk=test_queue Content-Header Content-Body 
58	2.872745	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=607 Ack=573 Win=2619136 Len=0
59	2.872862	172.20.10.2	172.20.10.2	AMQP	56	Tx.Commit 
60	2.872876	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=607 Ack=585 Win=2619136 Len=0
61	2.873178	172.20.10.2	172.20.10.2	AMQP	56	Tx.Commit-Ok 
62	2.873205	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=585 Ack=619 Win=2619136 Len=0
63	2.873667	172.20.10.2	172.20.10.2	AMQP	65	Channel.Close reply=OK 
64	2.873689	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=619 Ack=606 Win=2619136 Len=0
65	2.873856	172.20.10.2	172.20.10.2	AMQP	56	Channel.Close-Ok 
66	2.873880	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=606 Ack=631 Win=2619136 Len=0
67	2.877826	172.20.10.2	172.20.10.2	AMQP	65	Connection.Close reply=OK 
68	2.877867	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=631 Ack=627 Win=2619136 Len=0
69	2.877993	172.20.10.2	172.20.10.2	AMQP	56	Connection.Close-Ok 
70	2.878017	172.20.10.2	172.20.10.2	TCP	44	609415672 [ACK] Seq=627 Ack=643 Win=2619136 Len=0


四次握手了解下,FIN表示我要彻底关闭连接
改进的四次握手
对于一个已经建立的连接,TCP使用改进的四次握手来释放连接(使用一个带有FIN附加标记的报文段)。TCP关闭连接的步骤如下:
第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。
第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。
第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。
第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。
71	2.879282	172.20.10.2	172.20.10.2	TCP	44	609415672 [FIN, ACK] Seq=627 Ack=643 Win=2619136 Len=0
72	2.879321	172.20.10.2	172.20.10.2	TCP	44	567260941 [ACK] Seq=643 Ack=628 Win=2619136 Len=0
如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST
(Reset)包将被发送. 注意在,由于RST包不是TCP连接中的必须部分, 可以只发送RST包(即不带ACK标记).
73	2.880547	172.20.10.2	172.20.10.2	TCP	44	567260941 [RST, ACK] Seq=643 Ack=628 Win=0 Len=0

随便右键点击一个包,右键点击追踪流
在这里插入图片描述
查看整个TCP连接从开始到结束的数据交互,抓重点!使用协议名,实现AMQP协议的程序信息,绑定想要使用的虚拟空间名,想要绑定使用的队列,使用队列中想要传输的消息!(这里还能在折腾折腾)
在这里插入图片描述
筛选只看amqp协议,直接输入:amqp
在这里插入图片描述

3.带着问题去看代码

3.1 消息是如何入队的?
3.2 消息是如何被消费的?
3.3 客户端是如何建立连接的?
3.4 channel是什么?
3.5 RabbitMQ是如何实现异步的?
3.6 RabbitMQ如何确保消息不会丢失?Ack
3.7 为什么消费者在启动后会自动消费队列中的消息?
3.8 消费者接收到消费信息,停止运行消费者后,会继续执行消费行为?

4.核心思想

RabbitMQ实现MQ的核心思想是?其特点是?其中用到了什么设计模式?最有印象的是?

idea中下载源码,阅读。
在这里插入图片描述

七、参考

Guide哥:https://github.com/Snailclimb/JavaGuide
小傅哥:https://bugstack.cn/itstack/itstack-demo-design.html

TCP/IP: http://docs.52im.net/extend/docs/book/tcpip/vol1/1/
AMQP0-9-1: 模型http://rabbitmq.mr-ping.com/AMQP/AMQP_0-9-1_Model_Explained.html
WireShark抓包使用指南: https://wangxiaoming.blog.csdn.net/article/details/83070771
百度百科
B站

欢迎转载,请注明出处。

八、更新日志

  • 9-7
  • 修复了个别错别字
  • 添加了补充
  • 新增整体学习架构图,及第二节点(设计模式)内容
  • 9-7
  • 更新了第三部分网络分析数据交互部分流程
  • 9-8
  • 设计模式部分版本迭代,更新错别字,调整格式
  • 序号调整,虽然有重复,但不影响阅读
  • 9-10
  • 调整阅读体验,迁移设计模式篇
  • 增加网络篇理解通信小知识、协议通信过程、wireShark抓包分析
  • 源码对应协议请求分析

我是林熙,非常感谢大家的支持!一起学习,一起进步!

  • 10
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值