RabbitMQ

RabbitMQ

​ 应用场景以及简单的原理

异步处理

场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种1.串行的方式;2.并行的方式
(1)串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.

在这里插入图片描述

(2)并行方式:将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。

请添加图片描述
(3)消息队列
引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理
请添加图片描述

应用解耦

​ 将各种业务系统拆开 通过队列来交互

流量削峰

​ 用于秒杀活动之类 控制活动人数 当活动请求到达一定阈值直接丢弃

​ 可以缓解短时间的高流量压垮应用

​ 下面是rabbitmq的一些基本操作

​ 安装 erlang 和 mq
​ 通过命令 net start RabbitMQ启动
​ 在http://127.0.0.1:15672/ 访问 guset guset

添加角色 admin角色
共有5种角色
1、超级管理员(administrator)
可登陆管理控制台,可查看所有的信息,并且可以对用户,策略(policy)进行操作。
2、监控者(monitoring)
可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等)
3、策略制定者(policymaker)
可登陆管理控制台, 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)4、普通管理者(management)
仅可登陆管理控制台,无法看到节点信息,也无法对策略进行管理。
5、其他
无法登陆管理控制台,通常就是普通的生产者和消费者。

通过设定消费数量来限制消费者人数 channel.basicQos(1);
工作模式 轮询分发

消费者1和消费者2获取到的消息内容是不同的,同一个消息只能被一个消费者获取。
消费者1和消费者2获取到的消息的数量是相同的,一个是消费奇数号消息,一个是偶数。
RabbitMQ 默认将消息顺序发送给下一个消费者,这样,每个消费者会得到相同数量的消息。即轮询(round-robin)分发消息。

怎样才能做到按照每个消费者的能力分配消息呢?联合使用 Qos 和 Acknowledge 就可以做到。
basicQos 方法设置了当前信道最大预获取(prefetch)消息数量为1。消息从队列异步推送给消费者,消费者的 ack 也是异步发送给队列,从队列的视角去看,总是会有一批消息已推送但尚未获得 ack 确认,Qos 的 prefetchCount 参数就是用来限制这批未确认消息数量的。设为1时,队列只有在收到消费者发回的上一条消息 ack 确认后,才会向该消费者发送下一条消息。prefetchCount 的默认值为0,即没有限制,队列会将所有消息尽快发给消费者。
2个概念

轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。

公平分发 :虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。

为了解决这个问题,我们使用basicQos( prefetchCount = 1)方法,来限制RabbitMQ只发不超过1条的消息给同一个消费者。当消息处理完毕后,有了反馈,才会进行第二次发送。
还有一点需要注意,使用公平分发,必须关闭自动应答,改为手动应答。

Work模式的“能者多劳”

Connection connection = ConnectionMq.getConnection(); // 获取连接
        //连接创建通道
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        channel.basicQos(1);//只接受一条消息
        //定义队列的消费者 并将通道赋值给消费者
        QueueingConsumer consumer= new QueueingConsumer(channel);
        channel.basicConsume(QUEUE_NAME,false,consumer); // 监听队列 有消息就消费 开启手动监听
        while(true){
            QueueingConsumer.Delivery delivery = consumer.nextDelivery();
            String message = new String(delivery.getBody()); // 获取队列消息
            System.out.println(" [x] Received '" + message + "'");
            Thread.sleep(1000);
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // 开启手动模式
        }

消息的确认模式
模式1 自动确认 只要消息从队列中获取了 那么无论消费者是否成功获取消息都认为消息已经成功发送并消费
模式2 手动确认 消费者从队列中获取了消息,服务器会将该消息标记为不可用状态,等待消费者的反馈。如果消费者没有反馈该消费会一直处于不可用状态

channel.basicConsume("queueName",false,consumer);
订阅模式 即交换机状态

请添加图片描述

大致是

  1. 1个生产者,多个消费者
  2. 每一个消费者都有自己的一个队列
  3. 生产者没有将消息直接发送到队列,而是发送到了交换机
  4. 每个队列都要绑定到交换机
  5. 生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的

​ 注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费

请添加图片描述

创建生产者

 //获取连接以及通道
Connection connection = ConnectionMq.getConnection();
//获取 连接并创建通道
 Channel channel = connection.createChannel();
 //声明交换机
 channel.exchangeDeclare(Exchange_NAME,"fanout");
 String message = "this is change message";
 channel.basicPublish(Exchange_NAME,"",null,message.getBytes());
 //创建消息内容
 //关闭通道和连接
 channel.close();
 connection.close();

创建消费者

