RabbitMQ --基础练习测试

先安装erlong,然后安装RabbitMQ  *一定记得版本很容易冲突

安装完这两个以后,浏览器打开访问 localhost:15672  账户密码 都是guest

如果失败,就把 C:\Users\Administrator\AppData\Roaming\RabbitMQ\db 目录下的那两个文件给删除了,然后再重新安装RabbitMQ

 

添加用户

 

添加数据库

 

给用户授权访问数据库

 

删除用户

点进去用户名,然后下拉到最下面,delete 就好了

 

使用java 玩转RabbitMQ

导入依赖:

     <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>4.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

简单队列

创建一个工具类 ConnectionUtils  ,获取MQ连接

public class ConnectionUtils {


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

        // 定义一个连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置服务地址
        factory.setHost("127.0.0.1");
        // AMQP协议:5672
        factory.setPort(5672);
        // vohst
        factory.setVirtualHost("/test");    // 这个/test是刚刚创建的数据库
        // 用户名
        factory.setUsername("user");
        // 密码
        factory.setPassword("user");    // 用户名和密码一定要正确

        return factory.newConnection();
    }
}

创建一个类发送消息 

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_simple_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_queue";

        // 发送消息
        //           要将消息发送到的Exchange(交换器)      路由Key   消息内容
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

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

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

然后创建一个消费者,只要发送者 发送信息到消息队列里,消费者就获取信息

import com.rabbitmq.client.*;

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

// 消费者
public class Customer {
    private static final String QUEUE_NAME = "test_simple_queue";

    @SuppressWarnings("deprecation")
    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        Channel channel = connection.createChannel();

        // 声明队列
        // 注意: 生产者和消费者都能够使用queueDeclare 来声明一个队列,但是如果消费者在同一个信道上订阅了另一个队列,就无法再声明队列了。必须先取消订阅,然后将信道置为"传输"模式,之后才能声明队列。

        // 不带任何参数的queueDeclare: 方法默认创建一个由RabbitMQ 命名的(类似这种amq.gen-LhQzlgv3GhDOv8PIDabOXA 名称,这种队列也称之为匿名队列)、排他的、自动删除的、非持久化的队列。
        //                  队列名称    是否持久化   是否排他    是否自动删除  设置队列的其他一些参数,如 x-message-ttl等
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) {
                String msg = new String(body);
                System.out.println("new api customer:" + msg);
            }
        };
        // 监听队列          队列         是否自动监听
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

 简单队列的缺点:提供者和消费者一 一对应的,如果有多个消费者消费队列里的数据,就不行了,如果队列名变更的话,要同时变更

工作队列

轮询分发

