消息队列-RabbitMQ
什么叫消息队列
**消息(Message)**指在应用间传送的任意数据,可以是纯文本,也可以包含嵌入对象
**消息队列(Message Queue)**是一种应用间的异步写作机制,消息发送后可以立即返回,有消息系统来
确保消息的可靠传递。消息发布者只管把消息发布到MQ中而不需要管水接受,消息接受这只管从MQ中取
消息而不用管谁发布的。
为何用消息队列
订单系统举例
未使用消息队列:用户点击下单按钮->扣减库存->生成相应单据->发红包->发短信通知,
这些步骤都是同步操作,当订单量增长,需要提升服务的性能,可以将一些不需要立即生效的步骤分离开,如发红包h、发短行通知等
使用后:用户点击下单按钮->扣减库存->生成相应单据,主流程完成后发送条消息到MQ,让主流程快速
完成后,由另外的单独县城拉取MQ的消息,当发现MQ中有发红包和短信之类的消息是,执行相应的逻辑其他常见场景包括最终一致性、广播、错峰流控等
RabbitMQ基本特性
- 高可靠
RabbitMQ使用一些机制来保证可靠性,如持久化、传输确认、发布确认
- 灵活的路由
在消息进入队列之前,通过Exchange来路由消息,对于典型的路由功能,RabbitMQ提供了
一些内置的Exchange来实现,复杂的可以将多个Exchange绑定在一起或通过插件机制实现自己的Exchange
- 消息集群
多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker
- 高可用队列
队列可以在集群中的机器上进行镜像,使部分节点出问题后队列仍然可用
- 多种协议
RabbitMQ支持多种消息队列协议,如STOMP、MQTT等
- 插件机制
RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件
- 跟踪机制
如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。
RabbitMQ工作模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kT214dWF-1637160206869)(img.png)]
- Message
消息,消息是不具名的,有消息头和消息体组成。消息体是透明的,而消息头则由一系列的可选
属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的权重)、
delivery-mode(指出该消息可能需要持久性存储)等等
- Producer
消息的生产者,也是一个项交换机发布消息的客户端应用程序
- Consumer
消息的消费者,也是一个项交换机发布消息的客户端应用程序
- Exchange
交换器,用来接受生产者发送的消息并将这些消息路由给服务器中的队列,
要实现消息的接收,一个队列必须到绑定一个交换机
- Binding
绑定,用于消息队列和交换器之间的关联.一个绑定就是基于路由键将交换器和消息队列连接
起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表
- Queue
消息队列,用来保存消息知道发送给消费者。它是消息的容器,也是消息的重点。一个消息可以放入
一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走
- Connection
连接RabbitMQ和应用服务器的TCP连接
- Channel
通道,多路复用连接中的一条独立的双向数据流通道。当你通过消息队列发送或接收消息都是通过
通道进行的
- VHost
虚拟主机,表示一批交换器、消息队列和相关对象虚拟主机是共享相同的身份认证和加密环境的独立服务器域。
每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。
vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
- Broker
表示消息队列服务器实体。
Exchange路由消息
Direct直连类型交换器
该类型的交换器路由规则也很简单,它会把消息 路由到那些BindingKey和RoutingKey完全匹配的队列中
Topic主题类型交换器
topic类型的交换器在匹配规则上进行了扩展,它与direct类型的交换器相似,也是将消息路由到BindingKey和RoutingKey相匹配的队列中,但这里的匹配规则有些不同,它约定RoutingKey为一个点号’.'分割的字符串,如com.lm.study,com.java.test;BindingKey与RoutingKey一样也是点号"." 分割的字符串。 BindingKey中可以存在两种特殊的字符串"" 和 “#” , 用于模糊匹配,
其中 "#“用于匹配一个单词,”"用于匹配多个单个(可以是零个)
Fanout广播类型交换器
它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列 中。
JAVA API基本使用
- 创建maven项目
- pom.xml中添加RabbitMQ依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.13.1</version>
</dependency>
- 创建消息消费者
/**
* 消费者
*/
public class MyConsumer {
public static final String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
public static final String QUEUE_NAME = "SIMPLE_QUEUE";
public static void main(String[] args) throws IOException, TimeoutException {
//创建消息通道
Channel channel = Utils.getChannel();
//声明交换器
channel.exchangeDeclare(EXCHANGE_NAME,"direct",false,false,null);
//声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
System.out.println("Waiting for message...");
//绑定队列和交换器
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"test.msg");
//创建消费者
Consumer consumer = new DefaultConsumer(channel){
//监听到消息后处理
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body,"UTF-8");
System.out.println("Received message :'"+message+"'");
System.out.println("ConsumerTag : "+ consumerTag);
System.out.println("DeliveryTag : "+ envelope.getDeliveryTag());
}
};
//消费者开始获取消息
channel.basicConsume(QUEUE_NAME,true,consumer);
}
}
- 创建消息生产者
/*生产者*/
public class MyProducer {
public static final String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
public static void main(String[] args) throws IOException, TimeoutException {
//创建消息通道
Channel channel = Utils.getChannel();
//定义要发送的消息
String message = "Hello RabbitMQ";
//发布消息
channel.basicPublish(EXCHANGE_NAME,"test.msg",null,message.getBytes(StandardCharsets.UTF_8));
//关闭通道
channel.close();
}
}
- 工具类代码
public class Utils {
public static Channel getChannel(){
ConnectionFactory factory = new ConnectionFactory();
//连接IP
factory.setHost("127.0.0.1");
//连接端口
factory.setPort(5672);
//连接虚拟机
factory.setVirtualHost("/");
//连接用户名和密码
factory.setUsername("guest");
factory.setPassword("guest");
//建立连接
Connection conn = null;
//创建消息通道
Channel channel = null;
try {
conn = factory.newConnection();
channel = conn.createChannel();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
return channel;
}
}