##目录
//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重启之后仍然可以使用!
不过和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类似。
//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)。
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.
接受任何地方的狗
当一个队列的绑定键为 “#”(井号) 的时候,这个队列将会无视消息的路由键,接收所有的消息。
当 * (星号) 和 # (井号) 这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。