RabbitMQ基础学习

  1. 什么是mq?

mq简称消息队列(也叫消息中间件),通过生产者消费者模型实现系统间的解耦。生产者不断的向消息队列中去写一下信息,消息队列接收到消息会依次把消息放到队列中,日后通过消费者去消费生产者往队列中生产的消息。生产者无需关心消息是否被消费,消费者无需关心生产者有没有正常运行,整个过程没有任何api的侵入。整个过程是异步的。跨系统通信时首选消息队列。

  1. 市场上主流的消息中间件

老牌的Apache下的ActiveMQ、RabbitMQ、Apache下炙手可热的Kafka,阿里巴巴自主开发RocketMQ等。

  1. 不同MQ特点

1.ActiveMQ

ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线,它是一个完全支持JMS规范的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎!

2.Kafka

Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输,0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,追求产生大量数据的互联网服务的数据收集业务。

3.RocketMQ

RocketMQ是阿里开源的消息中间件,它是纯java开发,具有高吞吐量、高可用性,适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算,消息推送、日志流式处理、binglog分发等场景。

4.RabbitMQ

RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

RabbitMQ比Kafka可靠,kafka更适合IO高吞吐的处理,一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用,比如ELK日志收集。

AMQP协议

AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领域不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议类型:

------------Server----------------

Virtual  host(基本一个应用一个虚拟主机,类似于关系型数据库中的库,虚拟主机需要与用户进行绑定)

----------------------------------

----------------------------------------               Exchange

Publisher application---------生产消息------------>  +-----------------------+

----------------------------------------            +-----------------------+

----------------------------------------              Message Queue

Consumer application<--------消费消息-----------  +-----------------------+

----------------------------------------    +-----------------------+

以下协议可以理解为生产者先把消息发给Server(可以理解rabbitmq的一个节点,也就是一个rabbitmq服务器),生产者通过虚拟主机把消息发给交换机,交换机与队列之间进行绑定关系(点对点(一对一)、路由、发布订阅),消费者直接从队列中消费消息。

  1. rabbitmq安装
  1. 将rabbitmq安装包上传到linux系统中

erlang-22.0.7-1.el7.x86_64.rpm

rabbitmq-server-3.7.18-1.el7.noarch.rpm

  1. 安装erlang依赖包

rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm

  1. 安装rabbitmq安装包(需要联网)

yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm

注意:默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.

config.example目录中,需要将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq

.config

  1. 复制配置文件

cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example  /etc/rabbitmq/rabbit

mq.config

  1. 查看配置文件位置

ls /etc/rabbitmq/rabbitmq.config

  1. 修改配置文件(参见下图:)

vim /etc/rabbitmq/rabbtmq.config

61 行 %%{loopback_users, []},

去掉%%,以及最后的,逗号,修改为{loopback_users, []}

  1. 执行如下命令,启动rabbitmq中的插件管理

rabbitmq-plugins  enable  rabbitmq.management

出现如下说明:

Enabling plugins on node rabbit@localhost:

     Rabbitmq_management

The following plugins have been configured:

Rabbitmq_management

Rabbitmq_management_agent

Rabbitmq-web_dispatch

  Applying plugin configuration to rabbit@localhost...

The following plugins have been enabled:

Rabbitmq_management

Rabbitmq_management_agent

Rabbitmq_web_dispatch

Set 3 plugins.

Offline change: changes will take effect at broker restart.

  1. 启动rabbitmq的服务

启动:systemctl start rabbitmq-server

重启:systemctl restart rabbitmq-server

停止:systemctl stop rabbitmq-server

  1. 查看服务状态

systemctl start rabbitmq-server

  1. 关闭防火墙服务

从开机启动服务列表移除:systemctl disable firewalld

停止防火墙:systemctl stop firewalld

  1. 访问web管理界面

http://10.15.0.8:15672/

默认用户名/密码:guest/guest

  1. rabbitmq配置

1.服务启动相关

systemctl start|restart|stop|status rabbimq-server

  1. 管理命令行 用来在不使用web管理界面情况下命令Rabbitmq

rabbitmqctl help 可以查看更多命令

  1. 插件命令行

Rabbitmq-plugins enable|list|disable

  1. Rabbitmq的第一个程序
  1. AMQP协议的回顾

