消息中间件_RabbitMQ

一、消息中间件解决了什么问题?

1. 异步处理
2. 应用解耦
3. 流量削峰
4. 日志处理

二、RabbitMQ的安装与配置

	暂不更新

三、Java操作RabbitMQ

1. simple(简单队列)

1.1 模型:
 	P:消息生产者
 	红色:队列
 	C:消息消费者	

在这里插入图片描述
1.2 示例:
1.2.1 获取连接:

public class ConnectionUtil {
    /**
     * 获取MQ的连接
     * @return
     */
    public static Connection getConnection() throws IOException, TimeoutException {
        // 定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        // 设置服务地址
        factory.setHost("xxx.xxx.xx.xx");
        // 设置服务端口
        factory.setPort(xxxx);
        // 设置库
        factory.setVirtualHost("/xx");
        // 用户名
        factory.setUsername("xx");
        // 密码
        factory.setPassword("xx");
        return factory.newConnection();
    }
}
	1.2.2 定义生产者:
public class Producer {

    private static final String QUEUE_NAME = "test_simple_queue";

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

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

        // 从连接中获取通道
        Channel channel = connection.createChannel();
        // 创建队列声明,QUEUE_NAME指定向哪个队列发送消息
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        String msg = "Hello Simple";
        channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());

        System.out.println("======= Producer msg :"+msg);

        channel.close();
        connection.close();
    }
}
	1.2.2 定义消费者:
public class Consumer {

    private static final String QUEUE_NAME = "test_simple_queue";

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

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

        // 创建通道
        Channel channel = connection.createChannel();
        // 创建队列声明,QUEUE_NAME指定向哪个队列发送消息
        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("======= Consumer msg :"+msg);
            }
        };

        // 监听队列
        channel.basicConsume(QUEUE_NAME,true,consumer);
        
        channel.close();
        connection.close();
    }
}
1.3 简单队列的不足:
	耦合性高  生产者一一对应消费者(如果模型有多个消费者消费队列中的消息,就会出现问题)
	队列名变更需要同时变更

2. work queues(工作队列)
2.1 模型:
消息中间件_RabbitMQ_第1张图片
工作队列:可以解决消息队列中消息积压
2.2 示例:
2.2.1:定义生产者

private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        // 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 创建通道
        Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        for (int i = 0; i < 50; i++) {
            String msg = "Hello Work_queue msg :"+i;
            System.out.println(msg);
            channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
            Thread.sleep(i*10);
        }

        channel.close();
        connection.close();
    }
	2.2.2:定义消费者 1
public class Consumer1 {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        Connection connection = ConnectionUtil.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("======= Consumer{1} msg :"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("Consumer{1} done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}
	2.2.3:定义消费者 2
public class Consumer2 {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        Connection connection = ConnectionUtil.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("======= Consumer{2} msg :"+msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("Consumer{2} done");
                }
            }
        };
        boolean autoAck = true;
        // 监听队列
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}
2.3 结果:
	在Consumer 1睡眠1秒,Consumer 2睡眠2秒的情况下,Consumer 1 与 Consumer 2 处理的消息是一样的 ,
	并且Consumer 1都为偶数,Consumer 2为奇数,这种方式就叫做轮询分发(round-robin)

3. 公平分发 fair dipatch

	/* 
	 * 在上一份示例上做改动
	 * 改动_1 :分别在生产者 消费者的代码中增加如下:
     * 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
     * 限制发送给同一个消费者不得超过一条数据或消息
     * */
    channel.basicQos(1);
	/* 
	 * 改动_2 :消费者的代码中增加如下:
     * */
    // 手动回执
	channel.basicAck(envelope.getDeliveryTag(),false);
	// 将自动应答改为手动
	channel.basicConsume(QUEUE_NAME,false,consumer);
3.1 生产者:
public class Producer {

   private static final String QUEUE_NAME = "test_work_queue";

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

       /*
       * 每个消费者发送确认消息之前,消息队列不发送下一个消息到消费者,一次只处理一个消息
       * 限制发送给同一个消费者不得超过一条数据或消息
       * */
       int prefatchCount = 1;
       channel.basicQos(prefatchCount);

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

       channel.close();
       connection.close();
   }
}
3.2 消费者1:
public class Consumer1 {

    private static final String QUEUE_NAME = "test_work_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        Connection connection = ConnectionUtil.getConnection();
        // 定义通道
        final Channel channel = connection.createChannel();
        // 声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        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("======= Consumer{1} msg :"+msg);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("Consumer{1} done");

