1. 消息队列MQ
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
-
使用MQ的好处
异步处理消息,提升系统的吞吐性能,解耦合,消峰填谷 -
为什么使用MQ
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。 -
开发中消息队列通常有如下应用场景:
1、任务异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
2、应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
1.2. AMQP 和 JMS
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
1.2.1. AMQP
AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP
不从API层进行限定,而是直接定义网络交换的数据格式
1.2.2. JMS
JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的
API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
1.2.3. AMQP 与 JMS 区别 -
JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
-
JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
-
JMS规定了两种消息模式;而AMQP的消息模式更加丰富
1.3. 消息队列产品
市场上常见的消息队列有如下: -
ActiveMQ:基于JMS
-
ZeroMQ:基于C语言开发
-
RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
-
RocketMQ:基于JMS,阿里巴巴产品
-
Kafka:类似MQ的产品;分布式消息系统,高吞吐量
1.4. RabbitMQ
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
RabbitMQ官方地址:http://www.rabbitmq.com/
RabbitMQ提供了6种模式:简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;不作介绍);
3. 原生RabbitMQ入门
3.1.2. 添加依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
3.2. 编写生产者
/**
* 简单模式用来发送消息
*/
public class Producer {
//简单队列
static final String QUEUE_NAME = "simple_queue";
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
//connectionFactory.setVirtualHost("testhost");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//创建连接
Connection connection = connectionFactory.newConnection();
//创建频道
Channel channel = connection.createChannel();
/**
* 参数一:队列名称
* 参数二,是否定义持久化队列
* 参数三:是否独占本次连接
* 参数四:是否在不使用的时候自动删除队列
* 参数五:队列其他参数
*/
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
/**
* 参数一:交换机名称,如果没使用指定则默认使用Default Exchange
* 参数二:路由key,简单模式可以传递队列名称
* 参数三:消息的其他属性
* 参数四:消息的内容
*/
String message = "你好,小兔子";
//发送消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
在执行上述的消息发送之后;可以登录rabbitMQ的管理控制台,可以发现队列和其消息:
3.3. 编写消费者
抽取创建connection的工具类ConnectionUtil;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.impl.AMQImpl;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author liuz
* @version 1.0
* @date 2020/9/19 13:04
* RabbitMq连接工具类
*/
public class ConnectionUtil {
public static Connection getConnection(String host, int port, String username, String password) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(host);
connectionFactory.setPort(port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
Connection connection = connectionFactory.newConnection();
return connection;
}
}
编写消息的消费者
import com.rabbitmq.client.*;
import com.sq.rabbitmq.util.ConnectionUtil;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author liuz
* @version 1.0
* @date 2020/9/19 13:03
* 简单模式,消费者接收消息
*/
public class Consumer {
public static void main(String[] args) throws Exception {
//获取连接
Connection connection = ConnectionUtil.getConnection("127.0.0.1", 5672, "guest", "guest");
Channel channel = connection.createChannel();
channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
//需要我们自己重写实现
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("路由key:" + envelope.getRoutingKey());
System.out.println("交换机为::" + envelope.getExchange());
System.out.println("消息ID为::" + envelope.getDeliveryTag());
System.out.println("接收到的消息:" + new String(body, "utf-8"));
}
};
channel.basicConsume(Producer.QUEUE_NAME, true, defaultConsumer);
}
}
3.4. 小结
上述的入门案例中中其实使用的是如下的简单模式:
在上图的模型中,有以下概念:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
4. RabbitMQ工作模式
4.1.1. 模式说明
4.1.2. 代码
Work Queues 与入门程序的 简单模式 的代码是几乎一样的;可以完全复制,并复制多一个消费者进行多个消费者同时消费消息的测试。
小结:在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
4.2. 订阅模式类型
前面2个案例中,只有3个角色:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分
而在订阅模型中,多了一个exchange角色,而且过程略有变化: - P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
- C:消费者,消息的接受者,会一直等待消息到来。
- Queue:消息队列,接收消息、缓存消息。
Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:- anout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
4.3.4. 小结
交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
4.4. Routing路由模式
路由模式特点:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey (路由key)
- 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey 。
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息
图解: - P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。
- X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列
- C1:消费者,其所在队列指定了需要routing key 为 error 的消息
- C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息
4.4.4. 小结
Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列。
4.5. Topics通配符模式
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符!Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: mq.insert
通配符规则:
#: 匹配一个或多个词
: 匹配不多不少恰好1个词
举例:
mq.# :能够匹配 mq.insert.abc 或者 mq.insert
mq. :只能匹配 mq.insert
图解: - 红色Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到
- 黄色Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配
- 4.5.4. 小结
Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic在配置routingkey 的时候可以使用通配符,显得更加灵活。
4.6. 模式总结
1、RabbitMQ工作模式: 1、简单模式 HelloWorld 一个生产者、一个消费者,不需要设置交换机(使用默认的交换
机)
2、工作队列模式 Work Queue 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到
交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息
到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,
当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列