AMQP(advanced message queuing protocol)在2003年时被提出,最早用于解决金融领域不同平台之间的消息传递交互问题。顾名思义,AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。这使得实现了AMQP的provider天然性就是跨平台的。以下是AMQP协议类型:

------------Server----------------

Virtual  host(基本一个应用一个虚拟主机,类似于关系型数据库中的库,虚拟主机需要与用户进行绑定)

----------------------------------

----------------------------------------               Exchange

Publisher application---------生产消息------------>  +-----------------------+

----------------------------------------            +-----------------------+

----------------------------------------              Message Queue

Consumer application<--------消费消息-----------  +-----------------------+

----------------------------------------     +-----------------------+

以下协议可以理解为生产者先把消息发给Server(可以理解rabbitmq的一个节点,也就是一个rabbitmq服务器),生产者通过虚拟主机把消息发给交换机,交换机与队列之间进行绑定关系(点对点(一对一)、路由、发布订阅),消费者直接从队列中消费消息。

  1. RabbitMQ支持的消息模型

  1. 引入依赖

com.rabbitmq

amqp-client

5.7.2

  1. 第一种模式(直连,也就是点对点,使用rabbitmq默认交换机(代码里面是空字符串),生产者直接与队列绑定关系,这种模式一次只允许一个消费者消费,适用于登陆注册功能,比如注册成功之后啊,可能要在注册的过程中发短信,可以把业务代码放在当前系统,发短信放到消息队列中,由另外一个短信系统完成短信发送服务。

在rabbitmq页面创建虚拟主机,然后创建相应用户,再把虚拟主机与用户进行绑定。

在上面的模型中,有以下概念:

  1. P:生产者,也就是要发送消息的程序;
  1. C:消费者:消费的接收者,会一直等到消息到来;
  2. queue: 消息队列,图中红色部分,类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从中取出消息;
  3. 生产者与消费者绑定的队列及其属性需要一模一样

channel.queueDeclare(hello,false,false,false,null);

不好的地方:消费者消费太慢,生产者不停的生产消息,消费者来不及及时处理,会导致队列里面的消息堆积。

1.开发生产者

//创建连接mq的连接工厂对象

ConnectionFactory connectionFactory = new ConnectionFactory();

//设置连接rabbitmq主机

connectionFactory.setHost(“10.15.0.9”);

//设置端口号

connectionFactory.setPort(5672);

//设置连接哪个虚拟主机

connectionFactory.setVirtualHost(“/ems”);

connectionFactory.setUsername(“ems”);

connectionFactory.setPassword(“123”);

//获取连接对象

Connection connection = connectionFactory.newConnection();

//获取连接中通道

Channel channel = connection .createChannel();

//通道绑定对应消息队列

//参数1:队列名称 如果队列不存在自动创建

//参数2:用来定义队列特性是否要持久化( true 持久化队列 false不持久化,不设置持久化,队列会随着rabbitmq服务的重启而丢失,看公司具体业务需求要不要持久化但是并不代表队列里面的消息持久化。如果需要将队列里面的消息也持久化,需要在发送消息的时候设置消息的持久化。)

//参数3:exclusive 是否独占队列(独占的意思只允许当前链接使用)true:独占队列; false不独占

//参数4:autoDelete 是否在消费完成后自动删除队列 true自动删除 false不自动删除

//参数5: 额外附加参数

channel.queueDeclare(“hello”,false,false,false,null);

//发布消息

//参数1:交换机名称;参数2:队列名称;

参数3:传递消息额外设置(MessageProperties.PERSISTENT_TEXT_PLAN设置mq消息持久化,服务重启消息不会丢失);

参数4:消息的具体内容

channel.basicPublish(“”,”hello”,MessageProperties.PERSISTENT_TEXT_PLAN,”hello rabbitmq”.getBytes());

channel.close();

connection.close();

2.开发消费者

//创建连接mq的连接工厂对象

ConnectionFactory connectionFactory = new ConnectionFactory();

//设置连接rabbitmq主机

connectionFactory.setHost(“10.15.0.9”);

//设置端口号

connectionFactory.setPort(5672);

//设置连接哪个虚拟主机

connectionFactory.setVirtualHost(“/ems”);

connectionFactory.setUsername(“ems”);

connectionFactory.setPassword(“123”);

//获取连接对象

Connection connection = connectionFactory.newConnection();

//获取连接中通道

Channel channel = connection .createChannel();

//通道绑定对应消息队列

//参数1:队列名称 如果队列不存在自动创建

//参数2:用来定义队列特性是否要持久化 true 持久化队列 false不持久化

//参数3:exclusive 是否独占队列(独占的意思只允许当前链接使用)true:独占队列; false不独占

//参数4:autoDelete 是否在消费完成后自动删除队列 true自动删除 false不自动删除

//参数5: 额外附加参数

channel.queueDeclare(“hello”,false,false,false,null);

//消费消息

//参数1:消费哪个队列的消息 队列名称

//参数2:开始消息的自动确认机制

//参数3:消费时的回调接口

Channel.basicConsume(“hello”,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“==============”+new String(body));

}

});

