3、RabbitMQ交换机作用_分类_特点_使用案例_五种形式队列

1、RabbitMQ交换机的作用:
生产者发送消息不会像传统方式直接将消息投递到队列中,而是先将消息投递到交换机中,在由交换机转发到具体的队列,队列再将消息以推送或者拉取方式给消费者进行消费。交换机的作用根据具体的路由策略分发到不同的队列中。
2、RabbitMQ的Exchange(交换器)分为四种类型:
direct(默认)、headers、fanout、topic。其中headers交换器允许你匹配AMQP消息的header而非路由键,除此之外headers交换器和direct交换器完全一致,但性能却很差,几乎用不到,所以本文也不做讲解。fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。
2.1 Direct Exchange(直连交换器):
1)也是默认的交换机,是根据消息携带的路由键(routing key)将消息投递给对应队列的
2)直连交换机的特性:
a.公平调度:当接收端订阅者有多个的时候,direct会轮询公平的分发给每个订阅者(订阅者消息确认正常);
b.消息的发后既忘特性:发后既忘模式是指接受者不知道消息的来源,如果想要指定消息的发送者,需要包含在发送内容里面,这点就像我们在信件里面注明自己的姓名一样,只有这样才能知道发送者是谁;
c.消息确认:消息接收到之后必须使用channel.basicAck()方法手动确认(非自动确认删除模式下);
c.1如果应用程序接收了消息,缺忘记确认接收的话(可能因为bug),消息在队列的状态会从“Ready”变为“Unacked”;
c.2如果消息收到却未确认,Rabbit将不会再给这个应用程序发送更多的消息了,这是因为Rabbit认为你没有准备好接收下一条消息。此条消息会一直保持Unacked的状态,直到你确认了消息,或者断开与Rabbit的连接,Rabbit会自动把消息改回Ready状态,分发给其他订阅者。当然你可以利用这一点,让你的程序延迟确认该消息,直到你的程序处理完相应的业务逻辑,这样可以有效的防治Rabbit给你过多的消息,导致程序崩溃。
d.消息拒绝:消息在确认之前,可以有两个选择:
d.1 断开与Rabbit的连接,这样Rabbit会重新把消息分派给另一个消费者;
d.2 拒绝Rabbit发送的消息使用channel.basicReject(long deliveryTag, boolean requeue)
参数说明:【参数1:消息的id;
参数2:处理消息的方式;
如果是true,Rabbib会重新分配这个消息给其他订阅者,如果设置成false的话,Rabbit会把消息发送到一个特殊的“死信”队列,用来存放被拒绝而不重新放入队列的消息;

2.2 Fanout Exchange(广播交换器):Fanout 中文意思为 扇出
1)fanout有别于direct交换器,fanout是一种发布/订阅模式的交换器,当你发送一条消息的时候,交换器会把消息广播到所有附加到这个交换器的队列上。
比如用户上传了自己的头像,这个时候图片需要清除缓存,同时用户应该得到积分奖励,你可以把这两个队列绑定到图片上传的交换器上,这样当有第三个、第四个上传完图片需要处理的需求的时候,原来的代码可以不变,只需要添加一个订阅消息即可,这样发送方和消费者的代码完全解耦,并可以轻而易举的添加新功能了。
2)广播交换机的特点(也可以说是与直连交换器的区别)
a.在发送消息时需新增channel.exchangeDeclare(ExchangeName, "fanout"),这行代码声明fanout交换器;
b.在接受消息时需要声明fanout路由器,并且fanout需要绑定队列到对应的交换器用于订阅消息;
channel.queueDeclare().getQueue()为随机队列,Rabbit会随机生成队列名称,一旦消费者断开连接,该队列会自动删除。
注意:对于fanout交换器来说routingKey(路由键)是无效的,这个参数是被忽略的。
2.3 Topic Exchange(主题交换器):是一种匹配订阅模式
1)topic交换器运行和fanout类似,但是可以更灵活的匹配自己想要订阅的信息,这个时候routingKey路由键就排上用场了,使用路由键进行消息(规则)匹配。
2)假设我们现在有一个日志系统,会把所有日志级别的日志发送到交换器,warning、log、error、fatal,但我们只想处理error以上的日志,要怎么处理?这就需要使用topic路由器了。topic路由器的关键在于定义路由键,定义routingKey名称不能超过255字节。主题交换器使用“.”作为分隔符;"*"匹配一个分段(用“.”分割)的内容; "#"匹配0和多个字符;
示列:例如发布了一个“com.mq.rabbit.error”的消息:
能匹配上的路由键:
cn.mq.rabbit.*
cn.mq.rabbit.#
#.error
cn.mq.#
#
不能匹配上的路由键:
cn.mq.*
*.error
*
所以如果想要订阅所有消息,可以使用“#”匹配。
注意:fanout、topic交换器是没有历史数据的,也就是说对于中途创建的队列,获取不到之前的消息。
3、RabbitMQ 直连交换机、广播交换机、主题交换机使用案例:
3.1 依赖引入:
<!--rabbitmq依赖引用-->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.5</version>
</dependency>
mq.properties文件配置:
rabbitmq.address=127.0.0.1
rabbitmq.port=5672
rabbitmq.username=guest
rabbitmq.password=guest
3.2 代码示列:
1)生产和消费请求:RabbitMqController
package com.zj.weblearn.controller.rabbitmq;

