RabbitMq 消息队列的安装、使用 、原理

来源:互联网 发布:lol30级号淘宝 编辑:程序博客网 时间:2024/05/16 11:58

               年后刚上班这几天有点清闲,有时间要赶快利用,去年我们有一个项目要将视频传到YouTube上,上传成功后YouTube有一个自动截图的功能,截取视频图片后国外的一台服务器上的程序就会调用国内的一台服务器上的接口然后将图片传过来进行更新数据,原来的实现方式就是通过HttpClient连接接口传输数据实现的,这样会有很大问题,我想大家都知道如果在数据传输的过程中国内服务器上的服务进行了从新启动那么就会造成数据丢失或者数据传输过来后服务器挂了但是数据还没有消费也是一样的,所以我们才采用了消息队列来解决这一问题,原来我只会在LINUX上安装并且简单的应用但是不懂原理,现在我想是时候深入了解了

一 Rabbit的安装

    

1、安装erlang(官网地址http://www.erlang.org )
      rabbitmq是erlang语言编写的,我们要安装erlang语言环境

    # mkdir mq  

    # cd mq/  

    # wget http://www.erlang.org/download/otp_src_R15B01.tar.gz  

    # tar -zxvf otp_src_R15B01.tar.gz  

    # cd otp_src_R15B01    

    # yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel  

    # ./configure --prefix=/usr/local/erlang --enable-hipe --enable-threads --enable-smp-support --enable-kernel-poll  

    # make;make install 

   配置环境

   # vim /etc/profile 

  ERL_HOME=/usr/local/erlang

  PATH=$ERL_HOME/bin:$PATH

  export ERL_HOME PATH

  # source /etc/profile
  然后输入erl,出现erlang shell

2、安装rabbitmq
 #wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.0.1/rabbitmq-server-3.0.1.tar.gz
 #tar xzvf rabbitmq-server-3.0.1.tar.gz
 #cd rabbitmq-server-3.0.1
 #sudo make TARGET_DIR=/usr/local SBIN_DIR=/usr/local/sbin MAN_DIR=/usr/local/man install
如果报"/bin/sh: line 1: xmlto: command not found"错误,执行"yum -y install xmlto".

3、安装web插件
# mkdir /etc/rabbitmq/
# rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
  mochiweb
  webmachine
  rabbitmq_mochiweb
  amqp_client
  rabbitmq_management_agent
  rabbitmq_management
Plugin configuration has changed. Restart RabbitMQ for changes to take effect.

4、启动服务
# rabbitmq-server start &

5、页面验证
http://service-ip:55672

到此已经安装成功,可以进行简单的应用了

管理

Rabbitmq服务器的主要通过rabbitmqctlrabbimq-plugins两个工具来管理,以下是一些常用功能。

1. 服务器启动与关闭

      启动: rabbitmq-server detached

      关闭:rabbitmqctl stop

      若单机有多个实例,则在rabbitmqctlh后加指定名称

2. 插件管理

      开启某个插件:rabbitmq-plugins enable xxx

      关闭某个插件:rabbitmq-plugins disablexxx

      注意:重启服务器后生效。

3. virtual_host管理

      新建virtual_host: rabbitmqctl add_vhost  xxx

      撤销virtual_host:rabbitmqctl  delete_vhost xxx

4. 用户管理

      新建用户:rabbitmqctl add_user xxx pwd

      删除用户:   rabbitmqctl delete_user xxx

      改密码: rabbimqctl change_password {username} {newpassword}

      设置用户角色:rabbitmqctl set_user_tags {username} {tag ...}

      Tag可以为 administrator, monitoring, management

Tag

Capabilities

(None)

No access to the management plugin

management

Anything the user could do via AMQP plus:

List virtual hosts to which they can log in via AMQP

View all queues, exchanges and bindings in "their" virtual hosts

View and close their own channels and connections

View "global" statistics covering all their virtual hosts, including activity by other users within them

monitoring

Everything "management" can plus:

List all virtual hosts, including ones they could not log in to via AMQP

View other users's connections and channels

View node-level data such as memory use and clustering

View truly global statistics for all virtual hosts

administrator

Everything "monitoring" can plus:

Create and delete virtual hosts

View, create and delete users

View, create and delete permissions

Close other users's connections

5. 权限管理

      权限设置:set_permissions [-p vhostpath] {user} {conf} {write} {read}

               Vhostpath

               Vhost路径

               user

      用户名

              Conf

      一个正则表达式match哪些配置资源能够被该用户访问。

              Write

      一个正则表达式match哪些配置资源能够被该用户读。

               Read

      一个正则表达式match哪些配置资源能够被该用户访问。

6. 获取服务器状态信息

       服务器状态:rabbitmqctl status

       队列信息: rabbitmqctl list_queues [-p vhostpath] [queueinfoitem ...]

                          Queueinfoitem可以为:namedurableauto_deleteargumentsmessages_ready

                          messages_unacknowledgedmessagesconsumersmemory

 

  Exchange信息:rabbitmqctllist_exchanges [-p vhostpath] [exchangeinfoitem ...]  Exchangeinfoitem有:nametypedurableauto_deleteinternalarguments.

 Binding信息:rabbitmqctllist_bindings [-p vhostpath] [bindinginfoitem ...]    Bindinginfoitem   有:source_namesource_kinddestination_namedestination_kindrouting_keyarguments

Connection信息:rabbitmqctl list_connections [connectioninfoitem ...]

Connectioninfoitem有:recv_octrecv_cntsend_octsend_cntsend_pend等。

Channel信息:rabbitmqctl  list_channels[channelinfoitem ...]

Channelinfoitemconsumer_countmessages_unacknowledgedmessages_uncommitted,acks_uncommittedmessages_unconfirmedprefetch_countclient_flow_blocked

集群配置

1)配置erlang

        Rabbitmq的集群是依赖于erlang的集群来工作的,所以必须先构建起erlang的集群环境。Erlang的集群中各节 点是通过一个magic cookie来实现的,这个cookie存放在$HOME/.Erlang.cookie 中,文件是400的权限。所以必须保证各节点cookie保持一致,否则节点之间就无法通信。

       将其中一台节点上的.erlang.cookie值复制下来保存到其他节点上。或者使用scp的方法也        可,但是要注意文件的权限和属主属组。

                $ cat .erlang.cookie  

                IYZNFNJIUWABJUELIUFE  

                将值保存到其他节点上先更改文件权限,给予其可写权限:

                $chmod 700 .erlang.cookie  

                $ echo -n " IYZNFNJIUWABJUELIUFE" >$HOME/.erlang.cookie  

                $chmod 400 .erlang.cookie  

        直接把.erlang.cookie这个文件scp到其它节点也可以。


   (2 配置各节点下的hosts文件,确保节点之间主机名称可以相互解析。

         192.168.1.**   nodename

         192.168.1.**   nodename

         192.168.1.**   nodename

         192.168.1.**   nodename

(1)手动配置rabbitmq集群

a)首先在每个节点上启动RabbitMq

     host1#rabbitmq-server -detached

     host2#rabbitmq-server -detached

     host3#rabbitmq-server -detached

    此时每个节点自成集群,每个集群只有本节点

    可以通过export RABBITMQ_NAME=xxx 指定节点名称,若不指定默认为rabbit

    完整的节点名为xxx@hostname

