RabbitMQ---学习笔记

RabbitMQ的介绍:

RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。


消息队列的使用过程:

  1. 客户端连接到消息队列服务器,打开一个channel。
  2. 客户端声明一个exchange,并设置相关属性。
  3. 客户端声明一个queue,并设置相关属性。
  4. 客户端使用routing key,在exchange和queue之间建立好绑定关系。
  5. 客户端投递消息到exchange。

获取MQ链接:

ConnectionFactory是获取MQ连接的连接工厂,所有的连接都在工厂用获取、复用。

Connection是RabbitMQ的socket链接,它封装了socket协议相关部分逻辑。Connection 从 ConnectionFactory中获取

        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("localhost");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("testhost");
        factory.setUsername("admin");
        factory.setPassword("admin");
        // 通过工程获取连接
        Connection connection = factory.newConnection();

RabbitMQ的组件:

Channel是消息通道,MQ的很多业务操作是在Channel这个接口中完成的,包括定义Queue、定义Exchange、绑定Queue与Exchange、发布消息等。Channel从Connection中获取,每个连接可建立多个channel,每个channel代表一个会话任务。

consumer: 消息生产者,就是投递消息的程序。

producer: 消息消费者,就是接受消息的程序。

Exchange: Exchange是交换机/交换器,生产者将消息发送到Exchange,由Exchange将消息路由到一个或多个Queue中(无对应queue则丢弃)。从而实现生产者和消费者隔离

queue: 消息队列,用于存储消息。

vhost: 虚拟主机,一个虚拟主机持有一组交换机、队列和绑定。用户可以划分虚拟主机的粒度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创 建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。


RabbitMQ的四种投递方式/四种Exchange模式:

RouteKey : 即路由关键字,exchange会RouteKey进行消息投递。RouteKey设定的长度限制为255 bytes。

binding : 将Exchange 和 queue 绑定起来的操作。(可以理解为该队列对这个exchange上的消息感兴趣)

RabbitMQ中,所有生产者提交的消息都由Exchange来接受,然后Exchange按照特定的策略转发到Queue进行存储。

RabbitMQ提供了四种Exchange:fanout,direct,topic,header

性能排序:fanout > direct >> topic。比例大约为11:10:6

  1. Direct Exchange

任何发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue。

1. 一般情况可以使用rabbitMQ自带的Exchange:(该Exchange的名字为空字符串,下文称其为default Exchange)。
2. 这种模式下不需要将Exchange进行任何绑定(binding)操作
3. 消息传递时需要一个“RouteKey”,可以简单的理解为要发送到的队列名字。
4. 如果vhost中不存在RouteKey中指定的队列名,则该消息会被抛弃。

特点:不需要Exchange 绑定队列,需要指定RouteKey。

  1. Fanout Exchange

任何发送到Fanout Exchange的消息都会被转发到与该Exchange绑定(Binding)的所有Queue上。

1. 可以理解为路由表的模式
2. 这种模式不需要RouteKey
3. 这种模式需要提前将Exchange与Queue进行绑定,一个Exchange可以绑定多个Queue,一个Queue可以同多个Exchange进行绑定。
4. 如果接受到消息的Exchange没有与任何Queue绑定,则消息会被抛弃。

特点:不需要指定RouteKey,需要Exchange 和 绑定队列。

  1. Topic Exchange

任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上

1. 这种模式较为复杂,简单来说,就是每个队列都有其关心的主题,所有的消息都带有一个“标题”(RouteKey),Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。
2. 这种模式需要RouteKey,也需要提前绑定Exchange与Queue。
3. 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)。
4. “#”表示0个或若干个关键字,“*”表示一个关键字。如“log.*”能与“log.warn”匹配,无法与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
5. 同样,如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息。

特点:既需要RouteKey,也需要Exchange 绑定队列,且可以使用通配符匹配RouteKey;需要完全匹配绑定的队列和RouteKey才能正确发送。

  1. Header Exchange
headers类型的Exchange不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。
在绑定Queue与Exchange时指定一组键值对;当消息发送到Exchange时,RabbitMQ会取到该消息的headers(也是一个键值对的形式),对比其中的键值对是否完全匹配Queue与Exchange绑定时指定的键值对;如果完全匹配则消息会路由到该Queue,否则不会路由到该Queue。
该类型的Exchange没有用到过(不过也应该很有用武之地),所以不做介绍。

一些重要的参数/机制:

  1. 声明队列:
//参数分别是:name,durable,exclusive,auto-delete,消息其他参数
channel.queueDeclare(QUEUE_NAME, false, false, false, null);

durable : 持久化设置,适用于Exchange、Queue

exclusive : 排他队列/独占队列,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。

auto-delete: 自动删除,当该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。

deliveryMode=2 : 表示发送的消息会持久化。

