RabbitMq

RabbitMq
一、docker安装rabbitmq
# 拉取镜像
docker pull rabbitmq

# 启动rabbitmq
docker run -d  --name rabbitmq -p 15672:15672 -p 5672:5672 rabbitmq

# 进入容器
docker exec -it rabbitmq /bin/bash

# 安装插件
rabbitmq-plugins enable rabbitmq_management

# 退出
ctrl + p + q

# 访问 用户名和密码默认都是guest
http://****:15672/

在这里插入图片描述

二、消息队列

场景一 : 简单队列

img
  • P : 生产者
  • **C **: 消费者
  • 中间红色 : 队列,队列只受主机内存和磁盘的限制,本质是一个大型消息缓冲区
# pom
  <!--amqp协议-->
  <dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.5.3</version>
  </dependency>
生产者
Send.java
/**
 * Created with IntelliJ IDEA.
 *
 * @author: L-JiaHui
 * @date: 2021/07/20/14:41
 * @description: * 简单的队列----producing---生产者
 * 1.RabbitMQ的核心就是基于通道(Channel)进行开展的
 * 2.通道的概念非常常见,例如:NIO的三大核心其中有一项就是通道
 */
@Slf4j
public class Send {

    /**
     * 队列名称
     */
    private static final String QUEUE_NAME = "554_queue";

