rabbitMQ--快速入门

自古美人都是妖i 提交于 2020-02-06 02:32:32

1、简介

  MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。

  

  如上图所示:  

  1.Server(broker): 接受客户端连接,实现AMQP消息队列和路由功能的进程。

  2.Virtual Host:其实是一个虚拟概念,类似于权限控制组,一个Virtual Host里面可以有若干个Exchange和Queue,但是权限控制的最小粒度是Virtual Host

  3.Exchange:接受生产者发送的消息,并根据Binding规则将消息路由给服务器中的队列。ExchangeType决定了Exchange路由消息的行为,例如,在RabbitMQ中,             ExchangeType有direct、Fanout和Topic三种,不同类型的Exchange路由的行为是不一样的。

  4.Message Queue:消息队列,用于存储还未被消费者消费的消息。

  5.Message: 由Header和Body组成,Header是由生产者添加的各种属性的集合,包括Message是否被持久化、由哪个Message Queue接受、优先级是多少等。而Body是           真正需要传输的APP数据。

  6.Binding:Binding联系了Exchange与Message Queue。Exchange在与多个Message Queue发生Binding后会生成一张路由表,路由表中存储着Message Queue所需消         息的限制条件即Binding Key。当Exchange收到Message时会解析其Header得到Routing Key,Exchange根据Routing Key与Exchange Type将Message路由到               Message Queue。Binding Key由Consumer在Binding Exchange与Message Queue时指定,而Routing Key由Producer发送Message时指定,两者的匹配方式由             Exchange Type决定。 

  7.Connection:连接,对于RabbitMQ而言,其实就是一个位于客户端和Broker之间的TCP连接。

  8.Channel:信道,仅仅创建了客户端到Broker之间的连接后,客户端还是不能发送消息的。需要为每一个Connection创建Channel,AMQP协议规定只有通过Channel才能           执行AMQP的命令。一个Connection可以包含多个Channel。之所以需要Channel,是因为TCP连接的建立和释放都是十分昂贵的,如果一个客户端每一个线程都需要与             Broker交互,如果每一个线程都建立一个TCP连接,暂且不考虑TCP连接是否浪费,就算操作系统也无法承受每秒建立如此多的TCP连接。RabbitMQ建议客户端线程之间不·         要共用Channel,至少要保证共用Channel的线程发送消息必须是串行的,但是建议尽量共用Connection。

  9.Command:AMQP的命令,客户端通过Command完成与AMQP服务器的交互来实现自身的逻辑。例如在RabbitMQ中,客户端可以通过publish命令发送消息,txSelect开         启一个事务,txCommit提交一个事务。

2、特点及应用场景

  MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。MQ和JMS类似,但不同的是JMS是SUN JAVA消息中间件服务的一个标准和API定义,而MQ则是遵循了AMQP协议的具体实现和产品。

  在项目中,将一些无需即时返回且耗时的操作提取出来,进行了异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。

3、安装

  RabbitMQ是基于Erlang的,所以首先必须配置Erlang环境。  

  从Erlang的官网 http://www.erlang.org/download.html 下载最新的erlang安装包,我下载的版本是 otp_src_R14B03.tar.gz 。

 

  然后:

    $ tar xvzf otp_src_R14B03.tar.gz
    $ cd otp_src_R14B03
    $ ./configure

 

   编译后的输出如下图:

          

  提示没有wxWidgets和fop,但是问题不大。继续:
    $ make
    $ sudo make install

  安装完Erlang,开始安装RabbitMQ-Server。  

  主要参考官方文档:http://www.rabbitmq.com/build-server.html

  需要安装一个比较新的Python版本。安装略。

  需要安装simplejson。从此处下载最新的版本: http://pypi.python.org/pypi/simplejson#downloads 。我下载的版本是 simplejson-2.2.1.tar.gz

    $ tar xvzf simplejson-2.2.1.tar.gz
    $ cd simplejson-2.2.1
    $ sudo python setup.py install

  然后安装RabbitMQ Server。从此处下载源代码版本的RabbitMQ: http://www.rabbitmq.com/server.html。我下载的版本是 rabbitmq-server-2.6.1.tar.gz

    $ tar xvzf rabbitmq-server-2.6.1.tar.gz
    $ cd rabbitmq-server-2.6.1
    $ make
    # TARGET_DIR=/usr/local SBIN_DIR=/usr/local/sbin MAN_DIR=/usr/local/man make install 

  在sbin/目录下出现了三个命令:

    rabbitmqctl  rabbitmq-env  rabbitmq-server

  安装成功。

  运行

    找到sbin/目录,运行程序:
      /usr/local/sbin/rabbitmq-server –detached 

    停止程序:
      /usr/local/sbin/rabbitmqctl stop 

 4、java 实现rabbitMQ---生产者

    

