简介

  消息队列是一种先进先出的数据结构。

  当服务处在分布式系统中时,不同的机器之间是需要做数据交互的,而涉及到数据交互了,我们自然就需要一个专业的消息队列来做中转处理。

  没错,RabbitMQ就是其中之一,也是我们今天要讲解的主题。

  能做什么?应用解耦流量消峰:假设有一个订单系统,其一秒钟最多能处理一万次订单,那么正常时段我们下单是没问题的,一秒后就能返回结果。但是在高峰时段呢?超出后服务器就处理不了了呀,此时我们要么限制数量,要么就横向、纵向扩容服务器(土豪请随意)。哈哈,这个时候我们的消息队列就派上用场了,使用它可以把这一秒内的订单放入队列中分散成一段时间来处理,这样做虽然用户可能在下单几十秒后才能收到下单成功的操作,但是比不能下单用户体验要好得多(主要是穷没得选)。消息分发:当A发送一次消息,B对消息感兴趣,就只需监听消息,C感兴趣,C也去监听消息,而A完全不需要改动。实现了大家各自玩各自的游戏规则。异步消息(Celery 就是对消息队列的分装)和Kafka对比

  我们知道,Kafka也是同类型软件中比较有名的一个,那么它与我们本次说明的RabbitMQ的区别在哪里呢?

  最大的区别其实就是数据吞吐量和消息可靠性,请看下面:

  RabbitMQ :吞吐量小,有消息确认(对消息可靠性有要求,就用它)

  Kafka:吞吐量高,注重高吞吐量,不注重消息的可靠性,数据量特别大

  二、安装RabbitMQ

  1、原生安装

  # 安装扩展epel源

  wget -O /etc/yum.repos.d/epel.repo mirrors.aliyun/repo/epel-7.repo

  # 因为RabbitMQ是erlang语言开发的,所以需要先安装erlang

  yum -y install erlang

  # 安装RabbitMQ

  yum -y install rabbitmq-server

  # 启动RabbitMQ

  systemctl start rabbitmq-server

  # 创建用户

  rabbitmqctl add_user 用户名 密码

  # 分配权限(这里的administrator代表设置用户为管理员角色)

  rabbitmqctl set_user_tags 用户名 administrator

  # 设置权限

  rabbitmqctl set_permissions -p "/" 用户名 ".*" ".*" ".*"

  # 重启

  systemctl reatart rabbitmq-server

  2、docker拉取安装

  # 自动开启了web管理界面

  docker pull rabbitmq:3.8.3-management

  # 启动需要配置用户名和密码

  docker run -di --name rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:3.8.3-management

  # 端口说明

  5672:是RabbitMQ的默认端口

  15672:web管理界面的端口

  三、基本使用

  生产者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列

  channel.queue_declare(queue='haoqixin')

  # 生产者向队列中放入一条消息

  channel.basic_publish(exchange='',

  routing_key='haoqixin', # 指定向那个队列放入

  body='头条@技术好奇心') # 放入的内容

  # 关闭连接

  connection.close()

  消费者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先起来,那么就先声明一个队列

  channel.queue_declare(queue='haoqixin')

  def callback(ch, method, properties, body):

  print(f'我是技术好奇心:{body}')

  # 消费者从指定的队列中拿消息消费,一旦有一条转到 callback 里

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

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  四、确认机制

  消息确认机制说白了,其实就是消费者中 auto_ack 的设置,这里生产者是不需要做什么的。

  消费者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.queue_declare(queue='haoqixin')

  def callback(ch, method, properties, body):

  print(f'我是技术好奇心:{body}')

  # 如果已经设置了auto_ack=False,

  # 也可以如下这样设置,当真正的消息处理完了,在发确认也是可以的

  ch.basic_ack(delivery_tag=method.delivery_tag)

  # auto_ack=True,队列收到确认,就会自动把消费过的消息删除。

  # auto_ack=False,则不会给队列发送确认消息,没有发送消息队列就不会删除消息。不会自动回复确认消息,

  channel.basic_consume(queue='test', on_message_callback=callback, auto_ack=False)

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  五、持久化

  队列持久化:在声明队列的时候,指定持久化durable=True,注意队列必须是新的才可以哦:

  # haoqixin 队列持久化

  channel.queue_declare(queue='haoqixin', durable=True)

  消息持久化:在发布消息的时候添加

  # 生产者向队列中放入一条消息

  channel.basic_publish(exchange='',

  # 指定放入队列

  routing_key='haoqixin',

  # 设置放入内容

  body='你好啊,我是头条@技术好奇心',

  # 设置消息持久化

  properties=pika.BasicProperties(delivery_mode=2)

  )六、闲置消费

  正常情况下如果有多个消费者,那么队列就会按照顺序第一个消息给 第一个消费者,第二个消息给第二个消费者,这样依次进行。

  但是当第一个消息的消费者处理信息很耗时,一直没有结束,队列就可以选择让第二个消费者优先获取闲置消息。

  这就是闲置消费概念,同样和发布者没关系。

  消费者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.queue_declare(queue='haoqixin')

  def callback(ch, method, properties, body):

  print(f'我是技术好奇心:{body}')

  # 设置闲置消费

  channel.basic_qos(prefetch_count=1)

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

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  七、发布订阅

  发布订阅就是:我可以有多个订阅者来订阅你发布的消息,这样作为发布者你只需要发布一条即可, 而所有订阅了你的消息的订阅者都可以消费你的消息。

  模型:当我的订阅者工作起来之后,他们中每一个就会创建一个队列,多个订阅者自然就会创建多个队列。当发布者生产了消息之后,会传给 exchange ,然后 exchange(相当于房屋中介) 会把消息复制分别分发到订阅者创建的队列中,这样就实现了只要监听你,那就能收到你发的消息。

  基本使用

  发布者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 不指定队列,指定了 exchange 复制分发消息

  channel.exchange_declare(exchange='conn', exchange_type='fanout')

  # 生产者向队列中放入一条消息

  channel.basic_publish(exchange='conn', # 指定复制分发消息的 exchange

  routing_key='', # 不设置指定向那个队列放入

  body='我是头条@技术好奇心', # 放入的内容

  )

  # 关闭连接

  connection.close()

  订阅者:启动多次,都绑定到了同一个 exchange,所以就会都收到同一个 exchange 分发的消息了。

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.exchange_declare(exchange='conn', exchange_type='fanout')

  # queue 不能制定名字,因为它们的名字都是不一样的

  result=channel.queue_declare(queue='', exclusive=True)

  # 生成一个随机的 queue 名字

  queue_name=result.method.queue

  # 把随机生成的队列绑定到exchange上

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

  def callback(ch, method, properties, body):

  print(f'哈哈,我是头条@技术好奇心:{body}')

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

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  关键字

  需要设置 exchange_type 的类型为 direct,并且在发布消息的时候设置多个关键字:routing_key;在订阅者中也需要设置 exchange_type 的类型 direct;并且当订阅者绑定 exchange 的时候也需要设置 routing_key。

  这样的话在发布者发布消息后,exchange 会根据发布者和订阅者设置的 routing_key 进行匹配,当订阅者的 routing_key 匹配上了发布者的 routing_key 的话,那么订阅者就可以接收到发布者发布的消息,反之收不到消息。

  发布者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 不指定队列,指定了 exchange 复制分发消息,exchange_type='direct'

  channel.exchange_declare(exchange='conn1', exchange_type='direct')

  # 生产者向队列中放入一条消息

  channel.basic_publish(exchange='conn1', # 指定复制分发消息的 exchange

  routing_key='haoqi', # 指定关键字

  body='哈哈,我是头条@技术好奇心', # 放入的内容

  )

  # 关闭连接

  connection.close()

  消费者1:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.exchange_declare(exchange='conn', exchange_type='direct')

  # queue 不能制定名字,因为它的名字都是不一样的

  result=channel.queue_declare(queue='', exclusive=True)

  # 生成一个随机的 queue 名字

  queue_name=result.method.queue

  print(queue_name)

  # 把随机生成的队列绑定到exchange上,

  # 并设置routing_key='haoqi',也就是说只有发布者的routing_key中包含有'haoqi',此订阅者才会收到消息

  channel.queue_bind(exchange='conn1', queue=queue_name, routing_key='haoqi')

  def callback(ch, method, properties, body):

  print(f'技术好奇心haoqi:{body}')

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

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  消费者2:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.exchange_declare(exchange='conn', exchange_type='direct')

  # queue 不能制定名字,因为它的名字都是不一样的

  result=channel.queue_declare(queue='', exclusive=True)

  # 生成一个随机的 queue 名字

  queue_name=result.method.queue

  print(queue_name)

  # 把随机生成的队列绑定到exchange上,

  # 并设置了多个routing_key,也就是说只有发布者的routing_key中包含有入下两个之一,此订阅者都会收到消息

  channel.queue_bind(exchange='conn1', queue=queue_name, routing_key='haoqi')

  channel.queue_bind(exchange='conn1', queue=queue_name, routing_key='haoqixin')

  def callback(ch, method, properties, body):

  print(f'技术好奇心:{body}')

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

  # 阻塞主,一直等待拿消息消费

  channel.start_consuming()

  模糊匹配

  在订阅者绑定匹配的时候可以进行模糊匹配发布者的 routing_key ,匹配上了就能接收到发布者发布的消息。

  # 表示后面可以跟任意字符

  * 表示后面只能跟一个单词

  发布者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 不指定队列,指定了 exchange 复制分发消息,exchange_type='topic'

  channel.exchange_declare(exchange='conn1', exchange_type='topic')

  # 生产者向队列中放入一条消息

  channel.basic_publish(exchange='conn2', # 指定复制分发消息的 exchange

  routing_key='haoqixin', # 指定关键字

  body='你好,我是头条@技术好奇心', # 放入的内容

  )

  # 关闭连接

  connection.close()

  订阅者:

  import pika

  # 有用户名密码

  credentials=pika.PlainCredentials('haoqixin', 'haoqixin')

  # 拿到连接对象

  connection=pika.BlockingConnection(pika.ConnectionParameters('192.168.53.434', credentials=credentials))

  # 拿到channel对象

  channel=connection.channel()

  # 声明一个队列,如果消费者先工作起来,那么就先声明一个队列

  channel.exchange_declare(exchange='conn', exchange_type='direct')

  # queue 不能制定名字,因为它的名字都是不一样的

  result=channel.queue_declare(queue='', exclusive=True)

  # 生成一个随机的 queue 名字

  queue_name=result.method.queue

  print(queue_name)

  # 把随机生成的队列绑定到exchange上,

  # 并设置routing_key='haoqixin#',也就是说只有发布者的routing_key中包含有'haoqixin'开头,此订阅者才会收到消息

  channel.queue_bind(exchange='conn2', queue=queue_name, routing_key='haoqixin#')

  def callback(ch, method, properties, body):

  print(f'技术好奇心:{body}')

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

  # 阻塞中,一直等待拿消息消费

  channel.start_consuming()

  八、python中的RPC框架

  RPC :远程过程调用

  例如:两个服务之间调用,服务1通过网络调用服务2的方法。

  SimpleXMLRPCServer

  自带的:数据包大,速度慢

  服务端:

  from xmlrpc.server import SimpleXMLRPCServer

  class RPCServer(object):

  def getObj(self):

  return 'get obj'

  def sendObj(self, data):

  return 'send obj'

  # SimpleXMLRPCServer

  server=SimpleXMLRPCServer(('localhost', 4242), allow_none=True)

  server.register_introspection_functions()

  server.register_instance(RPCServer())

  server.serve_forever()

  客户端:

  from xmlrpc.client import ServerProxy

  client=ServerProxy('localhost:4242')

  ret=client.getObj()

  print(ret)

  ZeroRPC

  第三方的:底层使用 ZeroMQ 和 MessagePack ,速度快,响应时间短,并发高。

  服务端:

  import zerorpc

  class RPCServer(object):

  def getObj(self):

  return 'get obj'

  def sendObj(self, data):

  return 'send obj'

  server=zerorpc.Server(RPCServer())

  server.bind('tcp://0.0.0.0:4243') # 允许连接的

  server()

  客户端:

  import zerorpc

  client=zerorpc.Client()

  client.connect('tcp://127.0.0.1:4243') # 连接

  ret=client.getObj()

  print(ret)总结

  好了,关于RabbitMQ消息队列的知识就总结到这里了,希望对你的学习有帮助。