    public static void main(String[] args) {
        try {
            //获取RabbitMQ连接
            Connection connection = ConnectionUtil.getConnection();
            //获取管道
            Channel channel = connection.createChannel();
            /*
             * @param_one : 队列名称
             * 其余参数后续场景会依次开启
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            /*
             * channel.basicPublish() --- 进行发送消息
             * @param_two:队列名称
             * @param_four: 存储的数据(字节形式)
             * 可通过循环发送信息到队列中
             */
            for (int i = 0; i < 1; i++) {
                String msg = "Hell Word,this is queue" + (i + 1);
                channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            }
            //消息发送成功
            log.info("message:{}", "send msg success");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ConnectionUtil.java
/**
 * Rabbitmq的工具类
 * method_one:连接
 * method_two关闭链接
 *
 * @author l-jiahui
 */
public class ConnectionUtil {

    /**
     * amqp访问地址
     */
    private static final String HOST = "*****";

    /**
     * 用户名
     */
    private static final String USER_NAME = "guest";

    /**
     * 密码
     */
    private static final String PWD = "guest";

    /**
     * 虚拟主机
     */
    private static final String VIRTUAL_HOST = "/";

    /**
     * amqp协议端口
     */
    private static final Integer PORT = 5672;

    /**
     * 获取连接对象
     *
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //Rabbitmq连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //主机
        factory.setHost(HOST);
        //用户名
        factory.setUsername(USER_NAME);
        //pwd
        factory.setPassword(PWD);
        factory.setVirtualHost(VIRTUAL_HOST);
        factory.setPort(PORT);
        return factory.newConnection();
    }

    /**
     * 关闭连接
     *
     * @param con
     * @param channel
     */
    public static void closeConnection(Connection con, Channel channel) {
        try {
            if (channel != null) {
                channel.close();
            }
            if (con != null) {
                con.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
消费者
/**
 * 1. 简单的队列---Recv----消费者
 * 2. 1.消费者无非就是获取数据/使用数据
 *
 * @author l-jiahui
 */
@Slf4j
public class Recv {

    /**
     * 队列名称---在某个队列中读取数据
     */
    private static final String QUEUE_NAME = "554_queue";

    public static void main(String[] args) {
        try {
            //连接RabbitMQ
            Connection connection = ConnectionUtil.getConnection();
            //创建队列
            Channel channel = connection.createChannel();
            /*
             * 创建队列---和生产者代码一致
             * 注意:生产者或消费者程序不存在优先级关系,所以在此声明队列的原因,是保证队列存在
             */
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            /*
             * 内部静态类 --实现方法①
             * Lambda表达式--实现方法②
             *    lambda实现方式前提:必须是函数式接口(一个接口中只有一个方法)
             */
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    //delivery.getBody()返回的是二进制数组-字节数组
                    String returnMsg = new String(delivery.getBody(), StandardCharsets.UTF_8);
                    log.info("returnMessage:{}", returnMsg);
                }
            };
            /*
             * 消费者
             */
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumer -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

场景二: 工作队列/任务队列

img

Send.java
/**
 * 工作队列(任务队列)---生产者
 * ①工作任务:背后思想避免立即执行密集资源型任务,并不得不等待它完成
 * ②工作任务:用于在多个工作者/消费者之间分配耗时任务
 */
public class Send {
    private static final String QUEUE_NAME = "554_queue"; //队列名称

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //获取核心管道/通道
            Channel channel = connection.createChannel();
            //创建队列
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //正常发送信息
            String mgs = "vritual msg";
            for (int i = 0; i < 30; i++) {
                //发布
                channel.basicPublish("", QUEUE_NAME, null, (mgs+i).getBytes());
                //模拟资源紧张
                Thread.sleep(500);
            }
            System.out.println("send msg success");
            //关闭连接
            ConnectionUtil.closeConnection(connection,channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Recv.java
/**
 * 工作队列(任务队列)---消费者
 * 该Recv类进行通用,启动两个,消费者一sleep时间间隔为1秒,而消费者二的sleep时间间隔为2秒。
 */
public class Recv {
    private static final String QUEUE_NAME = "554_queue"; //队列名称

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //获取管道
            Channel channel = connection.createChannel();
            //创建队列--生产者或消费者不存在优先级顺序,生产者发送消息,消费者监听信息并获取信息
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
            DeliverCallback callback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    String msg = new String(delivery.getBody());
                    System.out.println("接收的消息========" + msg);
                    try {
                        //模仿处理任务所耗时长
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //消费
            channel.basicConsume(QUEUE_NAME, callback, consuer -> {
            });
          System.out.println("消费者1启动成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
在这里插入图片描述

测试总结
	消费者1处理任务快,但执行的任务数量却和消费者2一致,这就是RabbitMQ默认情况下的轮询分发,如果队列中的数据总数是奇数的话,那么最后一条数据会发给最先获取数据的消费者

消息确认

正常情况下,RabbitMQ将数据传递给消费者,同时也会将这条数据标记为删除,然后消费者拿到数据进行处理,但如果是非正常情况,例如:RabbitMQ将信息传递给消费者,但传递过程中,消费者挂掉或者连接丢失了,这就会导致这条数据丢失,并且无法还原,针对这一点我们可以将autoAck=true 的标识关闭,将它改为false,这样RabbitMQ在传递消息给消费者时不会将该消息标记为删除,而是等待着消费者接收到消息之后的ack响应(消息确认),RabbitMQ成功接收到消费者的ack响应之后才会将该数据标记为删除,而不是消息一传递出去就标记删除
Recv.java
/**
 * 工作队列(任务队列)---消费者
 * 该Recv类进行通用,启动两次,消费者一sleep时间间隔为1秒,而消费者二的sleep时间间隔为2秒。
 */
public class Recv {
    private static final String QUEUE_NAME = "554_queue"; //队列名称

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //获取管道
            final Channel channel = connection.createChannel();
            //创建队列--生产者或消费者不存在优先级顺序,生产者发送消息,消费者监听信息并获取信息
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            //模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
            DeliverCallback callback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    String msg = new String(delivery.getBody());
                    System.out.println("接收的消息========" + msg);
                    try {
                        //模仿处理任务所耗时长
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //新增finally块
                    finally {
                        //消息获取成功后ack响应
                        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    }
                }
            };
            //消费
            boolean autoAck = false; //关闭
            channel.basicConsume(QUEUE_NAME, autoAck, callback, consuer -> {
            });
            System.out.println("消费者1启动成功");
        } catch (
                Exception e) {
            e.printStackTrace();
        }
    }
}

消息持久性

消息确认解决了消费者即使没有拿到数据,也可以保证数据不丢失的隐患,但如果RabbitMQ在使用的中途中挂掉了、服务器崩溃了、网络异常或RabbitMQ重启了等这些情况,还是会使数据丢失,所以我们在通过生产者发送信息的给RabbitMQ的时候就要告知RabbitMQ该系列数据是需要标记持久状态的
# 注意一:不能在原有的队列中进行消息持久性设置,否则会报错,解决方法:①删除原有的队列。②重新创建一个不存在的队列。
# 注意二:此场景下生产者创建队列,消费者就无需创建队列了。
Send.java
/**
 * 工作队列(任务队列)---生产者
 * ①工作任务:背后思想避免立即执行密集资源型任务,并不得不等待它完成
 * ②工作任务:用于在多个工作者/消费者之间分配耗时任务
 */
public class Send {
    private static final String QUEUE_NAME = "554_queue"; //队列名称

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //获取核心管道/通道
            Channel channel = connection.createChannel();
            //创建队列
            boolean durable = true;//持久性开启--解决RabbitMQ重启不会丢失数据
            channel.queueDeclare(QUEUE_NAME, durable, false, false, null);
            //正常发送信息
            String mgs = "vritual msg";
            for (int i = 0; i < 3; i++) {
                //MessageProperties.PERSISTENT_TEXT_PLAIN---解决了数据持久化,类似zk存储节点的持久化设置
                //发布
                channel.basicPublish("", QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, (mgs + i).getBytes());
            }
            System.out.println("send msg success");
            //关闭连接
            ConnectionUtil.closeConnection(connection, channel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
官方的原话:消息标记为持久性并不能一定保证数据的持久性,虽然它告诉RabbitMQ将消息存储到磁盘上,但当RabbitMQ已经接收到一条消息却还没有保存的时候,仍然有丢失的可能性,RabbitMQ不会对每条消息都执行fsync(2)—它可能只是保存到缓存中,而不是真正的写到磁盘中。


fsync(2):把内存中的数据存储到磁盘中。

公平调度

轮询分发的弊端:处理消息快的工作者会很闲,而处理消息慢的工作者会很忙,这种情况RabbitMQ自身是不知道的,它只知道要将信息均匀的分摊,但是并不是所有场景都适合轮询分发,为了解决此问题,我们可以使用prefetchCount = 1设置basicQos()方法,告诉RabbitMQ一次不要给一个消费者发多个消息,而是当消费者处理完上一条消息之后,在传递下一条消息,这种模式的话完美的解决了轮询分发的弊端,并且会出现多个消费者之间抢占资源情况,哪个消费者处理数据快,哪个消费者就会接收到的消息多。

img

生产者先启动,随后在启动消费者,因为设置成了消息持久化,只能一个程序创建队列,多个程序创建同一个队列的话会报错。
Recv.java
**
 * 工作队列(任务队列)---消费者
 *Recv类进行通用,启动两次,消费者一sleep时间间隔为1,而消费者二的sleep时间间隔为2秒。
 */
public class Recv {
    private static final String QUEUE_NAME = "yunkai_queue"; //队列名称
    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //获取管道
            final Channel channel = connection.createChannel();             
            //模拟真实场景,消费者接收到数据,并使用数据的时候会有一定的延迟--DeliverCallback实时缓冲数据,并告诉队列将信息以异步形式传递给我们
            int prefetchCount = 1;
            channel.basicQos(prefetchCount); //公平调度
            DeliverCallback callback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    String msg = new String(delivery.getBody());
                    System.out.println("接收的消息========" + msg);
                    try {
                        //模仿处理任务所耗时长
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //新增finally块
                    finally {
                        //消息获取成功后ack响应
                        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                    }
                }
            };
            //消费
            boolean autoAck = false; //关闭
            channel.basicConsume(QUEUE_NAME, autoAck, callback, consuer -> {
            });
            System.out.println("消费者1启动成功");
        } catch (
                Exception e) {
            e.printStackTrace();
        }
    }
}

场景三:订阅者(publish)/发布者(Subscribe)

生产者将消息发送给交易所,交易所将接收到的消息在发送给已绑定的队列上,最终消费者会接收队列传递的消息

img

临时队列/绑定

# 什么是临时队列?用完即丢,拿来即用,一旦与消费者断开连接的话,队列就会自动删除。
//自动生成成队列名称,非持久、独占、自动删除队列。
String queueName = channel.queueDeclare().getQueue(); 

持久队列

# (即使将队列重启后,队列也依旧存在)
独占:(仅由一个连接使用,该链接关闭时候队列将被删除)
自动删除:(当最后一个消费者取消订阅时,删除至少有一个消费者的队列)
ExSend.java
/**
 * exchange(交换)
 * <p>
 * RabbitMQ的核心模型概念:生产者永远不会直接向队列发送任何消息。实际上,
 * 很多时候,生产者甚至根本不知道消息是否会被传递到任何队列
 *
 * @author l-jiahui
 */
@Slf4j
public class ExSend {

    /**
     * 交换机名称
     */
    private static String EXCHANGE_NAME = "exchange_name1";

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //管道/通道
            Channel channel = connection.createChannel();
            /*
             * 交换机
             * @param_one:交换机名称
             * @param_two:交换类型(扇出)
             */
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
            //消息
            String msg = "发布者/订阅者以及它的使用方式";
            /*
             * 发布
             * @param_one:交换机名称  @
             * param_two:原队列名称,现已不需要填写
             * @param_three:配置
             * @param_four:消息
             */

            channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
            log.info("provider===消息发送成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv1.java
/**
 * 消费者1
 *
 * @author l-jiahui
 */
@Slf4j
public class ExRecv1 {

    /**
     * 交换机名称
     */
    private static String EXCHANGE_NAME = "exchange_name1";

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //通道
            Channel channel = connection.createChannel();
            /*
             * 创建交换机--如果存在则免创建
             * @param_one:交换机名称
             * @param_two:交换类型(扇出)
             */
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
            //非持久、独占式、自动删除的队列
            String queue = channel.queueDeclare().getQueue();
            /*
             * 绑定队列
             * @param_one:队列名称
             * @param_two:交换机名称
             */
            channel.queueBind(queue, EXCHANGE_NAME, "");
            //接收信息
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info("consumer1=====" + new String(delivery.getBody()));
                }
            };
            /*
             * 消费信息-到指定的队列中获取信息
             * @param_one:到指定队列中获取信息
             * @param_three:回调
             */
            channel.basicConsume(queue, true, deliverCallback, isempty -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv2.java
/**
 * 消费者2
 * @author l-jiahui
 */
@Slf4j
public class ExRecv2 {
    /**
     * 交换机名称
     */
    private static String EXCHANGE_NAME = "exchange_name1";

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //通道
            Channel channel = connection.createChannel();
            /*
             * 创建交换机--如果存在则免创建
             * param_one:交换机名称
             * @param_two:交换类型(扇出)
             * */
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
            //非持久、独占式、自动删除的队列
            String queue = channel.queueDeclare().getQueue();
            /*
             * 绑定队列
             * param_one:队列名称
             * @param_two:交换机名称
             * */
            channel.queueBind(queue, EXCHANGE_NAME, "");
            //接收信息
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info("consumer2=====" + new String(delivery.getBody()));
                }
            };
            /*
             * 消费信息-到指定的队列中获取信息
             * param_one:到指定队列中获取信息
             * @param_three:回调
             * */
            channel.basicConsume(queue, true, deliverCallback, isempty -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

场景四:路由(Routing)

路由
   什么是路由?个人理解:路由很抽象,在每个场景都有不同的介绍,例如:在消息队列中,路由负责过滤消息,如果你发送的消息符合条件则放行,如果不符合则拒之门外,而在Http请求中,路由负责将你提前配置好的虚拟请求转发到实际请求上,每个场景都有不同的用法,可能实现方式上有一定的差别,但在概念上却都是一致的。
   为什么需要路由?如果细看的话,会发现路由的模型图和发布/订阅场景的模型图类似,不同点在于交换机的类型变了,以及routingkey的参数也进行了设置,其实原因很简单,RabbitMQ对消息的存储方式为二种:磁盘上、内存上,而如果我们不想将所有的消息进行存储,只想将某些特定的消息或重要的消息进行存储,那怎么办呢?这种情况,通过路由则可以完美解决。
路由模型解析
 直接交换: 在发布/订阅场景下的时候,交换机设置的类型是扇出(fanout),但并没有一点灵活性,只是一味的无脑传递消息,所以在路由场景下,交换机的类型会设置为直接(direct),这样的话生产者在发送消息时可指定该消息的类型,而消费者在接收消息时,如果不满足生产者指定的类型的话,则无法获取消息。
 多重绑定: 概念如上,但可以实现多绑定,接收多种类型消息。

img

直接交换与多重绑定
ExSend.java
/**
 * routing:
 * 筛选,接收指定的格式信息
 * routing:只接收符合条件的消息,不会接收多余的消息,会将不符合条件的消息拒之门外
 *
 * @author l-jiahui
 */
@Slf4j
public class ExSendTwo {
    /**
     * 交换机名称
     */
    private static String EXCHANGE_NAME = "ex_queuechange";

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //管道
            Channel channel = connection.createChannel();
            //交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            //路由键数组
            String[] routingArray = new String[]{"lion", "Elephant", "frog"};
            //重复发送
            for (String s : routingArray) {
                /*
                 * 发布
                 * @param_one:交换机
                 * @param_two:路由键
                 * @param_four:发送的消息
                 */
                channel.basicPublish(EXCHANGE_NAME, s, null, (s + ":可以收到我的消息吗").getBytes());
                log.info("消息发送成功");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv3.java
/**
 * 消费者 --大象 青蛙 狮子
 *
 * @author l-jiahui
 */
@Slf4j
public class ExRecv3 {

    /**
     * 交换机名称
     */
    private static final String EXCHANGE_NAME = "ex_queuechange";

    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //管道
            Channel channel = connection.createChannel();
            //交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            //随机队列接收
            String queue = channel.queueDeclare().getQueue();
            /*
             * 绑定队列--多重绑定
             * @param_one:绑定的队列名称
             * @param_two:交换机中获取数据
             * @param_three:路由键
             */
            channel.queueBind(queue, EXCHANGE_NAME, "frog");
            channel.queueBind(queue, EXCHANGE_NAME, "Elephant");
            channel.queueBind(queue, EXCHANGE_NAME, "lion");
            //获取数据
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info("接收的信息为:{}" + new String(delivery.getBody()));
                }
            };
            //消费
            channel.basicConsume(queue, true, deliverCallback, comeback -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv4.java
/**
 * 消费者2 --大象
 * @author l-jiahui
 */
@Slf4j
public class ExRecv4 {

    /**
     * 交换机名称
     */
    private static final String EXCHANGE_NAME = "ex_queuechange";
    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //管道
            Channel channel = connection.createChannel();
            //交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            //随机队列接收
            String queue = channel.queueDeclare().getQueue();
            /*
             * 绑定队列--多重绑定
             * @param_one:绑定的队列名称
             * @param_two:交换机中获取数据
             * @param_three:路由键
             */
            channel.queueBind(queue, EXCHANGE_NAME, "Elephant");
            //获取数据
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info("接收的信息为======" + new String(delivery.getBody()));
                }
            };
            //消费
            channel.basicConsume(queue, true, deliverCallback, comeback -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv5.java
/**
 * 消费者2 --大象
 * @author l-jiahui
 */
@Slf4j
public class ExRecv5 {

    /**
     * 交换机名称
     */
    private static final String EXCHANGE_NAME = "ex_queuechange";
    public static void main(String[] args) {
        try {
            //连接
            Connection connection = ConnectionUtil.getConnection();
            //管道
            Channel channel = connection.createChannel();
            //交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "direct");
            //随机队列接收
            String queue = channel.queueDeclare().getQueue();
            /*
             * 绑定队列--多重绑定
             * @param_one:绑定的队列名称
             * @param_two:交换机中获取数据
             * @param_three:路由键
             */
            channel.queueBind(queue, EXCHANGE_NAME, "lion");
            //获取数据
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info("接收的信息为======" + new String(delivery.getBody()));
                }
            };
            //消费
            channel.basicConsume(queue, true, deliverCallback, comeback -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

多重绑定

在这里插入图片描述

直接绑定

在这里插入图片描述

直接绑定

在这里插入图片描述

场景五:主题(topic)

Exchange类型描述
扇出(fanout):发布/订阅场景中使用,将消息无条件推送给已绑定的队列。
直接(direct):路由场景中使用,将消息推送给满足条件已绑定的队列,用于精准匹配。
主题(topic):路由场景中使用,将消息推送给满足条件已绑定的队列,用于模糊匹配。
注意:交换机类型在某个场景下都可以任意使用,不存在固定场景固定类型。
主题场景模型解析
在发布/订阅场景下的时候,交换机设置的类型是扇出(fanout),但并没有一点灵活性,只是一味的无脑传递消息,在路由场景下的时候,交换机设置的类型是直接(direct),虽然做到了对消息的限制,但是还是存在局限性-----不能基于多个标准进行路由,所以在路由的基础之上,我们再次改变交换机的类型,实现主题场景

img

主题场景
*:星号代表一个词或一个匹配项。
#:井号代表n个匹配项。
①当为“#”符号的时候,就类似于交换机的扇出(fanout)类型。
②当为“*”符号的时候,就类似于交换机的直接(direct)类型。
③当一个路由键中满足多个类型的时候,是无法与多个类型匹配的,只会匹配第一个类型。
④路由键可以为任意字,最大长度为255个字节,当路由键不在跟符号“*”或“#”符号的话,那么它的匹配方式就会和路由完全一致。
ExSendThree.java
/**
 * //第五个场景:主题
 * 生产者
 *
 * @author l-jiahui
 */
@Slf4j
public class ExSendThree {

    /**
     * exchangeName
     */
    private static final String EX_CHANGE_NAME = "ex_name1";

    public static void main(String[] args) {
        try {
            //1.connection
            Connection connection = ConnectionUtil.getConnection();
            //2.channel
            Channel channel = connection.createChannel();
            /* 3.exchange
             * @param_one:exchangeName
             * @param_two:exchangeType ---->topic主题
             */
            channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
            //sendType
            String[] sendType = new String[]{"frog.consumer1", "Elephant.consumer1", "frog", "lion", "lion.consumer1", "lion.frog.Elephant.frog"};
            for (String s : sendType) {
                channel.basicPublish(EX_CHANGE_NAME, s, null, (s + "=====msg send suc").getBytes());
            }
            log.info("消息发送成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv6.java
/**
 * topic
 * 青蛙系列消费者
 * @author l-jiahui
 */
@Slf4j
public class ExRecv6 {

    /**
     * exchangeName
     */
    private static final String EX_CHANGE_NAME = "ex_name1";

    public static void main(String[] args) {
        try {
            //1.connection
            Connection connection = ConnectionUtil.getConnection();
            //2.channel
            Channel channel = connection.createChannel();
            /* 3.exchange
             * @param_one:exchangeName
             * @param_two:exchangeType ---->topic主题
             */
            channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
            //4.queue
            String queue = channel.queueDeclare().getQueue();
            //5.binding
            channel.queueBind(queue, EX_CHANGE_NAME, "frog.#");
            //6.get msg
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info(new String(delivery.getBody()));
                }
            };
            //7.consumer
            channel.basicConsume(queue, true, deliverCallback, cancel -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv7.java
/**
 * topic
 * 青蛙系列消费者
 * @author l-jiahui
 */
@Slf4j
public class ExRecv7 {

    /**
     * exchangeName
     */
    private static final String EX_CHANGE_NAME = "ex_name1";

    public static void main(String[] args) {
        try {
            //1.connection
            Connection connection = ConnectionUtil.getConnection();
            //2.channel
            Channel channel = connection.createChannel();
            /* 3.exchange
             * @param_one:exchangeName
             * @param_two:exchangeType ---->topic主题
             */
            channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
            //4.queue
            String queue = channel.queueDeclare().getQueue();
            //5.binding
            channel.queueBind(queue, EX_CHANGE_NAME, "Elephant.#");
            //6.get msg
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info(new String(delivery.getBody()));
                }
            };
            //7.consumer
            channel.basicConsume(queue, true, deliverCallback, cancel -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ExRecv8.java
/**
 * topic
 * 青蛙系列消费者
 *
 * @author l-jiahui
 */
@Slf4j
public class ExRecv8 {

    /**
     * exchangeName
     */
    private static final String EX_CHANGE_NAME = "ex_name1";

    public static void main(String[] args) {
        try {
            //1.connection
            Connection connection = ConnectionUtil.getConnection();
            //2.channel
            Channel channel = connection.createChannel();
            /* 3.exchange
             * @param_one:exchangeName
             * @param_two:exchangeType ---->topic主题
             */
            channel.exchangeDeclare(EX_CHANGE_NAME, "topic");
            //4.queue
            String queue = channel.queueDeclare().getQueue();
            //5.binding
            channel.queueBind(queue, EX_CHANGE_NAME, "lion.#");
            //6.get msg
            DeliverCallback deliverCallback = new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) {
                    log.info(new String(delivery.getBody()));
                }
            };
            //7.consumer
            channel.basicConsume(queue, true, deliverCallback, cancel -> {
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

满足多个但只匹配第一个

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值