RabbitMQ工作队列之发布/订阅模式(三)

RabbitMQ工作队列之发布/订阅模式(三)

本篇博客开始接触到exchange交换器,后续的几个模式都与exchange有莫大的关系,本身RabbitMQ就是基于exchange进行消息分发,如发布订阅就是基于exchange实现。与传统遵循JMS规范稍有不同。

RabbitMQ发布/订阅模式三种使用姿势:

  • 原生客户端连接RabbitMQ
  • 基于Spring xml集成RabbitMQ
  • 基于spring boot集成RabbitMQ

官网模式截图:

RabbitMQ工作队列之发布/订阅模式(三)_第1张图片

目录

[TOC]来生成目录:

文章目录

  • RabbitMQ工作队列之发布/订阅模式(三)
    • 官网模式截图:
      • 目录
    • @[toc]
    • 1、原生客户端连接RabbitMQ
        • 1.1、构建maven项目,pom文件:
        • 1.2、创建连接工厂
        • 1.3、构建生产者
        • 1.4、构建1号消费者
        • 1.5、构建消费者2号
        • 1.6、运行效果(注意,先启动消费者让队列绑定交换器,再启动生产者)
    • 2、spring集成RabbitMQ
        • 2.1、构建maven工程
        • 2.2、创建context.xml
        • 2.3、创建rabbitmq.xml
        • 2.4、创建生产者
        • 2.5、定义1号消费者
        • 2.6、定义消费者2号
        • 2.7、运行效果
    • 3、spring boot 集成RabbitMQ
        • 3.1、构建spring boot项目,pom文件加入依赖
        • 3.2、创建application.yml配置文件(如果是application.properties,修改一下后缀)
        • 3.3、spring boot入口文件
        • 3.4、静态参数获取类
        • 3.5、配置RabbitMQ各种定义
        • 3.6、使用web操作模拟生产者
        • 3.7、创建消费者1
        • 3.8、 创建消费者2
        • 3.9、 运行效果图
        • 3.10、简析RabbitMQDeclareUtil类
    • 4、小结

1、原生客户端连接RabbitMQ

1.1、构建maven项目,pom文件:



    4.0.0

    edurabbitmq-client
    rabbitmq-client
    1.0-SNAPSHOT


    
        
        
        
            com.rabbitmq
            amqp-client
            5.3.0
        
    


1.2、创建连接工厂

package com.edu.rabbitmq;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 22:01
 * @description :获取连接
 * @note 注意事项
 */
public class ConnectionUtils {

    //获取接连
    public static Connection getConnection() throws IOException, TimeoutException {

        ConnectionFactory factory = new ConnectionFactory();

        //设置连接MQ的IP地址
        factory.setHost("192.168.199.128");
        //设置连接端口号
        factory.setPort(5672);
        //设置要接连MQ的库(域)
        factory.setVirtualHost("/test_vh");
        //连接帐号
        factory.setUsername("root");
        //连接密码
        factory.setPassword("123456");
        return factory.newConnection();
    }

}

1.3、构建生产者

package com.edu.handler;

import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 22:16
 * @description :消息生产者
 * @note 注意事项
 */
public class Send {

    //队列名称
    public static String QUEUE_NAME = "test_publish_and_subscribe_queue";

    //定义交换器名称
    public static  String EXCHANGE_NAME = "test_ps";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {

        //获取连接
        Connection connection = ConnectionUtils.getConnection();

        //从连接中获取一个通道
        Channel channel = connection.createChannel();

        //创建消息声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        /**
         *
         * 声明一个交换器
         *
         * 文档地址:https://rabbitmq.github.io/rabbitmq-java-client/api/current/com/rabbitmq/client/Channel.html#exchangeDeclare-java.lang.String-com.rabbitmq.client.BuiltinExchangeType-boolean-boolean-java.util.Map-
         * 该方法定义了许多的重载,拿出完整参数的方法来讲
         * AMQP.Exchange.DeclareOk exchangeDeclare​(String exchange,
         *                                         BuiltinExchangeType type,
         *                                         boolean durable,
         *                                         boolean autoDelete,
         *                                         Map arguments)
         *                                  throws IOException
         *
         *  @param exchange 交换器的名字
         *  @param type 交换器的类型:direct, topic, headers, fanout
         *  @param durable 是否持久化,true持久化,false不持久化
         *  @param autoDelete 服务器不再使用该队列时,是否自动删除,true删除,false不删除
         *  @param arguments 其他参数,其实是定义交换器的构造方法
         */
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");



        for (int i = 0; i < 10; i++) {
            Thread.sleep(500);//模拟耗时操作,别发那么快
            //自定义消息
            String msg = "hello word" + i;
            //发布消息,通过交换器名称进行发布消息,无需指定队列名称
            channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
            System.out.println("-->send " + msg);
        }

        channel.close();//关闭通道
        connection.close();//关闭连接


    }

}

