常用中间件(消息队列MessageQueue,高级消息队列Rabbitmq)

MesssageQueue(MQ)消息队列是一种应用程序对应用程序的通信方法。是消费者生产者模型的一个典型代表。

  • 消息队列的实际意义
    • 异步消息解耦,例如在外卖订单处理系统中,订单系统通过消息队列和配送系统、商家系统、后台系统等进行消息通信,实现了消费者和生产者之间对数据信息操作的解耦
    • 流量削峰作用,在请求系统的外层,使用消息队列作为一个请求的缓冲池
  • 常见的消息队列
    • ActiveMQ
    • RabbitMQ,应用场景广泛
    • ZeroMQ
    • Kafka
    • MetaMQ
    • RocketMQ

 

rabitmq,中文文档http://rabbitmq.mr-ping.com/,其他参考:https://blog.csdn.net/weixin_39735923/article/details/79288578

  • rabbitMQ是用Erlang开发的基于AMQP协议的消息中间件,或消息队列
  • 使用rabbitmy之前,先要启动rabbitmq的服务。
  • 安装
    • mac,Linux系统
      • 安装brew
        • 方式一,按brew官方推荐方式,https://brew.sh/,资源不稳定
        • 方式二,使用镜像自动安装,源不稳定,不一定能成功,在终端执行     /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"
        • 方式三,按镜像官网步骤,安装清华镜像https://mirrors.tuna.tsinghua.edu.cn/help/homebrew/
      • 使用brew install rabbitmq
        • 配置环境变量
          • vim ~/.bash_profile   
          • 添加上   export PATH=$PATH:/usr/local/sbin
          • 立即启用环境变量,source ~/.bash_profile
    •  windows系统
      • 先安装erlang,
      • 下载rabbitmq
      • erlang下载地址:http://www.erlang.org/downloads
        • 在安装完erlang程序后,注意配置环境变量 
        • 配置完成,在cmd终端中,输入erl执行看效果   
      • rabbitmq下载地址:http://www.rabbitmq.com/download.html

        • 下载双击安装rabbitmq
        • 在cmd终端中,进入rabbitmq的安装目录中的sbin文件夹
        • 执行,rabbitmq-plugins enable rabbitmq_management
        • 添加环境变量,rabbitmq下面的sbin目录到path路径中
        • 在sbin文件夹中执行rabbitmq-server开启服务
        • rabbitmqctl stop停止服务
  •   启动命令:rabbitmq-server -detached(后台运行)
  •       启用web管理插件  rabbitmq-plugins enable rabbitmq_management 
  •   浏览器中,访问rabbitmq,默认地址,http://localhost:15672
    • 默认登录用户名和密码均为guest  

消息的生产端控制流程:

1、实例化链接,2、声明管道,3、声明队列queue,4、发布消息,5、关闭链接

消息的消费端控制流程:

1、实例化链接,2、声明管道,3、声明队列(在确认已经由生产端声明好的前提下可以省略,为防止消费端先于生产端启动,进而因无队列报错。),4、定义回调的功能函数,5、接收消息的设置(),6、开始通过管道接收消息。

主要的消息处理模式及关键参数:

  • 简单模式,常用于追求效率的情况下
    • 应答参数,常用于追求数据安全情况下
    • 消息持久化处理
  • 交换机模式
    • 发布订阅
    • 关键字模式
    • 通配符模式

