初识RabbitMq

一、安装,

因为Rabbitmq是erlang编写的,所以先安装erlang,在安装rabbitmq。
安装成功后可以登录RabbitMQ(http://localhost:15672  本地电脑)。
python操作RabbitMQ 主要用到pika库(不同版本库可能对应某些函数参数不一样)

二、使用场景

有些东东不想保存在数据库却又不得不保存,比如创建订单,创建了想过会儿再给钱,这时候就可以放在队列里面,等付钱了自然就出队列了。点外卖点了几个菜,然后推荐app了,再进去的时候发现点的菜还在。一样。当然也可以保存在redis中。基本例子都是1对1的消息发送和接收,即消息只能发送到指定的queue里。但有些时候你想让你的消息被所有的queue收到(前提已绑定),类似广播的效果,这时候就要用到exchange了(即使1对1消息发送和接受也用exchange,只定义了一个queue就行)

组成部分说明如下:

  • Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。
  • Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
  • Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。
  • Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。
  • Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。

三、工作模式

Work queues、Publish/Subscribe、Routing、Topics、Header、RPC 

exchange(交换机类型有):最后一个是自己定义的direct类型的交换机。

 区别:

Exchange在定义的时候是有类型的,以决定到底是哪些Queue符合条件,可以接收消息

  • fanout: 所有bind到此exchange的queue都可以接收消息
  • direct: 通过routingKey和exchange决定的那个唯一的queue可以接收消息
  • topic: 所有符合routingKey(此时可以是一个表达式)的routingKey所bind的queue可以接收消息(一般使用direct,topic再direct基础上分的更加详细,工作中不推荐使用fanout)
1、Work queues

无需指定交换机,RabbitMQ会通过默认的default AMQP交换机将我们的消息投递到指定的队列
多个消费端消费同一个队列中的消息,队列采用轮询的方式将消息是平均发送给消费者;
特点:
1、一条消息只会被一个消费端接收;
2、队列采用轮询的方式将消息是平均发送给消费者的;
3、消费者在处理完某条消息后,才会收到下一条消息

生产者端:

# 生产者.py
import pika

# 建立与RabbitMQ服务器的连接
connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 声明一个队列,如果队列不存在则创建
channel.queue_declare(queue='hello')

# 发布消息到队列
channel.basic_publish(exchange='',
                      routing_key='hello',
                      body='Hello World!')

print(" [x] Sent 'Hello World!'")

# 关闭连接
connection.close()

消费者端:

# 消费者.py
import pika

def callback(ch, method, properties, body):
    print(f" [x] Received {body}")

# 建立与RabbitMQ服务器的连接
connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

# 声明一个队列,如果队列不存在则创建
channel.queue_declare(queue='hello')

# 设置当有消息时,调用callback函数
channel.basic_consume(queue='hello',
                      on_message_callback=callback,
                      auto_ack=True)

print(' [*] Waiting for messages. To exit press CTRL+C')
channel.start_consuming()
2、Publish/subscribe 模式

这种模式又称为发布订阅模式,相对于Work queues模式,该模式多了一个fanout类型的交换机,生产端先把消息发送到交换机,再由交换机把消息发送到绑定的队列中,每个绑定的队列都能收到由生产端发送的消息。
发布订阅模式:
1、每个消费者监听自己的队列;
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

  • publish/subscribe与work queues有什么区别。
  • 1)work queues不用定义交换机,而publish/subscribe需要定义交换机。
  • 2)publish/subscribe的生产方是面向交换机发送消息,work queues的生产方是面向队列发送消息(底层使用默认交换机)。
  • 3)publish/subscribe需要设置队列和交换机的绑定,work queues不需要设置,实质上work queues会将队列绑定到默认的交换机 。
  • 相同点:
  • 所以两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息。
  • 工作用publish/subscribe还是work queues。
  • 建议使用 publish/subscribe,发布订阅模式比工作队列模式更强大,并且发布订阅模式可以指定自己专用的交换机。
3、Routing 路由模式