private final static String QUEUE_NAME = "test_queue_work";
private final static String Change_NAME = "test_queue_exchange";
public static void main(String[] args) throws IOException, InterruptedException {
    //        String message ="这是第"+i+"this is new massage";
    Connection connection = ConnectionMq.getConnection(); // 获取连接
    //连接创建通道
    Channel channel = connection.createChannel();
    //声明队列
    channel.queueDeclare(QUEUE_NAME,false,false,false,null);
    //绑定队列到交换机
    channel.queueBind(QUEUE_NAME,Change_NAME,"");
    channel.basicQos(1);
    //定义队列的消费者 并将通道赋值给消费者
    QueueingConsumer consumer= new QueueingConsumer(channel);
    //监听队列 true 表示自动返回队列
    channel.basicConsume(QUEUE_NAME,false,consumer); // 监听队列 有消息就消费
    //获取信息
    while(true){
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody()); // 获取队列消息
        System.out.println(" [x] Received '" + message + "'");
        Thread.sleep(10);
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);//开启手动模式
    }
}

创建消费者2

private final static String QUEUE_NAME = "test_queue_work";
private final static String Change_NAME = "test_queue_exchange";
public static void main(String[] args) throws IOException, InterruptedException {
    Connection connection = ConnectionMq.getConnection(); // 获取连接
    //连接创建通道
    Channel channel = connection.createChannel();
    //声明队列
    channel.queueDeclare(QUEUE_NAME,false,false,false,null);
    channel.queueBind(QUEUE_NAME,Change_NAME,"");
    channel.basicQos(1);//只接受一条消息
    //定义队列的消费者 并将通道赋值给消费者
    QueueingConsumer consumer= new QueueingConsumer(channel);
    channel.basicConsume(QUEUE_NAME,false,consumer); // 监听队列 有消息就消费
    while(true){
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody()); // 获取队列消息
        System.out.println(" [x] Received '" + message + "'");
        Thread.sleep(1000);
        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); // 开启手动模式
    }
}

注意的是 消息发送到没有队列绑定的交换机时,消息将会丢失,因为,交换机并没有储存消息的能力消息只能存在队列中

结论是 交换机的消息 被多个消费者消费 一个队列可以有多个消费者 但是只有一个消费者实例才能消费到

请添加图片描述

路由模式

请添加图片描述

例子如下
请添加图片描述请添加图片描述请添加图片描述

主题模式(通配符模式)

请添加图片描述
在这里插入图片描述
生产者

private final static String EXCHANGE_NAME = "test_exchange_topic";
public static void main(String[] argv) throws Exception {
    // 获取到连接以及mq通道
    Connection connection = ConnectionUtil.getConnection();
    Channel channel = connection.createChannel();

    // 声明exchange
    channel.exchangeDeclare(EXCHANGE_NAME, "topic");

    // 消息内容
    String message = "Hello World!!";
    channel.basicPublish(EXCHANGE_NAME, "routekey.1", null, message.getBytes());
    System.out.println(" [x] Sent '" + message + "'");

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

消费者1

private final static String EXCHANGE_NAME = "test_exchange_topic";
private final static String QUEUE_NAME = "test_queue_topic_work_1";
public static void main(String[] argv) throws Exception {

    // 获取到连接以及mq通道
    Connection connection = ConnectionUtil.getConnection();
    Channel channel = connection.createChannel();

    // 声明队列
    channel.queueDeclare(QUEUE_NAME, false, false, false, null);

    // 绑定队列到交换机
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "routekey.*");

    // 同一时刻服务器只会发一条消息给消费者
    channel.basicQos(1);

    // 定义队列的消费者
    QueueingConsumer consumer = new QueueingConsumer(channel);
    // 监听队列,手动返回完成
    channel.basicConsume(QUEUE_NAME, false, consumer);

    // 获取消息
    while (true) {
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody());
        System.out.println(" [Recv_x] Received '" + message + "'");
        Thread.sleep(10);

        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    }
}

消费者2

private final static String EXCHANGE_NAME = "test_exchange_topic";
 private final static String QUEUE_NAME = "test_queue_topic_work_2";
public static void main(String[] argv) throws Exception {

    // 获取到连接以及mq通道
    Connection connection = ConnectionUtil.getConnection();
    Channel channel = connection.createChannel();

    // 声明队列
    channel.queueDeclare(QUEUE_NAME, false, false, false, null);

    // 绑定队列到交换机
    channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "*.*");

    // 同一时刻服务器只会发一条消息给消费者
    channel.basicQos(1);

    // 定义队列的消费者
    QueueingConsumer consumer = new QueueingConsumer(channel);
    // 监听队列,手动返回完成
    channel.basicConsume(QUEUE_NAME, false, consumer);

    // 获取消息
    while (true) {
        QueueingConsumer.Delivery delivery = consumer.nextDelivery();
        String message = new String(delivery.getBody());
        System.out.println(" [Recv2_x] Received '" + message + "'");
        Thread.sleep(10);

        channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值