一个简单模式消息交互的例子 

  • 生产者消费者模型
    • import pika
      
      # 链接rabbitmq
      conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
      channel = conn.channel()
      
      # 创建队列
      channel.queue_declare(queue='hello')
      
      # 向指定队列插入数据,exchage为空表示简单模式,routing_key指定队列,body设置内容
      channel.basic_publish(exchage='',routing_key='hello',body='msg,hello world')
      
      #状态提示
      print('sent msg')
      简单模式生产者
      import pika
      
      # 链接rabbitmq
      conn = pika.BlockingConnection(pika.connectionParameters('localhost'))
      channel = connection.channel()
      
      # 创建队列,如果消费者执行时,生产者队列还未生成,则创建队列,如果生产者已经创建了对应的队列,不执行
      channel.queue_declare(queue='hello')
      
      # 回调函数
      def my_callback(ch,method,properties,body):
          print('received msg')
      
      # 设置监听参数
      channel.basic_consume(queue='hello',auto_ack=True,on_message_callback=my_callback)
      
      print('开始监听…………')
      channel.start_consuming()
      简单模式消费者

       

 一个应答模式消息交互的例子 

  • 在简单模式下,消费者拿到生产者在rabbitmq中存储的数据,处理过程中,如果出现异常,则数据丢失。生产者服务器崩
  • 为了防止这种情况,需要将应答参数auto_ack有True改为手动应答False。同时,在处理的回调函数中,必须添加一个删除数据命令, ch.basic_ack(delivery_tag=method.delivery_tag)
    •  应答模式的生产者和简单模式一致,主要是消费者端进行逻辑调整
    • # !/usr/bin/env python
      # -*- coding:utf-8 -*
      
      import pika
      
      conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
      my_channel = conn.channel()
      
      my_channel.queue_declare(queue='hello')
      
      
      def my_callback(ch, method, properties, body):
          print('received msg %s' % body)
          info = input('>>>>是否销毁数据Y/N:')
          if info.upper() == 'Y':
              ch.basic_ack(delivery_tag=method.delivery_tag)  # 手动应答,根据执行结果,判断是否删除内容
      
      
      my_channel.basic_consume(queue='hello',
                               auto_ack=False,  # 关闭自动应答
                               on_message_callback=my_callback)
      
      my_channel.start_consuming()
      应答模式消费者

