RabbitMQ学习(二):RabbitMQ的基本概念

RabbitMQ相关概念

  • RabbitMQ是一个Erlang开发的AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的开源实现。是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在。
  • 主要特征
    1. 可靠性:持久化、传输确认、发布确认等机制来保证可靠性。
    2. 扩展性:支持动态扩展集群中的节点
    3. 高可用:队列可在集群中设置镜像,部分节点出现问题仍然可用
    4. 多协议:AMQP协议、STOMP、MOTT等多种消息中间件协议
    5. 多语言:java、Python、Ruby、PHP、C#、JavaScript、Go、Object-C等
    6. 支持插件:如web管理端。
  • 消息队列有三个基本概念: 发送方、消息队列、消费方。RabbitMQ 在这个基本概念之上, 多做了一层抽象, 在发消息者和队列之间, 加入了交换器 (Exchange)。这样发消息者和消息队列就没有直接联系,转而变成发消息者把消息发给交换器,交换器根据调度策略再把消息转发给消息队列。消息生产者并没有直接将消息发送给消息队列,而是通过建立与Exchange的Channel,将消息发送给Exchange。Exchange根据路由规则,将消息转发给指定的消息队列。消息队列储存消息,等待消费者取出消息。消费者通过建立与消息队列相连的Channel,从消息队列中获取消息。

RabbitMQ几种应用模式

在这里插入图片描述

    // 获取MQ的连接  下面代码获取连接都是使用的这个工具类
    public static Connection newConnection() throws IOException, TimeoutException {
        // 1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 2.设置连接地址
        factory.setHost("192.168.100.150");
        // 3.设置用户名称
        factory.setUsername("admin");
        // 4.设置用户密码
        factory.setPassword("admin");
        // 5.设置amqp协议端口号
        factory.setPort(5672);
        // 6.设置VirtualHost地址
        factory.setVirtualHost("adminDemo");
        Connection connection = factory.newConnection();
        return connection;
    }
  • 简单模式
    在这里插入图片描述
    • 消息产生消息,将消息放入队列
    • 消息的消费者(consumer) 监听(while) 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除。
    • Demo演示
    private static String QUEUE_NAME = "demo_hello";
      //获取连接
        Connection connection = MQConnectionUtils.newConnection();
        //创建通道
        Channel channel = connection.createChannel();
        // 创建一个队列:队列名称,是否持久化,是否自动删除队列,是否排外
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 1; i <= 25; i++) {
            // 创建 msg
            String msg = "生成 ---" + i;
            // 生产者发送消息者     MessageProperties.PERSISTENT_TEXT_PLAIN 设置消息的持久化(消息没有接收到也不会丢失)
            channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes("UTF-8"));
        }
        // 关闭通道和连接
        channel.close();
        connection.close();

在这里插入图片描述

    private static String QUEUE_NAME = "demo_hello";
     	 //获取连接
        Connection connection = MQConnectionUtils.newConnection();
       	 //创建通道
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
                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");
            }
        };
        // 设置应答模式 如果为true情况下 表示为自动应答模式 false 表示为手动应答
        channel.basicConsume(QUEUE_NAME, true, defaultConsumer);

在这里插入图片描述

  • work queues(工作模式)
    在这里插入图片描述
    • 多个消费者可以订阅同一个队列,这时队列中的消息会被平均分摊给多个消费者进行处理,而不是每个消费
      者都收到所有的消息并处理。
    • 这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。
    • Demo演示
      工作队列只需要将上面的消费者多复制几分就可以了。
      如果发现消息分发不均匀可以设置
        /**
         * 公平队列原理:队列服务器向消费者发送消息的时候,消费者采用手动应答模式,
         * 队列服务器必须要收到消费者发送ack结果通知,才会继续发送一下一个消息
         * 此处设置一次只消费1个
         */
        channel.basicQos(1);

