其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq

文章目录

  • 安装跳过,不会的请百度,这篇文章主要记录理论以及编码
  • 什么是rabbitmq
  • 基本概念
  • 不与SpringBoot整合的写法
    • 先写一个工具类,获取连接等
    • 简单队列模式
      • 模型
      • 生产者生产消息
      • 消费者接受消息
      • 简单消息队列不足
    • Work queues 工作队列之轮询分发
      • 模型
        • 为什么会出现工作队列
      • 生产者
      • 消费者1
      • 消费者2
      • 现象
    • Work queues 工作队列之公平分发
      • 生产者
      • 消费者1
      • 消费者2
      • 现象
    • 消息应答
      • autoAck为false时
      • autoAck为true时
      • 消息应答默认是打开的 false
      • 问题 如果rabbitmq挂了怎么办 我们的任务仍然会丢失
    • 消息持久化
      • 注意
    • publish_subscribe订阅模式
      • 模型
      • 生产者
      • 消费者1
      • 消费者2
    • Exchange(交换机/转发器)
      • Fanout(不处理路由键)
      • Direct(处理路由键)
      • Topic Exhange(主题模式)
      • 路由模式
        • 生产者
        • 消费者1
        • 消费者2
      • Topic
        • 商品crud
        • 生产者
        • 消费者1
        • 消费者2
    • Rabbitmq的消息确认机制(事务+confirm)
      • 问题
        • 两种方式
      • 事务机制
      • 生产者
      • 消费者
    • confirm模式
      • 生产者端confirm的实现原理
      • 串行模式-单条
        • 生产者
        • 消费者
      • 串行模式-批量
        • 生产者
        • 消费者
      • confirm异步
        • 生产者
        • 消费者
  • 与SpringBoot整合的写法
    • yml配置
    • 依赖
    • 前提
    • 简单模型
      • 生产者
      • 消费者
    • 工作队列
      • 生产者
      • 消费者
    • fanout广播
      • 生产者
      • 消费者
    • route路由模式
      • 生产者
      • 消费者
    • 动态路由/广播模式
      • 生产者
      • 消费者

安装跳过,不会的请百度,这篇文章主要记录理论以及编码

什么是rabbitmq

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。

AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。

RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

基本概念

  • message: 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
  • Publisher: 消息的生产者,也是一个向交换器发布消息的客户端应用程序。
  • Exchange: 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
  • Binding: 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
  • Queue: 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
  • Connection: 网络连接,比如一个TCP连接。
  • Channel: 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
  • Consumer: 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
  • Virtual Host: 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
  • Broker: 表示消息队列服务器实体。

不与SpringBoot整合的写法

先写一个工具类,获取连接等

package cn.com.laoli.rabitmq.utils;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConnectionUtils {
    /**
     * 获取mq的连接
     *
     * @return
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        //定义一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();

        //设置服务地址
        factory.setHost("127.0.0.1");

        //设置端口号 AMQP协议
        factory.setPort(5672);

        //设置vhost
        factory.setVirtualHost("你设置的vhost");

        //用户名
        factory.setUsername("你的用户名");

        //密码
        factory.setPassword("你的密码");
        return factory.newConnection();
    }
}

简单队列模式

模型

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第1张图片

  • p:消息的生产者
  • 红色的: 队列
  • c: 消费者

生产者生产消息

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {

    private static final String QUEUE_NAME = "test_sample_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {

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

        //创建一个通道
        Channel channel = connection.createChannel();
        //创建队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        String msg = "hello simple !";
        
        //发布
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

        System.out.println("------send msg:"+msg);

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

消费者接受消息

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Receive {

    private static final String QUEUE_NAME = "test_sample_queue";


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

        //获取链接
        Connection connection = ConnectionUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();

            /*//老版本  定义队列消费者
            QueueingConsumer queueingConsumer = new QueueingConsumer(channel);
            //监听队列
            channel.basicConsume(QUEUE_NAME,true,queueingConsumer);
            while (true){
                QueueingConsumer.Delivery delivery = queueingConsumer.nextDelivery();
                String msg  = new String(delivery.getBody());
                System.out.println("[recv] msg :"+msg);
            }*/

        //队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
        //事件模型,一旦有消息进入就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body, "utf-8");
                System.out.println("new  api recv:" + msg);
            }
        };
        
        //监听队列
        channel.basicConsume(QUEUE_NAME,true,defaultConsumer);
        
    }

}