//channel.close();消费端消费时一般不关闭通道及其连接,最好一直消费

//connection.close();

3.封装工具类

public class RabbitMQUtils{

private static ConnectionFactory connectionFactory;

private static Properties properties;

static{

connectionFactory = new ConnectionFactory();

//设置连接rabbitmq主机

connectionFactory.setHost(“10.15.0.9”);

//设置端口号

connectionFactory.setPort(5672);

//设置连接哪个虚拟主机

connectionFactory.setVirtualHost(“/ems”);

connectionFactory.setUsername(“ems”);

connectionFactory.setPassword(“123”);

}

//定义提供连接对象的方法

public static Connection getConnection(){

try{

return connectionFactory.newConnection();

}catch(Exception e){

e.printStackTrace();

}

return null;

}

//关闭通道和关闭连接工具方法

public static void closeConnectionAndChanel(Channel channel, Connection conn){

try{

if(channel != null) channel.close();

if(conn != null) conn.close();

}catch(Exception e){

e.printStackTrace();

}

}

}

5.第二种模型(work queue使用rabbitmq默认交换机(代码里面是空字符串))

Work queue,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。

Work Queues

(using the java Client)

角色:

  1. P:生产者:任务的发布者
  2. C1:消费者-1:领取任务并完成任务,假设完成速度较慢
  3. C2:消费者-2:领取任务并完成任务,假设完成速度较快
  1. 开发生产者

channel.queueDeclare(“hell0”,true,false,false,null);

for(int i = 0; i < 10; i++){

channel.basicPublish(“”,”hello”,null,(i+”=====>:我是消息”).getBytes())

}

  1. 开发消费者-1

channel.queueDeclare(“hell0”,ture,false,false,null);

channel.basicConsume(“hello”,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

}

});

  1. 开发消费者-2

channel.queueDeclare(“hell0”,ture,false,false,null);

channel.basicConsume(“hello”,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

try{

Thread.sleep(1000);//处理消息比较慢,一秒处理一个消息

}catch(InterruptedException e){

e.printStackTrace();

}

System.out.println(“消费者2:”+new String(body));

}

});

  1. 测试结果

消费者1: 1====>:我是消息

消费者1: 3====>:我是消息

消费者1: 5====>:我是消息

消费者1: 7====>:我是消息

消费者1: 9====>:我是消息

消费者2: 0====>:我是消息

消费者2: 2====>:我是消息

消费者2: 4====>:我是消息

消费者2: 6====>:我是消息

消费者2: 8====>:我是消息

总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息,这种分发消息的方式称为循环。

  1. 消息自动确认机制

生产消息

平均分配

消息自动确认开启,假如消息消费者1平均分配了5个完整消息,消费到第3个消息时,自己宕机自己死了,两个消息丢失,所以说消息自动确认开启机制就有问题了。此时需要如下解决方案:

channel.basicQos(1);//一次只接受一条未确认的消息

//参数2:关闭自动确认消息

channel.basicConsume(“hello”,false,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息

}

});

  1. 设置通道一次只能消费一个消息
  2. 关闭消息的自动确认,开启手动确认消息

5.第三种模型(fanout)

fanout 扇出 也称为广播

Putting it all together