import com.zj.weblearn.serviceImpl.rabbitmq.DirectExchangeServiceImpl;
import com.zj.weblearn.serviceImpl.rabbitmq.FanoutExchangeServiceImpl;
import com.zj.weblearn.serviceImpl.rabbitmq.TopicExchangeServiceImpl;
import com.zj.weblearn.utils.ResponseDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;

/*
 * @Copyright (C), 2002-2020,
 * @ClassName: MqController
 * @Author:
 * @Date: 2020/9/23 9:49
 * @Description:
 * @History:
 * @Version:1.0
 */
@Controller
@RequestMapping("/mq/")
public class RabbitMqController {

    @Autowired
    DirectExchangeServiceImpl directExchangeServiceImpl;

    @Autowired
    FanoutExchangeServiceImpl fanoutExchangeServiceImpl;

    @Autowired
    TopicExchangeServiceImpl topicExchangeServiceImpl;

    private final static String CANCLED_ORDER_QUEUE = "cancled_order_queue";

    private final static String CANCLED_ORDER_FAIR_QUEUE = "cancled_order_queue";

    /**
     * @Method:
     * @Author:
     * @Description: 将退货的订单通过直连交换器(Direct Exchange)让入消费队里中
     * 访问路径:http://localhost:8080/mq/sendCancleOrderToRabbitMqQueue.do?orderNos=1111111,2222222
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/12/8 14:18
     */
    @RequestMapping("/sendCancleOrderToRabbitMqQueue")
    @ResponseBody
    public Object saveOrderInfoToQueue(HttpServletRequest request) {
        String orderNos = request.getParameter("orderNos");
        List orderList = null;
        if (StringUtils.isNotEmpty(orderNos)) {
            orderList = Arrays.asList(orderNos.split(","));
        }
        return directExchangeServiceImpl.productMessageByDirectExchange(orderList, CANCLED_ORDER_QUEUE);
    }


    /**
     * @Method:
     * @Author:
     * @Description: 通过直连交换器(Direct Exchange)消费 队列 cancle_order_queue 消费消息
     * http://localhost:8080/mq/consumerQueueInfo.do
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/12/8 15:07
     */
    @RequestMapping("/consumerQueueOnNoFairWay")
    @ResponseBody
    public Object toConsumeQueueInfo() {
        return directExchangeServiceImpl.consumeMessageByDirectExchange(CANCLED_ORDER_QUEUE);
    }

    //http://localhost:8080/mq/saveOrderToQueueFairForward.do

    /**
     * @Method:
     * @Author:
     * @Description:  通过直连交换器(Direct Exchange)创建消费队列,但是采用 RabbitMQ的公平转发进行消费
     *
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/12/8 15:32
     */
    @RequestMapping("/sendCancleOrderToRabbitMqFairQueue")
    @ResponseBody
    public Object saveOrderToQueueFairForward(HttpServletRequest request) {
        String orderNos = request.getParameter("orderNos");
        List orderList = null;
        if (StringUtils.isNotEmpty(orderNos)) {
            orderList = Arrays.asList(orderNos.split(","));
        }
        return directExchangeServiceImpl.productMessQueueOnFairForward(CANCLED_ORDER_FAIR_QUEUE,orderList);
    }