简单消息队列不足

耦合性高,生产者一一对应消费者(如果我想有多个消费者消费队列中的消息,这时就不行了)
队列名变更,这时候得同时变更

Work queues 工作队列之轮询分发

模型

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第2张图片

为什么会出现工作队列

Simple队列 是一一对应的,且实际开发,生产者发送消息是毫不费力的,而消费者一般是要跟业务相结合的。消费者接收到消息之后就需要处理 可能需要花费时间,这时候队列就积压了好多消息

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {
    private static final String QUEUE__NAME = "test_work_queue";
    /**                 |----c1
     *   p----Queue-----
     *                  |----c2
     * @param args
     * @throws IOException
     * @throws TimeoutException
     */
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明一个队列
        channel.queueDeclare(QUEUE__NAME,false,false,false,null);
        for (int i=0;i<50;i++){
            String msg = "hello "+i;
            channel.basicPublish("",QUEUE__NAME,null,msg.getBytes());

            Thread.sleep(i*10);
        }
        channel.close();
        connection.close();
    }
}

消费者1

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Receive {

    private static final String QUEUE__NAME = "test_work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE__NAME,false,false,false,null);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");

                System.out.println("recv1 msg:"+msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("1    done");
                }
            }
        };
        //后续会说明这个是什么
        boolean autoAck = true;
        //监听队列
        channel.basicConsume(QUEUE__NAME,autoAck,consumer);
    }
}

消费者2

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Receive2 {

    private static final String QUEUE__NAME = "test_work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE__NAME,false,false,false,null);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv2 msg:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("2    done");
                }
            }
        };
        //后续会说明这个是什么
        boolean autoAck = true;
        //监听队列
        channel.basicConsume(QUEUE__NAME,autoAck,consumer);
    }
}

现象

消费者1和消费者2处理的消息是一样的
消费者1:偶数
消费者2:奇数
这种方式叫轮询分发(round-robin)结果就是不管谁忙或闲,都不会多给一个
任务消息总是你一个我一个

Work queues 工作队列之公平分发

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {

    private static final String QUEUE__NAME = "test_work_queue";

    /**                 |----c1
     *   p----Queue-----
     *                  |----c2
     * @param args
     * @throws IOException
     * @throws TimeoutException
     */
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明一个队列
        channel.queueDeclare(QUEUE__NAME,true,false,false,null);
        /**
         * 每个消费者 发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
         *
         * 限制发送给同意消费者不超过一条
         */
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        for (int i=0;i<50;i++){
            String msg = "hello "+i;
            channel.basicPublish("",QUEUE__NAME,null,msg.getBytes());

            System.out.println("发送"+i+":"+msg);
            Thread.sleep(i*10);
        }
        channel.close();
        connection.close();
    }
}

消费者1

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Receive {

    private static final String QUEUE__NAME = "test_work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
       final Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE__NAME,false,false,false,null);
        //保证一次只发一个
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv1 msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("1    done");
                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE__NAME,autoAck,consumer);
    }
}

消费者2

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Receive2 {

    private static final String QUEUE__NAME = "test_work_queue";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
        final Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE__NAME,false,false,false,null);
        //保证一次只发一个
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv2 msg:"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("2    done");

                    //第一个参数是一个标识 表示已经收到消息  手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE__NAME,autoAck,consumer);
    }
}

现象

消费者2处理的比消费者1多 能者多劳 公平分发

消息应答

boolean autoAck = false;
channel.basicConsume(QUEUE__NAME,autoAck,consumer);

autoAck为false时

手动模式 如果有一个消费者挂掉 就会交付给其他消费者
rabbitmq支持消息应答,消费者发送一个消息应答告诉rabbitmq这个消息我已经处理完成,可以删了,然后rabbitmq就删除内存中的数据

autoAck为true时

自动确认模式 表示一旦rabbitmq将消息发送给消费者,消息就会从内存中删除
如果rabbitmq挂掉了 消息可能会丢失
如果杀死正在执行的消费者,就会丢失正在处理的消息

消息应答默认是打开的 false

问题 如果rabbitmq挂了怎么办 我们的任务仍然会丢失

消息持久化

//声明队列
boolean durable = false
channel.queueDeclare(QUEUE__NAME,durable,false,false,null);