在广播模式下,消息发送流程是这样的:

  1. 可以有多个消费者
  2. 每个消费者有自己的queue(队列)
  3. 每个队列都要绑定到Exchange(交换机)
  4. 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
  5. 交换机把消息发送给绑定过的所有队列
  6. 队列的消费者都能拿到消息。实现一条消息被多个消费者消费。

1.开发生产者

//声明交换机

channel.exchangeDeclare(“logs”,”fanout”);//广播 一条消息多个消费者同时消费

//发布消息

channel.basicPublish(“logs”,””,null,”hello”.getBytes());

2.开发消费者-1

//声明交换机

channel.exchangeDeclare(“logs”,”fanout”);

//创建临时队列

String queue = channel.queueDeclare().getQueue();

//将临时队列绑定exchange

channel.queueBind(queue,”logs”,””);

//处理消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

}

});

2.开发消费者-2

//声明交换机

channel.exchangeDeclare(“logs”,”fanout”);

//创建临时队列

String queue = channel.queueDeclare().getQueue();

//将临时队列绑定exchange

channel.queueBind(queue,”logs”,””);

//处理消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者2:”+new String(body));

}

});

3.开发消费者-3

//声明交换机

channel.exchangeDeclare(“logs”,”fanout”);

//创建临时队列

String queue = channel.queueDeclare().getQueue();

//将临时队列绑定exchange

channel.queueBind(queue,”logs”,””);

//处理消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者3:”+new String(body));

}

});

5.第四种模型(Routing)

1.Routing之订阅模型-Direct(直连)

在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费,这时就要用到Direct模型的Exchange

在Direct模型下:

  1. 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)。
  2. 消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey。
  3. Exchange不再把消息交给每一个绑定的队列。而是根据消息的RoutingKey进行判断,只有队列的RoutingKey与消息的RoutingKey完全一致,才能接收到消息流程:

 图解:

  1. P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key;
  2. X:Exchange(交换机),接收生产者的消息,然后把消息递交给与routing key完全匹配的队列;
  3. C1:消费者-1,其所在队列指定了需要routing key为error(根据个人程序随便定)的消息;
  4. C2:消费者-2,其所在队列指定了需要routing key为info、error、warning的消息;

2.开发生产者

//获取连接对象

Connection connection = RabbitMQUtils.getConnection();

//获取连接对象通道

Channel channel = Connection.createChannel();

String exchangeName = “logs_direct”;

//声明交换机 参数1:交换机名称 参数2:交换机类型 基于指令的Routing key转发

channel.exchangeDeclare(exchangeName ,”direct”);

//发送消息

String routingKey= “error”;//info、warning

//发布消息

channel.basicPublish(exchangeName,routingKey,null,(“指定的route key”+key+”的消息”).getBytes());

//关闭资源

RabbitMQUtils.closeConnectionAndChanel(channel,connection);

3.开发消费者1

Connection connection = RabbitMQUtils.getConnection();

Channel channel = Connection.createChannel();

//通道声明交换机以及交换的类型

channel.exchangeDeclare(“logs_direct”,”direct”);

//创建一个临时队列

String queue = channel.queueDeclare().getQueue();

//基于route key绑定队列和交换机

channel.queueBind(queue,”logs_direct”,”error”);

//获取消费的消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

}

});

3.开发消费者2

Connection connection = RabbitMQUtils.getConnection();

Channel channel = Connection.createChannel();

//通道声明交换机以及交换的类型

channel.exchangeDeclare(“logs_direct”,”direct”);

//创建一个临时队列

String queue = channel.queueDeclare().getQueue();

//基于route key绑定队列和交换机

channel.queueBind(queue,”logs_direct”,”info”);

channel.queueBind(queue,”logs_direct”,”error”);

channel.queueBind(queue,”logs_direct”,”warning”);

//获取消费的消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者2:”+new String(body));

}

});

5.第五种模型(Routing之订阅模型-Topic)

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routingkey的时候使用通配符!这种模型Routingkey一般是由一个或多个单词组成,多个单词之间以“.”分割,例如:item.insert

#通配符

*(star) can substitute for exactly one word. 匹配不多不少恰好1个词

#(hash) can substitute for zero or more words.匹配一个或多个词

#如