比如,来个提供者发送数据,发送了50条数据

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

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

        for (int i = 0; i < 50; i++) {
            String msg = "hello"+i;
            System.out.println("send "+msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            Thread.sleep(i*20);
        }

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

消费者1  给他线程休眠时2秒

import com.rabbitmq.client.*;

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

// 消费者
public class Customer1 {
    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("new api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

消费者2  线程休眠时1秒

import com.rabbitmq.client.*;

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

// 消费者
public class Customer2 {
    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException,InterruptedException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("new api customer:" + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
    }
}

消费者1 和 消费者2 代码是一样的,只是设置了线程休眠时间不一样,但是最后的结果是两个消费者轮流者获取的消息队列里的消息,你一个我一个的,消费者1 获取的都是奇数,消费者2 获取的都是偶数的,获取数量是一样的,这个是轮询分发

公平分发

必须要关闭自动监听队列里(true)  改成手动的,改成false

公平分发就是说能者多劳,谁跑得快谁得到的消息就多

提供者发送消息:

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_justice_queue";

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

        // 一次只发送一个消息到消息队列,这样的话等于是两个消费者在抢消息
        int prefetchCount = 1;
        channel.basicQos(prefetchCount);

        // 这里可以省略成一行
        // 限流方案
        /**  设置限流机制
         *  param1: prefetchSize,消息本身的大小 如果设置为0  那么表示对消息本身的大小不限制
         *  param2: prefetchCount,告诉rabbitmq不要一次性给消费者推送大于N个消息
         *  param3:global,是否将上面的设置应用于整个通道,false表示只应用于当前消费者
         */
//        channel.basicQos(0, 5, false);


        for (int i = 0; i < 50; i++) {
            String msg = "hello"+i;
            System.out.println("send "+msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            Thread.sleep(i*20);
        }

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

消费者1:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer1 {
    private static final String QUEUE_NAME = "test_justice_queue";

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException,InterruptedException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        // 保证只分发一个
        channel.basicQos(1);

        // 这里可以省略成一行
        // 限流方案
        /**  设置限流机制
         *  param1: prefetchSize,消息本身的大小 如果设置为0  那么表示对消息本身的大小不限制
         *  param2: prefetchCount,告诉rabbitmq不要一次性给消费者推送大于N个消息
         *  param3:global,是否将上面的设置应用于整个通道,false表示只应用于当前消费者
         */
//        channel.basicQos(0, 5, false);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("new api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

消费者2:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer2 {
    private static final String QUEUE_NAME = "test_justice_queue";

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException,InterruptedException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        // 保证只分发一个
        channel.basicQos(1);

        // 这里可以省略成一行
        // 限流方案
        /**  设置限流机制
         *  param1: prefetchSize,消息本身的大小 如果设置为0  那么表示对消息本身的大小不限制
         *  param2: prefetchCount,告诉rabbitmq不要一次性给消费者推送大于N个消息
         *  param3:global,是否将上面的设置应用于整个通道,false表示只应用于当前消费者
         */
//        channel.basicQos(0, 5, false);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("new api customer:" + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

 

 消息应答

 就是说,消费者里面最后的 监听队列里的配置 的那个boolean 值

// 监听队列
channel.basicConsume(QUEUE_NAME,false,consumer);
如果里面的这个boolean 值是true,就是自动确认模式,一旦消息给了消费者,就会从内存中删除消息,如果消费者瘫痪,或被黑客攻击导致,就会直接丢失正在处理的消息,但是如果把这个boolean 值给成false,就是手动模式,如果这个消费者挂掉了,就不会再给这个消费者消息了。如果是正常的,消费者处理完成消息以后,就会相应给消息队列,这个请求已经处理完成了,可以删除了,
然后rabbitMQ就会从内存中把消息删除


持久化
在写声明队列时候,里面的第二个boolean 值
// 声明队列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
把这个boolean 值改成 true,就可以持久化了,但是要注意:
  *:就算代码是正确的,如果改成true是会报错的,
  原因:这个是根据里面的这个:QUEUE_NAME 来判断的,rabbitMQ不允许重新定义一个已经存在的消息队列,除非再写一个,改个名字,的类似于这种方法。


订阅模式
模式解读:
1、一个生产者,多个消费者
2、每一个消费者都有一个自己的队列
3、提供者没有直接把消息发送到队列里,而是发送到了 转发器
4、每个队列都要绑定到这个转发器上
5、提供者发送消息,经过转发器,到达队列,从而就算一个消息被多个消费者消费。

代码实现:
提供者发送消息:
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");     // 分发  fanout:不处理路由键

        // 发送消息
        String msg = "hello ps";
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());

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

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

消费者1:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer1 {
    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, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        // 绑定队列到转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");

        // 保证只分发一个
        channel.basicQos(1);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【1】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

消费者2:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer2 {
    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, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        // 绑定队列到转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");

        // 保证只分发一个
        channel.basicQos(1);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【2】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

 

 路由模式

就是分发消息的时候,要指定发送什么类型的,如果消费者里没有设置有这种类型的,就接受不到
发送者:
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();

        // 声明转发器
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");     // 分发  之前这里是fanout,把路由改成direct

        // 发送消息
        String msg = "hello direct";

        // 这里如果写error 的话,消费者1 和2 都能接收到,如果改成info,只有消费者2 才能接收,因为消费者1 里面没有设置
        channel.basicPublish(EXCHANGE_NAME,"info",null,msg.getBytes());

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

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

消费者1:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer1 {
    private static final String QUEUE_NAME = "test_queue_direct_1";     // 队列
    private static final String EXCHANGE_NAME = "test_exchange_direct";     // 转发器

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 绑定队列到转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"error");    // 提供者发送error 时候,只能这个消费者1接受,
        // 保证只分发一个
        channel.basicQos(1);
        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【1】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

消费者2:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer2 {
    private static final String QUEUE_NAME = "test_queue_direct_2";           // 队列
    private static final String EXCHANGE_NAME = "test_exchange_direct";         // 转发器

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException,InterruptedException {
        // 获取链接
        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");         // 提供者发送error 时候,消费者2能接受这三种的

        // 保证只分发一个
        channel.basicQos(1);

        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【2】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

 

主题模式  和路由模式差不多的,只是通过匹配符绑定消费者可以接受哪种消息

发送消息指定执行的什么方法,消费者里要设置方法,没有包含发送者所指定的方法,就接收不到,所以也就无法执行
比如:goods.add  表示添加的方法,goods.delete 就表示删除方法
goods.#  表示可以接受所有的方法
发送者:
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");     // 分发  之前这里是fanout,把路由改成topic,就是主题模式

        // 发送消息
        String msg = "商品。。。。。";
        channel.basicPublish(EXCHANGE_NAME,"goods.add",null,msg.getBytes());
        // 这里的goods.add 是表示添加商品,比如:goods.delete 就是代表删除商品
        System.out.println("send msg:"+msg);

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

消费者1:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer1 {
    private static final String QUEUE_NAME = "test_queue_topic_1";     // 队列
    private static final String EXCHANGE_NAME = "test_exchange_topic";     // 转发器

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        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");    // 消费者1 添加时候可以拿到数据
        // 保证只分发一个
        channel.basicQos(1);
        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【1】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[1]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

消费者2:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer2 {
    private static final String QUEUE_NAME = "test_queue_topic_2";     // 队列
    private static final String EXCHANGE_NAME = "test_exchange_topic";     // 转发器

    public static void main(String[] args) throws IOException, TimeoutException, ShutdownSignalException, ConsumerCancelledException {
        // 获取链接
        Connection connection = ConnectionUtils.getConnection();
        // 创建通道
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 绑定队列到转发器
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.delete");    //        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"goods.#");    // 消费者2 所有的都能拿到

        // 保证只分发一个
        channel.basicQos(1);
        // 定义消费者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 一旦队列里有消息就触发这个方法获取消息
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body,"utf-8");
                System.out.println("【2】 api customer:" + msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("[2]");
                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 监听队列
        channel.basicConsume(QUEUE_NAME,false,consumer);
    }
}

 

消息确认机制

在rabbitMQ 中,我们可以通过持久化数据,解决服务器异常的造成数据丢失。
但是,生产者发送消息出去以后,消息有没有到达rabbitMQ 服务器,默认情况是不知道的。
两种方式:
AMQP 实现事务机制
Confirm 模式
事务机制

txSelect  用户将当前channel(就是那个通道) 设置成 事务模式
txCommit  提交事务
txRollback  回滚事务
这些都和mysql 里面一样的

生产者发送消息:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

// 发送
public class TxSend {
    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 tx message";
        try {
            // 设置成事务模式
            channel.txSelect();
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());

            int s = 1 / 0;    //       【【这个是测试异常用的,如果有了异常,消费者就接受不到消息了,因为事务回滚了】】

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

            // 提交事务
            channel.txCommit();
        } catch (IOException e) {

            // 有异常回滚事务
            channel.txRollback();
            System.out.println("send message txRollback");
        }

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

消费者:

import com.rabbitmq.client.*;

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

// 消费者
public class TxCustomer {
    private static final String QUEUE_NAME = "test_queue_tx";     // 队列

    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.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("customer[tx] msg =====>>  " + new String(body, "utf-8"));
            }
        });
    }
}

这种事务模式的缺点是提交的请求太多,降低了服务器的吞吐量,相比起来,confirm串行模式要好很多

 confirm串行模式

confirm模式最大的好处就是,异步执行!

channel.chonfirmSelect()  开启confirm 模式

编程模式:

普通  发一条  waitForConfirms()

批量的  发一批  waitForConfirms()  

异步  confirm 模式:提供一个回调方法

 

普通、单条:

发送消息:

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_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 模式
        // 切记,如果这个队列是之前的已经存在的队列,这样设置会报异常
        channel.confirmSelect();

        // 发送消息
        String msg = "hello confirm message";
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

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

消费者接收:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer {
    private static final String QUEUE_NAME = "test_queue_confirm1";     // 队列

    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.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("customer msg =====>   " + new String(body, "utf-8"));
            }
        });
    }
}

 

批量消息:

和单条信息发送的消费者是一样的,只是提供者发送消息时候,用循环发送了多条,然后再 确认

提供者发送消息:

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_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 模式
        // 切记,如果这个队列是之前的已经存在的队列,这样设置会报异常
        channel.confirmSelect();

        // 发送消息
        String msg = "hello confirm message";
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
        }
        // 确认
        if ( !channel.waitForConfirms()){
            System.out.println("发送失败");
        }else {
            System.out.println("发送成功");
        }
        channel.close();
        connection.close();
    }
}

 消费者:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer {
    private static final String QUEUE_NAME = "test_queue_confirm1";     // 队列

    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.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("customer msg =================>   " + new String(body, "utf-8"));
            }
        });
    }
}

 