1.4、构建1号消费者

package com.edu.handler;

import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 22:16
 * @description :消息消费者
 * @note 注意事项
 */
public class Customer {

    //队列名称
    public static String QUEUE_NAME = "test_publish_and_subscribe_queue";

    //定义交换器名称
    public static  String EXCHANGE_NAME = "test_ps";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接
        Connection connection = ConnectionUtils.getConnection();

        //获取通道
        Channel channel = connection.createChannel();

        //获取交换器
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        //创建消息声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);


        /**
         * 交换器绑定队列
         * 该方法定义了2个重载
         * AMQP.Queue.BindOk queueBind​(String queue,
         *                             String exchange,
         *                             String routingKey,
         *                             Map arguments)
         *                      throws IOException
         *
         *  @param queue 队列名称
         *  @param exchange 交换器名称
         *  @param routingKey 用于绑定的路由密钥
         *  @param arguments 其他参数,其实是定义交换器的构造方法
         */
        channel.queueBind(QUEUE_NAME, "test_ps", "");

        //定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                //获取并转成String
                String message = new String(body, "UTF-8");
                System.out.println("-->1号消费者收到消息,msg:"+message);
            }
        };

        //监听队列
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

}

1.5、构建消费者2号

package com.edu.handler;

import com.edu.rabbitmq.ConnectionUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 22:16
 * @description :消息消费者2
 * @note 注意事项
 */
public class Customer2 {

    //队列名称
    public static String QUEUE_NAME = "test_publish_and_subscribe_queue2";

    //定义交换器名称
    public static  String EXCHANGE_NAME = "test_ps";

    public static void main(String[] args) throws IOException, TimeoutException {

        //创建连接
        Connection connection = ConnectionUtils.getConnection();

        //获取通道
        Channel channel = connection.createChannel();

        //获取交换器
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        //创建消息声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        
        /**
         * 交换器绑定队列,请记住,优先启动消费者,让队列先绑定交换器,再由生产者发送消息
         * 该方法定义了2个重载
         * AMQP.Queue.BindOk queueBind​(String queue,
         *                             String exchange,
         *                             String routingKey,
         *                             Map arguments)
         *                      throws IOException
         *
         *  @param queue 队列名称
         *  @param exchange 交换器名称
         *  @param routingKey 用于绑定的路由密钥
         *  @param arguments 其他参数,其实是定义交换器的构造方法
         */
        channel.queueBind(QUEUE_NAME, "test_ps", "");

        //定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                    throws IOException {
                //获取并转成String
                String message = new String(body, "UTF-8");
                System.out.println("-->2号消费者收到消息,msg:"+message);
            }
        };

        //监听队列
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }

}

1.6、运行效果(注意,先启动消费者让队列绑定交换器,再启动生产者)

RabbitMQ工作队列之发布/订阅模式(三)_第2张图片
RabbitMQ工作队列之发布/订阅模式(三)_第3张图片
RabbitMQ工作队列之发布/订阅模式(三)_第4张图片
RabbitMQ工作队列之发布/订阅模式(三)_第5张图片
RabbitMQ工作队列之发布/订阅模式(三)_第6张图片

2、spring集成RabbitMQ

2.1、构建maven工程



    4.0.0

    edurabbitmq-ps-springxml
    ps-springxml
    1.0-SNAPSHOT


    

        
        
            com.rabbitmq
            amqp-client
            4.0.0
        
        
        
            org.springframework.amqp
            spring-rabbit
            1.7.3.RELEASE
        

        
        
            org.springframework
            spring-core
            5.0.7.RELEASE
        

        
        
            org.springframework
            spring-beans
            5.0.7.RELEASE
        

        
        
            org.springframework
            spring-context
            5.0.7.RELEASE
        
    


2.2、创建context.xml




    
    


2.3、创建rabbitmq.xml




    
    


    
    

    
    

    
    
    
    
    


    
    
        
            
            
        
    
    

    
    
    

    
    
        
        
    




2.4、创建生产者

package com.rabbit;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: Alex
 * @DateTime: 2018/8/22 19:40
 * @Description: 描述
 * @Version: 1.0.0
 **/
public class Send {

    //交换器名称,这里的exchange名称要和rabbitmq.xml里面配置对应
    private static String EXCHANGE_NAME = "test_ps_spring_exchange";