    @RequestMapping("/consumerQueueOnFairWay")
    @ResponseBody
    public Object consumerQueueOnFairWay() {
        ResponseDTO responseDTO= directExchangeServiceImpl.consumerMessageOnFariWay(CANCLED_ORDER_FAIR_QUEUE);
        ResponseDTO responseDTO2=  directExchangeServiceImpl.consumerMessageOnFariWay(CANCLED_ORDER_FAIR_QUEUE);
        if(!responseDTO.isSuccess()){
            return responseDTO;
        }else if(!responseDTO2.isSuccess()){
            return responseDTO2;
        }
        return responseDTO2;
    }


}
View Code
        2)创建连接工具方法:RabbitMqConnectionUtils
package com.zj.weblearn.utils;

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

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

/*
 * @Copyright (C), 2002-2020,
 * @ClassName: RabbitMqConnectionUtils
 * @Author:
 * @Date: 2020/9/23 9:42
 * @Description:
 * @History:
 * @Version:1.0
 */
public class RabbitMqConnectionUtils {

    public static Connection getConnection() {
        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务器地址
        factory.setHost(ReadPropertiesUtils1.getValue("rabbitmq.address"));
        //设置端口号
        factory.setPort(Integer.parseInt(ReadPropertiesUtils1.getValue("rabbitmq.port")));
        //设置用户名
        factory.setUsername(ReadPropertiesUtils1.getValue("rabbitmq.username"));
        //设置密码
        factory.setPassword(ReadPropertiesUtils1.getValue("rabbitmq.password"));
        //设置vhost
        factory.setVirtualHost("/");
        try {
            //创建连接
            return factory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return null;
    }
}
View Code
        3)通过直连交换器生产和消费消息:DirectExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq;

import com.rabbitmq.client.*;
import com.zj.weblearn.enums.ErrorCodeEnum;
import com.zj.weblearn.utils.RabbitMqConnectionUtils;
import com.zj.weblearn.utils.ResponseDTO;
import org.springframework.stereotype.Service;

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

/*
 * @Copyright (C), 2002-2020,
 * @ClassName: DirectExchangeServiceImpl
 * @Author:
 * @Date: 2020/9/23 20:39
 * @Description:
 * @History:
 * @Version:1.0
 */
@Service
public class DirectExchangeServiceImpl {

