RabbitMQ入门(简介、搭建环境、五种工作模式)介绍

1. RabbitMQ介绍

RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP
的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有很多公开标准(如 COBAR的 IIOP ,或者是 SOAP
等),但是在异步消息处理中却不是这样,只有大企业有一些商业实现(如微软的 MSMQ ,IBM 的 Websphere MQ 等),因此,在
2006 年的 6 月,Cisco 、Redhat、iMatix 等联合制定了 AMQP 的公开标准。 RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的。该公司在2010年4月被SpringSource(VMWare的一个部门)收购。在2013年5月被并入Pivotal。其实VMWare,Pivotal和EMC本质上是一家的。不同的是VMWare是独立上市子公司,而Pivotal是整合了EMC的某些资源,现在并没有上市。
RabbitMQ的官网是http://www.rabbitmq.com
百度百科amqp协议介绍https://baike.baidu.com/item/AMQP/8354716?fr=aladdin
注意:RabbitMQ是采用erlang语言开发的,所以必须有erlang环境才可以运行


2. RabbitMQ与其他MQ对比

RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第1张图片


3. RabbitMQ环境搭建

  • Windows环境搭建
  1. 安装erlang运行环境,配置环境变量,跟安装JDK和配置JDK环境变量一样,这里就不赘述了。
  2. 下载rabbitmq-server,启动即可
  • 基于Docker 环境搭建(CentOS 7.3)
  1. 不会安装Docker的可以参阅:Docker安装教程(CentOS 7.3)
  2. 一行命令即可安装好RabbitMQ:
    docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq rabbitmq:3-management
    上面命令行含义:
    15672 :表示 RabbitMQ 控制台端口号,可以在浏览器中通过控制台来执行 RabbitMQ 的相关操作。
    5672 : 表示 RabbitMQ 所监听的 TCP 端口号,应用程序可通过该端口与 RabbitMQ 建立 TCP 连接,并完成后续的异步消息通信
    RABBITMQ_DEFAULT_USER:用于设置登陆控制台的用户名,这里我设置 admin
    RABBITMQ_DEFAULT_PASS:用于设置登陆控制台的密码,这里我设置 admin

4. RabbitMQ管理平台介绍

RabbitMQ环境搭建好后浏览器访问http://127.0.0.1:15672即可看到如下界面:
RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第2张图片
简单描述下上图中中控制台的列表的作用:

  • Overview :用于查看 RabbitMQ 的一些基本信息(消息队列、消息发送速率、节点、端口和上下文信息等)
  • Connections:用于查看 RabbitMQ 客户端的连接信息
  • Channels:用户查看 RabbitMQ 的通道信息
  • Exchange:用于查看 RabbitMQ 交换机
  • Queues:用于查看 RabbitMQ 的队列
  • Admin:用于管理用户,可增加用户

5. Virtual Host与权限管理

  • RabbitMQ有一个Virtual Host概念:
    它表示运行在rabbit service上的一个独立的空间,每个Virtual Host都有一个名字,我们连接使用RabbitMQ消息服务时,需要指定Virtual Host名来连接;
    可能还是不太明白,下面我举个例子吧:
    其实就类似于MySQL中数据库的概念,一个MySQL服务可以创建多个数据库(同样一个rabbit service也可以创建多个Virtual Host),每个数据库都有一个名字(每个Virtual Host也有一个名字,以/开始),连接MySQL时需要指定数据库的名字(连接ribbit service时也需要指定一个Virtual Host的名字);

  • 创建用户,为用户分配权限
    RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第3张图片
    设置用户能访问哪些Virtual Host,一个用户可以分配多个Virtual Host,用逗号隔开
    创建Virtual Host(注意:以/开头表示一个路径):
    RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第4张图片
    为用户分配访问Virtual Host权限:
    点击表格中的用户名,打开页面在Permissions下的VirtualHost下拉列表中选择即可。
    RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第5张图片

  • 使用Virtual Host的好处:

  1. 不同团队可以使用自己不同的独立Virtual Host,每个独立的Virtual Host之间是相互隔离的,也就是说在不同的Virtual Host中可以创建同名的queue也可以的,避免不同团队创建queue时命名冲突。
  2. 适合大型互联网公司进行使用区分不同的业务逻辑。
    RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第6张图片
    部分图片来源:蚂蚁课堂


引入:

RabbitMQ的五种模式:

  1. 简单队列(p2p)
  2. 工作队列(Work Queue)
  3. 发布/订阅模式(Publish/Subscribe)
  4. 路由模式(Routing)
  5. 通配符模式(Topics)

6. RabbitMQ"简单队列"(p2p)与"工作队列"模式