b)加入集群

         让host2 host3上的rabbitmq节点加入到 host1rabbitmq集群

         使用rabbitmqctl命令对rabbitmq进行操作

         加入集群前必须停止当前app,加入后在重启app,过程如下:

         host2#rabbitmqctl stop_app

         host2#rabbitmqctl cluster rabbit@host1

         host2#rabbitmqctl start_app

         host3#rabbitmqctl stop_app

         host3#rabbitmqctl cluster rabbit@host1

         host3#rabbitmqctl start_app

关于节点类型(ram |disk

ram节点的状态保存在内存中,disk节点保存在磁盘中被加入的节点为disk,如本例中rabbit@host1disk节点,rabbit@host2rabbit@host3ram节点可以通过rabbitmqctl cluster命令改变加入的集群以及节点类型该命令后可以加多个节点名称,指定的节点就会变成disk节点

如可以将本例中所有节点都改成disk节点

host2#rabbitmqctl stop_app

host2#rabbitmqctl reset 

host2#rabbitmqctl cluster rabbit@host1 rabbit@host2

host2#rabbitmqctl start_app

host3#rabbitmqctl stop_app

host3#rabbitmqctl reset 

host3#rabbitmqctl cluster rabbit@host1 rabbit@host3

host3#rabbitmqctl start_app

2)自动配置rabbitmq集群:

       rabbitmq的默认配置脚本为

         /etc/rabbitmq/rabbitmq.conf

     在其中添加

          [

           ...

           {rabbit, [

            ...

           {cluster_nodes, ['rabbit@host1', 'rabbit@host2', 'rabbit@host3']},

            ...

            ]},

            ...

            ].

     分别启动每个rabbitmq节点

     rabbitmq -detached

     rabbitmq就自动组成集群了