    /**
     * @Method:
     * @Author:
     * @Description: 通过直连交换器(Direct Exchange)向 queueName 中生产消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:43
     */
    public ResponseDTO productMessageByDirectExchange(List<String> orderNos, String queueName) {
        ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK);
        //1、获取连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
            channel = connection.createChannel();
            //3、声明队列 如果Rabbit中没有此队列将自动创建。
            //【方法入参:
            // 参数1:队列的名称;
            // 参数2:是否持久化,代表队列在服务器重启后是否还存在;
            // 参数3:是否独占此链接,是否是排他性队列。排他性队列只能在声明它的 Connection中使用(可以在同一个 Connection 的不同的 channel 中使用),连接断开时自动删除;
            // 参数4:队列不再使用时是否自动删除;
            // 参数5:队列的其他属性 Map<String, Object> arguments
            //
            channel.queueDeclare(queueName, false, false, false, null);
            //4、发送消息:
            // 【方法入参:
            // 参数1: Exchange的名称,如果没有指定,则使用Default Exchange;
            // 参数2:routingKey,消息的路由Key,是用于Exchange(交换机)将消息转发到指定的消息队列;
            // 参数3:消息包含的属性;
            // 参数4:消息体
            ////这里没有指定交换机,消息将发送给默认交换机,每个队列也会绑定哪个默认的交换机,但是不能显示绑定或解除绑定认的交换机,routingKey(路由键)等于队列名称
            for (String orderNo : orderNos) {
                channel.basicPublish("", queueName, null, orderNo.getBytes());
            }
            System.out.println("message send body orderNos:" + orderNos);
            channel.close();
            connection.close();
        } catch (IOException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setErrorMsg(e.toString());
        } catch (TimeoutException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setErrorMsg(e.toString());
        }
        return responseDTO;
    }

    /**
     * @Method:
     * @Author:
     * @Description: 通过直连交换器(Direct Exchange)消费 队列queueName 消费消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:43
     */
    public ResponseDTO consumeMessageByDirectExchange(String queueName) {
        ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK);
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建一个通道
            channel = connection.createChannel();
            //3、定义消费方法
            DefaultConsumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                        throws IOException {
                    //得到交换机
                    String exchange = envelope.getExchange();
                    //路由key
                    String routingKey = envelope.getRoutingKey();
                    //消息id
                    long deliveryTag = envelope.getDeliveryTag();
                    //消息内容
                    String message = new String(body, "utf-8");
                    System.out.println("Consumer consumption news>>" + message);
                }
            };
            //4、监听队列
            // 【方法入参:
            // 参数1: 队列名称;
            // 参数2: 是否自动确认收到
            //      设置为true表示消息接收到自动向mq回复接收到了,mq接收到回复会删除消息,
            //      设置为false则需要手动回复;
            // 参数3: 消费消息的方法,消费者接收到消息后调用此方法
            //
            channel.basicConsume(queueName, true, consumer);
        } catch (IOException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setData(e.toString());
        }
        return responseDTO;
    }


    /*
    * 目前消息转发机制是平均分配,这样就会出现俩个消费者,奇数的任务很耗时,偶数的任何工作量很小,造成的原因就是近当消息到达队列进行转发消息。并不在乎有多少任务消费者并未传递一个应答给RabbitMQ。仅仅盲目转发所有的奇数给一个消费者,偶数给另一个消费者。
    为了解决这样的问题,我们可以使用basicQos方法,传递参数为prefetchCount= 1。这样告诉RabbitMQ不要在同一时间给一个消费者超过一条消息。
   换句话说,只有在消费者空闲的时候会发送下一条信息。调度分发消息的方式,也就是告诉RabbitMQ每次只给消费者处理一条消息,也就是等待消费者处理完毕并自己对刚刚处理的消息进行确认之后,才发送下一条消息,防止消费者太过于忙碌,也防止它太过去清闲。通过 设置channel.basicQos(1);
    * */
    public ResponseDTO productMessQueueOnFairForward(String queueName, List<String> orderNos) {
        ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK);
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
        Channel channel = null;
        try {
            channel = connection.createChannel();
            //3、声明队列 如果Rabbit中没有此队列将自动创建
            channel.queueDeclare(queueName, false, false, false, null);

            channel.basicQos(1);// 保证一次只分发一次 限制发送给同一个消费者 不得超过一条消息
            for (String orderNo : orderNos) {
                //4、发送消息【参数说明:参数一:交换机名称如果没有指定,则使用Default Exchange;参数二:队列名称,参数三:消息的其他属性-路由的headers信息;参数四:消息主体】
                channel.basicPublish("", queueName, null, orderNo.getBytes());
            }
            System.out.println("message send over :" + orderNos);
            channel.close();
            connection.close();
        } catch (IOException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setData(e.toString());
        } catch (TimeoutException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setData(e.toString());
        }
        return responseDTO;
    }


    /**
     * @Method:
     * @Author:
     * @Description: param:
     * @Return:
     * @Exception:
     * @Date: 2020/12/8 14:26
     */
    public ResponseDTO consumerMessageOnFariWay(String queueName) {
        ResponseDTO responseDTO = new ResponseDTO(ErrorCodeEnum.OK);
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        //2、创建一个通道
        Channel channel = null;
        try {
            channel = connection.createChannel();
            //3、声明队列
            channel.basicQos(1);// 保证一次只分发一次 限制发送给同一个消费者 不得超过一条消息
            //4、定义消费方法
            Channel finalChannel = channel;
            DefaultConsumer consumer = new DefaultConsumer(finalChannel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
                        throws IOException {
                    //得到交换机
                    String exchange = envelope.getExchange();
                    //路由key
                    String routingKey = envelope.getRoutingKey();
                    //消息id
                    long deliveryTag = envelope.getDeliveryTag();
                    //消息内容
                    String message = new String(body, "utf-8");
                    System.out.println("消费者消费:" + message);
                    try {
                        //睡眠1s
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // 手动回执消息
                        finalChannel.basicAck(envelope.getDeliveryTag(), false);
                    }
                }
            };
            //5、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;)
            channel.basicConsume(queueName, false, consumer);
        } catch (IOException e) {
            responseDTO = new ResponseDTO(ErrorCodeEnum.FAIL_501);
            responseDTO.setData(e.toString());
        }
        return responseDTO;
    }


}
View Code
        4)通过广播交换器生产和消费消息:FanoutExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq;

import com.rabbitmq.client.*;
import com.zj.weblearn.utils.RabbitMqConnectionUtils;
import org.springframework.stereotype.Service;

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

/*
 * @Copyright (C), 2002-2020,
 * @ClassName: FanoutExchangeServiceImpl
 * @Author:
 * @Date: 2020/9/23 20:26
 * @Description:
 * @History:
 * @Version:1.0
 */
@Service
public class FanoutExchangeServiceImpl {

    final String fanoutExchangeName = "fanoutExchange"; //交换器名称(广播交换器)

