RabbitMQ笔记

5 篇文章 0 订阅

##目录

  1. 简单实现
  2. 消息调度
    1. 循环调度
    2. 公平调度
  3. 消息确认
  4. 消息持久化
  5. 交换机 - 消息多路分发
    1. 交换机简介
    2. 匿名交换机
    3. 扇形交换机
    4. 直连交换机

##简单实现 使用python语言进行开发,先下载pika库
//send.py
import pika
connection = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connection.channel()

channel.basic_publish(
	exchange='',
	routing_key='hello',
	body='Hello RabbitMQ'
)

print("send successful")
connection.close()
//recive

import pika
conneciton = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connetion.channel()
channel.queue_declare(queue="hello")

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

channel.basic_consume(
	queue='hello',
	auto_ack=True,
	on_message_callback=callback
)

print("all down")
#开始监听
channel.start_consuming()

##消息调度 ####公平调度 当开启多个recive 端时候,每次发消息,接受的端总是不一样,基本上是平均的 A 一次 B一次。这种发送消息的方式叫做 轮询

####公平调度
这个调度中有个问题,就是轮询的话,如果在奇数消息序列比较繁忙的话,就会出现不公平的现象。解决类似的问题就需要公平调度来处理。
basic_qos来解决,
设置 perfetch_count = 1
这是告诉RabbitMQ,同一时刻,不要发超过一条消息给工作者(意思是说之前都是在工作端没有恢复ack前就已经预备好队列了吧)

//send.py
import pika
connection = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connection.channel()
channel.queue_basic(queue="hello",durable=True)
channel.basic_publish(
	exchange='',
	routing_key='hello',
	body='Hello RabbitMQ',
	#add code
	properties=pika.BasicProperties(
		delivery_mode = 2,
	)
)
#add code
channel.basic_qos(prefetch_count=1)
#add end
print("send successful")

connection.close()

注意:如果所有的工作者都处理繁忙状态,你的队列就会被填满。你需要留意这个问题,要么添加更多的工作者(workers),要么使用其他策略。
这样大家吼工作队列就可以使用了。而且RabbitMQ重启之后仍然可以使用!

##消息确认 因为是轮询工作,如果消息发出后,某个工作端,在处理文件的时候挂掉怎么办?当然是在工作端处理完消息后,给发送端一个反馈。 这个就是RabbatMQ消息确认机制。代码能看出ack这个在TCP协议里见到的东西了。

不过和TCP协议不同,这里的消息是没有超时的概念,当工作端和RabbatMQ断开的时候,RabbitMQ就会重新发送消息。这个时候发送时间长的端就不会出现问题了。但是没有auto_ack=True,就意味着RabbatMQ不知道客户端是否接受到消息。我们去掉这句话试试。

把上段代码的 auto_ack=True 去掉后,当A客户端掉线,那么A所有接收的消息都会被B再次接受,因为发送端不知道A是否处理完毕,也就把所有消息默认为没有处理,所以把消息全部给B了。

如果人工相应的

//recive

import pika
conneciton = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connetion.channel()
channel.queue_declare(queue="hello")

def callback(ch,method,properties,body)
	print("[x] recived %r" % body)
	time.sleep(5)
	ch.basic_ack(delivery_tag=method.delivery_tag)
channel.basic_consume(
	queue='hello',
	#auto_ack=True,
	on_message_callback=callback
)

print("all down")
#开始监听
channel.start_consuming()

此时只返回最后一个,因为每次处理完问题,都及时返回了。
basic_ack告知RabbitMQ已经收到并处理了消息,然后RabbitMQ会删除这条消息。
如果一条消息不能够释放,就会被称之为死信。RabbitMQ就会占用越来越多的内存。
这句是查看未发出消息的方法
sudo rabbitmqctl list_queues name messages_ready messages_unacknowledged
结果是
hello 3 0
明显上下一一对应,应该和MySQL select类似。

##消息持久化 一切有关服务器内存的重要内容当然都要持久化( durable) 在队列声明的时候加个参数 `channel.queue_declare(queue="task_queue",durable=True);` 发表里加上属性 `delivery_mode = 2`
//send.py
import pika
connection = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connection.channel()
channel.queue_basic(queue="hello",durable=True)
channel.basic_publish(
	exchange='',
	routing_key='hello',
	body='Hello RabbitMQ',
	#add code
	properties=pika.BasicProperties(
		delivery_mode = 2,
	)
)

print("send successful")

connection.close()

#####注意使用
将消息设为持久化并不能完全保证不会丢失。以上代码只是告诉了 RabbitMq 要把消息存到硬盘,但从 RabbitMq 收到消息到保存之间还是有一个很小的间隔时间。因为 RabbitMq 并不是所有的消息都使用 fsync(2) —— 它有可能只是保存到缓存中,并不一定会写到硬盘中。并不能保证真正的持久化,但已经足够应付我们的简单工作队列。如果你一定要保证持久化,你需要改写你的代码来支持事务(transaction)。