注意事项:

1)   每个节点的erlang cookie须相同,可以在启动服务器加setcookie参数设置相同的参数,也可以在home目录下设置相同的.erlang.cookie文件。

2)   节点间应该能相互解析,可以通过修改/etc/hosts文件实现

 监控

          RabbitMQ提供了一个web的监控页面系统,这个系统是以Plugin的方式进行调用的。

          首先,确定rabbitmq-env.conf中配置好的plugins目录的位置

           启动管理插件命令 :

        rabbitmq-plugins enable rabbitmq_management

        重新启动RabbitMQ,输入http://server-name:55672/mgmt/ 就能够进入到监控页面。默认的用户名和密码是: guest 和 guest


二 、应用实例   下载JAR包rabbitmq-java-client-3.4.1.zip地址:

                          http://download.csdn.net/detail/wang_qi_liang/8468533 

        (1)生产队列

package com.tennis.slave.mq;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.tennis.slave.bean.CallBackVideo;
public class VideoProduceQueueMq {
    private static final Logger logger = LoggerFactory
            .getLogger(VideoProduceQueueMq.class);
    private static final ConnectionFactory connf = new ConnectionFactory();
    public static boolean publish(CallBackVideo callBackVideo) {
        try {
            connf.setUri(“amqp://210.14.121.101”); //amqp://+服务器IP地址 现实中应该放入配置文件
            connf.setPort("5672");//RabbitMq的默认端口号为5672
            connf.setUsername("guest");//如果没有新建 用户默认用户geust 密码也是guest
            connf.setPassword("guest");
            Connection conn = connf.newConnection(); //获取连接
            Channel ch = conn.createChannel(); //channel是消息读写的通道 也就是信道
            ch.queueDeclare(QUEUE_NAME, false, false, false, null); //创建一个名为queue的队列,然后把消息发送过去
            String message = JSONObject.toJSONString(callBackVideo);//将对象转换成String

            /*现在可以发送消息,但是RabbitMQ不能把消息直接发送到队列,要发送到交换器,这个稍后介绍,这里使用默认交换器(exchange),它使用一个空字符串标识,routing_key参数必须指定为队列名称也就是第二个参数,这里为 QUEUE_NAME*/

            ch.basicPublish(exchange='', QUEUE_NAME, null, message.getBytes());
            logger.info("success==vidoe==ProduceQueueMq生产队列成功添加了Id为:{},evid为:{},上传状态statusMsg为:{}的视频信息",
            callBackVideo.getVid(), callBackVideo.getEvid(),//得到消费队列返回的状态,如果已经消费成功就删除该消息
            callBackVideo.getStatusMsg());
            ch.close();//关闭信道
            conn.close();//关闭连接
            return true;
        } catch (Exception e) {
            logger.error(
                    "error==vidoe==ProduceQueueMq生产队列添加视频信息Id为:{},evid为:{},上传状态statusMsg为:{}失败,异常信息e:{}",
                    callBackVideo.getVid(), callBackVideo.getEvid(),
                    callBackVideo.getStatusMsg(), e.getMessage());
            return false;
        }
    }

    public static void main(String[] args) {
        CallBackVideo c = new CallBackVideo();
        c.setVid(557L);
        c.setEvid("888888888");
        c.setStatusMsg("success");
        VideoProduceQueueMq.publish(c);

    }
}