    public static void main(String[] args) throws InterruptedException {


        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:context.xml");

        //获取rabbit模版(等价于@Autowired)
        RabbitTemplate bean = context.getBean(RabbitTemplate.class);

        //循环发送30条消息
        for (int i =0;i<30;i++) {
	        //对应exchange发送消息
            bean.convertAndSend(EXCHANGE_NAME,"","hello word"+i);
            Thread.sleep(500);//休眠0.5秒
        }

        Thread.sleep(10000);//休眠2秒后,关闭spring容器
        context.close();

    }
}

2.5、定义1号消费者

package customer;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 23:39
 * @description :我的消费者1
 * @note 注意事项
 */
public class Customer1{

    public void listen(String foo){
        System.out.println("消费者消费1,获取消息msg:"+foo);
    }

}

2.6、定义消费者2号

package customer;

/**
 * @author : alex
 * @version :1.0.0
 * @Date : create by 2018/7/19 23:39
 * @description :我的消费者2
 * @note 注意事项
 */
public class Customer2 {

    public void listen(String foo){
        System.out.println("消费者消费2,获取消息msg:"+foo);
    }

}

2.7、运行效果

RabbitMQ工作队列之发布/订阅模式(三)_第7张图片
fanout的exchange模式,为扇形广播模式,相当于绑定到该exchange的所有queue都会受到消息。

3、spring boot 集成RabbitMQ

3.1、构建spring boot项目,pom文件加入依赖

		
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            org.springframework.boot
            spring-boot-starter-amqp
        

3.2、创建application.yml配置文件(如果是application.properties,修改一下后缀)

spring:
  rabbitmq:
    username: root
    password: 123456
    host: 192.168.199.128
    port: 5672
    virtual-host: /test_vh


#rabbitMQ配置文件
rabbitMQconfig:
  queueName:
    first: test_spring_boot_ps1
    second: test_spring_boot_ps2
  exchangeName:
    fanoutName: test_spring_boot_exchange_name

3.3、spring boot入口文件

package cn.edu.rabbitps.ps;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class PsApplication {

    public static void main(String[] args) {
        SpringApplication.run(PsApplication.class, args);
    }
}

3.4、静态参数获取类

package cn.edu.rabbitps.ps.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author: Alex
 * @DateTime: 2018/8/23 11:19
 * @Description: 静态参数类
 * @Version: 1.0.0
 **/
@Component
public class ParamUtil {

    @Value("${rabbitMQconfig.queueName.first}")
    public String queueNameFirst;

    @Value("${rabbitMQconfig.queueName.second}")
    public String queueNameSecond;

    @Value("${rabbitMQconfig.exchangeName.fanoutName}")
    public String fanoutName;
}

3.5、配置RabbitMQ各种定义

package cn.edu.rabbitps.ps.utils;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author: Alex
 * @DateTime: 2018/8/23 10:52
 * @Description: rabbitMQ配置声明
 * @Version: 1.0.0
 **/
@Configuration
public class RabbitMQDeclareUtil {

    @Autowired
    private ParamUtil paramUtil;

    @Bean
    Queue getQueue1(){
        //定义第一个队列队列,Ctrl+鼠标左键,点击Queue可以看到定义
        return new Queue(paramUtil.queueNameFirst);
    }

    @Bean
    Queue getQueue2(){
        //定义第二个队列
        return new Queue(paramUtil.queueNameSecond);
    }

    @Bean
    FanoutExchange getExchange() {
        //定义一个FanoutExchange交换器
        return new FanoutExchange(paramUtil.fanoutName);
    }


    /**
     * 第一个队列与交换器绑定
     * @param getQueue1     定义的第一个队列
     * @param getExchange   定义的交换器
     * @return
     */
    @Bean
    Binding binding1(Queue getQueue1, FanoutExchange getExchange) {
        return BindingBuilder.bind(getQueue1).to(getExchange);
    }

    /**
     * 第二个队列与交换器绑定
     * @param getQueue2
     * @param getExchange
     * @return
     */
    @Bean
    Binding binding2(Queue getQueue2, FanoutExchange getExchange) {
        return BindingBuilder.bind(getQueue2).to(getExchange);
    }
}

3.6、使用web操作模拟生产者

package cn.edu.rabbitps.ps.controller;

import cn.edu.rabbitps.ps.utils.ParamUtil;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: Alex
 * @DateTime: 2018/8/17 16:36
 * @Description: web访问模拟发送
 * @Version: 1.0.0
 **/
@RestController
public class IndexController {


    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ParamUtil paramUtil;

    /**
     * 使用AmqpTemplate
     * @return
     * @throws Exception
     */
    @PostMapping("/amqpSend")
    public String amqpSend() throws Exception{
        String msg = "amqp";
        for (int i=0;i<20;i++){
            amqpTemplate.convertAndSend(paramUtil.fanoutName,"",msg);//根据指定的exchange发送数据
//            System.out.println("序号:"+i+",发送时间:"+System.currentTimeMillis()+",发送消息:"+msg);
            Thread.sleep(1000);//1秒
        }
        return msg;
    }

