使用 RabbitMQ
这几年很火的一个概念微服务,在一个大型业务系统架构中,会被拆分成很多小的业务系统,这些业务系统之间如何建立通信,熟知的 HTTP、RPC 可以实现不同系统、不同语言之间的通信,除了这些往往还会使用消息队列(RabbitMQ、ActiveMQ、Kafafa 等)将这些系统链接起来,达到各系统间的解耦。通常关注下游执行结果的用RPC,不关注下游执行结果的用MQ。
RabbitMQ 应用场景
- 同步转异步
在项目中对于一些没必要同步处理的,可以借助 MQ 进行异步处理,例如,我们的短信发送就可以通过 MQ 队列来做。
- 应用解耦
例如商城业务场景中,订单系统与库存系统,下单的同步可能也要去减少库存,将原本耦合在一块的逻辑可以通过消息队列进行,订单系统发布消息,库存系统订阅消息,这样的好处是一般库存系统出现问题也不会影响到订单系统。
MQ能够做到上下游物理上和逻辑上都解耦:
物理上解耦,增加MQ之后,上游互不知道彼此的存在,不会建立物理连接了,大家都只与MQ建立物理连接。
逻辑上解耦,事件发布方甚至不用知道哪些下游订阅了这个消息,新增消息的订阅方只需要连接MQ就行了,不需要上游关注。
3. 流量削峰
流量削峰在一些营销活动、秒杀活动场景中应用还是比较广泛的,如果短时间流量过大,可以通过设置阀值丢弃掉一部分消息或者根据服务的承受能力设置处理消息限制,也就是限流,之后也会单独进行讲解。
MQ 的空间与时间解耦
从空间上来看,消息的生产者无需提前知道消费者的存在,反之消费者亦是,两者之间得到了解耦,不会强依赖,从而实现空间上的解耦。
从时间上来看,消息的生产者只负责生产数据将数据放入队列,之后无需关心消费者什么时间去消费,消费则可以根据自己的业务需要来选择实时消费还是延迟消费,两者都拥有了自己的生命周期,从而实现了时间上的解耦。
主流消息中间件一览
一,Ubuntu 18.04安装RabbitMQ
由于RabbitMQ需要erlang语言的支持,在安装RabbitMQ之前需要安装erlang
首先配置源
echo "deb https://dl.bintray.com/rabbitmq/debian trusty main" | sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list
echo "deb http://packages.erlang-solutions.com/ubuntu trusty contrib" | sudo tee -a /etc/apt/sources.list.d/erlang_solutions.list
导入对应的键
wget -c -O- http://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | sudo apt-key add -
wget -O- https://dl.bintray.com/rabbitmq/Keys/rabbitmq-release-signing-key.asc |sudo apt-key add -
开始安装erlang和RabbitMQ
sudo apt-get update
sudo apt-get install erlang-nox
sudo apt-get install rabbitmq-server
安装完之后RabbitMQ便已经自动启动了,可以使用如下的命令对RabbitMQ进行操作:
sudo service rabbitmq-server start # 启动
sudo service rabbitmq-server stop # 停止
sudo service rabbitmq-server restart # 重启
sudo service rabbitmq-server status # 查看当前状态
以下列举一些在终端常用的操作命令
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:清除队列里消息
配置RabbitMQ
添加管理员用户,密码设置为admin。
sudo rabbitmqctl add_user admin admin
授予权限
sudo rabbitmqctl set_user_tags admin administrator
奖励虚拟主机中所有资源的配置,写,读权限管理其中的资源
sudo rabbitmqctl set_permissions -p / admin '.*' '.*' '.*'
管理RabbitMQ
RabbitMQ提供了一个Web管理工具(rabbitmq_management),方便在浏览器端管理
sudo rabbitmq-plugins enable rabbitmq_management
之后在浏览器访问[http:// server-ip:15672 /],账号和密码都是刚才设置的admin
生产者步骤
创建链接工厂
通过链接工厂创建链接
通过链接创建通道(channel)
通过 channel 发送数据
关闭链接
消费者步骤
创建链接工厂
通过链接工厂创建链接
通过链接创建通道(channel)
声明一个队列
创建消费者
设置 channel
Node.js 版本
amqplib 客户端
Github: https://github.com/squaremo/amqp.node
配置
OPTIONS: {
protocol: 'amqp',
hostname: '127.0.0.1', // 连接地址
port: 5672,
username: 'txapi',
password: '123456',
locale: 'en_US',
frameMax: 0,
heartbeat: 0,
vhost: 'txapi',
},
EXCHANGE_TYPE: {
topic: 'direct',
ex: 'tx',
},
构建消费者
class Consumer {
constructor(key) {
this.q = key;
this.key =key;
this.ex = RABBITMQ_CONFIG.EXCHANGE_TYPE.ex;
}
async receive(callback) {
/*
1. 建立连接
2. 创建信道
3. 声明队列
4. 声明交换机
5. 队列绑定交换机
6. 接收消息
*/
try {
const conn = await mqServer.connect(OPTIONS);
const ch = await conn.createChannel();
process.once('SIGINT', function () {
console.log('意外退出');
ch.close();
conn.close();
});
// durable 持久化
let ok = ch.assertExchange(this.ex, EXCHANGE_TYPE.topic, {
durable: true,
});
ok = ok.then(() => {
// exclusive 独占
return ch.assertQueue(this.q, { exclusive: false });
});
ok = ok.then((qok) => {
const { queue } = qok;
return ch.bindQueue(queue, this.ex, this.key).then(() => queue);
});
ok = ok.then(function (queue) {
return ch.consume(queue, callback, { noAck: true });
});
return ok.then(function () {
console.log(' [*] Waiting for logs. To exit press CTRL+C');
});
} catch (error) {
console.error(error);
process.exit(1);
}
}
}
构建生产者
class Producer {
constructor(mqKey) {
this.ex = RABBITMQ_CONFIG.EXCHANGE_TYPE.ex;
this.key = mqKey;
}
async send(msg) {
/*
1. 建立连接
2. 创建信道
3. 声明队列
4. 声明交换机
5. 队列绑定交换机
5. 发送消息 --- 向所有消息队列发送消息
1. 关闭通道
*/
try {
const conn = await mqServer.connect(OPTIONS);
// 进程关闭的时候,关闭连接通道
process.once('SIGINT', () => {
console.log('意外退出');
conn.close();
});
const ch = await conn.createChannel();
let ok = ch.assertExchange(this.ex, EXCHANGE_TYPE.topic, {
durable: true,
});
return ok.then(() => {
// 向交换机指定路由发送信息
console.log('发送消息到消息队列');
var strMsg = JSON.stringify(msg);
ch.publish(this.ex, this.key, Buffer.from(strMsg));
return ch.close();
});
} catch (error) {
console.error(error);
}
}
}