队列服务的持久化:

  • 简单模式情况下,当rabbitmq服务器端停掉服务后,重启rabbitmq后数据丢失。
  • 为了实现,重启rabbitmq后队列仍然存在
    • 在声明队列的时候,用持久化参数    durable=True。同一队列的声明(包括生产者,消费者)都加上。可持久化的队列,即支持持久化的信息,同时支持普通信息
    • 在发布消息的时候,由生产者控制,通过   properties = pika.BasicProperties( delivery_mode = 2,)) 的设置,实现消息的持久化
    • 消息持久化示例
      • # !/usr/bin/env python
        # -*- coding:utf-8 -*-
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        channel = conn.channel()
        
        channel.queue_declare(queue='hello2',durable=True)  # 声明队列为可持久化的
        
        channel.basic_publish(exchange='',
                              routing_key='hello2',
                              body='msg:normal',
                              )   # 非持久化数据信息
        channel.basic_publish(exchange='',
                              routing_key='hello2',
                              body='msg_hello_world',
                              properties = pika.BasicProperties( delivery_mode = 2,))  #持久化的数据信息
        
        print('sent hello msg')
        持久化处理+应答生产者
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        my_channel.queue_declare(queue='hello2',durable=True)  # 声明队列为可持久化的
        
        
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
            info = input('>>>>是否销毁数据Y/N:')
            if info.upper() == 'Y':
                ch.basic_ack(delivery_tag=method.delivery_tag)  # 手动应答,根据执行结果,判断是否删除内容
        
        
        my_channel.basic_consume(queue='hello2',
                                 auto_ack=False,  # 关闭自动应答
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        持久化+应答消费者

         

实现消息的高效分发:

  • 默认的消息分发机制,是轮训分发。即,监听同一个队列的不同消费者客户端,接收消息是以简单轮训的顺序依次获取消息的。生产者向队列发布的第一条,给监听队列的第一个客户端,第二条,给第二个……这种效率是比较低的。
  • 为了高效的实现消息的分发,就要实现,消息处理未完成的客户端,不接受消息
    • 在生产者客户端,通过监测是否还有没处理完的消息,  channel.basic_qos(prefetch_count = 1)
  • 示例
    • # !/usr/bin/env python
      # -*- coding:utf-8 -*-
      import pika
      
      conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
      channel = conn.channel()
      
      # 声明队列为可持久化的
      channel.queue_declare(queue='hello3')
      i=1
      while i<50:
          channel.basic_publish(exchange='',
                                routing_key='hello3',
                                body='msg:normal',
                                )   # 非持久化数据信息
          print('sent hello msg')
          i+=1
      print('done')
      生产者
      # !/usr/bin/env python
      # -*- coding:utf-8 -*
      
      import pika
      import time,random
      
      conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
      my_channel = conn.channel()
      
      
      # 创建队列
      my_channel.queue_declare(queue='hello3')
      
      
      # 定义处理信息的回调函
      timer = random.randrange(1,50)
      def my_callback(ch, method, properties, body):
          print('处理时间%s'%timer)
          time.sleep(timer)
          print('received msg %s' % body)
      
      # 如果当前客户端未处理完就不接受新消息的分发
      my_channel.basic_qos(prefetch_count=1)
      
      # 设置监听队列
      my_channel.basic_consume(queue='hello3',
                               auto_ack=True,
                               on_message_callback=my_callback)
      
      my_channel.start_consuming()
      消费者,为了启动多个,pycharm需要在configurations中修改消费者的Allow parallelrun

广播和消息订阅:

  • exchange类似于一个消息的转发器,不同的exchange类型,提供了不同的不同的消息发布方式。
  • 纯广播模式,所有的订阅者都能接收到
  • 纯广播模式fanout具体实现:
    • 生产者,
      • 首先创建了链接和管道,
      • 不生声明队列而是声明exchange,定义消息转发器的名称、类型。exchage_type=‘fanout’
        • 交换机的类型exchage_type常见的方式:
          • fanout,纯广播方式。消息的生产者不用声明队列,在接收消息的时候消费者用声明随机队列,用完以后消息队列丢掉。       
          • direct,定向接收。用于关键字模式
          • topic,通配符模式
      • 然后消息发布的时候,根据交换机的名称,定义不同的消息发布方式。
        • 发布消息通过exchange的名称,指定交换机目标
    • 消费者
      • 创建链接和管道
      • 声明exchange
      • 声明需要监听的队列,这里队列的名称,可以设置为随机生成,使用queue_name = queue_ret.method.queue获取随机生成的队列名
      • 根据queue_name,绑定队列到交换机上
      • 定义处理信息的回调函数
      • 设置监听队列参数
      • 开始监听
    • 示例
      • # !/usr/bin/env python
        # -*- coding:utf-8 -*-
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        channel = conn.channel()
        
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        channel.exchange_declare(exchange='ex1',
                                exchange_type='fanout')
        
        # 发送消息,通过exchange定义发送消息的交换机目标
        channel.basic_publish(exchange='ex1',
                              routing_key='hello2',
                              body='msg:exchage_fanout',
                              )
        
        print('sent hello msg')
        发布订阅fanout模式生产者
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex1',
                                exchange_type='fanout')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('',exclusive=True) # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue   # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上
        my_channel.queue_bind(exchange='ex1',queue=queue_name)
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        发布订阅fanout模式消费者
  • 关键字模式direct:
  • 关键字模式的具体实现 

    • 生产者
      • 指定交换机类型为exchange_type为direct
      • 在设置发布信息的时候,使用routing_key指定当前信息的关键字。必须是消费者能接受的关键字,才能被收到
    • 消费者
      • 指定交换机类型exchange_type为direct
      • 向交换机绑定队列的时候,通过关键字参数,routing_key='自定义的消息关键字' 进行绑定,每次只能给管道绑定一个队列,如果需要绑定多个关键字的队列,需要逐一添加绑定,或者进行循环遍历。
    • 示例
      • # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex2',
                                    exchange_type='direct')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('', exclusive=True)  # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue  # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上
        key_list = ['warning', 'info', 'error']
        for msg_key in key_list:
            my_channel.queue_bind(exchange='ex2'
                                  , queue=queue_name,
                                  routing_key=msg_key)
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        关键字模式生产者
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex2',
                                    exchange_type='direct')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('', exclusive=True)  # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue  # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上,routing_key设置可以接收的消息的关键字
        my_channel.queue_bind(exchange='ex2',
                              queue=queue_name,
                              routing_key='error'
                              )
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        关键字模式消费者1,只接受error关键字的内容
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex2',
                                    exchange_type='direct')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('', exclusive=True)  # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue  # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上
        key_list = ['warning', 'info', 'error']
        for msg_key in key_list:
            my_channel.queue_bind(exchange='ex2'
                                  , queue=queue_name,
                                  routing_key=msg_key)
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        关键字模式消费者2,接受error,info,warning关键字的内容
  • 通配符模式topic:
    •   上面的关键字模式只能进行消息关键字的完全匹配,要实现模糊匹配就需要使用通配符模式
    •    通配符模式上,只有两个占位符(   #,*  ),其中,#匹配一个或多个此,*只匹配一个词,这里和正则表达式区别
    •   
  • 通配符模式的具体实现
    • 生产者
      • exchange_type,定义为topic
    • 消费者
      • exchange_type同样指定为topic
      • 定义routing_key的时使用通配符  *,#,实现模糊匹配
    • 示例  
      • # !/usr/bin/env python
        # -*- coding:utf-8 -*-
        import random
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        channel.exchange_declare(exchange='ex3',
                                 exchange_type='topic')
        
        key_list = ['warning.0','warning.2', 'info', 'error']
        # 发送消息,通过exchange定义发送消息的交换机目标,routing_key设置当前消息的关键字信息
        i = 1
        while i < 50:
            msg_type = random.choice(key_list)
            print('关键字%s'%msg_type)
            channel.basic_publish(exchange='ex3',
                                  routing_key=msg_type,
                                  body='msg:exchage_%s' % msg_type,
                                  )
            i += 1
        print('sent hello msg')
        生产者
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex3',
                                    exchange_type='topic')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('', exclusive=True)  # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue  # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上,routing_key设置可以接收的消息的关键字,使用占位符定义匹配模式
        my_channel.queue_bind(exchange='ex3',
                              queue=queue_name,
                              routing_key='warning.*'
                              )
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        消费者1,使用*通配符定义规则
        # !/usr/bin/env python
        # -*- coding:utf-8 -*
        
        import pika
        
        conn = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        my_channel = conn.channel()
        
        # 声明交换机,exchange定义交换机名称,exchange_type定义交换机类型
        my_channel.exchange_declare(exchange='ex3',
                                    exchange_type='topic')
        
        # 创建队列
        queue_ret = my_channel.queue_declare('', exclusive=True)  # 第一个参数 ‘’,表示不指定队列名称,第二个参数执行自动生成随机的队列名称
        queue_name = queue_ret.method.queue  # 获取上面随机生成的队列名称
        
        # 将队列绑定到交换机上
        key_list = ['info', 'error']
        my_channel.queue_bind(exchange='ex3'
                                  , queue=queue_name,
                                  routing_key='warning.#')
        for msg_key in key_list:
            my_channel.queue_bind(exchange='ex3'
                                  , queue=queue_name,
                                  routing_key=msg_key)
        
        
        # 定义处理信息的回调函数
        def my_callback(ch, method, properties, body):
            print('received msg %s' % body)
        
        
        # 设置监听队列
        my_channel.basic_consume(queue=queue_name,
                                 auto_ack=True,
                                 on_message_callback=my_callback)
        
        my_channel.start_consuming()
        消费者2,使用#通配符进行匹配规则指定

         

 rpc(remote proceduer call)消息队列服务的实现

 

 

rabbitmq常用方法

常用的方法介绍

 

posted @ 2019-05-18 14:45  林山风火  阅读(3)  评论(0)    收藏  举报