消息队列,RabbitMQ、Kafka、RocketMQ

1、消息列队概述

1.1消息队列MQ

MQ全称为Messages Queue ,消息队列是应用程序和应用程序之间的通信方法
为什么使用MQ?
在项目中,可将一些无需耗时的操作提取出来,进行异步操作,而这种异步处理方式大大的姐生了服务器的请求响应时间,从而提高了系统的吞吐量。

开发中消息列队通常有以下应用场景

1.任务异步处理
	将不需要同步处理的斌且好使长的操作由消息队列通知消息接收方进行异步处理。提高了应用的响应时间
2.应用程序解耦合
	MQ相当于一个中介,生产放通过MQ与消费方交互,它将应用程序进行解耦合

1.2AMQP和JMS

MQ是消息通信模型;实现MQ的大致有两种主流方式:AMQP、JMS

1.2.1AMQP

六种消息模式

AMQP是高级消息队列协议,是一个进程间传递异步消息的网络协议,更准确的说是一种binary wire-level protocol(链接协议)。这是和JMS的本质区别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式

1.2.2JMS

两种消息模式

JMS即JAVA消息服务应用程序接口,是一个java平台中关于面向消息中间件的API,用于在两个应用程序之间,或分布系统中发送消息,进行异步通信

1.2.3AMOP 与 JMS 区别

· JMS 是定义了统的接口,来对消息摄作进行统一,AMQP是通过规定协议来统一统一数据交互的格式 
· JMS 限定了必须使用 Java 语宫: AMQP只是协议,不规定实现方式.因此是跨语宫的. 
· JMS 规定了两种消息模式(点对点模式,订阅模式):而 AMQP的消息模式更加丰富

1.3消息队列产品

市场上常见的息队列有如下:
目前市面上成熟主流的MQ有Kafka、RocketMQ、RabbitMQ,我们这里对每款款MQ做一个简介绍。

1.3.1 Kafka

所有开源的MQ,吞吐量最强的,天然支持集群,消息堆积能力非常强悍
Apache下的一个子项目,使用scala语言实现的一个高性能分布式Publish/Subscribe消息队列系统
1.快速持久化:通过磁盘顺序读写与零拷贝机制,可以在0(1)的系统开销下进行消息持久化
2.高吞吐:在一台普通的服务器上即可以达到10W/s的吞吐速率。
3.高堆积:支持topoc下消费者较长时间离线,消息堆积量大
4.完全的分布式系统:Brocker、Producer、Consumer都原生自动支持分布式,依赖zookeeper自动实现复杂均衡
5.支持Hadoop数据并行加载:对于像Hadoop的一样的日志数据和离线分系统,但又要求实时处理的限制,这是一个可行的解决方案

1.3.2 RocketMQ

RocketMQ国产阿里的,经过双十一的检验。也非常强悍,基于java语言写的
RocketMQ的前身是Metaq,当Metaq3.0发布时,产品名称改为RocketMQ.RocketMQ是一款分布式、队列模型的消息中间件
    1.能够保证严格的消息顺序
    2.提供丰富的消息拉取模式
    3.高效的订阅水平扩展能力
    4.实时的消息订阅机制
    5.支持事务消息
    6.亿级消息堆积能力

1.3.3RabbitMQ

使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP,SMTP,STOMP,正是如此使它变得非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由,负载均衡、数据持久化都有很好的支持。多用于进行企业级的ESB整合。

1.4RabbitMQ

RabbitMQ是erlang语言开发,基于AMQP(Advanced Message Queue高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的涌信方法,消息队列在分布式系统开发中应用非常广乏
RabbitMQ官方地址:http://www.rabbitmq.com/
RabbitMQ提供了6种模式:
	1.简单模式
	2.work工作模式
	3.publish/Subscribe发布与订阅模式
	4.Routing路由模式
	5.Topics主题模式
	6.RPC远调用模式(远程调用,不太算MQ;不作介绍)

简单模式

一个生产者,一个消费者

工作模式

一个生产者、2个消费者。
一个消息只能被一个消费者获取。

订阅模式

解读:
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机。交换机分别都发送给队列
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的
注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费

交换机(图中X):只负责转发消息,不具备储备消息的能力。一方面接收生产者P的消息,另一方面处理消息(发送给队列,或者特定队列,或者丢弃),取决于交换机的类别
交换机的类别:
	Fanout:广播,将消息交给所有绑定到交换机的队列
	Direct:定向,把消息交给符合指定的队列中
	Topic:通配符,把消息交给符合routing pattern(路由模式)的队列

路由模式

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

图解:
P:生产者
X:交换机
C1:消费者,指定所在队列需要routing key为error的消息
C2:消费者,指定所在队列需要routing key为info,erroe,warning的消息

通配符模式(主题模式)

可以根据Routingkey把消息路由到不同的队列。只不过通配符类型交换机可以让队列在绑定routing key的时候使用通配符
routingkey一般都是由一个或多个单词组成,多个单词之间以‘.’点分割,例如:item.add.hello

通配符规则:
	#:匹配一个或多个词
	*:匹配一个词
	
举例:
	item.#:能够匹配item.index.add或者item.index
	itrm.*:只能匹配item.index或者item.xxx

2.安装及配置RabbitMQ

用户角色

1、超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。
4、普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、其他
无法登陆管理控制台,通常就是普通的生产者和消费者。

3.python使用RabbitMQ

轮询消费模式

此模式下,发送队列的一方把消息存入mq的指定队列后,若有消费者端联入相应队列,即会获取到消息,并且队列中的消息会被消费掉。

若有多个消费端同时连接着队列,则会已轮询的方式将队列中的消息消费掉。

接下来是代码实例:

producer生产者

# !/usr/bin/env python
import pika
credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

# 声明queue
channel.queue_declare(queue='balance')

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='balance',
                      body='Hello World!')