audit.# 匹配audit.irs.corporate或者 audit.irs等

audit.* 只能匹配 audit.irs

1.开发生产者

//获取连接对象

Connection connection = RabbitMQUtils.getConnection();

//获取连接对象通道

Channel channel = Connection.createChannel();

String exchangeName = “topics”;

//声明交换机以及交换机类型

channel.exchangeDeclare(exchangeName ,”topic”);

//发送消息

String routingKey= “user.save”;

//发布消息

channel.basicPublish(exchangeName,routingKey,null,(“这里是topic动态路由模型,routekey”+routingKey+”的消息”).getBytes());

//关闭资源

RabbitMQUtils.closeConnectionAndChanel(channel,connection);

2.开发消费者1

Connection connection = RabbitMQUtils.getConnection();

Channel channel = Connection.createChannel();

String exchangeName = “topics”;

//声明交换机以及交换机类型

channel.exchangeDeclare(exchangeName ,”topic”);

//创建一个临时队列

String queue = channel.queueDeclare().getQueue();

//基于route key绑定队列和交换机

channel.queueBind(queue,exchangeName ,”user.*”);

//获取消费的消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

}

});

3.开发消费者2

Connection connection = RabbitMQUtils.getConnection();

Channel channel = Connection.createChannel();

String exchangeName = “topics”;

//声明交换机以及交换机类型

channel.exchangeDeclare(exchangeName ,”topic”);

//创建一个临时队列

String queue = channel.queueDeclare().getQueue();

//基于route key绑定队列和交换机

channel.queueBind(queue,exchangeName ,”user.#”);

//获取消费的消息

channel.basicConsume(queue ,true,new DefaultConsumer(channel){

@override//最后一个参数:消息队列中取出的消息

public void handleDelivery(String consumerTag,Envelope envelope,AMQP.BasicProperti

Es,byte[] body) throws IOException{

System.out.println(“消费者1:”+new String(body));

}

});

  1. SpringBoot中使用RabbitMQ

记住每种不同类型模式的消费者类名不要相同,不然不知道是给哪个消费者发送消息,即使你的routerkey不同。交换机以及交换机类型由消费者去创建。

1.搭建初始环境

1.引入依赖

org.springframework.boot

spring-boot-starter-amqp

2.配置配置文件

spring:

application:

name: springboot_rabbitmq

rabbitmq:

host: 10.15.0.9

port: 5672

username: ems

password: 123

virtual-host: /ems

1.第一种模型整合(直连)

1.开发生产者

//注入rabbitTemplate

@Autowired

private RabbitTemplate rabbitTemplate;

//hello world

@Test

public void test(){

rabbitTemplate.convertAndSend(“hello”,””,”hello world”);

}

2.开发消费者

@Component//默认持久化,非独占

@RabbitListener(queuesToDeclare = @Queue(value=”hello”,durable = false,autoDelete = true))

public class HelloCustomer{

@RabbitHandler

public void receive(String message){

System.out.println(“message = ”+message);

}

}

2.第二种模型整合(work)

1.开发生产者

//注入rabbitTemplate

@Autowired

private RabbitTemplate rabbitTemplate;

@Test

public void test(){

for(int i = 0; i < 10; i++){

rabbitTemplate.convertAndSend(“work”,””,”hello world”);

}

}

2.开发消费者

@Component

public class HelloCustomer{

//默认持久化,非独占

@RabbitListener(queuesToDeclare = @Queue(value=”work”,durable = false,autoDelete = true))

@RabbitHandler

public void receive1(String message){

System.out.println(“work message1 = ”+message);

}

@RabbitListener(queuesToDeclare = @Queue(value=”work”,durable = false,autoDelete = true))

@RabbitHandler

public void receive2(String message){

System.out.println(“work message2 = ”+message);

}

}

说明:默认在Spring AMQP实现中Work这种方式就是公平调度,如果需要实现能者多劳需要额外配置。

3.第三种模型整合(Fanout广播模型)

1.开发生产者

//注入rabbitTemplate

@Autowired

private RabbitTemplate rabbitTemplate;

@Test

public void test(){

rabbitTemplate.convertAndSend(“logs”,””,”这是Fanout的模型发送的消息”);

}

2.开发消费者

@RabbitListener(bindings = {

@QueueBinding(

value = @Queue,//创建临时队列

exchange = @Exchange(value=”logs”,type=”fanout”)//绑定的交换机

)

})

@RabbitHandler

public void receive1(String message){

System.out.println(“message = ”+message);

}

}

