中间件(Middleware)是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台),但在这组中间件中必须要有一个通信中间件,即中间件=平台+通信,这个定义也限定了只有用于分布式系统中才能称为中间件,同时还可以把它与支撑软件和实用软件区分开来。
单体架构
问题:如果项目中一个模块出现了问题,那么整个项目都要重启。
缺点:
分布式架构
一个请求由服务器端的多个服务协同处理完成
存在问题:
1:学习成本高,技术栈过多
2:运维成本和服务器成本增高
3:人员的成本也会增高
4:项目的负载度也会上升
5:面临的错误和容错性也会成倍增加
6:占用的服务器端口和通讯的选择的成本高
7:安全性的考虑和因素逼迫可能选择RMI/MQ相关的服务器端通讯。
优点:
1:服务系统的独立,占用的服务器资源减少和占用的硬件成本减少,
确切的说是:可以合理的分配服务资源,不造成服务器资源的浪费
2:系统的独立维护和部署,耦合度降低,可插拔性。
3:系统的架构和技术栈的选择可以变的灵活(而不是单纯的选择java)
4:弹性的部署,不会造成平台因部署造成的瘫痪和停服的状态。
从上图中可以看出来,消息中间件的是
1:利用可靠的消息传递机制进行系统和系统直接的通讯
2:通过提供消息传递和消息的排队机制,它可以在分布式系统环境下扩展进程间的通讯
1:跨系统数据传递
2:高并发的流量削峰
3:数据的分发和异步处理
4:大数据分析与传递
5:分布式事务
ActiveMQ、RabbitMQ、Kafka、RocketMQ等。
它是一种接受数据,接受请求、存储数据、发送数据等功能的技术服务。
什么是协议?
我们知道消息中间件负责数据的传递,存储,和分发消费三个部分,数据的存储和分发的过程中肯定要遵循某种约定成俗的规范,你是采用底层的TCP/IP,UDP协议还是其他的自己取构建等,而这些约定成俗的规范就称之为:协议。
#所谓协议是指:
1:计算机底层操作系统和应用程序通讯时共同遵守的一组约定,只有遵循共同的约定和规范,系统和底层操作系统之间才能相互交流。
2:和一般的网络应用程序的不同它主要负责数据的接受和传递,所以性能比较的高。
3:协议对数据格式和计算机之间交换数据都必须严格遵守规范。
网络协议三要素
1.语法。语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。
2**.语义**。语义是解释控制信息每个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
3.时序。时序是对事件发生顺序的详细说明。
比如我MQ发送一个信息,是以什么数据格式发送到队列中,然后每个部分的含义是什么,发送完毕以后的执行的动作,以及消费者消费消息的动作,消费完毕的响应结果和反馈是什么,然后按照对应的执行顺序进行处理。如果你还是不理解:大家每天都在接触的http请求协议:
1:语法:http规定了请求报文和响应报文的格式。
2:语义:客户端主动发起请求称之为请求。(这是一种定义,同时你发起的是post/get请求)
3:时序:一个请求对应一个响应。(一定先有请求在有响应,这个是时序
而消息中间件采用的并不是http协议,而常见的消息中间件协议有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议。
面试题:为什么消息中间件不直接使用http协议?
1: 因为http请求报文头和响应报文头是比较复杂的,包含了cookie,数据的加密解密,状态码,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速。
2:大部分情况下http大部分都是短链接,在实际的交互过程中,一个请求到响应很有可能会中断,中断以后就不会就行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息就行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行。
AMQP协议
AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
特性:
1:分布式事务支持。
2:消息的持久化支持。
3:高性能和高可靠的消息处理优势。
MQTT协议
MQTT协议:(Message Queueing Telemetry Transport)消息队列是IBM开放的一个即时通讯协议,物联网系统架构中的重要组成部分。
特点:
1:轻量
2:结构简单
3:传输快,不支持事务
4:没有持久化设计。
应用场景:
1:适用于计算能力有限
2:低带宽
3:网络不稳定的场景。
OpenMessage协议
是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式消息中间件、流处理等领域的应用开发标准。
特点:
1:结构简单
2:解析速度快
3:支持事务和持久化设计。
Kafka协议
Kafka协议是基于TCP/IP的二进制协议。消息内部是通过长度来分割,由一些基本数据类型组成。
特点:
1:结构简单
2:解析速度快
3:无事务支持
4:有持久化设计
简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而消失,使数据能够永久保存。
常见持久化方式:
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
文件存储 | 支持 | 支持 | 支持 | 支持 |
数据库 | 支持 | / | / | / |
MQ消息队列有如下几个角色
1:生产者
2:存储消息
3:消费者
那么生产者生成消息以后,MQ进行存储,消费者是如何获取消息的呢?一般获取数据的方式无外乎推(push)或者拉(pull)两种方式,典型的git就有推拉机制,我们发送的http请求就是一种典型的拉取数据库数据返回的过程。而消息队列MQ是一种推送的过程,而这些推机制会适用到很多的业务场景也有很多对应推机制策略。
比如我在APP上下了一个订单,我们的系统和服务很多,我们如何得知这个消息被那个系统或者那些服务或者系统进行消费,那这个时候就需要一个分发的策略。这就需要消费策略。或者称之为消费的方法论。
在发送消息的过程中可能会出现异常,或者网络的抖动,故障等等因为造成消息的无法消费,比如用户在下订单,消费MQ接受,订单系统出现故障,导致用户支付失败,那么这个时候就需要消息中间件就必须支持消息重试机制策略。也就是支持:出现问题和故障的情况下,消息不丢失还可以进行重发。
消息分发策略的机制与对比
ActiveMQ | RabbitMQ | Kafka | RocketMQ | |
---|---|---|---|---|
发布订阅 | 支持 | 支持 | 支持 | 支持 |
轮询分发 | 支持 | 支持 | 支持 | / |
公平分发 | / | 支持 | 支持 | / |
重发 | 支持 | 支持 | / | 支持 |
消息拉取 | / | 支持 | 支持 | 支持 |
什么是高可用机制
所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力。
当业务量增加时,请求也过大,一台消息中间件服务器的会触及硬件(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署。来达到高可用的目的。
集群部署1:主从共享
生产者将消费发送到Master节点,所有的都连接这个消息队列共享这块数据区域,Master节点负责写入,一旦Master挂掉,slave节点继续服务。从而形成高可用。
集群部署2:主从复制
这种模式写入消息同样在Master主节点上,但是主节点会同步数据到slave节点形成副本,和zookeeper或者redis主从机制很类同。这样可以达到负载均衡的效果,如果消费者有多个这样就可以去不同的节点就行消费,以为消息的拷贝和同步会暂用很大的带宽和网络资源。在后续的rabbitmq中会有使用。
集群部署3:多主集群同步
和上面的区别不是特别的大,但是它的写入可以往任意节点去写入。
集群部署4:多主集群转发
如果你插入的数据是broker-1中,元数据信息会存储数据的相关描述和记录存放的位置(队列)。
它会对描述信息也就是元数据信息就行同步,如果消费者在broker-2中进行消费,发现自己几点没有对应的消息,可以从对应的元数据信息中去查询,然后返回对应的消息信息,场景:比如买火车票或者黄牛买演唱会门票,比如第一个黄牛有顾客说要买的演唱会门票,但是没有但是他会去联系其他的黄牛询问,如果有就返回
集群部署5:Master-slave与Breoker-cluster组合的方案
实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高
总结:
1:要么消息共享
2:要么消息同步
3:要么元数据共享
什么是高可靠机制?
所谓高可用是指:是指系统可以无故障低持续运行,比如一个系统突然崩溃,报错,异常等等并不影响线上业务的正常运行,出错的几率极低,就称之为:高可靠。
在高并发的业务场景中,如果不能保证系统的高可靠,那造成的隐患和损失是非常严重的。
如何保证中间件消息的可靠性呢?可以从两个方面考虑:
1:消息的传输:通过协议来保证系统间数据解析的正确性。
2:消息的存储可靠:通过持久化来保证消息的可靠性。
官方地址:https://www.rabbitmq.com/download.html
RabbitMQ是一个开源的遵循AMQP协议实现的基于Erlang语言编写,支持多种客户端(语言)。用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征。
这里说明一下,我这边采用Docker安装!!!
#获取镜像
docker pull rabbitmq:management
#创建并运行容器
docker run -di --name=myrabbit -p 15672:15672 rabbitmq:management
#建议采用这个
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
查看日志:
docker logs -f myrabbit
浏览器访问—> http://ip地址:15672
1.none:
2.management:查看自己相关节点信息
3.Policymaker:
4.Monitoring:
5.Administrator:
实现步骤
1:jdk1.8
2:构建一个maven工程
3:导入rabbitmq的maven依赖
4:启动rabbitmq-server服务
5:定义生产者
6:定义消费者
7:观察消息的在rabbitmq-server服务中的过程
Java原生依赖:
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.10.0version>
dependency>
spring依赖:
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-amqpartifactId>
<version>2.2.5.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbitartifactId>
<version>2.2.5.RELEASEversion>
dependency>
springboot依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
这里我们只是做一个入门案例,仅仅导入原生的依赖即可
启动服务
systemctl start rabbitmq-server或者docker start myrabbit
代码测试
public class Producer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
//5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
String queueName = "queue1";
channel.queueDeclare(queueName, false, false, false, null);
//6.发送消息的内容
String message = "hello Rabbitmq";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routing
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue1", null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
我们去写一下消费者代码,测试
public class Consumer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到的消息是" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
什么是AMQP?
AMQP全称:Advanced Message Queuing Protocol(高级消息队列协议)。是应用层协议的一个开发标准,为面向消息的中间件设计。
AMQP生产者流程
AMQP消费者流程
核心概念:
Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server
Connection:连接,应用程序与Broker的网络连接 TCP/IP三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host :虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。
上述入门已经讲解过了
public class Producer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
for (int i = 0; i <= 10; i++) {
String message = "zjdzka:" + i ;
channel.basicPublish("","queue1", null, message.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public class Consumer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("消费者1");
//4.通过连接建立通道
channel = connection.createChannel();
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("消费者1收到的消息是" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public class Consumer2 {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("消费者2");
//4.通过连接建立通道
channel = connection.createChannel();
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("消费者2收到的消息是" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
公平分发机制生产者代码没变,则是将消费者代码有所改动
public class Consumer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("消费者1");
//4.通过连接建立通道
channel = connection.createChannel();
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("消费者1收到的消息是" + new String(message.getBody(), "UTF-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public class Consumer2 {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("消费者2");
//4.通过连接建立通道
channel = connection.createChannel();
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("消费者2收到的消息是" + new String(message.getBody(), "UTF-8"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
能者多劳,性能好的可能收到的消息就多,但是注意是必须采用手动应答
/**
* fanout 发布订阅
*/
public class Producer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
//5: 申明队列queue存储消息
//6.发送消息的内容
String message = "hello Rabbitmq";
String exchangeName = "fanout-exchange";
String routingKey = "";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routing
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public class Consumer {
public static Runnable runnable = ()-> {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("消费者");
//4.通过连接建立通道
channel = connection.createChannel();
channel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到的消息是" + new String(message.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收失败");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
};
public static void main(String[] args) {
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
}
/**
* routing 匹配路由
*/
public class Producer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
//5: 申明队列queue存储消息
//6.发送消息的内容
String message = "hello Rabbitmq";
String exchangeName = "direct-exchange";
String routingKey = "email";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routing
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
public class Producer {
public static void main(String[] args) {
//1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//2.设置连接信息
connectionFactory.setHost("8.133.189.144");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/"); //虚拟访问路径
Connection connection = null;
Channel channel = null;
try {
//3.创建连接
connection = connectionFactory.newConnection("生产者");
//4.通过连接建立通道
channel = connection.createChannel();
//5: 申明队列queue存储消息
//6.发送消息的内容
String message = "hello Rabbitmq";
String exchangeName = "topic-exchange";
String routingKey = "com.order.test";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routing
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("消息发送异常");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
因为我设置的路由key为com.order.test
,所以只要队列1,2符合,因此队列1和2 可以介绍到消息
串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端
代码:
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 2: 发送短信服务
messageService.sendSMS("order");//1-2 s
// 3: 发送email服务
emailService.sendEmail("order");//1-2 s
// 4: 发送APP服务
appService.sendApp("order");
}
并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
代码:
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 相关发送
relationMessage();
}
public void relationMessage(){
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 2: 发送短信服务
messageService.sendSMS("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 3: 发送email服务
emailService.sendEmail("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 4: 发送短信服务
appService.sendApp("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 4: 发送短信服务
appService.sendApp("order");
}
})
}
问题:
1:耦合度高
2:需要自己写线程池自己维护成本太高
3:出现了消息可能会丢失,需要你自己做消息补偿
4:如何保证消息的可靠性你自己写
5:如果服务器承载不了,你需要自己去写高可用
优点:
1:完全解耦,用MQ建立桥接
2:有独立的线程池和运行模型
3:出现了消息可能会丢失,MQ有持久化功能
4:如何保证消息的可靠性,死信队列和消息转移的等
5:如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用。
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍
代码:
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
rabbitTemplate.convertSend("ex","2","消息内容");
}
分布式事务的可靠消费和可靠生产
索引、缓存、静态化处理的数据同步
流量监控
日志监控(ELK)
下单、订单分发、抢票
=======================生产者=================================
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 8.133.189.144
port: 5672
=======================消费者=================================
# 服务端口
server:
port: 8081
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 8.133.189.144
port: 5672
@Service
public class OrderService {
@Autowired
private RabbitTemplate rabbitTemplate;
// 1: 定义交换机
private String exchangeName = "fanout_order_exchange";
// 2: 路由key
private String routeKey = "";
public void makeOrder(String userid,String productid,int num){
// 1: 模拟用户下单
String orderNumer = UUID.randomUUID().toString();
System.out.println("用户 " + userid + ",订单编号是:" + orderNumer);
// 发送订单信息给RabbitMQ fanout
rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
}
}
@Configuration
public class RabbitMQConfig {
@Bean
public Queue emailQueue() {
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
// exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
// autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
// return new Queue("TestDirectQueue",true,true,false);
//一般设置一下队列的持久化就好,其余两个就是默认false
return new Queue("email.fanout.queue", true);
}
@Bean
public Queue smsQueue() {
return new Queue("sms.fanout.queue", true);
}
@Bean
public Queue weixinQueue() {
return new Queue("weixin.fanout.queue", true);
}
@Bean
public FanoutExchange fanoutOrderExchange() {
return new FanoutExchange("fanout_order_exchange", true, false);
}
//绑定 将队列和交换机绑定
@Bean
public Binding smsBinding() {
return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange());
}
@Bean
public Binding weixinBinding() {
return BindingBuilder.bind(weixinQueue()).to(fanoutOrderExchange());
}
@Bean
public Binding emailBinding() {
return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
}
}
@SpringBootTest
class SpringbootRabbitmqApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
orderService.makeOrder("1","1",12);
}
}
@Component
@RabbitListener(queues = {
"email.fanout.queue"})
public class FanoutEmail {
@RabbitHandler
public void messagerevice(String message){
// 此处省略发邮件的逻辑
System.out.println("Email-------------->" + message);
}
}
============================================================================================================
@Component
@RabbitListener(queues = {
"sms.fanout.queue"})
public class FanoutSms {
@RabbitHandler
public void messagerevice(String message){
// 此处省略发邮件的逻辑
System.out.println("sms-------------->" + message);
}
}
============================================================================================================
@Component
@RabbitListener(queues = {
"weixin.fanout.queue"})
public class FanoutWeixin {
@RabbitHandler
public void messagerevice(String message){
// 此处省略发邮件的逻辑
System.out.println("weixin-------------->" + message);
}
}
ilder.bind(weixinQueue()).to(fanoutOrderExchange());
}
@Bean
public Binding emailBinding() {
return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
}
}
- 测试
```java
@SpringBootTest
class SpringbootRabbitmqApplicationTests {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
orderService.makeOrder("1","1",12);
}
}