print(" [x] Sent 'Hello World!'")
connection.close()

发送过队列后,可在MQ服务器中查看队列状态

[root@localhost ~]# rabbitmqctl list_queues
Listing queues ...
hello    1

consumer消费者

# _*_coding:utf-8_*_
__author__ = 'Alex Li'
import pika

credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='balance')


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)


channel.basic_consume(callback,
                      queue='balance',
                      no_ack=True)

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

接收队列后,查看一下队列状态

[root@localhost ~]#  rabbitmqctl list_queues
Listing queues ...
hello    0

队列持久化

当rabbitMQ意外宕机时,可能会有持久化保存队列的需求(队列中的消息不消失)。

producer

# Cheng
# !/usr/bin/env python
import pika

credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

# 声明queue
channel.queue_declare(queue='durable',durable=True)

# n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
channel.basic_publish(exchange='',
                      routing_key='durable',
                      body='Hello cheng!',
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                      )
                      )
print(" [x] Sent 'Hello cheng!'")
connection.close()

执行后查看队列,记下队列名字与队列中所含消息的数量

[root@localhost ~]# rabbitmqctl list_queues
Listing queues ...
durable    1
#重启rabbitmq
[root@localhost ~]# systemctl restart rabbitmq-server
#重启完毕后再次查看
[root@localhost ~]# rabbitmqctl list_queues
Listing queues ...
durable   #队列以及消息并未消失

执行消费者代码

cunsumer

# Cheng
# _*_coding:utf-8_*_
__author__ = 'Alex Li'
import pika

credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

# You may ask why we declare the queue again ‒ we have already declared it in our previous code.
# We could avoid that if we were sure that the queue already exists. For example if send.py program
# was run before. But we're not yet sure which program to run first. In such cases it's a good
# practice to repeat declaring the queue in both programs.
channel.queue_declare(queue='durable',durable=True)


def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    ch.basic_ack(delivery_tag=method.delivery_tag)

channel.basic_consume(callback,
                      queue='durable',
                      #no_ack=True
                      )

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()

可正确接收到信息。

再次查看队列的情况。

[root@localhost ~]# rabbitmqctl list_queues
Listing queues ...
durable    0

广播模式

当producer发送消息到队列后,所有的consumer都会收到消息,需要注意的是,此模式下producer与concerned之间的关系类似与广播电台与收音机,如果广播后收音机没有接受到,那么消息就会丢失。

建议先执行concerned

concerned

# _*_coding:utf-8_*_
__author__ = 'Alex Li'
import pika

credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

channel.exchange_declare(exchange='Clogs',
                         type='fanout')

result = channel.queue_declare(exclusive=True)  # 不指定queue名字,rabbit会随机分配一个名字,exclusive=True会在使用此queue的消费者断开后,自动将queue删除
queue_name = result.method.queue

channel.queue_bind(exchange='Clogs',
                   queue=queue_name)

print(' [*] Waiting for logs. To exit press CTRL+C')


def callback(ch, method, properties, body):
    print(" [x] %r" % body)


channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)

channel.start_consuming()

producer

import pika
import sys

credentials = pika.PlainCredentials('admin','123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
    '192.168.56.19',5672,'/',credentials))
channel = connection.channel()

channel.exchange_declare(exchange='Clogs',
                         type='fanout')

message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='Clogs',
                      routing_key='',
                      body=message)
print(" [x] Sent %r" % message)
connection.close()
posted @ 2020-05-07 09:20  Jeff的技术栈  阅读(1282)  评论(1编辑  收藏  举报
回顶部