什么是中间件
我国企业从20世纪80年代开始就逐渐进行信息化建设,由于方法和体系的不成熟,以及企业业务的市场需求的不断变化,一个企业可能同时运行着多个不同的业务系统,这些系统可能基于不同的操作系统、不同的数据库、异构的网络环境。现在的问题是,如何把这些信息系统结合成一个有机地协同工作的整体,真正实现企业跨平台、分布式应用。中间件便是解决之道,它用自己的复杂换取了企业应用的简单。
中间件(Middleware)是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台),但在这组中间件中必须要有一个通信中间件,即中间件+平台+通信,这个定义也限定了只有用于分布式系统中才能称为中间件,同时还可以把它与支撑软件和使用软件区分开来
为什么需要使用消息中间件
具体地说,中间件屏蔽了底层操作系统的复杂性,使程序开发人员面对一个简单而统一的开发环境,减少程序设计的复杂性,将注意力集中在自己的业务上,不必再为程序在不同系统软件上的移植而重复工作,从而大大减少了技术上的负担,中间件带给应用系统的,不只是开发的简便、开发周期的缩短,也减少了系统的维护、运行和管理的工作量,还减少了计算机总体费用的投入。
中间件特点
为解决分布异构问题,人们提出了中间件(middleware)的概念。中间件时位于平台(硬件和操作系统)和应用之间的通用服务,如下图所示,这些服务具有标准的程序接口和协议。针对不同
的操作系统和硬件平台,它们可以有符合接口的协议规范的多种实现。
也很难给中间件一个严格的定义,但中间件应具有如下的一些特点:
(1)满足大量应用的需要
(2)运行于多种硬件和 OS平台
(3)支持分布计算,提供跨网络、硬件和 OS平台的透明性的应用或服务的交互
(4)支持标准的协议
(5)支持标准的接口
由于标准接口对于可移植性和标准协议对于互操作性的重要性,中间件已成为许多标准化工作的主要部分。对于应用软件开发,中间件远比操作系统和网络服务更为重要,中间件提供的程序接口定义了一个相对稳定的高层应用环境,不管底层的计算机硬件和系统软件怎样更新换代,只要将中间件升级更新,并保持中间件对外的接口定义不变,应用软件几乎不需任何修改,从而保护了企业在应用软件开发和维护中的重大投资。
简单说:中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求的模块。这使得中间件一定是可替换的。如果一个系统设计中,中间件时不可替代的,不是架构、框架设计有问题,那么就是这个中间件,在别处可能是个中间件,在这个系统内是引擎。
在项目中什么时候使用中间件技术
在项目的架构和重构中,使用任何技术和架构的改变我们都需要谨慎斟酌和思考,因为任何技术的融入和变化都可能人员,技术,和成本的增加,中间件的技术一般现在一些互联网公司或者项目中使用比较多,如果你仅仅还只是一个初创公司建议还是使用单体架构,最多加个缓存中间件即可,不要盲目追求新或者所谓的高性能,而追求的背后一定是业务的驱动和项目的驱动,因为一旦追求就意味着你的学习成本,公司的人员结构以及服务器成本,维护和运维的成本都会增加,所以需要谨慎选择和考虑。
但是作为一个开放人员,一定要有学习中间件技术的能力和思维,否则很容易当项目发展到一个阶段在去掌握估计或者在面试中提及,就会给自己带来不小的困扰,在当今这个时代这些技术也并不是什么新鲜的东西,如果去掌握和挖掘最关键的还是自己花时间和经历去探讨和研究。
学习中间件的方式和技巧
什么是消息中间件
在实际的项目中,大部分的企业项目开发中,在早起都采用的是单体的架构模式
单体架构
在企业开发当中,大部分的初期架构都采用的是单体架构的模式进行架构,而这种架构的典型的特点:就是把所有的业务和模块,源代码,静态资源文件等都放在一个工程中,如果其中的一个模块升级或者迭代发生一个很小的变动都会重新编译和重新部署项目。这种这狗存在的问题是:
这样就有后续的分布式架构系统。如下
分布式架构
何谓分布式系统:
通俗一点:就是一个请求由服务器端的多个服务(服务或者系统)协同处理完成
和单体架构不同的是,单体架构是一个请求发起 jvm调度线程(确切的是 tomcat线程池)分配线程 Thread来处理请求直到释放,而分布式系统是:一个请求时由多个系统共同来协同完成,jvm和环境都可能是独立。如果生活中的比喻的话,单体架构就像建设一个小房子很快就能够搞定,如果你要建设一个鸟巢或者大型的建筑,你就必须是各个环节的协同和分布,这样目的也是项目发展到后期的时候要去部署和思考的问题。我们也不难看出来:分布式架构系统存在的特点和问题如下:
存在问题:
好处:
从上图中可以看出来,消息中间件的是
消息中间件应用的场景
比如你有10 W的并发请求下订单,我们可以在这些订单入库之前,我们可以把订单请求堆积到消息队列中,让它稳健可靠的入库和执行
常见的消息中间件
ActiveMQ、RabbitMQ、Kafka、RocketMQ等
消息中间件的本质及设计
它是一种接受数据、接受请求、存储数据、发送数据等功能的技术服务
MQ消息队列:负责数据的传接受,存储和传递,所以性能要高于普通服务和技术
消息中间件的核心组成部分
消息的协议
消息的持久化机制
消息的分发策略
消息的高可用,高可靠
消息的容错机制
小结
其实不论选择单体架构还是分布式架构都是项目开发的一个阶段,在什么阶段选择合适的架构方式,而不能盲目追求,最后造成的后果和问题都需要自己买单。但作为一个开发人员学习和探讨新的技术使我们每个程序开发者都应该去保持和思考的问题。当我们没办法去改变社会和世界的时候,我们为了生活和生存那就必须要迎合企业和市场的需求,发挥你的价值和所学的才能,创造价值和实现自我
什么是协议
所谓协议是指:
网络协议的三要素
比如我 MQ发送一个信息,是以什么数据格式发送到队列中,然后每个部分的含义是什么,发送完毕以后的执行的动作,以及消费者消费消息的动作,消费完毕的相应结构和反馈是什么,然后按照对应的执行顺序进行处理。如果你还是不理解:大家每天都在接触的 http请求协议:
而消息中间件采用的并不是 http协议,而常见的消息中间件协议有有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议
面试题:为什么消息中间件不直接使用 http协议
AMQP协议
AMQP:(全称:Advanced Message Queuing Protocol)是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。Erlang中的实现由 RabbitMQ等
特性:
MQTT协议
MQTT协议(Message Queueing Telemetry Transport)消息队列是 IBM开放的及时通讯协议,物联网系统架构中的重要组成部分
特点:
应用场景:
OpenMessage协议
是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式信息中间件、流处理等领域的应用开发标准
特点:
Kafka协议
Kafka协议是基于 TCP/IP的二进制协议。消息内部是 通过长度来分割,由一些基本数据类型组成
特点:
小结
协议:实在 tcp/ip协议基础之上构建的一种约定俗称的规范和机制、它的主要目的可以让客户端(应用程序 java,go)进行沟通和通讯。并且这种写一下规范必须具有持久性,高可用,高可靠的性能
持久化
简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而消失,使数据能够永久保存
常见的持久化方式
消息的分发策略
MQ消息 队列有如下几个角色
那么生产者生成消息以后,MQ进行存储,消费者是如何获取消息的呢?一般获取数据的方式无外乎推(push)或者拉(pull)两种方式,典型的 git就有推拉机制,我们发送的 http请求就是一种典型的拉取数据库数据返回的过程。而消息队列 MQ是一种推送的过程,而这些推机制会使用到很多的业务场景也有很多对应推机制策略
场景分析一
比如我在 APP上下了一个订单,我们的系统和服务很多,我们如何得知这个消息被哪个系统或者哪些服务器或者系统进行消费,那这个时候就需要一个分发的策略。这就需要消费策略。或者称之为消费的方法论
场景分析二
在发送消息的过程中可能会出现异常,或者网络的抖动,故障等等因为造成消息的无法消费,比如用户在下订单,消费 MQ接受,订单系统出现故障,导致用户支付失败,那么这个时候就需要消息中间件就必须支持消息重试机制策略。也就是支持:出现问题和故障的情况下,消息不丢失还可以进行重发
消息分发策略的机制和对比
什么是高可用机制
所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力
当业务量增加时,请求也过大,一台消息中间件服务器的会触及硬件(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署,来达到高可用的目的
集群模式1 - Master-slave主从共享数据的部署方式
集群模式2 - Master-slave主从同步部署方式
解释:这种模式写入消息同样在 Master主节点上,但是主节点会同步数据到 slave节点形成副本,和 zookeeper或者 redis主从机制很雷同。这样可以达到负载均衡的效果,如果消费者有多个这样就可以去不同的节点进行消费,以为消息的拷贝和同步会占用很大的带宽和网络资源。在后去的 rabbitmq中会有使用
集群模式3 - 多主集群同步部署模式
解释:和上面的区别不是特别的大,但是它的写入可以往任意节点去写入
集群模式4 - 多主集群转发部署模式
解释:如果你插入的数据是 broker-1中国,元数据信息会存储数据的相关描述和记录存放的位置(队列)。它会对描述信息也就是元数据信息进行同步,如果消费者在 broker-2中进行消费,发现自己节点没有对应的信息,可以从对应的元数据信息中去查询,然后返回对应的消息信息,场景:比如买火车票或者黄牛买演唱会门票,比如第一个黄牛有顾客说要买的演唱会门票,但是没有但是他回去联系其他的黄牛询问,如果有就返回
集群模式5 Master-slave与 Broker-cluster组合的方案
解释:实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高
什么是高可靠机制
所谓高可靠是指:系统可以无故障低持续运行,比如一个系统突然崩溃,报错,异常等等并不影响线上业务的正常运行,出错的几率极低,就称之为:高可靠
在高并发的业务场景中,如果不能保证系统的高可靠,那造成的隐患和损失是非常严重的
如何保证中间件消息的可靠性呢,可以从两个方面考虑:
https://www.bilibili.com/video/BV1dX4y1V73G?p=27
简单概述:
RabbitMQ是一个开源的遵循 AMQP协议实现的基于 Erlang语言编写,支持多种客户端(语言),用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征
我作为读者这里补充下,个人觉得B站飞哥关于安装过程讲的比较模糊,略过很多内容。关于’RabbitMQ’和’RabbitMQ+docker’的安装过程,我自己整理了一下,其他同为读者的你们不妨参考:
Rabbit安装教程
RabbitMQ+docker安装教程
RabbitMQ是采用 Erlang语言开发的,所以系统环境必须提供 Erlang环境,第一步就是安装 Erlang
查看系统版本号
安装下载
mkdir -p /usr/rabbitmq
ca /usr//rabbitmq
# 将安装包上传到linux系统中
erlang-22.0.7-1.el7.x86_64.rpm
rabbitmq-server-3.7.18-1.el7.noarch.rpm
rpm -Uvh erlang-solutions-2.0-1.noarch.rpm
yum install -y erlang
erl -v
安装下载
yum install -y socat
安装下载
rpm -Uvh rabbitmq-server-3.7.18-1.el7.noarch.rpm
yum install rabbitmq-server -y
启动服务
# 启动服务
systemctl start rabbitmq-server
# 查看服务状态,如图
systemctl status rabbitmq-server.service
# 开机自启动
systemctl enable rabbitmq-server
# 停止服务
systemctl stop rabbitmq-server
默认情况下,是没有安装web端的客户端插件,需要安装才可以生效
rabbitmq-plugins enable rabbitmq_management
说明:rabbitmq有一个默认账号和密码是:guest
默认情况只能在 localhost本计下访问,所以需要添加一个远程登录的用户
安装完毕以后,重启服务即可
systemctl restart rabbitmq-server
一定要记住,在对应服务器(阿里云,腾讯云等)的安全组中开放15672
端口
在浏览器访问
# 10.关闭防火墙服务
systemctl disable firewalld
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
systemctl stop firewalld
# 11.访问web管理界面
http://10.15.0.8:15672/
新增用户
rabbitmqctl add_user admin admin
设置用户分配操作权限
rabbitmqctl set_user_tags admin administrator
用户级别:
为用户添加资源权限
rabbitmqctl set_permissions -p / admin ".*"".*"".*"
网页登录成功
虚拟化容器技术 - Docker的安装
docker的相关命令
安装rabbitmq
可以直接走图中代码,不用走下面两项!
获取rabbit镜像
docker pull rabbitmq:management
创建并运行容器
docker run -id --name=myrabbit -p 15672:15672 rabbitmq:management
--hostname:指定容器主机名称
--name:指定容器名称
-p:将mq端口号映射到本地
或者运行时设置用户和密码
启动
访问网页,访问成功!
https://www.bilibili.com/video/BV1dX4y1V73G?p=44 实现步骤
java原生依赖
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.10.0version>
dependency>
在上图的模型中,有以下概念:
生产者
package com.chen.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;
/**
* @description: 简单模式Simple
*/
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1: 创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("128.197.157.151");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");//rabbitmq登录的账号
connectionFactory.setPassword("admin");//rabbitmq登录的密码
connectionFactory.setVirtualHost("/");
//springboot ---rabbitmq
Connection connection = null;
Channel channel = null;
try {
// 2: 创建连接Connection Rabbitmq为什么是基于channel去处理而不是链接? 长连接----信道channel
connection = connectionFactory.newConnection("生产者");
// 3: 通过连接获取通道Channel
channel = connection.createChannel();
// 4: 通过通创建交换机,声明队列,绑定关系,路由key,发送消息,和接收消息
String queueName = "queue1";
/*
* @params1 队列的名称
* @params2 是否要持久化durable=false 所谓持久化消息是否存盘,如果false 非持久化 true是持久化? 非持久化会存盘吗? 会存盘,但是会随从重启服务会丢失。
* @params3 排他性,是否是独占独立
* @params4 是否自动删除,随着最后一个消费者消息完毕消息以后是否把队列自动删除
* @params5 携带附属参数
*/
channel.queueDeclare(queueName, true, false, false, null);
// 5: 准备消息内容
String message = "Hello chenjinxian!!!";
// 6: 发送消息给队列queue
// @params1: 交换机 @params2 队列、路由key @params 消息的状态控制 @params4 消息主题
// 面试题:可以存在没有交换机的队列吗?不可能,虽然没有指定交换机但是一定会存在一个默认的交换机。
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("消息发送成功!!!");
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// 7: 关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 8: 关闭连接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
消费者
package com.chen.rabbitmq.simple;
import com.rabbitmq.client.*;
import java.io.IOException;
public class Consumer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1: 创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("128.197.157.151");//服务器IP
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2: 创建连接Connection
connection = connectionFactory.newConnection("消费者");
// 3: 通过连接获取通道Channel
channel = connection.createChannel();
// 4: 通过通创建交换机,声明队列,绑定关系,路由key,发送消息,和接收消息
// true = ack 正常的逻辑是没问题 死循环 rabbit 重发策略
// false = nack 消息这在消费消息的时候可能会异常和故障
final Channel channel2 = channel;
channel2.basicConsume("queue1", false, new DeliverCallback() {
public void handle(String consumerTag, Delivery message) throws IOException {
try {
System.out.println("收到消息是" + new String(message.getBody(), "UTF-8"));
channel2.basicAck(message.getEnvelope().getDeliveryTag(),false);
}catch (Exception ex){
ex.printStackTrace();
// 三次确认 -- reject + sixin
}
}
}, new CancelCallback() {
public void handle(String consumerTag) throws IOException {
System.out.println("接受失败了...");
}
});
System.out.println("开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// 7: 关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 8: 关闭连接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
AMQP全称:Advanced Message Queuing Protocol(高级消息队列协议)。是应用层协议的一个开发标准,为面向消息的中间件设计
图解
发布订阅模式的具体实现
生产者
package com.chen.rabbitmq.fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
发布订阅模式的具体实现
类型:fanout
特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("128.156.157.161");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello xuexi!!!";
// 6:准备交换机
String exchangeName = "fanout_change";
// 8: 指定交换机的类型
String type = "fanout";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
// #.course.* queue3
// *.order.# queue2 ta
// com.order.course.xxx collecion
channel.basicPublish(exchangeName,"", null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.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();
}
}
}
}
}
消费者
package com.chen.rabbitmq.fanout;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
发布订阅模式的具体实现
类型:fanout
特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式
*/
public class Consumer {
private static Runnable runnable = new Runnable() {
public void run() {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("128.156.157.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println(queueName + ":开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
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();
new Thread(runnable, "queue4").start();
//new Thread(runnable, "queue5").start();
}
}
此处没有通过代码去绑定交换机和队列,而是通过可视化界面去绑定的!
生产者
package com.chen.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
Direct 模式
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("128.176.157.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello direct_exchange!!!";
// 6:准备交换机
String exchangeName = "direct_exchange";
// 7: 定义路由key
String routeKey = "email";
// 8: 指定交换机的类型
String type = "direct";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
// #.course.* queue3
// *.order.# queue2 ta
// com.order.course.xxx collecion
channel.basicPublish(exchangeName, routeKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.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();
}
}
}
}
}
消费者
package com.chen.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
Direct 模式
*/
public class Consumer {
private static Runnable runnable = new Runnable() {
public void run() {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("123.156.147.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
//获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, false, false, null);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println(queueName + ":开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
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();
new Thread(runnable, "queue4").start();
// new Thread(runnable, "queue5").start();
}
}
生产者
package com.chen.rabbitmq.topics;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
Topic模式
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("125.156.157.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello topic_exchange!!!";
// 6:准备交换机
String exchangeName = "topic_exchange";
// 7: 定义路由key
String routeKey = "com.order.user";
// 8: 指定交换机的类型
String type = "topic";
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
// #.course.* queue3
// *.order.# queue2 ta
// com.order.course.xxx collecion
channel.basicPublish(exchangeName, routeKey, null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.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();
}
}
}
}
}
消费者
package com.chen.rabbitmq.all;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
完整案例
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("151.156.157.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("chenjinxian");
connectionFactory.setPassword("chenjinxian");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
String message = " 你好,小白";
// 交换机
String exchangeName = "direct_message_exchange";
// 交换机的类型 direct/topic/fanout/headers
String exchangeType = "direct";
// 如果你用界面把queueu 和 exchange的关系先绑定话,你代码就不需要在编写这些声明代码可以让代码变得更加简洁,但是不容读懂
// 如果用代码的方式去声明,我们要学习一下
// 7: 声明交换机 所谓的持久化就是指,交换机会不会随着服务器重启造成丢失,如果是true代表不丢失,false重启就会丢失
channel.exchangeDeclare(exchangeName,exchangeType,true);
// 8: 声明队列
channel.queueDeclare("queue5",true,false,false,null);
channel.queueDeclare("queue6",true,false,false,null);
channel.queueDeclare("queue7",true,false,false,null);
// 9:绑定队列和交换机的关系
channel.queueBind("queue5",exchangeName,"order");
channel.queueBind("queue6",exchangeName,"order");
channel.queueBind("queue7",exchangeName,"course");
channel.basicPublish(exchangeName, "course", null, message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.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();
}
}
}
}
}
图解
当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
主要有两种模式:
生产者
跟简单模式一样!
消费者
创建两个一样的!
生产者
package com.chen.rabbitmq.work.lunxun;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
轮询模式
*/
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("123.156.147.151");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("chenjinxian");
connectionFactory.setPassword("chenjinxian");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 6: 准备发送消息的内容
//===============================end topic模式==================================
for (int i = 1; i <= 20; i++) {
//消息的内容
String msg = "学相伴:" + i;
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue1", null, msg.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.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();
}
}
}
}
}
跟简单模式一样!
消费者
package com.chen.rabbitmq.work.lunxun;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
轮询模式
*/
public class Work1 {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("123.156.147.155");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("chenjinxian");
connectionFactory.setPassword("chenjinxian");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
// 6: 定义接受消息的回调
Channel finalChannel = channel;
//finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
创建两个一样的!
同步异步的问题(串行)
串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端
public void makeOrder(){
//1.发送订单
//2.发送短信服务
//3.发送email服务
//4.发送app服务
}
并行方式 异步线程池
并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
public void test(){
//异步
theadpool.submit(new Callable<Object>{
//1.发送短信服务
})
//异步
theadpool.submit(new Callable<Object>{
//2.
})
//异步
theadpool.submit(new Callable<Object>{
//3.
})
//异步
theadpool.submit(new Callable<Object>{
//4.
})
}
存在问题
异步消息队列的方式
好处:
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍
好处:
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍
https://www.bilibili.com/video/BV1dX4y1V73G?p=44
生产者
application.yml
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 127.0.0.1
port: 5672
OrderService.java
public class OrderService{
@Autowired
private RabbitTemplate rabbitTemplate;
//模拟用户下单
public void makeOrder(String userid,String productid,int num){
//1.根据商品id查询库存是否足够
//2.保存订单
String orderId = UUID.randomUUID().toString();
sout("订单生产成功:"+orderId);
//3.通过MQ来完成消息的分发
//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
}
}
消费者
application.yml
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 127.0.0.1
port: 5672
RabbitMqConfiguration.java
@Configuration
public class RabbitMqConfiguration{
//1.声明注册fanout模式的交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("fanout_order_exchange",true,false);
}
//2.声明队列
@Bean
public Queue smsQueue(){
return new Queue("sms.fanout.queue",true);
}
@Bean
public Queue duanxinQueue(){
return new Queue("duanxin.fanout.queue",true);
}
@Bean
public Queue emailQueue(){
return new Queue("email.fanout.queue",true);
}
//3.完成绑定关系
@Bean
public Binding smsBingding(){
return BindingBuilder.bin(smsQueue()).to(fanoutExchange());
}
@Bean
public Binding duanxinBingding(){
return BindingBuilder.bin(duanxinQueue()).to(fanoutExchange());
}
@Bean
public Binding emailBingding(){
return BindingBuilder.bin(emailQueue()).to(fanoutExchange());
}
}
FanoutSmsConsumer.java
@Component
@RabbitListener(queue = {"sms.direct.queue"})
public class FanoutSmsConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("sms接收到了的订单信息是:"+message);
}
}
FanoutDuanxinConsumer.java
@Component
@RabbitListener(queue = {"duanxin.direct.queue"})
public class FanoutDuanxinConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("duanxin接收到了的订单信息是:"+message);
}
}
FanoutEmailConsumer.java
@Component
@RabbitListener(queue = {"duanxin.direct.queue"})
public class FanoutEmailConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("email接收到了的订单信息是:"+message);
}
}
生产者
OrderService.java
public class OrderService{
@Autowired
private RabbitTemplate rabbitTemplate;
//模拟用户下单
public void makeOrder(String userid,String productid,int num){
//1.根据商品id查询库存是否足够
//2.保存订单
String orderId = UUID.randomUUID().toString();
sout("订单生产成功:"+orderId);
//3.通过MQ来完成消息的分发
//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "direct_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);
}
}
消费者
RabbitMqConfiguration.java
@Configuration
public class RabbitMqConfiguration{
//1.声明注册fanout模式的交换机
@Bean
public DirectExchange directExchange(){
return new DirectExchange("direct_order_exchange",true,false);
}
//2.声明队列
@Bean
public Queue smsQueue(){
return new Queue("sms.direct.queue",true);
}
@Bean
public Queue duanxinQueue(){
return new Queue("duanxin.direct.queue",true);
}
@Bean
public Queue emailQueue(){
return new Queue("email.direct.queue",true);
}
//3.完成绑定关系
@Bean
public Binding smsBingding(){
return BindingBuilder.bin(smsQueue()).to(fanoutExchange()).with("sms");
}
@Bean
public Binding duanxinBingding(){
return BindingBuilder.bin(duanxinQueue()).to(fanoutExchange()).with("duanxin");
}
@Bean
public Binding emailBingding(){
return BindingBuilder.bin(emailQueue()).to(fanoutExchange()).with("email");
}
}
生产者
public class OrderService{
@Autowired
private RabbitTemplate rabbitTemplate;
//模拟用户下单
public void makeOrder(String userid,String productid,int num){
//1.根据商品id查询库存是否足够
//2.保存订单
String orderId = UUID.randomUUID().toString();
sout("订单生产成功:"+orderId);
//3.通过MQ来完成消息的分发
//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "direct_order_exchange";
String routingKey = "com.duanxin";
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
}
}
消费者(采用注解)
FanoutSmsConsumer.java
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "sms.topic.queue",durable = "true",antoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange",type = "ExchangeTypes.TOPIC")
key = "#.sms.#"
))
public class TopicSmsConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("sms接收到了的订单信息是:"+message);
}
}
FanoutDuanxinConsumer.java
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "duanxin.topic.queue",durable = "true",antoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange",type = "ExchangeTypes.TOPIC")
key = "#.duanxin.#"
))
public classTopicDuanxinConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("duanxin接收到了的订单信息是:"+message);
}
}
FanoutEmailConsumer.java
@Component
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email.topic.queue",durable = "true",antoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange",type = "ExchangeTypes.TOPIC")
key = "#.email.#"
))
public class TopicEmailConsumer{
@RabbitHandler
public void reviceMessage(String message){
sout("email接收到了的订单信息是:"+message);
}
}
https://www.bilibili.com/video/BV1dX4y1V73G?p=44
概述
过期时间 TTl表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置 TTL,目前有两种方法可以设置
如果上述两种方法同时使用,则消息的过期时间以两者 TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的 TTL值,就称为 dead message被投递到死信队列,消费者将无法再收到该消息
设置队列TTL
RabbitMqConfiguration.java
@Configuration
public class TTLRabbitMQConfiguration{
//1.声明注册direct模式的交换机
@Bean
public DirectExchange ttldirectExchange(){
return new DirectExchange("ttl_direct_exchange",true,false);}
//2.队列的过期时间
@Bean
public Queue directttlQueue(){
//设置过期时间
Map<String,Object> args = new HashMap<>();
args.put("x-message-ttl",5000);//这里一定是int类型
return new Queue("ttl.direct.queue",true,false,false,args);}
@Bean
public Binding ttlBingding(){
return BindingBuilder.bin(directttlQueue()).to(ttldirectExchange()).with("ttl");
}
}
设置消息TTL
public class OrderService{
@Autowired
private RabbitTemplate rabbitTemplate;
//模拟用户下单
public void makeOrder(String userid,String productid,int num){
//1.根据商品id查询库存是否足够
//2.保存订单
String orderId = UUID.randomUUID().toString();
sout("订单生产成功:"+orderId);
//3.通过MQ来完成消息的分发
//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "ttl_order_exchange";
String routingKey = "ttlmessage";
//给消息设置过期时间
MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
public Message postProcessMessage(Message message){
//这里就是字符串
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
}
rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId,messagePostProcessor);
}
}
RabbitMqConfiguration.java
@Configuration
public class TTLRabbitMQConfiguration{
//1.声明注册direct模式的交换机
@Bean
public DirectExchange ttldirectExchange(){
return new DirectExchange("ttl_direct_exchange",true,false);}
//2.队列的过期时间
@Bean
public Queue directttlQueue(){
//设置过期时间
Map<String,Object> args = new HashMap<>();
args.put("x-message-ttl",5000);//这里一定是int类型
return new Queue("ttl.direct.queue",true,false,false,args);}
@Bean
public Queue directttlMessageQueue(){
return new Queue("ttlMessage.direct.queue",true,false,false,args);}
@Bean
public Binding ttlBingding(){
return BindingBuilder.bin(directttlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
}
}
概述
DLX,全称 Dead-Letter-Exchange
,可以称之为死信交换机,也有人称之为死信邮箱。当消息再一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是 DLX,绑定 DLX的队列就称之为死信队列。消息变成死信,可能是由于以下原因:
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性,当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的 DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数x-dead-letter-exchange
指定交换机即可
代码
DeadRabbitMqConfiguration.java
@Configuration
public class DeadRabbitMqConfiguration{
//1.声明注册direct模式的交换机
@Bean
public DirectExchange deadDirect(){
return new DirectExchange("dead_direct_exchange",true,false);}
//2.队列的过期时间
@Bean
public Queue deadQueue(){
return new Queue("dead.direct.queue",true);}
@Bean
public Binding deadbinds(){
return BindingBuilder.bind(deadDirect()).to(deadQueue()).with("dead");
}
}
RabbitMqConfiguration.java
@Configuration
public class TTLRabbitMQConfiguration{
//1.声明注册direct模式的交换机
@Bean
public DirectExchange ttldirectExchange(){
return new DirectExchange("ttl_direct_exchange",true,false);}
//2.队列的过期时间
@Bean
public Queue directttlQueue(){
//设置过期时间
Map<String,Object> args = new HashMap<>();
//args.put("x-max-length",5);
args.put("x-message-ttl",5000);//这里一定是int类型
args.put("x-dead-letter-exchange","dead_direct_exchange");
args.put("x-dead-letter-routing-key","dead");//fanout不需要配置
return new Queue("ttl.direct.queue",true,false,false,args);}
@Bean
public Queue directttlMessageQueue(){
return new Queue("ttlMessage.direct.queue",true,false,false,args);}
@Bean
public Binding ttlBingding(){
return BindingBuilder.bin(directttlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
}
}
参考帮助文档:http://www.rabbbitmq.com/configure.html
当出现警告的时候,可以通过配置去修改和调整
命令的方式
rabbitmqctl set_vm_memory_high_watermark
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
fraction/value 为内存阈值。默认情况是:0.4/2GB,代表的含义是:当 RabbitMQ的内存超过40%时,就会产生警告并且会阻塞所有生产者的连接。通过此命令修改阈值在 Broker重启以后将会失效,通过修改配置文件设置的阈值则不会随着重启而消失,但修改了配置文件一样要重启 Broker才会生效
配置文件方式 rabbitmq.conf
配置的前提是你的 rabbitmq可以运行起来,比如ps aix|grep rebbitmq
你能看到相关进程,又比如运行rabbitmqct status
你可以看到类似如下信息而不报错:
启动第二个节点
验证启动
ps aux|grep rabbitmq
rabbit-1操作作为主节点
rabbit-2操作作为从节点
验证集群状态
Web监控
rabbitmq-plugins enable rabbitmq_management
小结
分布式事务指事务的操作位于不同的节点上,需要保证事务的ACID特性。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务
在分布式系统中,要实现分布式事务,无外乎哪几种解决方案。
两阶段提交(Two-phase Commit,2PC),通过引协调者(coordinator)来协调参与者的行为,并最终决定这些参与者是否真正要执行事务。
协调者询问参与事务是否执行成功,参与者发回事务执行结果
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撒销)操作。它分为三个阶段:
举个例子,假入Bob要向Smith转账,思路大概是:我们有一个本地方法,里面依次调用
优点:跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点:缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终—致性。
优点:一种非常经典的实现,避免了分布式事务,实现了最终—致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如Kafka不支持。
以阿里的RabbitMQ中间件为例,其思路大致为:
优点:实现了最终一致性,不需要依赖本地数据库事务。
缺点:实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源。
通过本文我们总结并对比了几种分布式分解方案的优缺点,分布式事务本身是一个技术难题,是没有一种完美的方案应对所有场景的,具体还是要根据业务场景去抉择吧。阿里RocketMQ去实现的分布式事务,现在也有除了很多分布式事务的协调器,比如LCN等,大家可以多去尝试。
分布式事务的完整架构图
package com.xuexiangban .rabbitmq.service;2.
import com.xuexiangban.rabbitmq.dao.orderDataBaseService;
import com.xuexiangban.rabbitmq.pojo.Order;
import org.springframework.beans.factory .annotation.Autowired;
import org.springframework.http.client.SimpleclientHttpRequestFactory;
import org.springframework.stereotype. Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
public class OrderService {
@Autowired
private OrderDataBaseService orderDataBaseservice;
//创建订单
@Transactional(rollbackFor = Exception.class)//订单创建整个方法添加事务
public void createOrder(Order orderInfo) throws Exception {
// 1:订单信息--插入丁订单系统,订单数据库事务orderDataBaseService.saveOrder(orderInfo);
//2∶通通Http接口发途订单信息到运单系统
String result = dispatchHttpApi(orderInfo.getorderId());
if( !"success".equals(result)) {
throw new Exception("订单创建失败,原因是运单接口调用失败!");
}
}
/**
* 模拟http请求接口发途,运单系统,将订单号传过去 springcloud
*/
private String dispatchHttpApi(String orderId){
SimpleclientHttpRehyuestFactory factory - new SimpleClientHttpRequestFactory();
//链接超时>3秒
factory .setConnectTimeout ( 300e) ;
//处理超时>2秒
factory .setReadTimeout ( 2000) ;
//发送http请求
String url = "http: / /localhost:9000/dispatch/order?orderId="+orderId;
RestTemplate restTemplate = new RestTemplate(factory);//异常
String result = restTemplate.getForobject(url,string.class);
return result;
}
}
如果这个时候MQ服务器出现了异常和故障,那么消息是无法获取到回执信息。怎么解决呢?
解决消息重试的集中方案
如果死信队列报错就进行人工处理
本文来自B站狂神说的视频中的markdown文件,由于格式问题,作者对其进行整理并写在博客中。其中下载来源如下:
RabbitMQ笔记