       (2)消费队列

package com.gochinatv.vrs.framework.exend.taskassign;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.gochinatv.vrs.framework.bean.CallBackVideo;
import com.gochinatv.vrs.framework.bean.VideoProperty;
import com.gochinatv.vrs.framework.service.VideoPropertyService;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;

/**
 * 获取视频上传结果信息的消费者
 *
 * @author wangqiliang
 *
 */
public class VideoConsumerMq {
    private VideoPropertyService videoPropertyService;
    public void setVideoPropertyService(
            VideoPropertyService videoPropertyService) {
        this.videoPropertyService = videoPropertyService;
    }

    private static final Logger logger = LoggerFactory
            .getLogger(VideoConsumerMq.class);
    private static final ConnectionFactory connf = new ConnectionFactory();

   /*封装了获取信道的方式,包括连接和传进信息,因为消费是实时性的只要队列里面有就消费所以要有一个死循环进行不断连接也防止了由于一次连接超时还可以继续连接下一次所以单抽取处理进行了封装*/

    private Channel getChannel() {
        try {
            connf.setUri(“amqp://210.14.121.101”);//amqp://+服务器IP地址 现实中应该放入配置文件
            connf.setPort("5672");//RabbitMq的默认端口号为5672
            connf.setUsername("guest");//如果没有新建 用户默认用户geust 密码也是guest
            connf.setPassword("guest");
            Connection conn = connf.newConnection();//获取连接
            Channel ch = conn.createChannel();//创建信息
            return ch;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return null;
        }
    }
   /*由于是Spring 管理的实例所以容器启动后就启动一个线程启动消费方法进行消*/
    public void contextInit() {
        logger.debug("------------------init VideoConsumerMq  begin------------------");
        long begin = System.currentTimeMillis();
        new Thread(new Runnable() {
            @Override
            public void run() {
                consumer();
            }
        }).start();
        logger.debug("------------------init VideoConsumerMq end------------------");
        long end = System.currentTimeMillis();
        logger.debug("------------------init VideoConsumerMq cost:"
                + (end - begin) + " ms------------------");
    }

    @SuppressWarnings("unused")
    private void consumer() {
        try {
            Channel ch = getChannel(); //先获取连接创建信息调用封装的方法

/*为确保队列存在,再次执行queue_declare创建一个队列,我们可以多次运行该命令,如果不存在创建如果存在就不会在创建了,因为不能保证生产队列先执行还是消费队列先执行,所以重复声明队列来确保其存在*/
            ch.queueDeclare(QUEUE_NAME, false, false, false, null);
            QueueingConsumer consumer = new QueueingConsumer(ch);
            ch.basicConsume(QUEUE_NAME, consumer);
            while (true) {//无限消费
                if (consumer == null) {
                    ch = getChannel();
                    ch.queueDeclare(QUEUE_NAME, false, false, false, null);
                    consumer = new QueueingConsumer(ch);
                    ch.basicConsume(QUEUE_NAME, true, consumer);
                    if (consumer == null) {
                        // 1分钟后重连
                        Thread.sleep(60 * 1000);
                        continue;
                    }
                }
                QueueingConsumer.Delivery delivery = consumer.nextDelivery();
                String msg = new String(delivery.getBody());
                CallBackVideo callBackVideo = JSON.parseObject(msg,
                        CallBackVideo.class);//将消费的信息转换成对象
                updateVidoInfo(callBackVideo);//后台进行消费

              //消费成功后告诉队列该消息可以被删除了,如果不回调那么该消息永远存在就避免了消息还没有消费就丢失的问题了
                ch.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                logger.info("VideoConsumerMq消费队列成功消费了Id为:{},evid为:{},上传状态statusMsg为:{}的视频信息",
                        callBackVideo.getVid(), callBackVideo.getEvid(),
                        callBackVideo.getStatusMsg());
            }

        } catch (Exception e) {
            logger.error("VideoConsumerMq消费队列异常,导致原因肯能连接服务器:{}失败,异常信息为:" + e.getMessage());
        }
    }
}

三、浅谈原理