异步

 提供者:

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, InterruptedException {
        // 创建一个连接
        Connection connection = ConnectionUtils.getConnection();
        // 从连接中获取一个通道
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 生产者调用 confirmSelect,将channel设置为confirm 模式
        // 切记,如果这个队列是之前的已经存在的队列,这样设置会报异常
        channel.confirmSelect();

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

        // 通道添加监听
        channel.addConfirmListener(new ConfirmListener() {

            // 没有问题的 handleAck
            @Override
            public void handleAck(long l, boolean b) {
                if (b) {
                    System.out.println("handleAck------multiple");
                    confirmSet.headSet(l + 1).clear();
                } else {
                    System.out.println("handleAck------multiple  false");
                    confirmSet.remove(l);
                }
            }

            @Override
            public void handleNack(long l, boolean b) {
                if (b) {
                    System.out.println("handleNack------multiple");
                    confirmSet.headSet(l + 1).clear();
                } else {
                    System.out.println("handleNack------multiple  false");
                    confirmSet.remove(b);
                }
            }
        });

        // 发送消息
        String msg = "ssssssssssss";

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

消费者:

import com.rabbitmq.client.*;

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

// 消费者
public class Customer {
    private static final String QUEUE_NAME = "test_queue_confirm3";     // 队列

    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.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("customer msg ==============>    " + new String(body, "utf-8"));
            }
        });
    }
}

 