1. 简单队列(点对点模式):
如图:
RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第7张图片
伪代码:
生产者:
消费者:
2. 工作队列:

  • 工作队列也叫做公平队列,处理能力强的消费者就多消费,“能者多劳”;
  • 实现原理(应答模式):使用basicQos(1)方法,设置每次发送给同一消费者一条消息,消费者消费成功应答后,再发送下一条消息,这样就保证了应答快的消费者消费的消息多些。
    如图:
    RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第8张图片

7. RabbitMQ消息Ack应答模式

  • 为了确保消息不会丢失,RabbitMQ支持消息应答。消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕,RabbitMQ就可以删除它了。 如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。
  • 这样,你就可以确认即使消费者偶尔挂掉也不会丢失任何消息了,没有任何消息超时限制;只有当消费者挂掉时,RabbitMQ才会重新投递。即使处理一条消息会花费很长的时间。
  • 消息应答是默认打开的。我们通过显示的设置autoAsk=true关闭这种机制。现即自动应答开,一旦我们完成任务,消费者会自动发送应答。通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者。

8. RabbitMQ持久化机制

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。当然还是会有一些小概率事件会导致消息丢失。

队列持久化:
我们就hello队列持久化,在声明队列名称时,持久化队列,生产端和消费端都要
注意:此时只是做到了消息队列的持久化,消息还未持久化

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

消息持久化:
上面队列持久化仅仅是消息队列持久化,但是消息并非持久化

channel.basic_publish(exchange='',
                      routing_key='hello',
                      body='hello',
                      properties=pika.BasicProperties(
                          delivery_mode=2,  # make message persistent
                      ))
 
# 增加properties,这个properties 就是消费端 callback函数中的properties
# delivery_mode = 2  持久化消息

设置队列持久化和消息持久化后,重启RabbitMQ服务后,队列和消息都还在。

总结:

  • 队列持久化需要在声明队列时添加参数 durable=True,这样在rabbitmq崩溃时也能保存队列
  • 仅仅使用durable=True ,只能持久化队列,不能持久化消息
  • 消息持久化需要在消息生成时,添加参数 properties=pika.BasicProperties(delivery_mode=2)

9. RabbitMQ “发布/订阅”模式(常用)

生产者与队列之间多了一个交换机:
注:交换机不做缓存,只做转发

  • 生产者发送消息不会向传统方式直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列(若交换机没有绑定的队列,消息将会丢失,也就是说交换机不会保存消息),队列在将消息以推送或者拉取方式给消费者进行消费,这和我们之前学习Nginx有点类似。 交换机的作用根据具体的路由策略分发到不同的队列中。

     交换机有四种类型:

  1. Direct exchange(直连交换机)是根据消息携带的路由键(routing key)将消息投递给对应队列的
  2. Fanout exchange(扇型交换机)将消息路由给绑定到它身上的所有队列 (默认)
  3. Topic exchange(主题交换机)队列通过路由键绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列
  4. Headers exchange(头交换机)类似主题交换机,但是头交换机使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。

图1:
RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第9张图片
图2:
RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第10张图片
伪代码:

  • 生产者:
/**
 * pub/sub模式 生产者
 * 交换机类型:扇形交换机 fanout
 * 注意:交换机若没有绑定的队列,则消息会丢失(交换机不做缓存,只做转发)
 *      只要队列创建了,并与交换机绑定,队列就有缓存功能。
 */
public class Producer {
    //交换机名称
    private static final  String DESTINATION_NAME = "my_fanout_destination";

    public static void main(String[] args) throws Exception {
        System.out.println("生产者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 生产者绑定交换机 args1:交换机名称  args2: 交换机类型
        channel.exchangeDeclare(DESTINATION_NAME,"fanout");
        //4. 创建要发送的消息
        String msg = "my_fanout_destination_msg!!!";
        System.out.println("发送消息:"+msg);
        //5. 发送消息
        channel.basicPublish(DESTINATION_NAME,"",null,msg.getBytes());
        //6. 关闭通道和连接
        channel.close();
        connection.close();
    }
}
  • 消费者(邮件消费者):
/**
 *邮件消费者
 */
public class ConsumerEmailFanout {
    //交换机名称
    private static final  String DESTINATION_NAME = "my_fanout_destination";
    //队列名称
    private static final  String EMAIL_QUEUE = "email_queue";


    public static void main(String[] args) throws Exception {
        System.out.println("邮件消费者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 消费者声明队列
        channel.queueDeclare(EMAIL_QUEUE,false,false,false,null);
        //4. 消费者队列绑定交换机
        channel.queueBind(EMAIL_QUEUE,DESTINATION_NAME,"");
        //5. 消费者监听消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("邮件消费者获取生产消息:"+msg);
            }
        };
        channel.basicConsume(EMAIL_QUEUE,defaultConsumer);
    }
}

  • 消费者(短信消费者):