      AMQP(高级消息队列协议 Advanced Message Queue Protocol)
      AMQP当中有四个概念非常重要: 虚拟主机(virtual host),交换机(exchange),队列(queue)和绑定(binding)。一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创 建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。
      Producer 要产生消息必须要创建一个 Exchange ,Exchange 用于转发消息,但是它不会做存储,如果没有 Queue bind 到 Exchange 的话,它会直接丢弃掉 Producer 发送过来的消息,当然如果消息总是发送过去就被直接丢弃那就没有什么意思了,一个 Consumer 想要接受消息的话,就要创建一个 Queue ,并把这个 Queue bind 到指定的 Exchange 上,然后Exchange 会把消息转发到 Queue 那里,Queue 会负责存储消息,Consumer 可以通过主动 Pop 或者是 Subscribe 之后被动回调的方式来从 Queue 中取得消息。

Broker:消息队列服务器实体

消息:每个消息都有一个路由键(routing key)的属性。就是一个简单的字符串。

connection:应用程序与broker的网络连接。

channel:几乎所有的操作都在channel中进行,channel是进行消息读写的通道。客户端可建立多个channel,每个channel代表一个会话任务。

交换机:接收消息,根据路由键转发消息到绑定的队列

绑定:一个绑定就是基于路由键将交换机和队列连接起来的路由规则,所以交换机不过就是一个由绑定构成的路由表。

举例:一个具有路由键“key1”的消息要发送到两个队列,queueA和queueB。要做到这点就要建立两个绑定,每个绑定连接一个交换机和一个队列。两者都是由路由键“key1”触发,这种情况,交换机会复制一份消息并把它们分别发送到两个队列中。

队列:消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

三、交换机

交换机用来接收消息,转发消息到绑定的队列,是rabbitMq中的核心。

交换机共有4种类型:direct,topic,headers和fanout。

为什么不创建一种交换机来处理所有类型的路由规则?因为每种规则匹配时的CPU开销是不同的,所以根据不同需求选择合适交换机。

举例:一个"topic"类型的交换机会将消息的路由键与类似“dog.*”的模式进行匹配。一个“direct”类型的交换机会将路由键与 “dogs”进行比较。匹配末端通配符比直接比较消耗更多的cpu,所以如果用不到“topic”类型交换机带来的灵活性,就通过“direct”类型交 换机获得更高的处理效率。

1、Direct交换机:转发消息到routingKey指定队列(完全匹配,单播)。

routingKey与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发routingkey标记为dog的消息,不会转发dog.puppy,也不会转发dog.guard等。

2、Topic交换机:按规则转发消息(最灵活,组播)

 Topic类型交换机通过模式匹配分配消息的routing-key属性。将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。

它将routing-key和binding-key的字符串切分成单词。这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配不多不少一个单词。

例如,binding key:*.stock.#匹配routing key: usd.stock和eur.stock.db,但是不匹配stock.nana。

例如,“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*”只会匹配到“audit.irs”。

3、Fanout交换机:转发消息到所有绑定队列(最快,广播)

fanout交换机不处理路由键,简单的将队列绑定到交换机上,每个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。

很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。

4、Note

  • 如果没有队列绑定在交换机上,则发送到该交换机上的消息会丢失。
  • 一个交换机可以绑定多个队列,一个队列可以被多个交换机绑定。
  • 还有一些其他类型的交换机类型,如header、failover、system等,现在在当前的RabbitMQ版本中均未实现。
  • 因为交换机是命名实体,声明一个已经存在的交换机,但是试图赋予不同类型是会导致错误。客户端需要删除这个已经存在的交换机,然后重新声明并且赋予新的类型。
  • 交换机的属性:
    • 持久性:如果启用,交换机将会在server重启前都有效。
    • 自动删除:如果启用,那么交换机将会在其绑定的队列都被删掉之后删除自身。
    • 惰性:如果没有声明交换机,那么在执行到使用的时候会导致异常,并不会主动声明。

四、队列

  • 队列的属性:
    • 持久性:如果启用,队列将在Server服务重启前都有效。
    • 自动删除:如果启用,那么队列将会在所有的消费者停止使用之后自动删除自身。
    • 惰性:如果没有声明队列,那么在执行到使用的时候会导致异常,并不会主动声明。
    • 排他性:如果启用,队列只能被声明它的消费者使用。        

以上是我的心得,希望可以给大家带来帮助




 


0 0
原创粉丝点击