    /**
     * @Method:
     * @Author:
     * @Description: 通过广播交换器(Fanout Exchange)生产消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:27
     */
    public boolean byFanoutExchangeProductMessage(String orderMsg, String queueName) {
        boolean productResult = false;
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
            channel = connection.createChannel();
            //3、声明交换器为广播交换器(是一种发布订阅交换器)
            channel.exchangeDeclare(fanoutExchangeName, "fanout");
            //4、声明队列 如果Rabbit中没有此队列将自动创建
            channel.queueDeclare(queueName, false, false, false, null);
            //5、发送消息
            channel.basicPublish(fanoutExchangeName, queueName, null, orderMsg.getBytes());
            System.out.println("message send body:" + orderMsg);
            try {
                channel.close();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            connection.close();
            productResult = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return productResult;
    }

    /**
     * @Method:
     * @Author:
     * @Description: 通过广播交换器(Fanout Exchange)消费消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:30
     */
    public void byFanoutExchangeConsumeMessage() {
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建一个通道
            channel = connection.createChannel();
            //3、声明fanout交换器
            channel.exchangeDeclare(fanoutExchangeName, "fanout");
            //4、声明队列
            String queueName = channel.queueDeclare().getQueue();
            channel.queueBind(queueName, fanoutExchangeName, "");
            //5、定义消费方法
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                }
            };
            //6、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;)
            channel.basicConsume(queueName, true, consumer);//
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
View Code
        5)通过主题交换器生产和消费消息:TopicExchangeServiceImpl
package com.zj.weblearn.serviceImpl.rabbitmq;

import com.rabbitmq.client.*;
import com.zj.weblearn.utils.RabbitMqConnectionUtils;
import org.springframework.stereotype.Service;

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

/*
 * @Copyright (C), 2002-2020,
 * @ClassName: TopicExchangeServiceImpl
 * @Author:
 * @Date: 2020/9/23 20:33
 * @Description:
 * @History:
 * @Version:1.0
 */
@Service
public class TopicExchangeServiceImpl {

    final String topicExchangeName = "topicExchange"; //主题交换器名称

    /**
     * @Method:
     * @Author:
     * @Description: 通过主题交换器(Topic Exchange)生产消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:27
     */
    public boolean byTopicExchangeProductMessage(String orderMsg, String queueName) {
        boolean productResult = false;
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建与Exchange的通道,每个连接可以创建多个通道,每个通道代表一个会话任务
            channel = connection.createChannel();
            //3、声明交换器为主题交换器(是一种匹配订阅模式)
            channel.exchangeDeclare(topicExchangeName, "topic");
            //4、声明队列 如果Rabbit中没有此队列将自动创建
            channel.queueDeclare(queueName, false, false, false, null);
            //5、发送消息
            channel.basicPublish(topicExchangeName, queueName, null, orderMsg.getBytes());
            System.out.println("message send body:" + orderMsg);
            try {
                channel.close();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            connection.close();
            productResult = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return productResult;
    }


    /**
     * @Method:
     * @Author:
     * @Description: 通过主题交换器(Topic Exchange)消费消息
     * param:
     * @Return:
     * @Exception:
     * @Date: 2020/9/23 20:27
     */
    public void byTopicExchangeConsumeMessage(){
        //1、得到连接
        Connection connection = RabbitMqConnectionUtils.getConnection();
        Channel channel = null;
        try {
            //2、创建一个通道
            channel = connection.createChannel();
            //3、声明topic交换器
            channel.exchangeDeclare(topicExchangeName, "topic");
            //4、声明队列
            String queueName = channel.queueDeclare().getQueue();
            String routingKey = "#.error";
            channel.queueBind(queueName, topicExchangeName, routingKey);
            //5、定义消费方法
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                           byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println(routingKey + "|接收消息 => " + message);
                }
            };
            //6、监听队列(入参依次为:队列名称;设置为true表示消息接收到自动向mq回复接收到了,设置为false则需要手动回复;消费消息的方法,消费者接收到消息后调用此方法;)
            channel.basicConsume(queueName, true, consumer);//
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
View Code
        6)相关的实体类:
import com.zj.weblearn.enums.BaseEnum;
import com.zj.weblearn.enums.ErrorCodeEnum;

import java.io.Serializable;

public class ResponseDTO<T> implements Serializable {

    private boolean success;

    private String errorCode;

    /**
     * 原因
     */
    private String errorMsg;

