Live2D

消息队列

什么是消息队列

把服务器比喻成一个人

当这个人的亲人,朋友,工作(一大批客户端)【生产者】同时有事情(网络请求)找他, 这个人承受不了了 就容易崩溃

那现在可以把消息队列比喻成一个管道  这个管道让消息按照队列数据结构(先进先出)

然后这个人再把这些消息传给可以解决的人【消费者】

 

消息队列的优势

应用解耦

消息队列可以使消费者和生产者直接互不干涉,互不影响,只需要把消息发送到队列即可,而且可独立的扩展或修改两边的处理过程,只要能确保它们遵守同样的接口约定,可以生产者用Node.js实现,消费者用python实现。

灵活性和峰值处理能力

当客户端访问量突然剧增,对服务器的访问已经超过服务所能处理的最大峰值,甚至导致服务器超时负载崩溃,使用消息队列可以解决这个问题,可以通过控制消费者的处理速度生产者可进入消息队列的数量等来避免峰值问题

排序保证

消息队列可以控制数据处理的顺序,因为消息队列本身使用的是队列这个数据结构,FIFO(先进选出),在一些场景数据处理的顺序很重要,比如商品下单顺序等。

异步通信

消息队列中的有些消息,并不需要立即处理,消息队列提供了异步处理机制,可以把消息放在队列中并不立即处理,需要的时候处理,或者异步慢慢处理,一些不重要的发送短信和邮箱功能可以使用。

可扩展性

前面提到了消息队列可以做到解耦,如果我们想增强消息入队和出队的处理频率,很简单,并不需要改变代码中任何内容,可以直接对消息队列修改一些配置即可,比如我们想限制每次发送给消费者的消息条数等。

 

主流的消息队列

Kafka:高吞吐量的分布式发布订阅消息系统,支持单机每秒百万并发。用于日志(消息)系统

RocketMQ:低延迟、高可靠、可伸缩、易于使用的消息中间件,思路起源于 Kafka。最大的问题商业版收费,有些功能不开放。

RabbitMQ:基于 AMQP 协议的开源消息队列系统。能保证消息的可靠性、稳定性、安全性。高并发的特性,毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的erlang 语言,天生的分布式优势。

 

 

rabbitmq安装

mac:

 brew install rabbitmq

/usr/local/Cellar/rabbitmq/3.7.8

sbin/rabbitmq-server

浏览器输入 http://localhost:15672/#/ 默认用户名密码 guest

 

通信默认端口号 15672:管理控制台默认端口号 25672:集群通信端口号 注意: 阿里云 ECS 服务器如果出现 RabbitMQ 安装成功,外网不能访问是因为安全组的问题没有开放端口

 

 

  • whereis rabbitmq:查看 rabbitmq 安装位置
  • rabbitmqctl start_app:启动应用
  • whereis erlang:查看erlang安装位置
  • rabbitmqctl start_app:启动应用
  • rabbitmqctl stop_app:关闭应用
  • rabbitmqctl status:节点状态
  • rabbitmqctl add_user username password:添加用户
  • rabbitmqctl list_users:列出所有用户
  • rabbitmqctl delete_user username:删除用户
  • rabbitmqctl add_vhost vhostpath:创建虚拟主机
  • rabbitmqctl list_vhosts:列出所有虚拟主机
  • rabbitmqctl list_queues:查看所有队列
  • rabbitmqctl -p vhostpath purge_queue blue:清除队列里消息

以上终端所有命令,需要进入到rabbitmqctl的sbin目录下执行rabbitmqctl命令才有用,否则会报错

 

  • 生产者 :生产消息的
  • 消费者 :接收消息的
  • 通道 channel:建立连接后,会获取一个 channel 通道
  • exchange :交换机,消息需要先发送到 exchange 交换机,也可以说是第一步存储消息的地方(交换机会有很多类型,后面会详细说)。
  • 消息队列 : 到达消费者前一刻存储消息的地方,exchange 交换机会把消息传递到此
  • ack回执:收到消息后确认消息已经消费的应答

