RabbitMq相关技术点

1.使用java连接RabbitMq,需要配置链接virtaulhost、端口port、用户名userName、密码password、地址host

先安装好rabbitmq,登陆用户名密码默认为guest,创建一个用户,创建一个测试virtualhost,给用户分配virtualhost

pom导入amqp-client包

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.7.3</version>
</dependency>
 public static Connection getConnection(){
        ConnectionFactory factory = new ConnectionFactory();
        factory.setVirtualHost("/test");
        factory.setHost("127.0.0.1");
        factory.setUsername("test");
        factory.setPassword("test");
        factory.setPort(5672);
        Connection connection = null;
        try {
             factory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return connection;
    }
 @Test
    public void getConnection() throws IOException {
        Connection connection = ConnectMq.getConnection();
        connection.close();
    }

执行方法后可以看到界面多了一个连接

2.exchanges有七种类型,官网上指明了每种类型支持的开发语言

刚才创建的virtualhost后也默认创建了七种方式

3.helloworld模式,一个生产者,一个默认交化机,一个queue队列,一个消费者

(1)生产者

public class Publisher {

    @Test
    public void publish() throws Exception {

        //1.获取到链接
        Connection connection = ConnectMq.getConnection();

        //2.创建信道channel
        Channel channel = connection.createChannel();

        //3.发布消息,exchange不会把消息实例化到本地,queue才会实例化消息
        String mes = "Hello World !";
        //参数1:指定exchange,使用默认的“”,
        //参数2:指定路由的规则,使用queue名称
        //参数3:指定传递的消息所携带的properties,使用null
        //参数4:指定发布的具体消息,这是使用byte[]类型
        channel.basicPublish("","HelloWorld",null,mes.getBytes());
        System.out.println("生产者发布消息成功");
        //4.释放资源
        channel.close();
        connection.close();

    }


}

(2)消费者

public class Consumer {

    @Test
    public void consume() throws Exception {
        //1.获取到连接对象
        Connection connection = ConnectMq.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        //参数1:queue--指明队列的名称
        //参数2:durable--当前队列是否需要持久化
        //参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
        //参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
        //参数5:arguments --指定当前队列的其它信息
        channel.queueDeclare("HelloWorld",true,false,false,null);

        //4.开启监听QUEUE
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接收到消息:"+new String(body,"UTF-8"));
            }
        };
        //参数1:queue--指定消费哪个队列
        //参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
        //参数3:Consumer--指定消费回调
        channel.basicConsume("HelloWorld",true,consume);
        System.out.println("消费者开始监听消息!");
        System.in.read();
        //5.释放资源

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


    }
}

启动控制台生产者打印消息

消费者打打印消息

空控制台的queue队列有波峰

4.work queue模式,一个生产者,一个默认交化机,一个queue队列,两个消费者

(1)生产者

public class Publisher {

    @Test
    public void publish() throws Exception {

        //1.获取到链接
        Connection connection = ConnectMq.getConnection();

        //2.创建信道channel
        Channel channel = connection.createChannel();

        //3.发布消息,exchange不会把消息实例化到本地,queue才会实例化消息
        for (int i = 0; i < 10;i++){
            String mes = "Hello World !"+i;
            channel.basicPublish("","work",null,mes.getBytes());
        }
        System.out.println("生产者发布消息成功");
        //4.释放资源
        channel.close();
        connection.close();

    }


}

(2)消费者

public class Consumer1 {

    @Test
    public void consume() throws Exception {
        //1.获取到连接对象
        Connection connection = ConnectMq.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        //参数1:queue--指明队列的名称
        //参数2:durable--当前队列是否需要持久化
        //参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
        //参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
        //参数5:arguments --指定当前队列的其它信息
        channel.queueDeclare("work",true,false,false,null);

        //4.开启监听QUEUE
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1接收到消息:"+new String(body,"UTF-8"));
            }
        };

        //参数1:queue--指定消费哪个队列
        //参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
        //参数3:Consumer--指定消费回调
        channel.basicConsume("work",true,consume);
        System.out.println("消费者1开始监听消息!");
        System.in.read();
        //5.释放资源

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


    }
}
public class Consumer2 {

