rabbitMQ使用一
一、什么是MQ
MQ全称 Message queue(消息队列),是在消息的传输过程中保存消息的容器。用于分布式项目之间的进行通信。
二、MQ的优缺点
优点:
应用解耦:提高系统的可维护性和可容错性
异步提速:提升用户体验和系统吞吐量
削峰填谷:提高系统的稳定性
缺点:
服务复杂度增加:
系统的组件越多,就越复杂。增加一个MQ,系统的复杂度又提升了
服务稳定性降低
系统越复杂,出现错误的几率越大。
数据一致性问题
A系统处理完业务,通过MQ给B,C,D三个系统发消息数据,如果B,C处理成功,D处理失败,如何保证数据的一致性?
三、图解MQ
1、应用解耦
当有一个应用,有A,B,C等子服务,其中A服务需要将数据传递给B,C等服务,那么
直接在A服务中直接调用B,C等服务即可,但是如果有几百个服务需要A服务的数据,
并且这几百个服务的数量还是在波动,并且还要考虑下游服务的出错的情况,这就不
适合再使用传统的调用。
当这种情况下,我们可以使用MQ作为中间件,A服务将其他服务需要的数据放在MQ中,然后需要该数据的服务向MQ中取数据,这样并不影响A服务的运行。
2、流量削峰
假设我们的项目平常的访问量很少,每秒也就几百个访问,那么一台服务器就可以
应对了。但是如果有秒杀活动的话,那么访问量就是几十倍的增加,一台服务器肯定
不够用,那么我们再去买服务器吗?肯定不是,因为我们平常的访问量很少。这是否
就可以使用MQ了,访问请求先发送到MQ存储,排队等待处理,然后我们的服务再慢
慢的从MQ中读取请求去处理,减轻瞬间访问的压力。
普通情况:
秒杀情况:
3、异步调用
当我们一个流量高峰,比如五一回家使用12306抢票,当我们下订单的时候,还没有调用付款业务之前,是非常快的。但是调用付款业务,就速度很慢了,甚至卡死,如果等待下单成功的应答,那就玩完了,整条链路就挂掉了。此时使用MQ,将下单付款的请求先放到MQ中,付块业务可以使用异步的方式从队列中读取请求,这就不影响付款之前业务的运行了。
普通模式:耗时1min+200ms
加入MQ:耗时200ms+5ms
四、常见的MQ
五、Centos7安装RabbitMQ
1、上传安装软件(①erlang ②rabbitmq)
官网地址:https://www.rabbitmq.com/
2、安装rabbitmq依赖的环境
yum install -y gcc socat openssl openssl-devel
3、安装erlang和rabbitMQ
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
rpm -ivh rabbitmq-server-3.7.17-1.el7.noarch.rpm
4、开启管理界面及配置 ---可以通过网页访问
rabbitmq-plugins enable rabbitmq_management
开设一个用户用来页面访问时使用:
进入到rabbitMQ的安装目录:
cd /usr/share/doc/rabbitmq-server-3.7.17
我们需要更改rabbitmq.config.example文件,但是要将它改为rabbitmq.example才管用,所以我们复制出来一份:
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
然后修改 /etc/rabbitmq/rabbitmq.config 配置文件:
5、开启rabbitMQ服务器
systemctl start rabbitmq-server 开启命令
systemctl restart rabbitmq-server 重启的命令
使用网页连接:用户名和密码 guest
连接成功:
六、rabbitMQ的结构以及每个组件的作用
Broker: 接收和分发消息的应用,rabbitMQ的核心逻辑,RabbitMQ server 就是Message Broker.
Virtual host:出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的
分组中, 类似于网络中namespace的概念。当多个不同的用户使用同一个RabbitMQ
server提供服务时,可以划分出多个vhost,每个用户可以在自己的vhost创建
exchange/queue等。
Connection:publisher/consumer和broker之间的TCP连接。
channel:如果每一次访问RabbitMQ都建立一个Connection连接,在消息量大的时
候,建立TCP Connection的开销将时巨大的,效率也低。channel是在connection中
建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行
通信,AMQP method中包含了channel id来帮助客户端和message broker识别
channel,所以channel是完全隔离的。channel作为轻量级的connection,极大的减少
了创建TCP Connection的开销。·
Exchange:message到达broker的第一站,根据分发规则,匹配查询表中Routing key,分发消息到queue中去。常用的交换机路由类型有:direct(point-to-point),topic(publish-subscribe) and fanout(multicast)
queue:消息最终被送到这里,被consumer取走。
Binding:exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息保存在exchange的查询表中,用于message的分发依据
七、rabbitMQ的模式
以下模式中,只要销毁者监控的队列中有消息,就会自动消费
使用之前要导依赖:使用的是普通的maven项目
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.9.0</version>
</dependency>
</dependencies>
1、简单模式(默认交换机)
上图中只有三个角色:
p:producer:消息的生产者
红色【queue】:存储消息
c:consumer:消息的消费者
示例代码:
消息的生产者:
public class Producers {
public static void main(String[] args) {
// 创建生产者:创建连接工厂 配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
try {
// 创建连接对象 Connection
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 简单模式没有交换器,直接创建队列
/**
* String queue:队列的名称
* boolean durable:队列中的信息是否实例化,也就是rabbitmq重启后信息是否还存在
* boolean exclusive:是否独自占有,信道和队列之间
* boolean autoDelete:是否自动删除,长时间没有消息时,队列删除
* Map<String, Object> arguments 额外参数 先给null
*/
channel.queueDeclare("lyd", true, false, false, null);
// 产生消息
/**
* String exchange: 交换机的名称 如果没有则使用“” 它回自动采用默认
* String routingKey, 路由key 如果没有交换机的绑定 使用队列的名称
* BasicProperties props, 消息的一些额外配置 目前先不加 null
* byte[] body 消息的内容
*/
String msg="i love you !!!!";
channel.basicPublish("","lyd",null,msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消息的消费者:
public class Consumers {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("接收到的"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("lyd",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
2、工作者模式(work)
特点:
1、一个生产者
2、多个消费者
3、一个队列
4、消费者之间存在竞争关系(处理队列中消息的机会是相等的)
用处:
比如批量处理上。rabbitMQ里面积压了大量消息时
生产者:
public class Producers {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂 配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
Connection connection =null;
Channel channel =null;
try {
// 创建连接对象
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 创建队列
channel.queueDeclare("lyd_work", true, false, false, null);
//
for(int i=0;i<10;i++){
String msg="i love you !!!!";
// 发送消息
channel.basicPublish("","lyd_work",null,msg.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally {
// 释放资源
channel.close();
connection.close();
}
}
}
消费者:
消费者1:
public class Consumer01 {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 回调接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("消费者01:"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("lyd_work",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消费者2:
public class Consumer02 {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 回调接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("消费者02:"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("lyd_work",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
3、广播模式或者发布与订阅模式(fanout:扇形交换机)
特点:
1、一个生产者
2、一个交换机,转发消息
3、多个队列
4、多个 消费者
生产者:
public class Producers {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂 配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
Connection connection =null;
Channel channel =null;
try {
// 创建连接对象
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 创建交换机
/**
* @param exchange the name of the exchange
* @param type 交换机的类型
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @return a declaration-confirm method to indicate the exchange was successfully declared
*/
channel.exchangeDeclare("fy_fanout", BuiltinExchangeType.FANOUT,true);
// 创建队列
channel.queueDeclare("lyd_fanout01", true, false, false, null);
channel.queueDeclare("lyd_fanout02", true, false, false, null);
// 绑定队列
channel.queueBind("lyd_fanout01","fy_fanout","");
channel.queueBind("lyd_fanout02","fy_fanout","");
for(int i=0;i<10;i++){
String msg="i love you !!!!";
channel.basicPublish("fy_fanout","",null,msg.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally {
channel.close();
connection.close();
}
}
}
消费者:
消费者1:
public class Consumer01 {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("消费者01:"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("lyd_fanout01",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消费者2:
只要将消费者1中的 channel.basicConsume()中的队列名称改了就ok了
4、路由模式(直连交换机)
特点:
1、一个生产者
2、一个交换机
3、多个队列
4、多个消费者
5、交换机和队列有 routing key绑定
生产者:
public class Producers {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂 配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
Connection connection =null;
Channel channel =null;
try {
// 创建连接对象
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 创建交换机
/**
* @param exchange the name of the exchange
* @param type the exchange type
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @return a declaration-confirm method to indicate the exchange was successfully declared
*/
channel.exchangeDeclare("fy_direct", BuiltinExchangeType.DIRECT,true);
// 创建队列
channel.queueDeclare("lyd_direct01", true, false, false, null);
channel.queueDeclare("lyd_direct02", true, false, false, null);
/* 绑定队列
routingKey:路由标识,交换机分发消息时,就是根据routingkey来分发到符合的队列中
routingkey的值是可以自定义的
*/
channel.queueBind("lyd_direct01","fy_direct","info");
channel.queueBind("lyd_direct02","fy_direct","info");
channel.queueBind("lyd_direct02","fy_direct","error");
//
for(int i=0;i<10;i++){
String msg="i love you !!!!";
channel.basicPublish("fy_direct","info",null,msg.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally {
channel.close();
connection.close();
}
}
}
消费者:
消费者1:
public class Consumer01 {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("消费者01:"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("lyd_direct01",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
消费者2:
只要将消费者1中的 channel.basicConsume()中的队列名称改了就ok了
5、主题模式(topic:主题交换机)
特点:
1、一个生产者
2、一个交换机
3、多个队列
4、多个消费者
5、routing key 是模糊匹配的
*:统配一个
#:统配一个或者多个单词
生产者:
public class Producers {
public static void main(String[] args) throws IOException, TimeoutException {
// 创建连接工厂 配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
Connection connection =null;
Channel channel =null;
try {
// 创建连接对象
connection = factory.newConnection();
// 创建信道
channel = connection.createChannel();
// 创建交换机
/**
* @param exchange the name of the exchange
* @param type the exchange type
* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
* @return a declaration-confirm method to indicate the exchange was successfully declared
*/
channel.exchangeDeclare("fy_topic", BuiltinExchangeType.TOPIC,true);
// 创建队列
channel.queueDeclare("oo_topic01", true, false, false, null);
channel.queueDeclare("oo_topic02", true, false, false, null);
/* 绑定队列和交换机
routingKey:这里写规则,routingKey的规则
*/
channel.queueBind("oo_topic01","fy_topic","*.*.oo");
channel.queueBind("oo_topic02","fy_topic","*.fy.*");
channel.queueBind("oo_topic02","fy_topic","error.#");
for(int i=0;i<10;i++){
String msg="i love you !!!!";
channel.basicPublish("fy_topic","error.dd.cc",null,msg.getBytes());
channel.basicPublish("fy_topic","error.dd.oo",null,msg.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally {
channel.close();
connection.close();
}
}
}
生产者:
生产者1:
public class Consumer01 {
public static void main(String[] args) {
// 创建连接工厂 --配置连接信息
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.253.128");
// 创建连接对象
try {
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
// 接收消息
DefaultConsumer callback = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
// body:接收到的信息
System.out.println("消费者01:"+new String(body));
}
};
// 消费消息
/**
* @param queue the name of the queue
* @param autoAck true if the server should consider messages
* @param callback an interface to the consumer object
*/
channel.basicConsume("oo_topic01",true,callback);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
生产者2:
只要将消费者1中的 channel.basicConsume()中的队列名称改了就ok了