RabbitMQ核心API
Exchange属性
- name:Exchange名称
- type:Exchange类型
- direct
- topic
- fanout
- headers
- durability:是否持久化
- auto delete:当最后一个绑定到Exchange上的Queue删除后,自动删除该Exchange
- internal:当前Exchange是否用于RabbitMQ内部使用,默认为false
- arguments:扩展参数,用于扩展AMOP协议自定化使用
Direct Exchange
所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue
Topic Exchange
-
所有发送到Topic Exchange的消息被转发到所有关心的RouteKey中指定Topic的Queue上
-
Exchange将RouteKey和某Topic进行模糊匹配,此时Queue需要绑定一个Topic
-
可以使用通配符进行模糊匹配
"#":匹配一个或多个词 "*":匹配一个词 例如: "log.#"匹配"log.info.oa" "log.*"匹配"log.erro"
Fanout Exchange
- 不处理RouteKey,只需要简单的将Queue绑定到Exchange上
- 发送到Exchange的消息都会被转发到与该Exchange绑定的Queue上
- Fanout Exchange转发消息是最快的
Binding-绑定
- Exchange和Exchange、Exchange和Queue之间的连接关系
- Binding中可以包含RouteKey或者参数
Queue-消息队列
- 消息队列,实际存储消息数据
- Durability:是否持久化,Durable-是,Transient-否
- Auto Delete:入选yes,但最后一个监听被移除后,该Queue会自动被删除
Message-消息
- 服务器和应用程序之间传送的数据
- 本质上就是一段数据,由Properties和Payload组成
- 常用属性:delivery mode、headers(自定义属性)
- 其他属性:content_type、content_encoding、priority,correlation_id、reply_to、expiration、message_id,timestamp、type、user_id、app_id、cluster_id
Virtual host-虚拟主机
- 虚拟地址,用于进行逻辑隔离,最上层的消息路由
- 一个Virtual host里可以有若干个Exchange和Queue
- 同一个Virtual host里不能有相同名称的Exchange和Queue
RabbitMQ高级特性
生产端可靠性投递与消费端幂等性
如何保障消息100%投递成功
什么是生产端的可靠性投递?
- 保障消息的成功发出
- 保障MQ节点的成功接收
- 发送端收到MQ节点(Broker)确认应答
- 完善的消息补偿机制
生产端-可靠性投递
如何保障
-
消息落库,对消息状态进行打标
-
消息信息落库,对消息状态进行打标
消费端-幂等性保障
海量订单业务高峰,如何避免消息重复消费
- 消费端实现幂等性,消息不会消费多次,即使收到多条一样的消息
如何保障
- 业务唯一ID或指纹码机制,利用数据库主键去重
Confirm-确认消息
-
消息的确认,是指生产者投递消息后,如果Broker收到消息,则会给我们生产者一个应答
-
生产者进行接收应答,用来确定这条消息是否正常的发送到Broker,这种方式也是消息的可靠性投递的核心保障
如何实现
- 在channel上开启确认模式:channel.confirmSelect()
- 在channel上添加监听:addConfirmListener,监听成功和失败的返回结果,根据结果进行消息的重新发送或记录日志等处理
Return消息机制
Return Listener用于处理一些不可路由的消息
一般情况下,消息生产者通过指定一个Exchange和RouteKey,把消息送达某一队列中,让后消费者监听队列,进行消费处理;但在某些情况下,如果在发送消息的时候,当前的Exchange不存在或指定的RouteKey路由不到,这时候要监听这种不可达的消息,就要使用Return Listener
如何实现
-
Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后处理,如果为false,则broker端自动删除该消息
消费端特性-流控、ACK与重回队列
TTL消息、死信队列
RabbitMQ基础组件封装
- 基础组件封装设计-迅速消息发送
- 基础组件封装设计-确认消息发送
- 基础组件封装设计-延迟消息发送
基础组件实现功能点
环境搭建
- 首先在Linux上进行一些软件的准备工作,yum下来一些基础的软件包
yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
配置好主机名称:/etc/hosts /etc/hostname
- 下载RabbitMQ所需软件包
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-1.1.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
- 安装服务命令
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
- 修改用户登录与连接心跳检测,注意修改
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
修改点1:loopback_users 中的 <<"guest">>,只保留guest (用于用户登录)
修改点2:heartbeat 为10(用于心跳连接)
-
安装管理插件
5.1 首先启动服务(后面 | 包含了停止、查看状态以及重启的命令)
/etc/init.d/rabbitmq-server start | stop | status | restart
5.2 查看服务有没有启动: lsof -i:5672 (5672是Rabbit的默认端口)
rabbitmq-plugins enable rabbitmq_management
5.3 可查看管理端口有没有启动:
lsof -i:15672 或者 netstat -tnlp | grep 15672
-
一切OK 我们访问地址,输入用户名密码均为 guest :
http://你的ip地址:15672/
示例代码
示例代码(Hello World)
Sender.java
import java.util.HashMap;
import java.util.Map;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Sender {
public static void main(String[] args) throws Exception {
// 1 创建ConnectionFactory
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.0.71");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
// 2 创建Connection
Connection connection = connectionFactory.newConnection();
// 3 创建Channel
Channel channel = connection.createChannel();
// 4 声明
String queueName = "test001";
// 参数: queue名字,是否持久化,独占的queue(仅供此连接),不使用时是否自动删除, 其他参数
channel.queueDeclare(queueName, false, false, false, null);
Map<String, Object> headers = new HashMap<String, Object>();
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers).build();
for(int i = 0; i < 5;i++) {
String msg = "Hello World RabbitMQ " + i;
channel.basicPublish("", queueName , props , msg.getBytes());
}
}
}
Receiver.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
public class Receiver {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ConnectionFactory() ;
connectionFactory.setHost("192.168.0.71");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setNetworkRecoveryInterval(3000);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queueName = "test001";
// durable 是否持久化消息
channel.queueDeclare(queueName, false, false, false, null);
QueueingConsumer consumer = new QueueingConsumer(channel);
// 参数:队列名称、是否自动ACK、Consumer
channel.basicConsume(queueName, true, consumer);
// 循环获取消息
while(true){
// 获取消息,如果没有消息,这一步将会一直阻塞
Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息:" + msg);
}
}
}