默认情况下,rabbitmq开启了消息的自动应答。此时,一旦rabbitmq将消息分发给了消费者,就会将消息从内存中删除。这种情况下,如果正在执行的消费者被“杀死”或“崩溃”,就会丢失正在处理的消息。 如果想要确保消息不丢失,我们需要设置消息应答方式为手动应答。设置为手工应答后,消费者接受并处理完一个消息后,会发送应答给rabbitmq,rabbitmq收到应答后,会将该条消息从内存中删除。如果一个消费者在处理消息的过程中“崩溃”,rabbitmq没有收到应答,那么”崩溃“前正在处理的这条消息会重新被分发到别的消费者。

        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");
                // 手动应答 模式 告诉给队列服务器 已经处理成功
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 4.设置应答模式 如果为true情况下 表示为自动应答模式 false 表示为手动应答
        channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
  • publish/subscribe(发布订阅)
    在这里插入图片描述
    • 1个生产者,多个消费者。每一个消费者都有自己的一个队列。生产者没有将消息直接发送到队列,而是发送到了交换机。每个队列都要绑定到交换机。生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
    • X(Exchanges)接收生产者发送的消息。知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。
    • 这种模式不需要RouteKey
    • Demo演示
//生产者
	private static final String DESTINATION_NAME = "rabbitMq_fanout";
	// 1. 建立mq连接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.创建通道
		Channel channel = connection.createChannel();
		// 3.生产者绑定交换机 参数1:交换机名称 参数2:交换机类型
		channel.exchangeDeclare(DESTINATION_NAME, "fanout");
		// 4.创建消息
		String msg = "rabbitMq_fanout";
		System.out.println("生产者投递消息:" + msg);
		// 5.发送消息
		channel.basicPublish(DESTINATION_NAME, "",null , msg.getBytes());
		// 6.关闭通道 和连接
		channel.close();
		connection.close();
//消费者
 // 交换机名称
    private static final String DESTINATION_NAME = "rabbitMq_fanout";
    public static void main(String[] args) throws IOException, TimeoutException {
        // 1. 建立mq连接
        Connection connection = MQConnectionUtils.newConnection();
        // 2.创建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消费者启动");
        FanoutConsumer.smsConsumer(channel);
        System.out.println("邮件消费启动");
        FanoutConsumer.emailConsumer(channel);
    }
    private static final String SMS_QUEUE = "Sms";
    public static void smsConsumer(Channel channel) throws IOException {
        // 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, true, defaultConsumer);
    }
    private static final String EMAIL_QUEUE = "Email";
    public static void emailConsumer(Channel channel) throws IOException {
        // 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, true, defaultConsumer);
    }
  • routing(路由模式)
    在这里插入图片描述
    • 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。
    • 任何发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue
    • 一般情况可以使用rabbitMQ自带的Exchange:”"(该Exchange的名字为空字符串,下文称其为default Exchange)。
    • 这种模式下不需要将Exchange进行任何绑定(binding)操作。
    • 消息传递时需要一个“RouteKey”,可以简单的理解为要发送到的队列名字。
    • 如果vhost中不存在RouteKey中指定的队列名,则该消息会被抛弃。
    • Demo演示
//生产者
	// 交换机名称
	private static final String DESTINATION_NAME = "rabbitMq_direct";
		// 1. 建立mq连接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.创建通道
		Channel channel = connection.createChannel();
		// 3.生产者绑定交换机 参数1 交换机名称 参数2 交换机类型
		channel.exchangeDeclare(DESTINATION_NAME, "direct");
		//这个是路由键的名称,方便测试
		String s1="sms";
		// 4.创建消息
		String msg = "rabbitMq_direct---:" +s1;
		System.out.println("生产者投递消息:" + msg);
		// 5.发送消息  routingKey:email
		channel.basicPublish(DESTINATION_NAME, s1, null, msg.getBytes());
		// 6.关闭通道 和连接
		channel.close();
		connection.close();
