Rabbitmq的简介:
Rabbitmq是一种消息队列,Rabbitmq是实现了Amqp(高级消息队列协议)协议的一种消息队列,Mq(message queue)消息队列,队列中都是消息,队列中都是以先进先出的协议。mq通常用于系统通信。
同步通信:
接口直接调用,直接发送的形式,发送后直接返回。
异步通信:
系统通信实际上就是数据的传输,通过间接传输的方式进行传输,先放到消息队列,然后另一方进行拿去。
mq的作用:
可以把mq想象成一个仓库,将消息进行存储,然后再转发,谁需要就进行订阅数据,接收并且转发消息,mq在不同情况下表现是不同的,比如异步解耦,有一些操作比较耗费时间,比如用户注册的时候,通过mq邮箱通知用户注册成功,使用mq进行请求存储,进行请求挨个处理。
Rabbitmq的快速上手:
rabbitmq的安装(ubuntu):
rabbitmq是基于erlang语言进行开发的,安装mq之前需要先进行安装erlang语言。
更新软件包。
这时候就会发现erlang已经更新 完成了,接下来就是安装Rabbitmq。
这时候就可以看到mq已经安装好了。接着安装管理界面。
通过云服务器地址加上15672端口号访问。
添加管理员用户,并且为用户添加管理员权限。
# rabbitmqctl add_user ${账号} ${密码}
rabbitmqctl set_user_tags admin administrator
通过刚刚设置好的账号密码就可以登录界面了。
创建虚拟机:
使用哪个用户进行创建虚拟机,该用户就对新用户进行操作权限,也可以点击虚拟机进行设置权限。
Rabbitmq核心概念
启动rabbitmq,通过该命令启动rabbitmq。
sudo systemctl start rabbitmq-server
5672表示客户端和服务器通信的端口号为5672,15672是管理界面的端口号,25672表示集群使用的端口号。
实际上rabbitmq就是一个生产者消费者模型,生产者和消费者都是客户端,rabbitmq服务器就是blocker,阻塞者。就像我们(生产者)将快递交给快递公司,快递公司(rabbitmq)将数据交给收信人(消费者)。(先发送到交换机上面)。
一个broker有多个Exchange(交换机),由exchange进行分配到各个queue,如果没有匹配到queue,就会丢弃消息或者返回消息。
生产者发送消息,消费者接收消息,broker实际上就是Rabbitwq服务器,主要是接收和发送消息。通常发送的消息会带上标签,根据标签在exchange里面进行对queue进行分发。
virtual host
虚拟主机. 这是⼀个虚拟概念. 它为消息队列提供了⼀种逻辑上的隔离机制. 对于RabbitMQ⽽⾔, ⼀个 BrokerServer 上可以存在多个 Virtual Host. 当多个不同的⽤⼾使⽤同⼀个 RabbitMQ Server 提供的服务时,可以虚拟划分出多个 vhost,每个用户在⾃⼰的 vhost 创建exchange/queue 等。一个broker可以有多个虚拟机。
Queue
队列和消费者是多对多的,一个消费者可以订阅多个队列,多个消费者也可以订阅多个队列。
Exchange
交换机,message到达broker的第一站,他负责接收消费者的信息,并且通过特定的规则路由到一个或者多个queue中。
Connection
是客⼾端和RabbitMQ服务器之间的⼀个TCP连接. 这个连接是建⽴消息传递的基础, 它负责传输客户端和服务器之间的所有数据和控制信息。
Channel
信道. Channel是在Connection之上的⼀个抽象层. 在 RabbitMQ 中, ⼀个TCP连接可以 有多个Channel, 每个Channel 都是独⽴的虚拟连接. 消息的发送和接收都是基于 Channel的. 通道的主要作⽤是将消息的读写操作复⽤到同⼀个TCP连接上,这样可以减少建⽴和关闭连接的开销, 提高性能.
Rabbitmq工作流程
- Producer ⽣产了⼀条消息。
- Producer 连接到RabbitMQBroker, 建⽴⼀个连接(Connection),开启⼀个信道(Channel)。
- Producer 声明⼀个交换机(Exchange), 路由消息。
- Producer 声明⼀个队列(Queue), 存放信息。
- Producer 发送消息⾄RabbitMQ Broker。
- RabbitMQ Broker 接收消息, 并存⼊相应的队列(Queue)中, 如果未找到相应的队列, 则根据生产者的配置, 选择丢弃或者退回给生产。
Amqp:
AMqp约定了消息队列的协议,Rabbitmq是遵从amqp协议的,Amqp生产了一系列的消息交换功能,包括exchange,queue等,这些组件共同工作,使得生产者能够把消息发给消费者。Amqp也定义了网络协议,使得客户端应用通过该协议进行通信。
Web界面操作和快速入门
在这里表示了对哪些虚拟机有操作权限,也可以点击admin或者guest进行删除。
引入依赖
配置depency:
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.20.0</version>
</dependency>
生产者代码编写
建立连接需要ip,端口号,账号和密码以及虚拟主机名字
在这些操作的前提就是要记得要开放端口,并且要关闭连接。
消费者代码编写
创建连接,创建channel,声明队列,消费信息,释放资源。消费者实际上需要指定订阅的队列,如果队列不存在,队列就会报错。
Rabbitmq工作模式(七种) :
简单模式
点到点的模式,只有一个生产者一个消费者,只能一个消费者处理。
工作队列模式
一个生产者有多个消费者,有多个消费者,比如队列有十条信息,c1,c2共同消费,消息不会重复,而是分配给多个消费者。适合集群下做异步处理,c1,c2共同完成一个任务。
发布订阅模式(无routingkey)
这里的x表示交换机,下面有交换机的类型的介绍。交换机从p接收到消息,进行全部复制一份全部分发。也就是交换机的广播模式。
这里看到我们自己创建了一个交换机,并且绑定了两个队列。
生产者
package com.redis.demo.rabbitmq.Fanout;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
* 发布订阅模式
*
*
* */
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
//设置虚拟机的地址
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection=factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(Constants.FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
channel.queueDeclare(Constants.FANOUT_QUEUE1,true,false,false,null);
channel.queueDeclare(Constants.FANOUT_QUEUE2,true,false,false,null);
channel.queueBind(Constants.FANOUT_QUEUE1,Constants.FANOUT_EXCHANGE,"");
channel.queueBind(Constants.FANOUT_QUEUE2,Constants.FANOUT_EXCHANGE,"");
//routingkey为空表示是广播模式,
for (int i = 0; i < 10; i++) {
String msg="hello rabbitmq"+i;
channel.basicPublish(Constants.FANOUT_EXCHANGE ,"",null,msg.getBytes());
}
System.out.println("发送成功");
channel.close();
connection.close();
}
}
路由模式(有routingkey)direct模式
生产者将消息发送到交换机,交换机根据消息的路由键(routing key)将消息路由到与之绑定的队列,只有绑定键(binding key)与路由键匹配的队列才能接收到消息。
生产者
通过路由的方式将消息进行分发。
package com.redis.demo.rabbitmq.Direct;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
* 路由模式
*
*
* */
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(Constants.DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT,true);
channel.queueDeclare(Constants.DIRECT_QUEUE1,true,false,false,null);
channel.queueDeclare(Constants.DIRECT_QUEUE2,true,false,false,null);
//绑定交换机
channel.queueBind(Constants.DIRECT_QUEUE1,Constants.DIRECT_EXCHANGE,"a");
channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"a");
channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"b");
channel.queueBind(Constants.DIRECT_QUEUE2,Constants.DIRECT_EXCHANGE,"c");
String msg_a="hello rabbitmqaaaaaaaaaaaa";
channel.basicPublish(Constants.DIRECT_EXCHANGE ,"a",null,msg_a.getBytes());
String msg_b="hello rabbitmqbbbbbbbbbbbb";
channel.basicPublish(Constants.DIRECT_EXCHANGE ,"b",null,msg_b.getBytes());
String msg_c="hello rabbitmqcccccccccccc";
channel.basicPublish(Constants.DIRECT_EXCHANGE ,"c",null,msg_c.getBytes());
channel.close();
connection.close();
}
}
通配符模式:
通配符模式是路由模式的一个升级
根据通配符进行匹配,*表示一个单词,#表示多个单词。这里是单词,不是字符。
package com.redis.demo.rabbitmq.Topics;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(Constants.TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC,true);
channel.queueDeclare(Constants.TOPIC_QUEUE1,true,false,false,null);
channel.queueDeclare(Constants.TOPIC_QUEUE2,true,false,false,null);
//绑定交换机
channel.queueBind(Constants.TOPIC_QUEUE1,Constants.TOPIC_EXCHANGE,"*.a.*");
channel.queueBind(Constants.TOPIC_QUEUE2,Constants.TOPIC_EXCHANGE,"*.*.b");
channel.queueBind(Constants.TOPIC_QUEUE2,Constants.TOPIC_EXCHANGE,"c.#");
String msg_a="hello topic rabbitmqaaaaaaaaaaaa";
channel.basicPublish(Constants.TOPIC_EXCHANGE ,"ae.a.f",null,msg_a.getBytes());//转发到q1
String msg_b="hello rabbitmqbbbbbbbbbbbb";
channel.basicPublish(Constants.TOPIC_EXCHANGE ,"ef.a.b",null,msg_b.getBytes());//转发到q2和q1
String msg_c="hello rabbitmqcccccccccccc";
channel.basicPublish(Constants.TOPIC_EXCHANGE ,"c.ef.d",null,msg_c.getBytes());
channel.close();
connection.close();
}
}
在这个下面有一个查看消息,如果选择ack的话,那么消息就会在getmessage之后被删除。
Rpc模式(Rpc通信):
cli发送消息,会给消息设置属性,客户端指定响应放到某个指定队列,会设置关联id。
客户端:
发送请求,携带replyid,correlationid,接收响应,校验correlationid。
package com.redis.demo.rabbitmq.rpc;
import com.rabbitmq.client.*;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
/*
* 发送请求,接收响应
*
*
* */
public class RpcClient {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String correlationId= UUID.randomUUID().toString();
AMQP.BasicProperties props =new AMQP.BasicProperties().builder()
//回调
.replyTo(Constants.RPC_RESPONSE_QUEUE)
.correlationId(correlationId)
.build();
String msg="hello mq";
channel.basicPublish("",Constants.RPC_REQUEST_QUEUE,props,msg.getBytes());
final BlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
System.out.println("接收到回调消息");
if (correlationId.equals(properties.getCorrelationId())){
//如果correlationid校验成功
try {
String take = queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
channel.basicConsume(Constants.RPC_RESPONSE_QUEUE,true,consumer);
}
}
如果设置自动确认,当mq发给消费者,mq就认为对方已经确认收到了,如果没有的话,手动确认,就是得返回一个ack。
package com.redis.demo.rabbitmq.rpc;
import com.rabbitmq.client.*;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
/*
* 发送请求,接收响应
*
*
* */
public class RpcClient {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Constants.RPC_REQUEST_QUEUE,true,false,false,null);
channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE,true,false,false,null);
String correlationId= UUID.randomUUID().toString();
AMQP.BasicProperties props =new AMQP.BasicProperties().builder()
//回调
.replyTo(Constants.RPC_RESPONSE_QUEUE)
.correlationId(correlationId)
.build();
String msg="hello mq";
channel.basicPublish("",Constants.RPC_REQUEST_QUEUE,props,msg.getBytes());
final BlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
System.out.println("接收到回调消息");
if (correlationId.equals(properties.getCorrelationId())){
//如果correlationid校验成功
queue.offer(msg);
}
}
};
channel.basicConsume(Constants.RPC_RESPONSE_QUEUE,true,consumer);
String take = queue.take();
System.out.println(take);
}
}
服务器:
接收请求,进行响应,发送指定的replyto,设置correlationid。
package com.redis.demo.rabbitmq.rpc;
import com.rabbitmq.client.*;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.SortedSet;
import java.util.concurrent.TimeoutException;
public class RpcServer {
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(Constants.RPC_REQUEST_QUEUE,true,false,false,null);
channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE,true,false,false,null);
channel.basicQos(1);
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
AMQP.BasicProperties basicProperties=new AMQP.BasicProperties().builder()
.correlationId(properties.getCorrelationId())
.build();
channel.basicPublish("",Constants.RPC_RESPONSE_QUEUE,basicProperties,body);
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//非自动返回
channel.basicConsume(Constants.RPC_REQUEST_QUEUE,false,consumer);
}
}
发布确认模式(解决生产者问题)
发布确认模式是mq消息可靠性保证的机制,生产者发送消息到mq,发送的时候是否发送给mq呢?发送给mq服务器,服务器对其进行ack响应,生产者就知道已经发送给mq。Publisher Confirms模式是RabbitMQ提供的⼀种确保消息可靠发送到RabbitMQ服务器的机制。在这种模式下,⽣产者可以等待RabbitMQ服务器的确认,以确保消息已经被服务器接收并处理。
发布确认模式是对消息可靠性保证的一种解决方式,发送消息的时候常常会发生消息丢失的情况。
- 生产者发送消息给broker的时候,由于网络抖动的情况没有发送成功。
- broker将消息弄丢了。
- 消费者拿到消息,消费者在消费消息的时候,因为没有处理好,导致消费者将消费失败的消息从队列中删除。
针对问题1,可以使用发布确认模式。
发布数据的时候会给每个数据都带上序号标签,比如1,2,3,当broker收到消息的时候,就会返回一个ack,比如ack带的值是3,那就说明了3以前的数据都已经收到了,也就是收到了1,2,3。因为broker内部错误,就会返回一个nack,表示返回错误。
启动confirm模式
- 单独确认:对每一种都进行确认
- 批量确认:比如消息发送到达多少,比如发送的消息到达100后进行确认。
- 异步确认:发送和确认两个环节,用两个进程进行确认。
当数据量比较大的时候可以明显发现异步确认的时间比批量确认的时间短很多。
消息量越大,表现越好。
package com.redis.demo.rabbitmq.Publisher;
import com.rabbitmq.client.*;
import com.redis.demo.rabbitmq.Constants.Constants;
import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;
public class PublisherConfirms {
private static final Integer MESSAGE_COUNT=10000 ;
static Connection createConnection() throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost(Constants.HOST);
factory.setPort(Constants.PORT);
factory.setUsername(Constants.USERNAME);
factory.setPassword(Constants.PASSWORD);
//设置虚拟机的地址
factory.setVirtualHost(Constants.VIRTUALHOST);
Connection connection=factory.newConnection();
return connection;
}
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//单独确认
//publishingMessageIndividually();
//批量确认
publishingMessageInBatches();
//异步确认
handlingPublisherConfirmsAsynchronously();
}
private static void handlingPublisherConfirmsAsynchronously() throws IOException, TimeoutException, InterruptedException {
//异步确认
try(Connection connection=createConnection()) {
Channel channel = connection.createChannel();
channel.confirmSelect();
long start=System.currentTimeMillis();
SortedSet<Long> confirmSet=Collections.synchronizedSortedSet(new TreeSet<>());
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple){
//将小于deliveryTag的值删除
confirmSet.headSet(deliveryTag+1).clear();
}else {
confirmSet.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//nack需要重新发送
if (multiple){
//将小于deliveryTag的值删除
confirmSet.headSet(deliveryTag+1).clear();
}else {
confirmSet.remove(deliveryTag);
}
//需要处理,比如重发消息
}
});
for (int i = 0; i < MESSAGE_COUNT; i++) {
String msg="hello publisher confirm"+i;
//指定id
long seqNo=channel.getNextPublishSeqNo();
channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE3,null,msg.getBytes());
//在集合中放入序号
confirmSet.add(seqNo);
}
while (!confirmSet.isEmpty()){
Thread.sleep(10);
}
long end=System.currentTimeMillis();
System.out.println("异步确认" +( end-start));
}
}
private static void publishingMessageInBatches() throws IOException, TimeoutException, InterruptedException {
try(Connection connection=createConnection()) {
Channel channel = connection.createChannel();
channel.confirmSelect();
channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE2,true,false,false,null);
//设置批量数为100
int batchSize=100;
int outstandingMessageCount=0;
Long start=System.currentTimeMillis();
for (int i = 0; i < MESSAGE_COUNT; i++) {
outstandingMessageCount++;
String msg="hello publishingMessageInBatches"+i;
channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE2,null,msg.getBytes());
if (outstandingMessageCount==batchSize){
channel.waitForConfirmsOrDie(5000);
outstandingMessageCount=0;
}
}
if (outstandingMessageCount>0){
channel.waitForConfirmsOrDie(5000);
}
Long end=System.currentTimeMillis();
System.out.println("批量确认"+(end-start));
}
}
private static void publishingMessageIndividually() throws IOException, TimeoutException, InterruptedException {
try (Connection connection=createConnection()){
Channel channel = connection.createChannel();
//将信道设置为确认模式
channel.confirmSelect();
channel.queueDeclare(Constants.PUBLISHER_CONFIRMS_QUEUE1,true,false,false,null);
Long start=System.currentTimeMillis();
for (int i = 0; i <MESSAGE_COUNT ; i++) {
String msg="hello publishingMessageIndividually"+i;
channel.basicPublish("",Constants.PUBLISHER_CONFIRMS_QUEUE1,null,msg.getBytes());
//等待mq存储到硬盘上才会返回,这种策略比较慢
channel.waitForConfirmsOrDie(5000);
}
Long end=System.currentTimeMillis();
System.out.println("立即确认"+(end-start));
}
}
}