    @Test
    public void consume() throws Exception {
        //1.获取到连接对象
        Connection connection = ConnectMq.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        //参数1:queue--指明队列的名称
        //参数2:durable--当前队列是否需要持久化
        //参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
        //参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
        //参数5:arguments --指定当前队列的其它信息
        channel.queueDeclare("work",true,false,false,null);

        //4.开启监听QUEUE
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2接收到消息:"+new String(body,"UTF-8"));
            }
        };
        
        //参数1:queue--指定消费哪个队列
        //参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
        //参数3:Consumer--指定消费回调
        channel.basicConsume("work",true,consume);
        System.out.println("消费者2开始监听消息!");
        System.in.read();
        //5.释放资源

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


    }
}

控制台消息:两个消费者均匀的消费信息

(3)修改:给每个消费者指定每次消费的能力qos和手动ack,根据自己的能力去消费消息,而不是默认情况由RabbitMq评价分配了

public class Consumer1 {

    @Test
    public void consume() throws Exception {
        //1.获取到连接对象
        Connection connection = ConnectMq.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        //参数1:queue--指明队列的名称
        //参数2:durable--当前队列是否需要持久化
        //参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
        //参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
        //参数5:arguments --指定当前队列的其它信息
        channel.queueDeclare("work",true,false,false,null);

        //指定当前消费者一次消费多少个消息
        channel.basicQos(1);
        //4.开启监听QUEUE
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者1接收到消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue--指定消费哪个队列
        //参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
        //参数3:Consumer--指定消费回调
        channel.basicConsume("work",false,consume);
        System.out.println("消费者1开始监听消息!");
        System.in.read();
        //5.释放资源

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


    }
}

public class Consumer2 {

    @Test
    public void consume() throws Exception {
        //1.获取到连接对象
        Connection connection = ConnectMq.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        //参数1:queue--指明队列的名称
        //参数2:durable--当前队列是否需要持久化
        //参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
        //参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
        //参数5:arguments --指定当前队列的其它信息
        channel.queueDeclare("work",true,false,false,null);

        //指定当前消费者一次消费多少个消息
        channel.basicQos(1);
        //4.开启监听QUEUE
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者2接收到消息:"+new String(body,"UTF-8"));

                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        
        //参数1:queue--指定消费哪个队列
        //参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
        //参数3:Consumer--指定消费回调
        channel.basicConsume("work",false,consume);
        System.out.println("消费者2开始监听消息!");
        System.in.read();
        //5.释放资源

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


    }
}

消费结果

5.publish/Subscribe模式,一个生产者,一个交化机,两个queue队列,两个消费者

声明一个FANOUT类型的exchange,并且将exchange和queue绑定在一起,绑定的方式就是直接绑定

(1)生产者:创建一个exchange,和一个或多个queue绑定在一起

public class Publisher {


    public void publish() throws Exception {

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

        //2.创建chnnel信道
        Channel channel = connection.createChannel();

        //3.创建exchange  --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
        //参数1:exchange名称
        //参数2:exchange类型   FANOUT --pubsub  DIRECT --Routing TOPIC --Topics
        channel.exchangeDeclare("exchange-pubsub", BuiltinExchangeType.FANOUT);
        channel.queueBind("pubsub-queue1","exchange-pubsub","");
        channel.queueBind("pubsub-queue2","exchange-pubsub","");

        //4.发布消息到exchange

        for (int i = 0;i < 10;i++) {
            String mes = "Hello World !" +i;
            channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
        }
        //5.释放资源
        channel.close();
        connection.close();
        
    }

}

(2)消费者:监听某一个队列