/**
 *短信消费者
 */
public class ConsumerSmsFanout {
    //交换机名称
    private static final  String DESTINATION_NAME = "my_fanout_destination";
    //队列名称
    private static final  String SMS_QUEUE = "sms_queue";


    public static void main(String[] args) throws Exception {
        System.out.println("短息消费者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 消费者声明队列
        channel.queueDeclare(SMS_QUEUE,false,false,false,null);
        //4. 消费者队列绑定交换机
        channel.queueBind(SMS_QUEUE,DESTINATION_NAME,"");
        //5. 消费者监听消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("短信消费者获取生产消息:"+msg);
            }
        };
        channel.basicConsume(SMS_QUEUE,defaultConsumer);
    }
}

MqConnectionUtils工具类:

/**
 * RabbitMQ连接工具类
 */
public class MqConnectionUtils {

    public static Connection  newConnection() throws  Exception{
        //1. 定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置服务器地址
        factory.setHost("127.0.0.1");
        //3. 设置协议端口号
        factory.setPort(5672);
        //4. 设置Virtual Host(类似于数据库名)
        factory.setVirtualHost("/admin_vhost");
        //5. 设置用户名
        factory.setUsername("admin");
        //6. 设置密码
        factory.setPassword("admin");
        //7. 创建新的连接
        Connection connection = factory.newConnection();
        return connection;
    }
}

10. RabbitMQ路由模式(常用)

图:
RabbitMQ入门(简介、搭建环境、五种工作模式)介绍_第11张图片
伪代码:
生产者:

/**
 * 路由模式(RoutingKey) 生产者
 * 交换机类型:扇形交换机 direct
 */
public class Producer {
    //交换机名称
    private static final  String Direct_NAME = "direct_exchange";

    public static void main(String[] args) throws Exception {
        System.out.println("路由模式生产者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 生产者绑定交换机 args1:交换机名称  args2: 交换机类型
        channel.exchangeDeclare(Direct_NAME,"direct");
        //路由键
        String routingKey = "sms";
        //4. 创建要发送的消息
        String msg = "my_fanout_destination_msg!!!  "+routingKey;
        //5. 发送消息
        channel.basicPublish(Direct_NAME,routingKey,null,msg.getBytes());
        System.out.println("发送消息:"+msg);
        //6. 关闭通道和连接
        channel.close();
        connection.close();

        //注意:如果消费者没有绑定交换机和队列,则消息会丢失
    }
}

邮件消费者:

/**
 *邮件消费者
 */
public class ConsumerEmailDirect {
    //交换机名称
    private static final  String Direct_NAME = "direct_exchange";
    //队列名称
    private static final  String EMAIL_QUEUE = "email_queue";


    public static void main(String[] args) throws Exception {
        System.out.println("邮件消费者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 消费者声明队列
        channel.queueDeclare(EMAIL_QUEUE,false,false,false,null);
        //4. 消费者队列 路由键绑定交换机(可以绑定多个路由键)
        channel.queueBind(EMAIL_QUEUE,Direct_NAME,"email");
        channel.queueBind(EMAIL_QUEUE,Direct_NAME,"sms");
        //5. 消费者监听消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("邮件消费者获取生产消息:"+msg);
            }
        };
        //true 应答
        channel.basicConsume(EMAIL_QUEUE,true,defaultConsumer);
    }
}

短信消费者:

/**
 *短信消费者
 */
public class ConsumerSmsDirect {
    //交换机名称
    private static final  String Direct_NAME = "direct_exchange";
    //队列名称
    private static final  String SMS_QUEUE = "sms_queue";


    public static void main(String[] args) throws Exception {
        System.out.println("短息消费者启动...");
        //1. 建立MQ链接
        Connection connection = MqConnectionUtils.newConnection();
        //2. 创建通道
        Channel channel = connection.createChannel();
        //3. 消费者声明队列
        channel.queueDeclare(SMS_QUEUE,false,false,false,null);
        //4. 消费者队列 路由键绑定交换机(可以绑定多个路由键)
        channel.queueBind(SMS_QUEUE,Direct_NAME,"sms");
        //5. 消费者监听消息
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("短信消费者获取生产消息:"+msg);
            }
        };
        //true 应答
        channel.basicConsume(SMS_QUEUE,true,defaultConsumer);
    }
}


11. SpringBoot整合RabbitMQ

参见:SpringBoot整合RabbitMQ

12. RabbitMQ幂等性解决方案

待续…

你可能感兴趣的:(MQ)