##交换机 -分发一个消息给多个消费者 RabbitMQ 消息模型的核心理念是:发布者(producer)不会直接发送任何消息给队列。事实上,发布者(producer)甚至不知道消息是否已经被投递到队列。 发布者(producer)只需把消息发送给一个交换机(exchange)。交换机非常简单,它一边从发布者接收消息,一边把消息消息推送到队列。交换机必须知道如何处理它接收的消息,是应该推送到指定的队列还是多个队列,或者是直接忽略消息。这些规则是通过交换机类型(exchange type)来定义的。
import pika

connection = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)
channel = connection.channel()

channel.exchange_declare(exchange="logs",exchange_type="fanout")

connetion.close();

查看所有交换机
sudo rabbitmqctl exchange_lists
###匿名交换机
我们之前的代码exchange=’’,就是匿名交换机。这时候我们把名字填进去我们就可以发送一个有具体名字的交换机了。

channel.basic_publish(
	exchange='logs',
	···
	)

###扇形交换机(fanout)
####临时队列
前面的步骤里我们在声明队列的时候,给队列起了名字。但是更多的时候我们不关心队列的名字。
我们只需要一个全新的,空的队列。我们可以直接这样获得
result = channel.queue_declare()

然后通过
result.method.queue
来获取队列名字【一般都是这样式儿的amq.gen-U0srCoW8TsaXjNh73pnVAw==】
如果想让消费者断开链接时候队列删除,则加上exclusive标识

接下来当然是绑定,告诉交换机我们接受谁的消息
channel.queue_bind(exchange='logs',queue=result.method.queue)

我们可以使用以下命令绑定所有现存的绑定。
$ sudo rabbitmqctl list_bindings

####emit_log.py

import pika
import sys

connection = pika.BlockingConnection(
	pika.ConnectionParameters(host='localhost')
)

channel = connection.channel()
channel.exchange_declare(exchange='logs',exchange_type='fanout')

message = ''.join(sys.argv[1:]) or "Hello RMQ"
channel.basic_publish(
	exchange='logs',
	routing_key='',
	body=message
)

print("[x] send %r" %message)
connection.close()

####receive_logs.py

import pika
connection = pika.BlockingConnection(
	pika.ConnectionParameters(host="localhost")
)

channel = connection.channel()
channel.exchange_declare(exchange='logs',exchange_type='fanout')
#上面介绍的代码
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue

channel.queue_bind(exchange='logs',queue=queue_name)
print('[*] waitin for logs')

def callback(ch,method,properties,body):
	print("[x] %r " % body)
	
channel.basic_consume(
	queue=queue_name,on_message_callback=callback,auto_ack=True)
	
channel.start_consuming()
)

###直连交换机(direct)
扇形交换机并没有消息过滤。比如上个日志可能我们只打算把错误写入磁盘,不打算把其他信息写入磁盘。那么就需要直连交换机来操作。

在配置消息时候 通过 routing_key来决定接受什么样的消息。
示例代码
####emit.py

import pika
import sys

connection = pika.BlockingConnection(
	pika.ConnectionParameters(host = "localhost")
)

channel = connection.channel()
//type为direct
channel.exchange_declare(exchange='direct_logs',exchange_type='direct')

severity = sys.argv[1] if len(sys.argv)>1 else 'info'
message = ''.join(sys.argv[2:]) or 'Hello world!'
channel.basic_publish(
	exchange='direct_logs',
	//消息类型 重要
	routing_key=severity,
	body = message
)

print("already send")
connection.close()

####receive.py

import pika
import sys
connection = pika.BlockingConnection(
	pika.ConnectionParameters(host='localhost')
)

channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',exchange_type='direct')
result = channel.queue_declare(queue='',exclusive=True)
queue_name = result.method.queue

severities = sys.argv[1:]
for not severities:
	sys.stderr.write("Usage: %s [info] [warning] [error]" % sys.argv[0])
	sys.exit(1)
	

for severity in severities:
	channel.queue_bind(
		exchange='direct_logs',
		queue=queue_name,
		//绑定消息类型 重要
		routing_key=severity
	)
	
print("[*] recived")

def callback():
	print("[x] %r %r" %r (method.routing_key,body))

channel.basic_consume(
	queue=queue_name,
	on_message_callback=callback,
	auto_key=True
)

channel.start_consuming()

###主题交换机
跟上一节差不多不过是

chanenl.exchange_declare(exchange='topic_logs',exchange_type='topic')

然后routing_key的时候,进行了消息的分类。
主题交换机比较强大,它可以配置出其他交换机的行为
类似的我们设

red.apple.hainan
接受 海南产的红色苹果
..hainan
接受海南产的所有东西
red.#
接受任何红色的产品

.dog.
接受任何地方的狗

当一个队列的绑定键为 “#”(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。
当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值