    /**
     * 使用RabbitTemplate
     * @return
     * @throws Exception
     */
    @PostMapping("/rabbitSend")
    public String rabbitSend() throws Exception{
        String msg = "rabbit";
        for (int i=0;i<20;i++) {
            rabbitTemplate.convertAndSend(paramUtil.fanoutName,"test_mmr", msg+i);//根据指定的exchange发送数据
//            System.out.println("生产者,序号:"+i+",发送时间:"+System.currentTimeMillis()+",发送消息:"+msg);
            Thread.sleep(500);//0.5秒
        }
        return msg;
    }
}

3.7、创建消费者1

package cn.edu.rabbitps.ps.customer;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author: Alex
 * @DateTime: 2018/8/17 16:35
 * @Description: 模拟消费者1
 * @Version: 1.0.0
 **/
@Component
//监听的队列
@RabbitListener(queues = "test_spring_boot_ps1")
public class CustomerMsg {

    /**
     * 进行接收处理
     * @param string
     */
    @RabbitHandler
    public void onMessage(String string,Channel channel, Message message) throws IOException, InterruptedException {
        Thread.sleep(1000);
        System.out.println("消费者1,接收时间:"+System.currentTimeMillis()+",收到消息,消息: " + string);
//        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//手动确认
        //丢弃这条消息
        //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
    }

}

3.8、 创建消费者2

package cn.edu.rabbitps.ps.customer;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * @author: Alex
 * @DateTime: 2018/8/17 16:35
 * @Description: 模拟消费者2
 * @Version: 1.0.0
 **/
@Component
//监听的队列
@RabbitListener(queues = "test_spring_boot_ps2")
public class CustomerMsg2 {

    /**
     * 进行接收处理
     * @param string
     */
    @RabbitHandler
    public void onMessage(String string,Channel channel, Message message) throws IOException, InterruptedException {
        Thread.sleep(1000);
        System.out.println("消费者2,接收时间:"+System.currentTimeMillis()+",收到消息,消息: " + string);
//        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);//手动确认
        //丢弃这条消息
        //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false);
    }

}

3.9、 运行效果图

RabbitMQ工作队列之发布/订阅模式(三)_第8张图片

RabbitMQ工作队列之发布/订阅模式(三)_第9张图片

RabbitMQ工作队列之发布/订阅模式(三)_第10张图片

3.10、简析RabbitMQDeclareUtil类

spring boot使用@bean的方式注入配置RabbitMQ

首先是关于定义Queue:

	@Bean
    Queue getQueue1(){
        //定义第一个队列队列,Ctrl+鼠标左键,点击Queue可以看到定义
        return new Queue(paramUtil.queueNameFirst);
    }

RabbitMQ工作队列之发布/订阅模式(三)_第11张图片

上图为spring-amqp中定义的Queue
由此图可以看出,我们之前是通过Queue的构造方法进行定义队列,参数与原生client的一致,如队列名称,是否持久化,是否自动删除等等。

接下来就是定义exchange

	@Bean
    FanoutExchange getExchange() {
        //定义一个FanoutExchange交换器
        return new FanoutExchange(paramUtil.fanoutName);
    }

RabbitMQ工作队列之发布/订阅模式(三)_第12张图片

FanoutExchange父类继承与AbstractExchange
构造方法重载了父类三个构造方法
分别是:super(name)、super(name,durable,autoDelete)和super(name,durable,autoDelete,arguments)
exchange的Type固定给定为fanout

此处看得不够仔细,我们往父类跳,AbstractExchange实现于Exchange,那我们再跳到Exchange,跳了两次

RabbitMQ工作队列之发布/订阅模式(三)_第13张图片

看到这里,我们就明白了。定义Exchange实际上与原生的client客户端参数一致。

再往下就是队列绑定交换器,参数不多,实际效果与spring xml配置得差不多

/**
     * 第一个队列与交换器绑定
     * @param getQueue1     定义的第一个队列
     * @param getExchange   定义的交换器
     * @return
     */
    @Bean
    Binding binding1(Queue getQueue1, FanoutExchange getExchange) {
        return BindingBuilder.bind(getQueue1).to(getExchange);
    }

4、小结

本篇博文讲述了三类使用RabbitMQ的方式,由原生client客户端,到spring xml,再到spring boot,实际上使用原理都基本相通,同时接触了一个新的东西叫exchange,并且了解了第一个exchange的type类型叫fanout,fanout意为扇形,在rabbitMQ中即表示以扇形的方式进行广播消息,所有绑定此exchange的队列,都会收到消息。routingKey默认是不生效的。

你可能感兴趣的:(rabbitMQ)