RabbitMQ整合篇

1. Rabbit的安装

1.将文件erlang-21.3-1.el7.x86_64.rpm,rabbitmq_delayed_message_exchange-3.8.0.ez,   rabbitmq-server-3.8.8-1.el7.noarch.rpm三个文件使用xftp传入linux系统cenros77系统的/opt目录下。

2.打开/opt目录,顺序使用以下命令安装rabbitmq:  

    rpm -ivh erlang-21.3-1.el7.x86_64.rpm

    yum install socat -y

    rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm

3.常用命令(按照以下顺序执行) 添加开机启动 RabbitMQ 服务(在此之前关闭linux防火墙)

    chkconfig rabbitmq-server on

    启动服务 /sbin/service rabbitmq-server start

    查看服务状态 /sbin/service rabbitmq-server status

    停止服务(选择执行) /sbin/service rabbitmq-server stop

关闭防火墙:

         1.查看防火墙状态:systemctl status firewalld

         2.关闭防火墙  systemctl stop firewalld

         3.开机自动进制防火墙:systemctl disable firewalld

    开启 web 管理插件 rabbitmq-plugins enable rabbitmq_management

    用默认账号密码(guest)访问地址 http://192.168.5.130:15672/出现权限问题

           其中192.168.75.130是linux本机ip地址,通过ifconfig命令查看。

4.添加一个新的用户

    创建账号 rabbitmqctl add_user admin 123

   设置用户角色 rabbitmqctl set_user_tags admin administrator

   设置用户权限 set_permissions [-p ] rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

   用户 user_admin 具有/vhost1 这个 virtual host 中所有资源的配置、写、读权限 当前用户和角色     rabbitmqctl list_users

——————————————————————————————————————————

RabbitMQ生产者与消费者

 1.生产者代码:

package com.atguigu.rabbitmq.one;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

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

public class Producer {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.75.130");
        factory.setUsername("admin");
        factory.setPassword("123");
        Connection connection= factory.newConnection();
        Channel channel= connection.createChannel();

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

        String message="hello world";

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

        System.out.println("消息发送完毕");

    }
}

2.创建一个连接工厂ConnectionFactory,将rabbitMQ的主机号,用户名,用户密码传入进去

ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.75.130");
factory.setUsername("admin");
factory.setPassword("123");

3.建立一个连接connection,并创建一个信道channel。

Connection connection= factory.newConnection();
Channel channel= connection.createChannel();

4.并将数据传入channel生成一个队列:channel.queueDeclare(QUEUE_NAME,false,false,false,null);

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

而channel.queueDeclare里参数的各个意义如下:

/** * 生成一个队列

* 1.队列名称

* 2.队列里面的消息是否持久化 默认消息存储在内存中

* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费

* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除

* 5.其他参数

*/

5.发送一个消息

String message="hello world";

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

其中channel.basicPublish的各参数的意义如下:

/** * 发送一个消息

* 1.发送到那个交换机

* 2.路由的 key 是哪个

* 3.其他的参数信息

* 4.发送消息的消息体 */

———————————————————————————————————————————

RabbitMQ 抽取公共类并创建多进程消费者

 1.由于每次创建消费者和生产者总是要创建通道channel,所以将创建channel的过程抽取出来做成一个工具类,每次创建channel调用工具类即可:

package com.atguigu.rabbitmq.utils;

import com.rabbitmq.client.*;

public class RabbitMqUtils {

    public static Channel getChannel() throws Exception {
        ConnectionFactory factory=new ConnectionFactory();
        factory.setHost("192.168.75.130");
        factory.setUsername("admin");
        factory.setPassword("123");
        Connection connection=factory.newConnection();

        Channel channel=connection.createChannel();

       return channel;
    }
}

2.创建消费者worker01

package com.atguigu.rabbitmq.two;

import com.atguigu.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

public class Worker01 {

    public static final String QUEUE_NAME="hello";

    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();

        DeliverCallback deliverCallback=(consumerTag,message)->{
            System.out.println("接收到的信息:"+new String(message.getBody()));
        };

        CancelCallback cancelCallback=(consumerTag)->{
            System.out.println("消息者取消消费接口回调逻辑");
        };

        System.out.println("C2等待接受消息.....");
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);

    }


}

其中deliverCallback表示的是推送的消息如何进行消费的接口回调

DeliverCallback deliverCallback=(consumerTag,message)->{
            System.out.println("接收到的信息:"+new String(message.getBody()));
        };

cancelCallback表示的是取消消费的一个回调接口 如在消费的时候队列被删除掉了