Routing 模式又称路由模式,该种模式除了要绑定交换机Direct交换机外,发消息的时候还要制定routing key,即路由key,队列通过通道绑定交换机的时候,需要指定自己的routing key,这样,生产端发送消息的时候也会指定routing key,通过routing key就可以把相应的消息发送到绑定相应routing key的队列中去。
路由模式:
1、每个消费者监听自己的队列,并且设置routingkey;
2、生产者将消息发给交换机,由交换机根据routingkey来转发消息到指定的队列;
3、Routing模式要求队列在绑定交换机时要指定routingkey,消息会转发到符合routingkey的队列。

4、Topics 模式

统配符模式和Routing 路由模式最大的区别就是,Topics 模式发送消息和消费消息的时候是通过通配符去进行匹配的,交换机是Topic类型的交换机。
路由模式:
1、每个消费者监听自己的队列,并且设置带统配符的routingkey
2、生产者将消息发给broker,由交换机根据routingkey来转发消息到指定的队列

Topic模式更多加强大,它可以实现Routing、publish/subscirbe模式的功能。但是要会匹配。

5、Header 模式、RPC模式

header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列。RPC即客户端远程调用服务端的方法 ,使用MQ可以实现RPC的异步调用,基于Direct交换机实现。

目前常使用的是三:

代码:这里得pika为1.1.0,不是最新的,不同版本有的函数参数可能有差异
生产者端:

import pika
import json
# 连上rabbitMQ
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()  # 生成管道,在管道里跑不同的队列
# 声明queue,durable=True的作用只是把队列持久化。离消息持久化还差一步:发送端发送消息时,加上properties
# 如果有声明queue就会向此queue发送消息,如果没有声明,RabbitMQ会自己创建queue然后发消息,类似广播
channel.queue_declare(queue='edwin1', durable=True)
# 创建类型为direct的交换机
channel.exchange_declare(exchange='direct_edwin_test', exchange_type='direct', durable=True)
# n RabbitMQ a message can never be sent directly to the queue,it always needs to go through an exchange.
# 向routing_key(routing_key一般即为队列名)队列里发数据,先把数据发给exchange交换器,exchange再发给相应队列
# 只有绑定了exchange管理的队列才会发。Delivery mode: 是否持久化,1 - Non-persistent,2 - Persistent
channel.basic_publish(
    exchange='direct_edwin_test',
    routing_key='edwin1',
    properties=pika.BasicProperties(delivery_mode=2),
    body=json.dumps([{"value": True, "data": "edwin liu"}])
)
print("Sent 'Hello Edwin Edwin!'")
connection.close()


消费者端:

import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# 两种方式声明队列,指定与不指定queue名
# 指定queue名字,queue名根据生产者有的命名
result = channel.queue_declare(queue='edwin1', durable=True)
# 不指定queue名字(为了收广播),rabbit会随机分配一个queue名字, exclusive=True会在使用此queue的消费者断开后,自动将queue删除
# result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
# 把声明的queue绑定到交换器exchange上,生产者routing_key这里命名的queue名,所以routing_key为默认None。否则要指定routing_key
channel.queue_bind(exchange='direct_edwin_test', queue=queue_name)
def callback(ch, method, properties, body):
    print("-->ch", ch)
    print("-->method", method)
    print("-->properties", properties)
    print("Received %r" % body)
    # 告诉生产者,消息处理完成,保证消息被消费后,消费端发送一个ack,然后服务端从队列删除该消息.
    ch.basic_ack(delivery_tag=method.delivery_tag)
# 类似权重,按能力分发,如果当前消息还没处理完,就不在给你发,避免堆积消息
channel.basic_qos(prefetch_count=1)
channel.basic_consume('edwin1', callback)
print('Waiting for messages')
channel.start_consuming()
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
parser = argparse.ArgumentParser(description='select the config file to run')
parser.add_argument('confile')
args = parser.parse_args()
os.environ['DJANGO_SETTINGS_MODULE'] = args.confile
traceback.print_exc()
django.setup()
6、消息属性Properties