注意

我们将程序中的durable=false改为true是不可以的,尽管代码是正确的,也不会运行成功!!!
因为我们已经定义了一个叫test_work_queue,这个queue是未持久化的,tabbitmq不允许重新定以(不同参数)一个已存在的队列

publish_subscribe订阅模式

模型

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第3张图片

  • 一个生产者,多个消费者
  • 每一个消费者都有自己的队列
  • 生产者没有直接把消息发送到队列中,而是发到了exchange(交换器)
  • 每个队列都要绑定到交换器上
  • 生产者发送的消息经过交换器到达队列吗,就能实现一个消息被多个消费者消费

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {

    private static final String EXCHANGE_NAME="test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //通道
        Channel channel = connection.createChannel();
        //声明交换机\
        //分发类型
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
        //发送消息
        String msg = "hello_ps";
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
        System.out.println("send:"+msg);
        channel.close();
        connection.createChannel();
    }
}

消费者1

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv {

    private static final String QUEUE_NAME = "test_queue_fanout_email";
    private static final String EXCHANGE_NAME="test_exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
        final Channel channel = connection.createChannel();
         //队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //绑定交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        //保证一次只发一个
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv1 msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("1    done");

                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv2 {

    private static final String QUEUE_NAME = "test_queue_fanout_sms";
    private static final String EXCHANGE_NAME="test_exchange_fanout";
    
    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道channel
        final Channel channel = connection.createChannel();
         //队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        //绑定交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        //保证一次只发一个
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);
        //定义一个消费者
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv2 msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("2   done");
                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

Exchange(交换机/转发器)

  • 一方面接受生产者的消息
  • 另一方面是向队列推送消息

Fanout(不处理路由键)

只要是绑定了的都能收到消息

  channel.exchangeDeclare(EXCHANGE_NAME,"fanout");

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第4张图片

Direct(处理路由键)

发送的时候需要带一个key,队列也需要带一个key,如果相匹配就会把消息转到对应的队列里
其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第5张图片

Topic Exhange(主题模式)

将路由键和某个模式匹配
其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第6张图片

  • #匹配一个或者多个
  • *匹配一个

路由模式

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第7张图片

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {
    private static final String EXCHANGE_NAME = "test_exchange_direct";

    public static void main(String[] args) throws IOException, TimeoutException {
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //获取通道
        Channel channel = connection.createChannel();
        //声明exchange 声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        String msg = "hello Direct";
        String routingKey = "info";
        channel.basicPublish(EXCHANGE_NAME,routingKey,null,msg.getBytes());
        System.out.println("send:"+msg);
        channel.close();
        connection.close();
    }

}

消费者1

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv {
    private static final String EXCHANGE_NAME = "test_exchange_direct";
    private static final String QUEUE_NAME = "test_queue_direct1";
    
    public static void main(String[] args) throws IOException, TimeoutException {

        Connection connection = ConnectionUtils.getConnection();
        final  Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        //每次只发一个消息
        channel.basicQos(1);
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv1 msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("1    done");

                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

消费者2

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv2 {
    private static final String EXCHANGE_NAME = "test_exchange_direct";
    private static final String QUEUE_NAME = "test_queue_direct2";


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

        Connection connection = ConnectionUtils.getConnection();
        final  Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"info");
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"warning");
        //每次只发一个消息
        channel.basicQos(1);
        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");
                System.out.println("recv2 msg:"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("2    done");
                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}

Topic

其他文章太水,从头到尾写一篇rabbitmq,含Springboot整合rabbitmq_第8张图片
#匹配多个,*匹配一个

商品crud

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {

    private static  final String EXCHANGE_NAME = "test_exchange_topic";

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

        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.exchangeDeclare(EXCHANGE_NAME,"topic");

        String msg = "商品.....";
        System.out.println("send:"+msg);

        channel.basicPublish(EXCHANGE_NAME,"goods.add",null,msg.getBytes());

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

消费者1

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv {
    private static final String EXCHANGE_NAME = "test_exchange_topic";
    private static final String QUEUE_NAME = "test_queue_topic1";


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

        Connection connection = ConnectionUtils.getConnection();

        final  Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.add");

        //每次只发一个消息
        channel.basicQos(1);


        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");

                System.out.println("recv1 msg:"+msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("1    done");

                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };

        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);

    }

}

消费者2

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv2 {
    private static final String EXCHANGE_NAME = "test_exchange_topic";
    private static final String QUEUE_NAME = "test_queue_topic2";


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

        Connection connection = ConnectionUtils.getConnection();

        final  Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");

        //每次只发一个消息
        channel.basicQos(1);


        Consumer consumer =new DefaultConsumer(channel){
            //消息到达就会触发这个方法
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                String msg = new String(body,"utf-8");

                System.out.println("recv2 msg:"+msg);

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("2    done");

                    //第一个参数是一个标识 表示已经收到消息   手动回值
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };

        //自动应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);

    }

}