CancelCallback cancelCallback=(consumerTag)->{
            System.out.println("消息者取消消费接口回调逻辑");
        };

在多进程中,可以多次调用work01来多次接受生产者的消息

点击Allow multiple instance可以是work多次调用

3.创建消费者:

package com.atguigu.rabbitmq.two;

import com.atguigu.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;

import java.util.Scanner;

public class Task01 {

    public static final String QUEUE_NAME="hello";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMqUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);
        Scanner scanner=new Scanner(System.in);

        while(scanner.hasNext()){
            String message=scanner.next();
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());

            System.out.println("发送消息完成:"+message);
        }
    }

}

1.在生产者运行过程中,先在控制台接收消息

 channel.queueDeclare(QUEUE_NAME,false,false,false,null);
 Scanner scanner=new Scanner(System.in);

2.然后在发送消息

 while(scanner.hasNext()){
            String message=scanner.next();
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("发送消息完成:"+message);
        }

———————————————————————————————————————————

RabbitMQ 消息手动应答

  消息应答::消费者在接 收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。

消息应答的方法:

           A.Channel.basicAck(用于肯定确认) RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了 B.Channel.basicNack(用于否定确认) C.Channel.basicReject(用于否定确认) 与 Channel.basicNack 相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃

1.消息自动应答

      消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权 衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢 失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制, 当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终 使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并 以某种速率能够处理这些消息的情况下使用

2.消息手动应答

   手动应答的好处是可以批量应答并且减少网络拥堵

   

 消息自动重新入列:消息在发出之后发生错误,连接失败等。此时消息会重新进入队列。并重新                                     发布。

Task02消费者代码:


public class Task02 {
    public static final String TASK_QUEUE_NAME="ack_queue";

    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        //声明队列
        channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);
        //从控制台输入信息
        Scanner scanner=new Scanner(System.in);
        while(scanner.hasNext()){
            String message=scanner.next();
            channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes("UTF-8"));
            System.out.println("生产者发出消息:"+message);

        }
    }
}

work03消费者代码

/**
 * 消息再手动应答时是不丢失,放回队列中重新消费
 */
public class Work03 {
    //队列名称
    public static final String TASK_QUEUE_NAME="ack_queue";

    //接收消息
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        System.out.println("C1等待接收消息处理时间较短");

        DeliverCallback deliverCallback=(consumerTag,message)->{

            //沉睡一秒
          RabbitMqUtils.sleep(1);
          System.out.println("接受到的消息:"+new String(message.getBody(),"UTF-8"));

          //手动应答
            /**
             * 1.消息的标记
             * 2.是否批量应答  false:不批量应答信道中的消息  true:批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

        };

        boolean autoAck=false;
        channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        }));
    }
}

 work04消费者代码

/**
 * 消息再手动应答时是不丢失,放回队列中重新消费
 */
public class Work04 {
    //队列名称
    public static final String TASK_QUEUE_NAME="ack_queue";

    //接收消息
    public static void main(String[] args) throws Exception {
        Channel channel= RabbitMqUtils.getChannel();
        System.out.println("C2等待接收消息处理时间较短");

        DeliverCallback deliverCallback=(consumerTag,message)->{

            //沉睡一秒
          RabbitMqUtils.sleep(30);
          System.out.println("接受到的消息:"+new String(message.getBody(),"UTF-8"));

          //手动应答
            /**
             * 1.消息的标记
             * 2.是否批量应答  false:不批量应答信道中的消息  true:批量
             */
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);

        };

        boolean autoAck=false;
        channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        }));

    }
}

运行结果

Task02  分别发送aa,bb,cc,dd

work03  在work04中断后,消息dd没有丢失,重新进入队列,发送到work03

 work04  睡眠时间较长,在Task02发送dd时进行中断进程。导致work04没有接受

———————————————————————————————————————————

RabbitMQ 队列与消息持久化

 在消息手动应答中,在任务丢失的情况下保障了消息不丢失。而在RabbitMQ停到服务后,使用持久化来保证数据不丢失

在生产者->队列->消费者

1.队列持久化

需要让Queue进行持久化 

boolean durable=true;  

然后将durable进入队列。保证持久化

 2.消息持久化

在队列到消费者中,实现持久化。

———————————————————————————————————————————

RabbitMQ 不公平分发和预取值

 1.不公平分发

    在消费者work01中,睡眠时间较短,处理消息速度很快。相反,在消费者work02中睡眠时间较长,处理速度较慢。从而导致有的消费者一直在忙,而有的消费者一直处于空闲状态。这是一种不太好的处理方式

    所以我们需要在消费者中设置参数 channel.basicQos(1)

 来变成不公平分发