public interface EventTemplate {

    void send(String queueName,String exchangeName,Object eventContent) throws SendRefuseException;
        
    void send(String queueName,String exchangeName,Object eventContent,CodecFactory codecFactory) throws SendRefuseException;
}
public class DefaultEventTemplate implements EventTemplate {

    private static final Logger logger = Logger.getLogger(DefaultEventTemplate.class);

    private AmqpTemplate eventAmqpTemplate;

    private CodecFactory defaultCodecFactory;

//    private DefaultEventController eec;
//
//    public DefaultEventTemplate(AmqpTemplate eopAmqpTemplate,
//            CodecFactory defaultCodecFactory, DefaultEventController eec) {
//        this.eventAmqpTemplate = eopAmqpTemplate;
//        this.defaultCodecFactory = defaultCodecFactory;
//        this.eec = eec;
//    }
    
    public DefaultEventTemplate(AmqpTemplate eopAmqpTemplate,CodecFactory defaultCodecFactory) {
        this.eventAmqpTemplate = eopAmqpTemplate;
        this.defaultCodecFactory = defaultCodecFactory;
    }

    @Override
    public void send(String queueName, String exchangeName, Object eventContent)
            throws SendRefuseException {
        this.send(queueName, exchangeName, eventContent, defaultCodecFactory);
    }  

    @Override
    public void send(String queueName, String exchangeName, Object eventContent,
            CodecFactory codecFactory) throws SendRefuseException {
        if (StringUtils.isEmpty(queueName) || StringUtils.isEmpty(exchangeName)) {
            throw new SendRefuseException("queueName exchangeName can not be empty.");
        }
        
//        if (!eec.beBinded(exchangeName, queueName))
//            eec.declareBinding(exchangeName, queueName);

        byte[] eventContentBytes = null;
        if (codecFactory == null) {
            if (eventContent == null) {
                logger.warn("Find eventContent is null,are you sure...");
            } else {
                throw new SendRefuseException(
                        "codecFactory must not be null ,unless eventContent is null");
            }
        } else {
            try {
                eventContentBytes = codecFactory.serialize(eventContent);
            } catch (IOException e) {
                throw new SendRefuseException(e);
            }
        }

        // 构造成Message
        EventMessage msg = new EventMessage(queueName, exchangeName,
                eventContentBytes);
        try {
            eventAmqpTemplate.convertAndSend(exchangeName, queueName, msg);
        } catch (AmqpException e) {
            logger.error("send event fail. Event Message : [" + eventContent + "]", e);
            throw new SendRefuseException("send event fail", e);
        }
    }
}

5、java实现rabbitMQ---消费者

  

public interface EventProcesser {
    public void process(Object e);
}

  为了能够将不同类型的消息交由对应的程序来处理,我们还需要一个消息处理适配器

  

/**
 * MessageListenerAdapter的Pojo
 * <p>消息处理适配器,主要功能:</p>
 * <p>1、将不同的消息类型绑定到对应的处理器并本地缓存,如将queue01+exchange01的消息统一交由A处理器来出来</p>
 * <p>2、执行消息的消费分发,调用相应的处理器来消费属于它的消息</p>
 * 
 */
public class MessageAdapterHandler {

    private static final Logger logger = Logger.getLogger(MessageAdapterHandler.class);

    private ConcurrentMap<String, EventProcessorWrap> epwMap;