spring集成rabbitMQ

依赖:

        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>1.7.5.RELEASE</version>
        </dependency>

在spring的xml配置文件里配置,这个文件名声context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.7.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <!--定义rabbit MQ的连接工厂-->
    <rabbit:connection-factory id="connectionFactory"
                               host="127.0.0.1" port="5672" username="user" password="user" virtual-host="/test"/>
    <!--定义rabbit模板,指定连接工厂以及定义exchange-->
    <rabbit:template id="template" connection-factory="connectionFactory" exchange="fanoutExchange"/>
    <!--mq 管理,包括队列,交换器声明等-->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!--定义队列,自动声明-->
    <rabbit:queue name="myQueue" auto-declare="true" durable="true"/>
    <!--声明交换器,自动声明-->
    <rabbit:fanout-exchange name="fanoutExchange" auto-declare="true">
        <rabbit:bindings>
            <rabbit:binding queue="myQueue"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>
    <!--队列监听-->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="foo" method="listen" queue-names="myQueue"/>
    </rabbit:listener-container>
    <!--消费者-->
    <bean id="foo" class="com.biao.jiChengSpring.Consumer"/>

</beans>

然后简单测试一下:

public class SpringMain {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:context.xml");
        // rabbitMQ 模板
        RabbitTemplate template = ctx.getBean(RabbitTemplate.class);
        // 发送消息
        template.convertAndSend("hello world");
        Thread.sleep(1000);
        // 销毁,关闭
        ((ClassPathXmlApplicationContext) ctx).destroy();
    }
}

消费者:

public class Consumer {
    // 具体执行业务的方法
    public void listen(String foo){
        System.out.println("消费者:"+foo);
    }
}
posted @ 2020-03-20 19:38  aBiu--  阅读(200)  评论(0)    收藏  举报