Rabbitmq的消息确认机制(事务+confirm)

在rabbitmq中,我们可以通过持久化数据,解决rabbitmq服务器异常的数据丢失问题

问题

生产者将消息发送出去之后吗,消息到底有没有到达rabbitmq服务器,默认情况是不知道的

两种方式

  • AMQP协议实现了事务机制
  • Confirm模式

事务机制

  • TxSelect:用于将当前channel设置成transtation模式
  • TxCommit:用于提交数据事务
  • TxRollback:回滚事务
  • 缺点:降低了rabbitmq的吞吐量

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send {

    private static final String QUEUE_NAME = "test_queue_tx";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        String msg = "hello";

        try {
            channel.txSelect();

            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

            int xx = 1/0;

            channel.txCommit();

        }catch (Exception e){
            channel.txRollback();
            System.out.println("seng msg rollback");
        }
        channel.close();
        connection.createChannel();
    }

}

消费者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv {

    private static final String QUEUE_NAME = "test_queue_tx";


    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                System.out.println("rev msg :"+new String(body,"utf-8"));
            }
        });
    }

}

confirm模式

生产者端confirm的实现原理

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上发布的消息都会被指派一个唯一ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息会将消息写入磁盘之后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理
confirm最大的好处在于他是异步

串行模式-单条

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Send1 {

    private static final String QUEUE_NAME = "test_queue_confirm1";


    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);


        //生产者调用 confirmSelect 将channel设为confirm模式    注意得是一个新队列如果之前开了事务模式在开confirm模式会报错 rabbitmq不允许
        channel.confirmSelect();

        String msg = "hello confir message";

        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

        if (!channel.waitForConfirms()){
            System.out.println("消息发送失败");
        }else{
            System.out.println("发送成功");
        }

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

    }

}

消费者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Recv {

    private static final String QUEUE_NAME = "test_queue_confirm3";


    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        channel.basicConsume(QUEUE_NAME,true,new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                System.out.println("rev msg :"+new String(body,"utf-8"));
            }
        });
    }

}

串行模式-批量

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

/**
 * @author: LiDeLin  [email protected]
 * @description : 普通模式  多条  比单条效率高  但是如果有一条失败那么全部消息都会被返回
 */
public class Send2 {

    private static final String QUEUE_NAME = "test_queue_confirm1";


    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);


        //生产者调用 confirmSelect 将channel设为confirm模式    注意得是一个新队列如果之前开了事务模式在开confirm模式会报错 rabbitmq不允许
        channel.confirmSelect();

        String msg = "hello confir message";
        for (int i = 0; i<=9 ;i++){
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

        }

        if (!channel.waitForConfirms()){
            System.out.println("消息发送失败");
        }else{
            System.out.println("发送成功");
        }

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

    }

}

消费者

同上

confirm异步

channel对象提供的ConfirmListener()回调方法只包含deliverTag(当前Channelfa发出的消息序列号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publishi一条数据,集合元素加1,每回调一次handleAck方法吗,unconfirm集合删掉相应的一条(multiple=false)或者多条(multiple=true)记录,从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构

生产者

import cn.com.laoli.rabitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;

import java.io.IOException;
import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;

public class Send3 {

    private static final String QUEUE_NAME = "test_queue_confirm3";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        //生产者调用confirmSelect 将channel设置为confirm模式
        channel.confirmSelect();

        //存放未确认的消息的标识
        final SortedSet confirmSet = Collections.synchronizedSortedSet(new TreeSet());

        channel.addConfirmListener(new ConfirmListener() {
            //没有问题的
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple){
                    System.out.println("handleAck----multiple");
                    confirmSet.headSet(deliveryTag+1).clear();
                }else{
                    System.out.println("handleAck----multiple  false");
                    confirmSet.remove(deliveryTag);
                }
            }

            //回值有问题的
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                if (multiple){
                    System.out.println("nak-----multiple");
                    confirmSet.headSet(deliveryTag+1).clear();
                }else{
                    System.out.println("nak-----multiple---false");
                    confirmSet.remove(deliveryTag);
                }
            }
        });

        String msgStr = "sssss";

        while (true){
            long seqNo = channel.getNextPublishSeqNo();
            channel.basicPublish("",QUEUE_NAME,null,msgStr.getBytes());
            confirmSet.add(seqNo);
        }
    }


}