//消费者
   // 交换机名称
    private static final String DESTINATION_NAME = "rabbitMq_direct";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQConnectionUtils.newConnection();
        // 2.创建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消费者启动");
        DirectConsumer.smsConsumer(channel);
        System.out.println("邮件消费者启动");
        DirectConsumer.emailConsumer(channel);
    }
    private static final String SMS_QUEUE = "Sms_msg";
    public static void smsConsumer( Channel channel) throws IOException {
        // 3.消费声明队列
        channel.queueDeclare(SMS_QUEUE, false, false, false, null);
        // 4.消费者队列绑定交换机 绑定路由键
        channel.queueBind(SMS_QUEUE, DESTINATION_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);
            }
        };
        channel.basicConsume(SMS_QUEUE, true, defaultConsumer);
    }
    
    private static final String EMAIL_QUEUE = "Email_msg";
    public static void emailConsumer( Channel channel) throws IOException {
        // 3.消费声明队列
        channel.queueDeclare(EMAIL_QUEUE, false, false, false, null);
        // 4.消费者队列绑定交换机 绑定路由键,可以设置多个
        channel.queueBind(EMAIL_QUEUE, DESTINATION_NAME, "email");
        // 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, true, defaultConsumer);
    }

  • topic(主题模式)
    在这里插入图片描述
    • Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“ * ”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”
    • 任何发送到Topic Exchange的消息都会被转发到所有关心RouteKey中指定话题的Queue上
    • 这种模式较为复杂,简单来说,就是每个队列都有其关心的主题,所有的消息都带有一个“标题”(RouteKey),Exchange会将消息转发到所有关注主题能与RouteKey模糊匹配的队列。
    • 这种模式需要RouteKey,也许要提前绑定Exchange与Queue。
    • 在进行绑定时,要提供一个该队列关心的主题,如“#.log.#”表示该队列关心所有涉及log的消息(一个RouteKey为”MQ.log.error”的消息会被转发到该队列)。
    • “#”表示0个或若干个关键字,“ * ”表示一个关键字。如“log.*”能与“log.warn”匹配,无法与“log.warn.timeout”匹配;但是“log.#”能与上述两者匹配。
    • 同样,如果Exchange没有发现能够与RouteKey匹配的Queue,则会抛弃此消息。
    • Demo演示
// 交换机名称   生产者
	private static final String DESTINATION_NAME = "rabbitMq_topic";
		// 1. 建立mq连接
		Connection connection = MQConnectionUtils.newConnection();
		// 2.创建通道
		Channel channel = connection.createChannel();
		// 3.生产者绑定交换机 参数1 交换机名称 参数2 交换机类型
		channel.exchangeDeclare(DESTINATION_NAME, "topic");
		//routingKey
		String s1="log.sms.test.demo";
		// 4.创建消息
		String msg = "rabbitMq_msg_topic:"+s1 ;
		System.out.println("生产者投递消息:" + msg);
		// 5.发送消息  routingKey:email
		channel.basicPublish(DESTINATION_NAME, s1, null, msg.getBytes());
		// 6.关闭通道 和连接
		channel.close();
		connection.close();

 // 交换机名称
    private static final String DESTINATION_NAME = "rabbitMq_topic";
    public static void main(String[] args) throws Exception {
        // 1. 建立mq连接
        Connection connection = MQConnectionUtils.newConnection();
        // 2.创建通道
        Channel channel = connection.createChannel();
        System.out.println("短信消费者启动");
        TopicConsumer.smsConsumer(channel);
        System.out.println("邮件消费者启动");
        TopicConsumer.maileConsumer(channel);
    }
    private static final String SMS_QUEUE = "topic_sms";
    public static void smsConsumer(Channel channel) throws IOException {
        channel.queueDeclare(SMS_QUEUE, false, false, false, null);
        channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "log.sms");
        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, true, defaultConsumer);
    }
    private static final String MAILE_QUEUE = "topic_email";
    public static void maileConsumer(Channel channel) throws IOException {
        // 3.消费声明队列
        channel.queueDeclare(MAILE_QUEUE, false, false, false, null);
        // *只要前缀相同都能收到
		//channel.queueBind(SMS_QUEUE, DESTINATION_NAME, "log.*");
        //可以匹配后面所有的词
        channel.queueBind(MAILE_QUEUE, DESTINATION_NAME, "log.#");
        // 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(MAILE_QUEUE, true, defaultConsumer);
    }


  • RPC
    在这里插入图片描述
    这个模式听说用的很少,我也没有去了解这个模式。以后如果遇到会再来补充的。

猜你喜欢

转载自www.cnblogs.com/yangk1996/p/12663602.html