                    channel.basicAck(envelope.getDeliveryTag(),false);
                }
            }
        };
        // 自动应答 改为 false
        boolean autoAck = false;
        // 监听队列
        channel.basicConsume(QUEUE_NAME,autoAck,consumer);
    }
}
3.3 消费者2:
public class Consumer2 {

   private static final String QUEUE_NAME = "test_work_queue";

   public static void main(String[] args) throws IOException, TimeoutException {
       // 创建连接
       Connection connection = ConnectionUtil.getConnection();
       // 定义通道
       final Channel channel = connection.createChannel();
       // 声明队列
       channel.queueDeclare(QUEUE_NAME,false,false,false,null);

       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("======= Consumer{2} msg :"+msg);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }finally {
                   System.out.println("Consumer{2} done");
                   channel.basicAck(envelope.getDeliveryTag(),false);
               }
           }
       };
       boolean autoAck = false;
       // 监听队列
       channel.basicConsume(QUEUE_NAME,autoAck,consumer);
   }
}
3.4 结果:	在Consumer 1睡眠2秒,Consumer 2睡眠1秒的情况下,
			Consumer 2消费的消息明显高于Consumer 1,这种方式也叫做能者多劳

**4. 消息应答与持久化 **

  • 消息应答
   boolean autoAck = false;
   channel.basicConsume(QUEUE_NAME,autoAck,consumer);
  if(autoAck){
	为自动确认模式:一旦rabbitMQ将消息分发给消费者,就会从内存中删除;这种情况下,如果杀死正在执行的消费者,就会丢失正在处理的消息,显然,这种时不安全的
  }else {
	该情况为手动模式:如果有一个消费者挂掉,就会交付给其他的消费者,rabbitMQ支持消息应答,消费者发送一个消息应答,告诉rabbitMQ这个消息已经处理完成,可以将消息删除,然后rabbitMQ再将消息删除。
  }
 - 消息应答默认是打开的,false
  • 消息的持久化
    问题:消息应答可以解决消费者宕机的情况下,保证消息让下一个消费者消费,那么如果rabbitMQ挂掉了呢?
// 声明队列
boolean durable = false;
channel.queueDeclare(QUEUE_NAME,durable ,false,false,null);
注意:我们将程序中的durable = false改为true,是不可以的,尽管代码是正确的,也不会运行成功,因为我们已经定义了一个叫 test_work_queue 的队列,这个队列是未持久化的,rabbitMQ不允许重新定义一个不同参数已经存在的队列
办法:将test_work_queue的队列删除,或者,重新声明新的队列

5.publish / subscrobe
5.1 模型:
消息中间件_RabbitMQ_第2张图片
解读:
1、一个生产者,多个消费者
2、每个消费者都有自己的队列
3、生产者没有直接把消息发送到队列,而是发到了交换机,转发器Exchanges
4、每个队列都要绑定到交换机上
5、生产者发送的消息,经过交换机,到达队列,就能实现一条消息被多个消费者消费
业务场景:
|-----------------> 邮箱
1、注册 ---->|
|-----------------> 短信
5.2 生产者:

/**
 * 订阅发布
 * @author 马文达
 * @date 2020/6/1 19:41
 */
public class Producer {

    private static final String EXCHANGE_NAME = "test_exchange_fanout";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtil.getConnection();
        Channel channel = connection.createChannel();
        // 声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout"); // 分发
        // 发送消息
        String msg = "Hello pb";
        System.out.println("Producer msg" +msg);
        channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());

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

在这里插入图片描述
交换机没有存储的能力,在rabbitMQ里边,只有队列有存储能留,因为这时候还没有队列绑定到这个交换机,造成数据丢失。
5.3:消费者 1 :

/**
 * @author 马文达
 * @date 2020/6/9 10:07
 */
public class Consumer1 {
	// 声明队列名称
    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 = ConnectionUtil.getConnection();
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 队列绑定到交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        // 保证只分发一个
        channel.basicQos(1);
        DefaultConsumer defaultConsumer = 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]Consumer1 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,defaultConsumer);
    }
}

5.4:消费者 2

public class Consumer2 {

    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 = ConnectionUtil.getConnection();
        final Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        // 队列绑定到交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
        // 保证只分发一个
        channel.basicQos(1);
        DefaultConsumer defaultConsumer = 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]Consumer2 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,defaultConsumer);
    }
}

4. routing
5. Topics
6. 手动和自动确认消息
7. 队列的持久化与非持久化
8. RabbitMQ的延迟队列

四、Spring AMQP String-Rabbit

五、场景demo MQ实现搜索引擎的DIH增量

六、场景demo 未支付订单30分钟 取消

七、大数据应用 类似百度统计 cnzz架构 消息队列

你可能感兴趣的:(消息中间件,rabbitMQ,java)