4.第四种模型整合(direct路由模型)

1.开发生产者

//注入rabbitTemplate

@Autowired

private RabbitTemplate rabbitTemplate;

@Test

public void testRoute(){

rabbitTemplate.convertAndSend(“directs”,”info”,”发送info的key的路由信息”);

}

2.开发消费者

@RabbitListener(bindings = {

@QueueBinding(

value = @Queue,//创建临时队列

exchange = @Exchange(value=”directs”,type=”direct”)//自定义交换机名称和类型

key = {“info”,”error”,”warn”}

)

})

@RabbitHandler

public void receive1(String message){

System.out.println(“message = ”+message);

}

}

5.第五种模型整合(topic动态路由 订阅模式)

1.开发生产者

//注入rabbitTemplate

@Autowired

private RabbitTemplate rabbitTemplate;

@Test

public void testTopic(){

rabbitTemplate.convertAndSend(“topics”,”user.save”,”user.save路由消息”);

}

2.开发消费者

@RabbitListener(bindings = {

@QueueBinding(

value = @Queue,//创建临时队列

exchange = @Exchange(value=”topics”,type=”topic”)//自定义交换机名称和类型

key = {“user.save”,”user.*”}

)

})

@RabbitHandler

public void receive1(String message){

System.out.println(“message1 = ”+message);

}

@RabbitListener(bindings = {

@QueueBinding(

value = @Queue,//创建临时队列

exchange = @Exchange(value=”topics”,type=”topic”)//自定义交换机名称和类型

key = {“order.*”,”user.*”}

)

})

@RabbitHandler

public void receive2(String message){

System.out.println(“message2 = ”+message);

}

  1. MQ的应用场景

Spring的异步处理不能跨jvm,也就是不支持分布式系统,mq就是支付分布式系统的。

单机结构

我想大家最最最熟悉的就是单机结构,一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上就好了。整个项目所有的服务都由这台服务器提供。这就是单机结构。

那么,单机结构有啥缺点呢?我想缺点是显而易见的,单机的处理能力毕竟是有限的,当你的业务增长到一定程度的时候,单机的硬件资源将无法满足你的业务需求。此时便出现了集群模式,往下接着看。

集群结构

集群模式在程序猿界有各种装逼解释,有的让你根本无法理解,其实就是一个很简单的玩意儿,且听我一一道来。

单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个“集群”。集群中每台服务器就叫做这个集群的一个“节点”,所有节点构成了一个集群。每个节点都提供相同的服务,那么这样系统的处理能力就相当于提升了好几倍(有几个节点就相当于提升了这么多倍)。

但问题是用户的请求究竟由哪个节点来处理呢?最好能够让此时此刻负载较小的节点来处理,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”有个牛逼了名字——负载均衡服务器。

集群结构的好处就是系统扩展非常容易。如果随着你们系统业务的发展,当前的系统又支撑不住了,那么给这个集群再增加节点就行了。但是,当你的业务发展到一定程度的时候,你会发现一个问题——无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用微服务结构了。

分布式结构

先来对前面的知识点做个总结。

从单机结构到集群结构,你的代码基本无需要作任何修改,你要做的仅仅是多部署几台服务器,每台服务器上运行相同的代码就行了。但是,当你要从集群结构演进到微服务结构的时候,之前的那套代码就需要发生较大的改动了。所以对于新系统我们建议,系统设计之初就采用微服务架构,这样后期运维的成本更低。但如果一套老系统需要升级成微服务结构的话,那就得对代码大动干戈了。所以,对于老系统而言,究竟是继续保持集群模式,还是升级成微服务架构,这需要你们的架构师深思熟虑、权衡投入产出比。

OK,下面开始介绍所谓的分布式结构。

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。

举个例子,假设需要开发一个在线商城。按照微服务的思想,我们需要按照功能模块拆分成多个独立的服务,如:用户服务、产品服务、订单服务、后台管理服务、数据分析服务等等。这一个个服务都是一个个独立的项目,可以独立运行。如果服务之间有依赖关系,那么通过RPC方式调用。