npm install amqplib

 

生产者: product.js

const amqp =require('amqplib');

async function product(params) {
// 1. 创建链接对象
const connection = await amqp.connect('amqp://localhost:5672');
// 2. 获取通道
const channel = await connection.createChannel();
// 3. 声明参数
const routingKey = 'helloKoalaQueue';
const msg = 'hello koala';
for (let i=0; i<10000; i++) {
// 4. 发送消息
await channel.publish('', routingKey, Buffer.from(`${msg} 第${i}条消息`));
}
// 5. 关闭通道
await channel.close();
// 6. 关闭连接
await connect.close();
}
product();

消费者代码 consumer.js

// 构建消费者
const amqp = require('amqplib');

async function consumer() {
// 1. 创建链接对象
const connection = await amqp.connect('amqp://localhost:5672');
// 2. 获取通道
const channel = await connection.createChannel();
// 3. 声明参数
const queueName = 'helloKoalaQueue';
// 4. 声明队列,交换机默认为 AMQP default
await channel.assertQueue(queueName);
// 5. 消费
await channel.consume(queueName, msg => {
console.log('Consumer:', msg.content.toString());
channel.ack(msg);
});
}
consumer();

 

生产者发消息的时候必须指定一个 exchange,否则消息无法直接到达消息队列,Exchange将消息路由到一个或多个Queue中(或者丢弃)

交换机的种类

常用的四种类型

  • fanout: Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要设置路由键。

  • direct:direct 把消息路由到那些 binding key与 routing key 完全匹配的 Queue中。

  • topic:生产者指定 RoutingKey 消息根据消费端指定的队列通过模糊匹配的方式进行相应转发,两种通配符模式: #:可匹配一个或多个关键字 *:只能匹配一个关键字

  • headers:和主题交换机有点相似,但是不同于主题交换机的路由是基于路由键,头交换机的路由值基于消息的 header 数据。 主题交换机路由键只有是字符串,而头交换机可以是整型和哈希值 header Exchange 类型用的比较少,可以自行 google 了解。

不管是哪一种类型的交换机,都有一个绑定binding的操作,只不过根据不同的交换机类型有不同的路由绑定策略。

fanout: 所有消息都会路由到两个Queue中,是两个消费者都可以收到全部的完全相同的消息吗? 答案是的,两个消费者收到的队列消息正常应该是完全相同的。这种类型常用于广播类型的需求,或者也可以消费者1记录日志 ,消费者2打印日志

生产者:

const amqp = require('amqplib');

async function producer() {
// 创建链接对象
const connection = await amqp.connect('amqp://localhost:5672');
// 获取通道
const channel = await connection.createChannel();
// 声明参数
const exchangeName = 'fanout_koala_exchange';
const routingKey = '';
const msg = 'hello koala';
// 交换机
await channel.assertExchange(exchangeName, 'fanout', {
durable: true,
});
// 发送消息
await channel.publish(exchangeName, routingKey, Buffer.from(msg));
// 关闭链接
await channel.close();
await connection.close();
}
producer();

消费者:

const amqp = require('amqplib');

async function consumer() {
// 创建链接对象
const connection = await amqp.connect('amqp://localhost:5672');
// 获取通道
const channel = await connection.createChannel();
// 声明参数
const exchangeName = 'fanout_koala_exchange';
const queueName = 'fanout_kaola_queue';
const routingKey = '';
// 声明一个交换机
await channel.assertExchange(exchangeName, 'fanout', { durable: true });
// 声明一个队列
await channel.assertQueue(queueName);
// 绑定关系(队列、交换机、路由键)
await channel.bindQueue(queueName, exchangeName, routingKey);
// 消费
await channel.consume(queueName, msg => {
console.log('Consumer:', msg.content.toString());
channel.ack(msg);
});
console.log('消费端启动成功!');
}
consumer();

 

posted @ 2019-12-12 15:55  Candice&Gladys  阅读(235)  评论(0编辑  收藏  举报