消费者

同上

与SpringBoot整合的写法

yml配置

server:
  port: 8091

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    # 开启confirm
    publisher-confirms: true
    listener:
      simple:
        # 手动ack
        acknowledge-mode: manual

依赖


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

前提

注入

@Autowired
    private RabbitTemplate rabbitTemplate;

这个是springboot提供的

简单模型

生产者

/**
     * 简单模型
     */
    @Test
    public void test(){
        rabbitTemplate.convertAndSend("hello","yue xin 10k");
    }

消费者

@Component
/**
 * 简单模式
 * queuesToDeclare:没有队列就声明一个队列
 * queue的属性可以直接设置,布尔值是字符串,默认非持久化,非独占,不是自动删除的队列
 */
//@RabbitListener(queuesToDeclare = @Queue(value = "hello",declare = "true",autoDelete = "true"))
@RabbitListener(queuesToDeclare = @Queue("hello"))
@Slf4j
public class HelloConsumer {

    /**
     * 取出消息后的回调函数
     * @param message
     */
    @RabbitHandler
    public void receive(String message){
        log.info("收到的消息:{}",message);
    }

}

工作队列

生产者

/**
     * 工作队列,默认是轮询分发,开启自动应答即可能者多劳,配置文件中开启,当前已开启
     */
    @Test
    public void testWork(){
        for (int i = 0; i < 20; i++) {
            rabbitTemplate.convertAndSend("work","work模型");
        }
    }

消费者

@Component
@Slf4j
public class WorkConsumer {

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive1(String message) throws InterruptedException {
//        Thread.sleep(2000);
        System.out.println("消费者1");
        log.info("消费者1:------------------>{}",message);
    }

    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive2(String message) throws InterruptedException {
//        Thread.sleep(1000);
        System.out.println("消费者2");
    }

}

fanout广播

生产者

 /**
     * fanout广播
     */
    @Test
    public void testFanout(){
        rabbitTemplate.convertAndSend("logs","","Fanout的模型传递发送的消息");
    }

消费者

@Component
public class FanoutConsumer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "logs",type = "fanout")  //绑定的交换机
            )
    })
    public void receive1(String message){
        System.out.println("message1"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "logs",type = "fanout")  //绑定的交换机
            )
    })
    public void receive2(String message){
        System.out.println("message2"+message);
    }

}

route路由模式

生产者

/**
     * route 路由模式
     */
    @Test
    public void testRoute(){
        rabbitTemplate.convertAndSend("directs","info","发送info的key的路由信息");
    }

消费者

@Component
public class RouteConsumer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "directs",type = "direct"),  //绑定的交换机
                    key = {"info","error","debug"} //路由key
            )
    })
    public void receive1(String message){
        System.out.println("message1"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "directs",type = "direct"),  //绑定的交换机
                    key = {"error"}
            )
    })
    public void receive2(String message){
        System.out.println("message2"+message);
    }

}

动态路由/广播模式

生产者

/**
     * topic 动态路由/订阅模式
     */
    @Test
    public void testTopic(){
        rabbitTemplate.convertAndSend("topics","user.save","user.save 路由消息");
    }

消费者

@Component
public class TopicConsumer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "topics",type = "topic"),  //绑定的交换机
                    key = {"user.save","user.*"} //路由key
            )
    })
    public void receive1(String message){
        System.out.println("message1"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//创建临时队列
                    exchange = @Exchange(value = "topics",type = "topic"),  //绑定的交换机
                    key = {"order.#","product.#","user.*"}
            )
    })
    public void receive2(String message){
        System.out.println("message2"+message);
    }


}

你可能感兴趣的:(RabbitMQ,rabbitmq,java,队列,中间件)