发送消息可以为消息指定一些参数
Delivery mode: 是否持久化,1 - Non-persistent,2 - Persistent
Headers:Headers can have any name. Only long string headers can be set here.
Properties: You can set other message properties here (delivery mode and headers are pulled out as the most common cases). Invalid properties will be ignored. Valid properties are:
content_type : 消息内容的类型
content_encoding: 消息内容的编码格式
priority: 消息的优先级
correlation_id:关联id
reply_to: 用于指定回复的队列的名称
expiration: 消息的失效时间
message_id: 消息id
timestamp:消息的时间戳
type: 类型
user_id: 用户id
app_id: 应用程序id
cluster_id: 集群id 

 四、消息丢失的场景

  • 第一种:生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。

  • 第二种:RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了

  • 第三种:消费端弄丢了数据。刚消费到,还没处理,结果进程挂了,比如重启了。

五、消息丢失的解决方案

1.针对生产者
方案1 :开启RabbitMQ事务

可以选择用 RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务channel.txSelect,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务channel.txRollback,然后重试发送消息;如果收到了消息,那么可以提交事务channel.txCommit。

缺点:

RabbitMQ 事务机制是同步的,你提交一个事务之后会阻塞在那儿,采用这种方式基本上吞吐量会下来,因为太耗性能。

方案2:使用confirm机制

事务机制和 confirm 机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是 confirm 机制是异步的

在生产者开启了confirm模式之后,每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq之中,rabbitmq会给你回传一个ack消息,告诉你这个消息发送OK了;如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息失败了,你可以进行重试。而且你可以结合这个机制知道自己在内存里维护每个消息的id,如果超过一定时间还没接收到这个消息的回调,那么你可以进行重发。

2.针对RabbitMQ

要保证rabbitMQ不丢失消息,那么就需要开启rabbitMQ的持久化机制,即把消息持久化到硬盘上,这样即使rabbitMQ挂掉在重启后仍然可以从硬盘读取消息;

3.针对消费者

ACK确认机制:

多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?

使用rabbitmq提供的ack机制,服务端首先关闭rabbitmq的自动ack,然后每次在确保处理完这个消息之后,在代码里手动调用ack。这样就可以避免消息还没有处理完就ack。才把消息从内存删除。

这样就解决了,即使一个消费者出了问题,但不会同步消息给服务端,会有其他的消费端去消费,保证了消息不丢的case。

总结

如果需要保证消息在整条链路中不丢失,那就需要生产端、mq自身与消费端共同去保障。

  1. 消息确认机制问题: RabbitMQ支持消息确认机制,即生产者发送消息后,需要等待消费者返回确认,以确保消息已经被正确接收和处理。如果生产者没有正确处理确认机制,消息可能会在传输过程中丢失。
    解决方案:确保生产者正确处理消息确认机制,可以使用事务或者确认模式来确保消息在正确地被消费者接收后再进行删除。
  2. 持久化设置问题: 默认情况下,RabbitMQ中的队列和消息都是非持久化的,即在RabbitMQ服务器重启后,队列和消息会丢失。
    解决方案:使用持久化设置,将队列和消息设置为持久化,这样即使RabbitMQ服务器重启,数据也能得到保留。在声明队列和发送消息时,需要设置相应的持久化属性。
  3. 队列满了: 如果队列已经满了,新的消息可能会被丢弃。
    解决方案:增加队列的容量或者使用合适的策略进行队列消息的处理,例如使用死信队列来处理无法投递的消息。
  4. 消费者异常终止: 如果消费者在处理消息的过程中发生异常终止,消息可能会丢失。
    解决方案:为消费者添加异常处理机制,确保消费者能够正确处理异常情况,并在处理失败时,将消息重新放回队列或发送到死信队列。
  5. 网络问题: 如果RabbitMQ服务器与生产者或消费者之间的网络连接出现问题,消息可能无法正确传递。
    解决方案:确保网络连接稳定,可以使用心跳机制来检测连接状态,并及时重连。
  6. 其他配置问题: RabbitMQ有很多配置选项,不正确的配置可能导致消息丢失。
    解决方案:仔细检查RabbitMQ的配置,确保配置符合预期,并遵循最佳实践。

为了更好地应对消息丢失的情况,建议在设计和使用RabbitMQ时,充分考虑可靠性和容错性。这包括正确处理消息确认机制、使用持久化设置、合理设置队列容量和消息处理策略,以及编写健壮的消费者代码,等等。此外,监控RabbitMQ的运行状态,定期检查日志,也能帮助及早发现并解决潜在的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值