中间件MQ的学习
前文:这是时隔多年再次手写博客。旨在方便自己日后再次查看,也希望能帮助有需要的人,如果文中写的有错误,欢迎指正,拒绝乱喷,谢谢!
不出意外,这应该是MQ的系列文章。下面先附上常见MQ产品的的一个对比图
通用的基本概念:AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。 这个问题:面试可能会问(笔者自己就被问过,当时就有那种很熟悉但是想不起来的感受)
RabbitMQ的学习
一:看一下下面两个图,我觉得在学习完MQ之后其实很容易就会忘记(当然也可能是太久没使用的原因,笔者在两年前学习activemq的时候当时觉得学的还可以,应用也没问题,但是过了不久就忘记了),所以下面两个图很重要能让我们会议起rabbitmq的基本组成也就知道它的基本原理
- List item
1.1.1从上面简单的图可以看出,RabbitMQ主要是由Producer(生产者)和Consumer(消费者)****组成。生产者将消息发送到Queue(队列)****中(然后做它自己的事情)但是生产者和队列没有直接绑定关系,队列是和Exchange(交换机)****绑定的(队列和交换机的关系是在生产者中绑定的),生产者将消息发送到交换机中,消费者监听到队列中有消息进行消费。
1.1.2从上面复杂的图可以看出来,生产者和交换机建立Connection(长连接)****后通过连接中的channel(渠道)****进行通讯,消息发送到交换机,交换机和队列绑定,消息到达对应的队列,交换机和队列又通过virtualHost(虚拟机)****进行隔离(一个Broker中允许有多个虚拟机),消费者和队列建立连接,同过监听队列获取消息。
上面这段话中出现的名词解释:
(1)Broker:接收和分发消息的应用,RabbitMQ Server就是 Message
Broker
(2)Virtual host:出于多租户和安全因素设计的,把 AMQP
的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server
提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
(3)Connection:publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。
(4)Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销
(5)Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
(6)Queue:消息最终被送到这里等待 consumer 取走
(7)Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
二:下面针对RabbitMQ集中常见模式以图片、代码、文字的形式介绍
// Hello World 模式生产代码
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取TCP长连接
Connection conn = RabbitUtils.getConnection();
//创建通信“通道”,相当于TCP中的虚拟连接
Channel channel = conn.createChannel();
//创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
// channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null); 这里是介绍这个方法几个参数的意思,不需要声明队列,如果不指定特殊的参数
String message = "hello world";
//四个参数
//exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
//队列名称
//额外的设置属性
//最后一个参数是要传递的消息字节数组
channel.basicPublish("", RabbitConstant.QUEUE_HELLOWORLD, null,message.getBytes());
channel.close();
conn.close();
System.out.println("===发送成功===");
}
}
// 消费者代码
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//获取TCP长连接
Connection conn = RabbitUtils.getConnection();
//创建通信“通道”,相当于TCP中的虚拟连接
Channel channel = conn.createChannel();
//创建队列,声明并创建一个队列,如果队列已存在,则使用这个队列
//第一个参数:队列名称ID
//第二个参数:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
//第三个参数:是否队列私有化,false则代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用,其他消费者不让访问
//第四个:是否自动删除,false代表连接停掉后不自动删除掉这个队列
//其他额外的参数, null
// channel.queueDeclare(RabbitConstant.QUEUE_HELLOWORLD,false, false, false, null); 这里是介绍这个方法几个参数的意思,不需要声明队列,如果不指定特殊的参数
//从MQ服务器中获取数据
//创建一个消息消费者
//第一个参数:队列名
//第二个参数代表是否自动确认收到消息,false代表手动编程来确认消息,这是MQ的推荐做法
//第三个参数要传入DefaultConsumer的实现类
channel.basicConsume(RabbitConstant.QUEUE_HELLOWORLD, false, new Reciver(channel));
}
}
class Reciver extends DefaultConsumer {
private Channel channel;
//重写构造函数,Channel通道对象需要从外层传入,在handleDelivery中要用到
public Reciver(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());
//false只确认签收当前的消息,设置为true的时候则代表签收该消费者所有未签收的消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
``
// 建立连接使用的工具类
public class RabbitUtils {
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);//5672是RabbitMQ的默认端口号
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
connectionFactory.setVirtualHost("/xxx");
}
public static Connection getConnection(){
Connection conn = null;
try {
conn = connectionFactory.newConnection();
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
文字描述:
1.生产者
(1)获取生产者与brocker的连接,通过连接获去渠道
(2)向交换机中发送消息,没有指定就是默认的交换机
2.消费者
(1)获取生产者与brocker的连接,通过连接获去渠道
(2)监听队列等待消息进行消费(制定了队列,未指定交换机。则默认交换机与该队列绑定)
第二种模式:work queue
这种模式与第一种模式相同,只是消费者有多个。即有多个消费者监听了同一个队列,同一个队列中的消息不会被重复消费,所以三个消费者消费的消息总和等于队列中的消息。第三种模式:Publish/Subscribe
这种模式与work queue模式又存在相同之处,同样是存在多个消费者,只是每个消费者各自声明了队列,但是都将各自队列绑定到了同一个交换机。所以两个队列中的消息完全相同。第四种模式:Routing
这中模式与Publish/Subscribe模式存在相同之处,同样是多个消费者,且每个消费者声明了自己的队列,不同的是队列在与交换机绑定的时候指定了 routing(路由) key,这个关系可以指定多个,即一个队列和同一个交换机可以指定多个不同的路由key绑定。而且生产者在向交换机中发送消息时,也指定了每个消息的路由。所以最中的消息按照路由进入不同的队列,被消费者消费。第五种模式:Topics
这种模式和Routing模式存在相同之处。它们都需要指定路由key,不同的时Topics(主题)模式在指定路由key时允许使用通配符在匹配,而不需要具体的路由key名。
还有一种RPC模式,了解即可,这里不做过多介绍。
三:关于与框架整合简单介绍
上面介绍的是使用RabbitMQ方式的代码。具体与框架整合是整体步骤没有变化,只是有了框架代码量少了很多。
1)与spring框架整合:在生产者的配置文件中定义队列、交换机与队列绑定、rabbitTemplate。生产者发送消息通过操作rabbitTemplate的API就可以。消费者:声明监听器、监听的队列,当队列中有消息时监听器会监听到从而消费。
2)与spring boot整合:在配置类中以Bean的方式声明队列、交换机、队列与交换机绑定。消费者:通过注解@RabbitListener(queues = “xxx”)指定类为类监听某个队列的消息,当队列中有消息进行消费
到这里本篇就结束了,寥寥几百字写用了我四个多小时(效率确实有些慢,希望日后能快些)。还好这个编辑器优化了,要是还是之前的估计是劝退了。如有不足,麻烦评论补充。