public class Consumer1 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("pubsub-queue1",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("pubsub-queue1",false,consume);
        System.out.println("消费者1开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}
public class Consumer2 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("pubsub-queue2",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("pubsub-queue2",false,consume);
        System.out.println("消费者2开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}

消费结果

6.Routing方式:一个生产者,一个exchange,两个queue,两个消费者

创建DIRECT类型的exchange,并且根据RoutingKey绑定指定的队列

(1)生产者:创建DIRECT类型的exchange,绑定对应的队列,在发送消息时,指定消息的具体RoutingKey

public class Publisher {


    @Test
    public void publish() throws Exception {

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

        //2.创建chnnel信道
        Channel channel = connection.createChannel();

        //3.创建exchange  --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
        //参数1:exchange名称
        //参数2:exchange类型   FANOUT --pubsub  DIRECT --Routing TOPIC --Topics
        channel.exchangeDeclare("exchange-routing", BuiltinExchangeType.DIRECT);
        channel.queueBind("routing-queue-error","exchange-routing","ERROR");
        channel.queueBind("routing-queue-info","exchange-routing","INFO");

        //4.发布消息到exchange
        channel.basicPublish("exchange-routing","ERROR",null,"ERROR".getBytes());
        channel.basicPublish("exchange-routing","INFO",null,"INFO1".getBytes());
        channel.basicPublish("exchange-routing","INFO",null,"INFO2".getBytes());
        channel.basicPublish("exchange-routing","INFO",null,"INFO3".getBytes());


        System.out.println("生产者发布消息成功");
        //5.释放资源
        channel.close();
        connection.close();
        
    }

}

(2)消费者:监听自己的队列

public class Consumer1 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("routing-queue-error",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者ERROR接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("routing-queue-error",false,consume);
        System.out.println("消费者1开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}
public class Consumer2 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("routing-queue-info",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("消费者INFO接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("routing-queue-info",false,consume);
        System.out.println("消费者2开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}

输出结果

7.Topic:一个生产者,一个exchange,两个queue,两个消费者

(1)生产者:创建topic的exchange方式,并且绑定到队列中,绑定可以通过*和#关键字,对指定的RoutingKey内容,编写时注意格式xxx.xxx.xxx,*代表一个xxx,#代表多个xxx,指定具体的RoutingKey内容

public class Publisher {


    @Test
    public void publish() throws Exception {

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

        //2.创建chnnel信道
        Channel channel = connection.createChannel();

        //3.创建exchange  --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
        //参数1:exchange名称
        //参数2:exchange类型   FANOUT --pubsub  DIRECT --Routing TOPIC --Topics
        channel.exchangeDeclare("exchange-topic", BuiltinExchangeType.TOPIC);

        //绑定队列,指明绑定方式
        channel.queueBind("topic-queue-1","exchange-topic","*.red.*");
        channel.queueBind("topic-queue-2","exchange-topic","fast.#");
        channel.queueBind("topic-queue-2","exchange-topic","*.*.rabbit");

        //4.发布消息到exchange
        channel.basicPublish("exchange-topic","fast.red.cat",null,"快红猫".getBytes());
        channel.basicPublish("exchange-topic","slow.black.dog",null,"慢黑构".getBytes());
        channel.basicPublish("exchange-topic","fast.white.rabbit",null,"快白兔".getBytes());


        System.out.println("生产者发布消息成功");
        //5.释放资源
        channel.close();
        connection.close();
        
    }

}

(2)消费者:监听队列

public class Consumer1 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("topic-queue-1",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("对红色感兴趣接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("topic-queue-1",false,consume);
        System.out.println("消费者1开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}
public class Consumer2 {


    @Test
    public void Consume () throws Exception {
        //1.获取到链接
        Connection connection = ConnectionClient.getConnection();

        //2.创建channel
        Channel channel = connection.createChannel();

        //3.声明队列
        channel.queueDeclare("topic-queue-2",true,false,false,null);


        //4.指定当前消费者--指定一次消费多少个
        channel.basicQos(1);

        //5.开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("对快和兔子感兴趣接收到的消息:"+new String(body,"UTF-8"));
                //手动ack
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };

        //参数1:queue --指定消费哪个队列
        //参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
        //参数3:Consumer -- 消费回调
        channel.basicConsume("topic-queue-2",false,consume);
        System.out.println("消费者2开始监听消息");
        System.in.read();
        //6.释放资源
        channel.close();
        connection.close();

    }

}

输出结果

8.springboot整合rabbitmq

(1)创建完springboot项目后,在pom.xml文件中添加依赖,application.yml中添加链接信息

        <!--spring-boot结合rabbitmq的jar-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: test
    password: test
    virtual-host: /test

(2)创建一个TopicExchange、Queue,并把他们绑定到一起,此类使用@Configuration修饰,加载到spring容器中,绑定的方法直接从注册bean中获取交换机和队列

@Configuration
public class RabbitMqConfig {

    //1.创建exchange  topic方式
    @Bean
    public TopicExchange getTopicExchange(){
        return new TopicExchange("boot-topic-change",true,false);
    }


    //2.创建Queue
    @Bean
    public Queue getQueue(){
        return new Queue("boot-queue",true,false,false,null);
    }

    //3.绑定在一起
    @Bean
    public Binding getBinding(TopicExchange topicExchange,Queue queue){
        return BindingBuilder.bind(queue).to(topicExchange).with("*.red.*");
    }


}

(3)创建一个消费者,使用@Component注解修饰,让spring能够扫描到,使用@RabbitListener监听对应的queue

@Component
public class Consumer {

    //使用RabbitListener监听,使用queues配置监听的queue
    @RabbitListener(queues = "boot-queue")
    public void getMessage(Object object){
        System.out.println("监听到队列boot-queue收到的消息"+object);
    }

}

(4)发布消息,指明发布到的交互机,以及路由方式,消息信息,使用RabbitTemplate来发送

    @Autowired
    RabbitTemplate rabbitTemplate;


    @Test
    void contextLoads() {
        //发送消息:指定发送到哪个交换机下,消息的routingkey,消息内容
        rabbitTemplate.convertAndSend("boot-topic-change","slow.red.cat","跑得快的猫");
    }

(5)启动springboot项目进行监听,运行发布消息

9.手动ack(告诉rabbitMq消息已经正常消费完了)

(1)application.yml配置文件中添加配置,手动ack,模式是自动ack

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual

(2)接收消息的时候,使用channel信道手动ack

  //使用RabbitListener监听,使用queues配置监听的queue
    @RabbitListener(queues = "boot-queue")
    public void getMessage(String msg, Channel channel, Message message) throws IOException {
        System.out.println("监听到队列boot-queue收到的消息"+msg);

        //使用信道手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

10.保证消息的可靠性--confirm机制:保证生产者可以将消息发送到exchange

(1)使用事务:事务可以保证消息100%发送,可以通过事务的回滚去记录日志,后面定时再次发送此消息,但是效率太低

(2)使用confirm确认机制,效率比事务要高

①普通confirm方式

       //开启confirm
        channel.confirmSelect();

        //发送消息
        String mes = "Hello World !" ;
        channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());

        //判断消息是否发送成功
        if(channel.waitForConfirms()){
            System.out.println("消息发送成功");
        }else{
            System.out.println("消息发送失败");
        }

②批量confirm方式

        //开启confirm
        channel.confirmSelect();

        //批量发送消息
         for (int i = 0;i < 10;i++) {
            String mes = "Hello World !" +i;
            channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
        }
        //判断消息是否发送成功
        channel.waitForConfirmsOrDie();//当发送的消息有一个失败,则全部失败,抛出IoException异常

③异步confirm方式

        //开启confirm
        channel.confirmSelect();

        //批量发送消息
         for (int i = 0;i < 1000;i++) {
            String mes = "Hello World !" +i;
            channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
        }

         //异步的方式监听消息发送
         channel.addConfirmListener(new ConfirmListener() {
             @Override
             public void handleAck(long l, boolean b) throws IOException {
                 System.out.println("消息发送成功,标识:"+l+",是否批量:"+b);
             }

             @Override
             public void handleNack(long l, boolean b) throws IOException {
                 System.out.println("消息发送失败,标识:"+l+",是否批量:"+b);
             }
         });

11.保证消息的可靠性--return机制:保证消息从exchange发送到queue,发送消息时mandatory设置为true

       //开启return机制
        channel.addReturnListener(new ReturnListener() {
            @Override
            public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                //消息从exchange中发送到queue中失败时,才执行此方法
                System.out.println(new String(bytes,"UTF-8")+"消息从exchange到queue发送失败");
            }
        });
 public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException {
        this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
    }

12.springboot项目实现confirm和return机制

(1)配置文件中进行设置

    publisher-confirm-type: simple
    publisher-returns: true

(2)写一个confirm和return的配置类,实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback接口

/**
 * 生产者的确认和返回机制配置类
 */
@Component
public class PublisherConfirmAndReturnConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //@PostConstruct修饰为构建此类时执行
    @PostConstruct
    public void initMethed(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }

    //confirm确认机制
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String s) {
       if(ack){
           System.out.println("消息成功送到exchange");
       }else{
           System.out.println("消息没有送到exchange");
       }
    }

    //消息没有送达时执行
    @Override
    public void returnedMessage(Message message, int i, String s, String s1, String s2) {
        System.out.println("消息没有从exchange送达到queue"+message);
    }
}

13.消息重复消费

消息重复消费会对非幂等性操作造成影响;造成重复消费的原因,消费者没有给rabbitmq一个ack。

为了解决重复消费的问题,可以使用Redis,在消费者消费消息之前,将消息的id放到redis中,id-0:正在执行业务,id-1:消息执行完成

如果ack失败,rabbitmq将消息交个其它消费者时,先执行setnx(如果key存在,什么事情都不做,如果key不存在,正常的set方法);若果key已经存在,获取到它的值,如果是0,当前消费者什么都不做,如果是1,其它消费者已经消费过此消息,直接ack。

极端情况:当消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间

(1)生产者:传递一个uuid

       //发送消息
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
                 .deliveryMode(1)  //是否需要持久化消息到队列, 1--需要 2——不需要
                 .messageId(UUID.randomUUID().toString())
                 .build();
        String mes = "Hello World !";
        channel.basicPublish("exchange-pubsub","pubsub-queue4",true,properties,mes.getBytes());

(2)引入redis的包

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

(3)消费者:使用redis记录消息的消费情况

       //开启监听Queue
        DefaultConsumer consume = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //拿到消息id
                String messageId = properties.getMessageId();
                Jedis jedis = new Jedis("127.0.0.1",6379);
                String result = jedis.set(messageId,"0","NX","EX",10);
                //1.setnx到redis,value默认0
                if(null != result && "OK".equalsIgnoreCase(result)){
                    System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
                    //2.消费成功,设置value为1
                    jedis.set(messageId,"1");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }else{
                    //3.若是setnx失败,获取到value值,若值等于0,return什么都不做,若值等于1,直接ack
                    String s = jedis.get(messageId);
                    if("1".equals(s)){
                        channel.basicAck(envelope.getDeliveryTag(),false);
                    }

                }
            }
        };

14.避免消息重复消费:springboot方式

(1)引入redis的相关jar包

        <!--spring-boot结合redis方式-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

(2)编写配置文件

spring:
  redis:
    host: 127.0.0.1
    port: 6379

(3)生产者添加唯一主键参数

@SpringBootTest
class QingyunApplicationTests {

    @Autowired
    RabbitTemplate rabbitTemplate;


    @Test
    void contextLoads() {
        CorrelationData messageId= new CorrelationData(UUID.randomUUID().toString());
        //发送消息:指定发送到哪个交换机下,消息的routingkey,消息内容
        rabbitTemplate.convertAndSend("boot-topic-change","slow.red.cat","跑得快的猫",messageId);
    }

}

(4)消费者结合redis根据唯一主键进行相关操作

@Component
public class Consumer {

    //注入redis
    @Autowired
    private StringRedisTemplate redisTemplate;


    //使用RabbitListener监听,使用queues配置监听的queue
    @RabbitListener(queues = "boot-queue")
    public void getMessage(String msg, Channel channel, Message message) throws IOException {
       //1.获取messageid
        String messageId = message.getMessageProperties().getHeader("spring_returned_message_correlation");

        //2.使用redis设置value默认为0
        if(redisTemplate.opsForValue().setIfAbsent(messageId,"0",10, TimeUnit.SECONDS)){
            //3.消费消息
            System.out.println("监听到队列boot-queue收到的消息"+msg);
            //4.设置value为1
            redisTemplate.opsForValue().set(messageId,"1");
            //5.手动ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        } else{
            //若是redis设置值失败,获取值,根据值判断,为1则手动ack,为其它情况不做任何处理
            if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
                channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            }
        }
        //使用信道手动ack
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

}

15.channel信道

无论是生产者还是消费者,都需要和 RabbitMQ Broker 建立连接,这个连接就是一条 TCP 连接,也就是 Connection;一旦 TCP 连接建立起来,客户端紧接着可以创建一个 AMQP 信道(Channel),每个信道都会被指派一个唯一的 ID;信道是建立在 Connection 之上的虚拟连接,RabbitMQ 处理的每条 AMQP 指令都是通过信道完成的。

试想这样一个场景,一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么必然需要建立很多个 Connection,也就是多个 TCP 连接。

然而对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。

RabbitMQ 采用类似 NIO(Non-blocking I/O)的做法,选择 TCP 连接复用,不仅可以减少性能开销,同时也便于管理。

每个线程把持一个信道,所以信道复用了 Connection 的 TCP 连接。同时 RabbitMQ 可以确保每个线程的私密性,就像拥有独立的连接一样。当每个信道的流量不是很大时,复用单一的 Connection 可以在产生性能瓶颈的情况下有效地节省 TCP 连接资源。但是信道本身的流量很大时,这时候多个信道复用一个 Connection 就会产生性能瓶颈,进而使整体的流量被限制了。此时就需要开辟多个 Connection,将这些信道均摊到这些 Connection 中,至于这些相关的调优策略需要根据业务自身的实际情况进行调节。

16.RabbitMQ 的4种集群架构

(1)主备模式

    也称为 Warren (兔子窝) 模式。实现 rabbitMQ 的高可用集群,一般在并发和数据量不高的情况下,这种模式非常的好用且简单。

    也就是一个主/备方案,主节点提供读写,备用节点不提供读写。如果主节点挂了,就切换到备用节点,原来的备用节点升级为主节点提供读写服务,当原来的主节点恢复运行后,原来的主节点就变成备用节点,和 activeMQ 利用 zookeeper 做主/备一样,也可以一主多备。

(2)远程模式

远程模式可以实现双活的一种模式,简称 shovel 模式,所谓的 shovel 就是把消息进行不同数据中心的复制工作,可以跨地域的让两个 MQ 集群互联,远距离通信和复制。

有两个异地的 MQ 集群(可以是更多的集群),当用户在地区 1 这里下单了,系统发消息到 1 区的 MQ 服务器,发现 MQ 服务已超过设定的阈值,负载过高,这条消息就会被转到 地区 2 的 MQ 服务器上,由 2 区的去执行后面的业务逻辑,相当于分摊我们的服务压力。

(3)镜像模式

非常经典的 mirror 镜像模式,保证 100% 数据不丢失。

 用 KeepAlived 做了 HA-Proxy 的高可用,然后有 3 个节点的 MQ 服务,消息发送到主节点上,主节点通过 mirror 队列把数据同步到其他的 MQ 节点,这样来实现其高可靠。

(4)多活模式

也是实现异地数据复制的主流模式,因为 shovel 模式配置比较复杂,所以一般来说,实现异地集群的都是采用这种双活 或者 多活模型来实现的。这种模式需要依赖 rabbitMQ 的 federation 插件,可以实现持续的,可靠的 AMQP 数据通信,多活模式在实际配置与应用非常的简单。

17.rabbitmq如何保证消费的顺序

    (1)发送消息有序:自己确保是有序的,集群化部署,可以通过分布式锁确保消息有序,例如根据订单id加锁;相同类型的数据投递到同一个队列中。
    存储有序:队列中的数据是FIFO,先进先出,只要保证了投递的顺序,存储是有序的。
    消费有序:一个队列对应一个消费者,并且一个消费者一个Channel,使用同一个线程消费,不能并发消费。
(2)Rabbitmq解决方案:x-single-active-consumer(true或false):单活模式(队列中配置),表示是否最多只允许一个消费者消费,如果有多个消费者同时绑定,则只会激活第一个,除非第一个消费者被取消或者死亡,才会自动转到下一个消费者。

18.mq如何解决消息积压

    消息消费的速度,小于消息生产的速度时,会产出消息积压。
解决方案:
(1)检查消费端是否正常消费消息
(2)增加消费端数量
(3)增加消费端并行度,使用多线程、线程池并发处理消息
(4)消费端设置合理的超时时间,并在超时后对消息进行重新处理后者补偿操作
(5)扩容消息队列,增加消息队列节点数
(6)调整消息处理的优先级,优先处理重要紧急的消息
(7)消息发送端流控

19.rabbitmq我们项目中具体的运用

(1)创建项目时,根据选中指标生成决策树
(2)编辑指标值时,重新生成组合解释信息
(3)登陆系统日志信息记录
(4)增删改日志记录

crms项目-生产者模块

①pom.xml中引入rabbitmq

	   <dependency>
	        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
	   </dependency>

②application.properties配置信息

#Rabbitmq相关配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host=/test

③声明交换机、队列并绑定的配置类

@Configuration
public class RabbitMqConfig {
	
	
	@Bean
	public TopicExchange topicExchange() {
		return new TopicExchange("crms-logging-exchange",true,false);
	}
	
	@Bean
	public Queue queue() {
		return new Queue("crms-logging-queue");
	}
	
	@Bean
	public Binding bind(Queue queue,TopicExchange exchange) {
		return  BindingBuilder.bind(queue).to(exchange).with("crms.logging.*");
	}
	
	

}

④业务代码发送消息

	@Autowired
	private RabbitTemplate  rabbitTemplate;

	        //使用rabbitmq发送消息到交换机
			FwRightRole fwRightRole = new FwRightRole();
			fwRightRole.setId(this.getUUID());
			fwRightRole.setRemark("这是删除日志呀");
			fwRightRole.setCreateTime(new Date());
			fwRightRole.setCreateUserCode(this.getUserCode());
			rabbitTemplate.convertAndSend("crms-logging-exchange", "crms.logging.delete", JSONObject.toJSONString(fwRightRole));

ctms项目-消费者

①pom.xml中引入rabbitmq

	   <dependency>
	        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
	   </dependency>

②application.properties配置信息

#Rabbitmq相关配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host=/test
spring.rabbitmq.listener.simple.acknowledge-mode=manual

③声明交换机、队列并绑定的配置类

@Configuration
public class RabbitMqConfig {
	
	
	@Bean
	public TopicExchange topicExchange() {
		return new TopicExchange("crms-logging-exchange",true,false);
	}
	
	@Bean
	public Queue queue() {
		return new Queue("crms-logging-queue");
	}
	
	@Bean
	public Binding bind(Queue queue,TopicExchange exchange) {
		return  BindingBuilder.bind(queue).to(exchange).with("crms.logging.*");
	}
	
	

}

④业务代码消费消息

@Component
public class CrmsLoggingListener {

	
	@RabbitListener(queues="crms-logging-queue")
	public void getMessage(String msg,Channel channel,Message message) throws IOException {
		//1.获取routingkey
		String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
		
		//2.使用switch判读是否为删除,
		switch(receivedRoutingKey) {
		case "crms.logging.delete" :
			//3.插入日志记录里面
			System.out.println(msg);
			channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
			break;
		}
		
		
		
		
		
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值