//参数3设置为存储纯文本到磁盘
channel.basicPublish(x, x, MessageProperties.PERSISTENT_TEXT_PLAIN,x)
  1. 事务:

对事务的支持是AMQP协议的一个重要特性。假设当生产者将一个持久化消息发送给服务器时,因为consume命令本身没有任何Response返回,所以即使服务器崩溃,没有持久化该消息,生产者也无法获知该消息已经丢失。如果此时使用事务,即通过txSelect()开启一个事务,然后发送消息给服务器,然后通过txCommit()提交该事务,即可以保证,如果txCommit()提交了,则该消息一定会持久化,如果txCommit()还未提交即服务器崩溃,则该消息不会服务器就收。当然Rabbit MQ也提供了txRollback()命令用于回滚某一个事务。

  1. Comfirm机制:

使用事务固然可以保证只有提交的事务,才会被服务器执行。但是这样同时也将客户端与消息服务器同步起来,这背离了消息队列解耦的本质。Rabbit MQ提供了一个更加轻量级的机制来保证生产者可以感知服务器消息是否已被路由到正确的队列中——Confirm。如果设置channel为confirm状态,则通过该channel发送的消息都会被分配一个唯一的ID,然后一旦该消息被正确的路由到匹配的队列中后,服务器会返回给生产者一个Confirm,该Confirm包含该消息的ID,这样生产者就会知道该消息已被正确分发。对于持久化消息,只有该消息被持久化后,才会返回Confirm。Confirm机制的最大优点在于异步,生产者在发送消息以后,即可继续执行其他任务。而服务器返回Confirm后,会触发生产者的回调函数,生产者在回调函数中处理Confirm信息。如果消息服务器发生异常,导致该消息丢失,会返回给生产者一个nack,表示消息已经丢失,这样生产者就可以通过重发消息,保证消息不丢失。Confirm机制在性能上要比事务优越很多。但是Confirm机制,无法进行回滚,就是一旦服务器崩溃,生产者无法得到Confirm信息,生产者其实本身也不知道该消息吃否已经被持久化,只有继续重发来保证消息不丢失,但是如果原先已经持久化的消息,并不会被回滚,这样队列中就会存在两条相同的消息,系统需要支持去重。


RabbitMQ常见问题:

  1. 为什么使用消息队列?
    • 异步、解耦、削峰
  2. 使用消息队列有什么缺点?
    • 系统可用性降低
    • 系统复杂性增加
  3. 消息队列如何选型?(你为什么用这种MQ)
    • ActiveMQ,RabbitMQ,RocketMQ,Kafka 的对比
  4. 如何保证消息队列是高可用的?
    • rabbitMQ有普通集群和镜像集群模式
  5. 如何保证消息不被重复消费?(如何保证消息队列的幂等性)
    • ACK确认机制
    • (ack消息没有送达的前提)使用redis记录是否消费过
  6. 如何保证消费的可靠性传输?(包括重复消费和丢失消息等,重复消费已有,这里讨论消息丢失)
    • 生产者丢失消息:
      • 事务 (同步堵塞)
      • Comfirm机制(异步)(推荐)
    • Exchange、消息队列丢失消息:
      • Exchange、queue 持久化;deliveryMode=2
    • 消费者丢失消息:
      • 采用手动确认消息,不要用自动确认消息。
  7. 如何保证消息的顺序性?
    • 【保证消息的顺序入队】需要保持先后顺序的消息放到同一个消息队列中,然后只用一个消费者去消费该队列。

代码示例:Topic Exchange

//生产者
public class Send {

    private final static String EXCHANGE_NAME = "test_exchange_topic";

    public static void main(String[] argv) throws Exception {
        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");

        // 消息内容
        String message = "Hello World!!";
        channel.basicPublish(EXCHANGE_NAME, "routekey.1", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

        channel.close();
        connection.close();
    }
}

//消费者
public class Recv {

    private final static String QUEUE_NAME = "test_queue_topic_work_1";

    private final static String EXCHANGE_NAME = "test_exchange_topic";

    public static void main(String[] argv) throws Exception {

        // 获取到连接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();

        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 绑定队列到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "routekey.*");

        // 同一时刻服务器只会发一条消息给消费者
        channel.basicQos(1);

        // 定义队列的消费者
        QueueingConsumer consumer = new QueueingConsumer(channel);
        // 监听队列,手动返回完成
        channel.basicConsume(QUEUE_NAME, false, consumer);

        // 获取消息
        while (true) {
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody());
            System.out.println(" [Recv_x] Received '" + message + "'");
            Thread.sleep(10);
            //消费者成功后向queue发送确认
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
    }
}



RabbitMQ的集群(待更新)


参考:

https://blog.csdn.net/hellozpc/article/details/81436980

https://www.cnblogs.com/williamjie/p/9481780.html

https://www.cnblogs.com/williamjie/p/9481774.html

https://www.cnblogs.com/vipstone/p/9368106.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值