    /**
     * 返回数据值
     */
    private T data;


    public ResponseDTO() {
    }

    public ResponseDTO(ErrorCodeEnum errorCode) {
        this.errorCode = errorCode.getErrorCode();
        this.errorMsg = errorCode.getErrorMsg();
        if(!"0".equals(errorCode.getErrorCode())){
            success=false;
        }else{
            success=true;
        }
    }

    public ResponseDTO(String errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
        if(!"0".equals(errorCode)){
            success=false;
        }else{
            success=true;
        }
    }


    public void setErrorCodeEnum(BaseEnum errorCode) {
        this.errorCode = errorCode.getErrorCode();
        this.errorMsg = errorCode.getErrorMsg();
        if(!"0".equals(errorCode.getErrorCode())){
            success=false;
        }else{
            success=true;
        }
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    @Override
    public String toString() {
        return "ResponseDTO{" +
                "errorCode='" + errorCode + '\'' +
                ", errorMsg='" + errorMsg + '\'' +
                ", data=" + data +
                '}';
    }

}

public enum ErrorCodeEnum implements BaseEnum{

    OK("0", "成功"),
    FAIL_500("500", "系统开小差了,请稍后再试!"),
    FAIL_501("501", "服务异常,请联系管理员处理!"),
    PARAM_ERROR("502", "入参异常,请检查后重试!");

    private String errorCode;
    private String errorMsg;

    ErrorCodeEnum(String errorCode, String errorMsg) {
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    ErrorCodeEnum(BaseEnum errorCodeEnum) {
        this.errorCode = errorCodeEnum.getErrorCode();
        this.errorMsg = errorCodeEnum.getErrorMsg();
    }


    public String getErrorCode() {
        return errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }
}
View Code
4、RabbitMQ的五种形式队列
1)简单队列(点对点):一个生产者P发送消息到队列Q,一个消费者C接收。
点对点模式(一对一模式):一个生产者投递消息给队列,只能允许有一个消费者进行消费。如果是消费集群的话,会进行均摊消费。
推(由队列指向消费者):消费者已经启动了,建立长连接,一旦生产者向队列投递消息会立马推送给消费者;
取(由消费者指向队列):生产者先投递消息到队列进行缓存,这时候消费者在启动的时候就向队列中获取消息
队列:以先进先出原则存放消息集合;
2)工作(公平性)队列模式
work queues与简单队列相比,多了一个消费端,两个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
公平队列原理:队列服务器向消费者发送消息的时候,消费者采用手动应答模式,队列服务器必须要收到消费者发送ack结果通知,才会发送下一个消息。
默认消费者集群为均摊消费。假设生产者向队列发送10个消息,消息1和2都各自消费5个,保证消费唯一。
思考:均摊消费弊端-如果每个消费者处理消息的业务时间情况不相同,可能对消息处理比较慢的消费者不公平。应该能这多劳,谁消费快,就让其多消费消息。
3)发布/订阅模式(Publish/Subscribe):这个可能是消息队列中最重要的队列了,其他的都是在它的基础上进行了扩展。发布订阅模式说明:
(1)一个生产者,多个消费者
(2)每一个消费者都有自己的一个队列,并对其进行监听;
(3)生产者没有直接发消息到队列中,而是发送到交换机;
(4)每个消费者的队列都绑定到交换机上;
(5)消息通过交换机到达每个消费者的队列 该模式就是Fanout Exchange(广播交换机)将消息路由给绑定到它身上的所有队列 以用户发邮件案例讲解
注意:交换机没有存储消息功能,如果消息发送到没有绑定消费队列的交换机,消息则丢失。生产者将消息发给broker(消息队列服务),由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息;
4)路由模式Routing:
(1)每个消费者监听自己的队列,并且设置routingkey。
(2)生产者发送消息到交换机并指定一个路由key,消费者队列绑定到交换机时要指定路由key(key匹配就能接受消息,key不匹配就不能接受消息)
5)通配符模式Topics
(1)每个消费者监听自己的队列,并且设置带统配符的routingkey。
(2)生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配,由交换机根据routingkey来转发消息到指定的队列。
符号#:匹配一个或者多个词error.# 可以匹配error.order或者error.order.cancle
符号*:只能匹配一个词error.* 可以匹配error.order或者error.ceshi

参看文章:https://www.cnblogs.com/vipstone/p/9295625.html

posted @ 2021-01-22 23:33  爱笑的berg  阅读(2093)  评论(0编辑  收藏  举报