rabbitMQ安装需要的文件,链接:https://pan.baidu.com/s/1so8ta3CP1zI6JNO4zt2sNw 提取码:vi47
一.安装rabbitMQ
1.安装erlang环境
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
2.安装socat
若不安装socat,直接安装rabbit-server,则会出现
安装socat
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
这里的安装要加上 --force --nodeps,不加上则会出现
3.安装rabbitmq-server
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
4.修改rabbit.app配置
通过rpm安装的rabbitMQ,这个配置默认在 /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/文件件下
编辑rabbit.app
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
进入rabbit.app,通过 /loopback_users ,回车找到配置项,修改为
{loopback_users, []}
测试阶段,所有都能访问。在配置文件中也有默认用户名和默认密码
{default_user, <<"guest">>},
{default_pass, <<"guest">>},
启动
rabbitmq-server start &
5.安装管理页面的工具
rabbitmq-plugins enable rabbitmq_management
通过页面显示为,默认账号密码为 guest guest
==============================2020年6月12日19:36:20更新===============================
通过账号密码进入rabbitMQ的管理页面之后,这里说一下Overview页面
在页面上的其他选项,就是rabbitMQ的其他东西,有connection(连接数)channels(通道) exchanges(交换机)queues(队列),这些东西,在下面会写出。
二.走进rabbitMQ
1.AMQP协议
我理解的AMQP协议只是一种协议规范,任何遵守该规范的事务,都可以进行该规范实现的功能。类似于MQTT协议,也是一种规范。我这里就理解这么多,更多更深层关于AMQP协议还没有了解过。rabbitMQ是遵守AMQP协议规范的,所以这里先说一下AMQP协议的东西。
2.AMQP协议模型
该模型图借鉴于慕课网讲rabbitMQ课程里面的图。讲讲我对AMQP协议模型的理解。 Publisher application相当于生产者,生产者生产的消息会发送给broker,也可以叫server。我们可以看到右边是层级的关系。消息先到server,再到Virtual host(虚拟地址),用于进行逻辑的隔离,最上层的消息路由。一个Virtual host里面可以有若干个exchange和queue,但是不能有相同名称的exchange和queue,最后到达exchange(交换机)。Consumer application相当于消费者,他会读取Message Queue(队列)中的数据。但是现在数据在exchange(交换机)中,所以exchange(交换机)和queue(队列)需要建立绑定的关系,这样消费者就能通过队列拿到消息了。还有一个核心的名词,connection(链接),每次都要创建链接,channel(通道),message(消息体),routing key(路由key)。
3.rabbitMQ架构
P:生产者
X:交换机
红色的格子:队列
C:消费者
和上面的AMQP协议模型差不太多。
4.rabbitMQ消息流转
生产者生产消息Message放入到exchange,exchange和queue建立绑定,消息在到达queue,消费者相当于定于这个queue,当消息到达指定的queue之后,消费者消费掉消息。
5.rabbitMQ消息的可靠性投递
可靠性投递1:
对于可靠性投递,是对于生产端来讲的。如何100%的投递到MQ broker,这里说一下。
step1:生产者sender生产一条消息,分别入库到BIZ DB(业务库)和 MSG DB(日志信息库)。这里的业务库,就是正常的业务逻辑,比如订单的一些信息。日志信息库就是这条消息的状态,比如 status 0 1 2分别代表 0 未消费 1 消费成功 2 消息未达,消费失败。
step2:入库之后,再把消息发送到MQ Broker。
step3:MQ Broker发送确认消息,生产者中有个一个确认接收的监听器,会一直监听。
step4:当生产者受到确认消息后,同事向MSG DB(日志信息库)更新这条日志消息,状态改为status 1,消费成功。
以上步骤是全部都顺利的情况,当消息没有到达MQ Broker或者生产者没有接收到 MQ Broker的确认消息时。会有以下的步骤
step5:会有一个定时器,不断去查询 MSG DB(日志信息库)status=0状态的消息。
step6:查询到status=0的记录,并且在设置的时间内还有变为status=1,则认为消息没有到达MQ Broker或者producer未确认,则对这条消息进行重新发送。
step7:MSG DB(日志信息库)中的一条记录的Retry Count(重试次数)大于3时,则认为这条消息未达,消费失败,status=2。可以进行人工补偿。
可靠性投递2:
第一种可靠性投递在核心业务中,会有两次的入库操作,一次业务入库(必须),一次消息入库。而在核心业务中,有这样一次操作,是不太合适的,会消耗核心业务的时间,从而影响业务的响应时间。而这种可靠性投递,可以免去在核心业务的消息入库的操作。我们了解一下。
step1:发送消息到MQ Broker,同时业务数据也要落库 BIZ DB,
step2:这一步是和step1是同时进行的,这个是发一个延时消息(3分钟或5分钟)。
step3:Downstream service(下游服务)去监听来自生产者的消息。
step4:当Downstream service(下游服务)消费完消息,同时也向指定的队列中发送一个确认接收的消息A。
step5:有一个Callback service(回调服务)监听下游服务发送的确认接收消息A,在回调服务中,监听到来自于下游服务的确认消息,则进行消息落库。
step6:Callback service(回调服务)会监听step2发送的延时消息,受到消息后,去MSG DB(消息库)查询,若查到了,则消息顺利完成投递。若查询不到,则在回调服务中在放松一个RPC请求,到Upstream service(上游服务),在进行消息投递。
这样,在核心业务中,就只有了一次必须的业务落库,消息落库就会在非核心的回调服务中进行,这样的效率就会好点。
6.消息机制
rabbitMQ中有确认消息,return消息,消费端ack的限流,重回队列,死信队列,下面介绍一下。
maven依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.5</version>
</dependency>
6.1 确认消息(系统默认消费者)
确认消息就是当生产者发送一条消息时,设置一个监听器,当消费者确认接收到消息时,就会返回ack,否则返回 nack
生产者:
package com.an.rabbitmq.confirm;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 确认消息流程
* @author Administrator
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
// 创建一个新的通道
Channel channel = connection.createChannel();
// 指定消息的投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String routingKey = "confirm.save";
// 发送一条消息
String msg = "Hello rabbitMQ send confirm message!";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
// 添加一个确认监听器
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// 没有接收到确认消息
System.out.println("No ack!");
}
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 接收到了确认消息
System.out.println("ack!");
}
});
}
}
消费者
package com.an.rabbitmq.confirm;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.QueueingConsumer.Delivery;
/**
* 确认消息流程
* @author Administrator
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException ,Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
// 创建一个新的通道
Channel channel = connection.createChannel();
// 指定消息的投递模式:消息的确认模式
channel.confirmSelect();
String exchangeName = "test_confirm_exchange";
String queueName = "test_confirm_queue";
String routingKey = "confirm.#";
channel.exchangeDeclare(exchangeName, "topic",true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName,true, consumer);
while(true) {
Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("消费端:"+msg);
}
}
}
6.2 返回消息
在生产者中设置返回消息监听器,当生产者的消息没有被消费者接收到时,消息返回到生产者的监听器中。
生产者中,有一个参数mandatory,设置为true时,才会将消息返回到生产者的监听器中。若设置为false,那么broker端自动删除该消息。
生产者:
package com.an.rabbitmq.returnlistener;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 返回的消息。当消息不可达,切在消费者中的mandatory参数设置为true时,则将消息返回到生产者
* @author Administrator
*/
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String exchangeName = "test_biz_exchange";
String routingKey = "biz.save";
String routingKeyError = "abc.save";
String msg = "Hello rabbitMQ!";
// 设置监听器,当没有消费者接收到消息时,消息返回到生产者的监听器中
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange,
String routingKey, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("replyCode:"+replyCode);
System.err.println("replyText:"+replyText);
System.err.println("exchange:"+exchange);
System.err.println("routingKey:"+routingKey);
System.err.println("properties:"+properties);
System.err.println("body:"+new String(body));
}
});
// 第三个参数 mandatory 设置true时,当消息不可达时,会将消息返回给生产者中的returnListener
channel.basicPublish(exchangeName, routingKeyError, true, null, msg.getBytes());
}
}
消费者:
package com.an.rabbitmq.returnlistener;
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 Consumer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
String queueName = "test_return_queue";
String exchangeName = "test_biz_exchange";
String routingKey = "biz.#";
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchangeName, "topic",true,false,null);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
// param1: queue(队列名称),param2: autoAck(自动确认接收), param3: callback(消费者)
channel.basicConsume(queueName, true, queueingConsumer);
while(true) {
Delivery delivery = queueingConsumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("消费者:"+msg);
}
}
}
6.3 消费端限流(自定义消费者)
若rabbitMQ服务器有上万条数据没有处理,若果我们一打开消费者,大量的数据会涌向消费者,可能消费者服务器无法处理这么多的数据,就会出现一些问题。rabbitMQ提供了一种qos(服务质量保证)功能,在非自动确认消息的前提下,如果一定数目的消息未被确认,不进行消费新的消息。
生产者:
package com.an.rabbitmq.limit;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
String exchangeName = "test_my_exchange";
String routingKey = "my.save";
Channel channel = connection.createChannel();
for(int i=0;i<5;i++) {
channel.basicPublish(exchangeName, routingKey, null, ("Hello rabbitMQ "+i).getBytes());
}
}
}
消费者:
package com.an.rabbitmq.limit;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消费端限流
* @author Administrator
*
*/
public class Customer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String queueName = "my_queue";
String exchangeName = "test_my_exchange";
String routingKey = "my.#";
channel.exchangeDeclare(exchangeName, "topic");
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
// 质量保证功能,如果一定数量的消息未被确认前,不进行消费新的消息
// 第二个参数prefetchCount: 如果有 N个消息没有ack,则该消费者会阻塞,知道ack。在no_ack时才生效,要手工ack
channel.basicQos(0, 1, false);
// 限流方式,autoAck设置为false
channel.basicConsume(queueName,false, new MyCustomer(channel));
}
}
自定义消费者
package com.an.rabbitmq.limit;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 自定义消费者
* @author Administrator
*
*/
public class MyCustomer extends DefaultConsumer{
private Channel channel;
public MyCustomer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("-----------consume message----------");
System.err.println("consumerTag: " + consumerTag);
System.err.println("envelope: " + envelope);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
// 消费端手动确认,确认后才进行下一条消息的确认
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
6.4 消息确认和重返队列
当生产者发送消息时,消费者没有接收到,会再次放松到消费者中,直到消息确认成功。
生产者:
package com.an.rabbitmq.ack;
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;
/**
* 消息确认和重返队列
* @author Administrator
*
*/
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
String exchangeName = "test_ack_exchange";
String routingKey = "ack.save";
Channel channel = connection.createChannel();
for(int i=0;i<5;i++) {
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("num", i);
AMQP.BasicProperties prop = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers)
.build();
// mandatory 第三个参数 mandatory 设置true时,当消息不可达时,会将消息返回给生产者中的returnListener
channel.basicPublish(exchangeName, routingKey,true, prop, ("Hello rabbitMQ "+i).getBytes());
}
}
}
消费者:
package com.an.rabbitmq.ack;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消息确认和重返队列
* @author Administrator
*/
public class Customer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String queueName = "ack_queue";
String exchangeName = "test_ack_exchange";
String routingKey = "ack.#";
channel.exchangeDeclare(exchangeName, "topic");
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
// 质量保证功能,如果一定数量的消息未被确认前,不进行消费新的消息
// 第二个参数prefetchCount: 如果有 N个消息没有ack,则该消费者会阻塞,知道ack。在no_ack时才生效,要手工ack
// channel.basicQos(0, 1, false);
// 限流方式,autoAck设置为false
channel.basicConsume(queueName,false, new MyCustomer(channel));
}
}
自定义消费者:
package com.an.rabbitmq.ack;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 自定义消费者 重回队列
* @author Administrator
*/
public class MyCustomer extends DefaultConsumer{
private Channel channel;
public MyCustomer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("-----------consume message----------");
System.err.println("body: " + new String(body));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
Integer num = (Integer)properties.getHeaders().get("num");
if(num == 0) {
// 第三个参数requeue设置为true,当消息未被确认,重返队列
channel.basicNack(envelope.getDeliveryTag(), false, true);
}else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
}
6.4 死信队列
生产者:
package com.an.rabbitmq.dlx;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 死信队列
* @author Administrator
*
*/
public class Producer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
String exchangeName = "test_dlx_exchange";
String routingKey = "dlx.save";
Channel channel = connection.createChannel();
for(int i=0;i<1;i++) {
AMQP.BasicProperties prop = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.expiration("10000") // 设置队列消息10秒后过期
.build();
channel.basicPublish(exchangeName, routingKey, true,prop, ("Hello rabbitMQ "+i).getBytes());
}
}
}
消费者:
package com.an.rabbitmq.dlx;
import java.util.HashMap;
import java.util.Map;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Customer {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.101");
factory.setPort(5672);
factory.setVirtualHost("/");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String queueName = "test_dlx_queue";
String exchangeName = "test_dlx_exchange";
String routingKey = "dlx.#";
channel.exchangeDeclare(exchangeName, "topic");
Map<String, Object> arguement = new HashMap<String, Object>();
arguement.put("x-dead-letter-exchange", "dlx.exchange");
channel.queueDeclare(queueName, true, false, false, arguement);
channel.queueBind(queueName, exchangeName, routingKey);
// 生命死信队列
channel.exchangeDeclare("dlx.exchange", "topic",true,false,null);
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "#");
channel.basicConsume(queueName,true, new MyCustomer(channel));
}
}
自定义消费者:
package com.an.rabbitmq.dlx;
import java.io.IOException;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.AMQP.BasicProperties;
/**
* 自定义消费者 死信队列
* @author Administrator
*
*/
public class MyCustomer extends DefaultConsumer{
private Channel channel;
public MyCustomer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.err.println("-----------consume message----------");
System.err.println("consumerTag: " + consumerTag);
System.err.println("envelope: " + envelope);
System.err.println("properties: " + properties);
System.err.println("body: " + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
三. SpringBoot整合rabbitMQ
用SpringBoot整合rabbitMQ时,写了两个工程,一个生产端,一个消费端。
工程为 rabbitmq-springboot-consumer 和 rabbitmq-springboot-producer
git地址:https://gitee.com/an592655791/rabbitMQ (不想贴代码了 -.-)
其中,在进行对象之间生产消费的时候,两个工程之间要用同一个包下的类,否则出现序列化出错的情况。
所以最好用json进行生产消费