这样的好处有很多:

系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。

系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。假设这个商城要搞一次大促,下单量可能会大大提升,因此我们可以针对性地提升订单系统、产品系统的节点数量,而对于后台管理系统、数据分析系统而言,节点数量维持原有水平即可。

服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。

1.异步处理

场景说明:用户注册后,需要发送邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式

  1. 串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。只有一个问题是,邮件、短信并不是必须的,他只是一个通知,而这种做法让客户端等待没有必要等待的东西。

用户----->注册信息写入数据库 ---->发送邮件------>发送短信

响应150ms<-----  50ms 50ms        50ms

  1. 并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。

  1. 消息队列:假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高了处理时间。但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回。消息队列:引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理。

由此可以看出,引入消息队列后,用户的响应时间就等于数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列后处理后,响应时间是串行的3倍,是并行的2倍。

2.应用解耦

场景:双11是购物狂欢节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口。

这种做法有一个缺点:

当库存系统出现故障时,订单就会失败。订单系统和库存系统高耦合,引入消息队列。

  1. 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
  2. 库存系统:订阅下单的消息,获取下单消息,进行库操作。就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失。

3.流量削峰

场景:秒杀活动,一般因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端假如消息队列。

作用

  1. 可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^)
  2. 可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)

  1. 用户的请求,服务器收到之后,先写入消息队列,假如消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面。
  2. 秒杀业务根据消息队列中的请求消息,再做后续处理。
  1. RabbitMQ的集群

1.普通集群(副本集群)

默认情况下:RabbitMQ代理操作所需的所有数据/状态都将跨所有节点复制。这方面的一个例外是消息队列。默认情况下,消息队列位于一个节点上,尽管它们可以从所有节点看到和访问。

1.架构图

2.集群搭建

0.集群规划

node1: 10.15.0.3 mq1 master 主节点

node2: 10.15.0.4 mq2 repl1 副本节点

node3: 10.15.0.5 mq3 repl2 副本节点

1.克隆三台机器主机名和ip映射

vim /etc/hosts加入:

10.15.0.3 mq1

10.15.0.4 mq2

10.15.0.5 mq3

node1: vim /etc/hostname加入: mq1

node2: vim /etc/hostname加入: mq2

Node3: vim /etc/hostname加入: mq3

2.三个机器安装rabbitmq,并同步cookie文件,在node1上执行

scp /var/lib/rabbitmq/.erlang.cookie root@mq2: /var/lib/rabbitmq/

scp /var/lib/rabbitmq/.erlang.cookie root@mq3:/var/lib/rabbitmq/

3.查看cookie是否一致

node1: cat  /var/lib/rabbitmq/.erlang.cookie

node2: cat  /var/lib/rabbitmq/.erlang.cookie

node3: cat  /var/lib/rabbitmq/.erlang.cookie

4.后台启动rabbitmq所有节点执行如下命令,启动成功访问管理界面:

rabbitmq-server -detached

5.在node2和node3执行加入集群命令

1.关闭 rabbitmqctl stop_acp

2.加入集群 rabbitmqctl join_cluster rabbit@mq1

3.启动服务 rabbitmqctl start_app

6.查看集群状态,任意节点执行

rabbitmqctl cluster_status

7.如果出现如下显示,集群搭建成功

8.登录管理界面,展示如下状态

9.测试集群在node1上,创建队列

10.查看node2和node3节点

11.关闭node1节点,执行如下命令,查看node2和node3

执行如下命令:rabbitmqctl stop_app

2.镜像集群

镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。

1.集群架构图

2.配置集群架构

0.策略说明

1.查看当前策略

rabbitmqctl list_policies

2.添加策略

rabbitmq set_policy ha -all ‘^hello’ ‘{“ha-mode”:”all”,”ha-sync-mode”:”automatic”}’

说明:策略正则表达式为’^’表示匹配所有队列名称 ^hello:匹配hello开头队列

3.删除策略

rabbitmqctl clear_policy

4.测试集群

你可能感兴趣的:(mq消息队列,rabbitmq,kafka,java)