2.预取值

本身消息的发送就是异步发送的,所以在任何时候,channel 上肯定不止只有一个消息另外来自消费 者的手动确认本质上也是异步的。因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此 缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。这个时候就可以通过使用 basic.qos 方法设 置“预取计数”值来完成的。该值定义通道上允许的未确认消息的最大数量。一旦数量达到配置的数量, RabbitMQ 将停止在通道上传递更多消息,除非至少有一个未处理的消息被确认。

在消费者中设置代码、

work03

 work04

———————————————————————————————————————————

RabbitMQ 发布确认

 实现持久化需要三步:

1.队列持久化

2.消息持久化

3.发布确认

虽然进行了第1,2步。但是只有发布确认,才能真正知道消息真的写入磁盘,写入内存。真正实现持久化。

发布确认有三种方式:

1.单个确认

2.批量确认

3.异步i确认

1.单个确认

//单个确认
    public static void publishMessageIndividually() throws Exception{
        Channel channel= RabbitMqUtils.getChannel();

        //队列的声明
        String queueName= UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        long begin=System.currentTimeMillis();

        //批量发消息
        for(int i=0;i<MESSAGE_COUNT;i++){
            String message=i+"";
            channel.basicPublish("",queueName,null,message.getBytes());

            boolean flag=channel.waitForConfirms();
            if(flag){
                System.out.println("消息发送成功!");
            }
        }
        long end=System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个单独确认消息,耗时"+(end-begin)+"ms");
    }

2.批量确认

//批量发布确认
    public static void publishMessageBatch() throws Exception{
        Channel channel= RabbitMqUtils.getChannel();

        //队列的声明
        String queueName= UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        //开始时间
        long begin=System.currentTimeMillis();

        //批量确认消息大小
        int batchSize=100;

        //未确认消息的个数


        //批量发送消息,批量发布确认
        for(int i=0;i<MESSAGE_COUNT;i++){
            String message=i+"";
            channel.basicPublish("",queueName,null,message.getBytes());

            //批量发布确认,每一百次批量确认一次
            if (i%batchSize==0){
                //发布确认
                channel.waitForConfirms();
            }

        }
        //结束时间
        long end=System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");

    }

3.异步发布

//异步发布确认
    public static void publishMessageAsync()throws Exception{
        Channel channel= RabbitMqUtils.getChannel();

        //队列的声明
        String queueName= UUID.randomUUID().toString();
        channel.queueDeclare(queueName,true,false,false,null);
        //开启发布确认
        channel.confirmSelect();
        //开始时间
        long begin=System.currentTimeMillis();



        /**
         * 线程安全有序有一个哈希表,适用于高并发的情况下
         * 1.轻松的将信号与消息进行关联
         * 2.轻松批量删除条目  只要给到序号
         * 3.支持高并发
         */
        ConcurrentSkipListMap<Long,String> outstandingConfirms=
                new ConcurrentSkipListMap<>();


        //消息确认成功 回调函数
        ConfirmCallback ackCallback=(deliveryTag,multiple)->{
            if (multiple) {
                //2.删除到已经确认的消息,剩下的就是未确认的消息
                ConcurrentNavigableMap<Long, String> confirmed =
                        outstandingConfirms.headMap(deliveryTag);
                confirmed.clear();
            }else {
                outstandingConfirms.remove(deliveryTag);
            }
            System.out.println("确认的消息:"+deliveryTag);

        };

        //消息确认失败 回调函数
        /**
         * 1,消息的标记
         * 2.是否为批量确认
         */
        ConfirmCallback nackCallback=(deliveryTag,multiple)->{
            //3.打印下未确认的消息有哪些
            String message=outstandingConfirms.get(deliveryTag);

            System.out.println("未确认的消息是"+message+"::未确认的消息的Tag是:"+deliveryTag);
        };

        //准备消息的监听器,监听那些消息成功了,那些消息失败了
        /**
         * 1.监听那些消息成功了
         * 2.监听那些消息失败了
         */
        channel.addConfirmListener(ackCallback,nackCallback);  //异步通知

        //批量发送消息
        for(int i=0;i<MESSAGE_COUNT;i++){
            String message="消息"+i;
            channel.basicPublish("",queueName,null,message.getBytes());

            //1.此处记录下所有要发送的消息,消息的总和
            outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
        }

        //结束时间
        long end=System.currentTimeMillis();
        System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");

    }

最后结果如下

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值