    public MessageAdapterHandler() {
        this.epwMap = new ConcurrentHashMap<String, EventProcessorWrap>();
    }

    public void handleMessage(EventMessage eem) {
        logger.debug("Receive an EventMessage: [" + eem + "]");
        // 先要判断接收到的message是否是空的,在某些异常情况下,会产生空值
        if (eem == null) {
            logger.warn("Receive an null EventMessage, it may product some errors, and processing message is canceled.");
            return;
        }
        if (StringUtils.isEmpty(eem.getQueueName()) || StringUtils.isEmpty(eem.getExchangeName())) {
            logger.warn("The EventMessage's queueName and exchangeName is empty, this is not allowed, and processing message is canceled.");
            return;
        }
        // 解码,并交给对应的EventHandle执行
        EventProcessorWrap eepw = epwMap.get(eem.getQueueName()+"|"+eem.getExchangeName());
        if (eepw == null) {
            logger.warn("Receive an EopEventMessage, but no processor can do it.");
            return;
        }
        try {
            eepw.process(eem.getEventData());
        } catch (IOException e) {
            logger.error("Event content can not be Deserialized, check the provided CodecFactory.",e);
            return;
        }
    }

    protected void add(String queueName, String exchangeName, EventProcesser processor,CodecFactory codecFactory) {
        if (StringUtils.isEmpty(queueName) || StringUtils.isEmpty(exchangeName) || processor == null || codecFactory == null) {
            throw new RuntimeException("queueName and exchangeName can not be empty,and processor or codecFactory can not be null. ");
        }
        EventProcessorWrap epw = new EventProcessorWrap(codecFactory,processor);
        EventProcessorWrap oldProcessorWrap = epwMap.putIfAbsent(queueName + "|" + exchangeName, epw);
        if (oldProcessorWrap != null) {
            logger.warn("The processor of this queue and exchange exists, and the new one can't be add");
        }
    }

    protected Set<String> getAllBinding() {
        Set<String> keySet = epwMap.keySet();
        return keySet;
    }

    protected static class EventProcessorWrap {

        private CodecFactory codecFactory;

        private EventProcesser eep;

        protected EventProcessorWrap(CodecFactory codecFactory,
                EventProcesser eep) {
            this.codecFactory = codecFactory;
            this.eep = eep;
        }

        public void process(byte[] eventData) throws IOException{
            Object obj = codecFactory.deSerialize(eventData);
            eep.process(obj);
        }
    }
}

6、Python实现rabbitMQ---消费者(个人在项目中使用的)

   

#!/usr/bin/env python
#coding=utf8
import pika
import ConfigParser
import string, os, sys 

'''读取配置项'''
cf = ConfigParser.ConfigParser()
cf.read("agent.conf")
host = cf.get("agent", "host")
exchangeName = cf.get("agent", "exchangeName")
queueName = cf.get("agent", "queueName")
command = cf.get("agent", "command")

connection = pika.BlockingConnection(pika.ConnectionParameters(host))
channel = connection.channel()
#定义交换机
channel.exchange_declare(exchange=exchangeName, type='fanout')
 
#生成队列,并绑定到交换机上
result = channel.queue_declare(queue=queueName,durable=True,exclusive=False)
queue_name = result.method.queue
channel.queue_bind(exchange=exchangeName, queue=queue_name) 
#回调函数
def callback(ch, method, properties, body):
    ch.basic_ack(delivery_tag = method.delivery_tag)
    os.system(command+' '+body)
    print " [x] Received %r" % (body,)
 
channel.basic_consume(callback, queue=queue_name)
 
print ' [*] Waiting for messages. To exit press CTRL+C'
channel.start_consuming()

 

下面我们具体分析一下代码:

 rabbitmq的python实例工作队列:

  

  1、连接到rabbitmq服务器。

  connection = pika.BlockingConnection(pika.ConnectionParameters(host)) 2、声明消息队列,消息将在这个队列中进行传递。如果将消息发送到不存在的队列,rabbitmq将会自动清除这些消息。  
  result = channel.queue_declare(queue=queueName,durable=True,exclusive=False)
  queue_name = result.method.queue
 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!