展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

RabbitMQ(一):入门

  • RabbitMQ是一个消息中间件,相当于一个中转站;用于接收、存储、转发消息数据

  • RabbitMQ的作用:

    1. 流量消峰:当服务器处理不了过多的请求时,在进入服务器之前先进入MQ,MQ会对请求做排序,防止服务器宕机
    2. 应用解耦:当系统的某个子系统出现故障无法正常工作时,该子系统要处理的内存会被缓存到消息队列中,当该子系统正常后再继续处理;提高用户体验,避免用户再次请求
    3. 异步处理:有些服务间的调用是异步的,当某个服务在工作时,其他服务不能调用该服务,可使用消息队列来进行监听管理调用
  • MQ的核心:

    1. 生产者:产生数据发送消息的模块
    2. 交换机:用于接收生产者的消息、将消息推送到队列中、处理消息
    3. 队列:相当于消息缓存区
    4. 消费者:用于接收消息的模块
    5. 一个应用程序既可以是生产者,又可以是消费者;比如一个通讯工具,既可以发送信息,也可以接收信息
  • CentOS安装RabbitMQ:

vi /etc/yum.repos.d/rabbitmq_erlang.repo          # 配置 centos8 Erlang的下载源
yum install erlang           # 安装Erlang
# 下载rabbitmq的rpm,传输centos
yum install socat         # 安装socat依赖
rpm -ivh rabbitmq-server-3.8.1-1.el8.noarch.rpm         # 安装rabbitmq
rabbitmq-plugins enable rabbitmq_management          # 安装web插件
vi /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.1/ebin/rabbit.app           # 设置可远程访问
firewall-cmd --add-port=15672/tcp --permanent         # 开放端口
firewall-cmd --add-port=5672/tcp --permanent         # 开放端口
firewall-cmd --reload         # 使防火墙生效
rabbitmq-server -detached         # 后台启动
http://ip:15672/        # 浏览器访问rabbitmq页面
sudo rabbitmq-server       # 控制台启动

详情查看:https://www.jianshu.com/p/8aece9517533

RabbitMQ启动时报错:error: node with name "rabbit" already running on "bogon"
错误原因:后台进程被占用
解决方案:

ps -ef|grep rabbitmq       # 查看进程
kill  <进程id>      # 杀死进程
# 再次启动

启动时报错:Warning: PID file not written; -detached was passed.
错误原因:警告信息,不影响

  • 创建账号:
rabbitmqctl add_user <用户名> <密码>
rabbitmqctl set_user_tags <用户名> administrator           # 为当前账号设置超级管理员角色权限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"       # 表示admin用户在"/"这个主机中具有配置、读、写权限
# 设置格式:set_permissions [-p <vhostpath>] <user> <conf> <write> <read>      "/"表示主机路径,也可自定义其他路径
rabbitmqctl list_users        # 查看所有账户和对应的角色
# 此时可在window系统的浏览器中访问Linux的rabbitmq,输入创建的账号
rabbitmqctl stop_app       # 关闭
rabbitmqctl reset           # 重启
rabbitmqctl start_app       # 启动

  • window10上RabbitMQ环境配置:
1. 安装erlang,配置环境变量
	i. ERLANG_HOME = 安装路径
	ii. path中添加:%ERLANG_HOME%\bin
	iii. cmd输入erl查看版本验证是否安装成功
2. 安装RabbitMQ
	i. 进入rabbitMQ安装路径的sbin文件夹,打开cmd
	ii. rabbitmq-plugins enable rabbitmq_management           # 安装可视化插件
	iii. 浏览器输入:http://localhost:15672/            # 访问RabbitMQ页面,用户名、密码为guest
3. RabbitMQ常用命令:
	i. net start RabbitMQ         # 启动 
	ii. net stop RabbitMQ            # 停止 
	iii. rabbitmqctl status           # 查看状态

详情查看:https://blog.csdn.net/qq_39915083/article/details/107034747

  • 消息传递入门案例:

    1. 导入所需依赖,新建一个类作为消息生产者,定义一个队列,创建连接工厂,创建连接,生成队列,发送消息
    2. 运行后发现massage发送到了rabbitmq,可在rabbitmq中查看
    3. 新建一个类作为消息消费者,创建连接工厂,创建连接,接收rabbitmq中的消息,可指定回调方法用于处理成功和失败的情况
  • 工作队列是为了避免立即执行大量任务,生产者发送大量消息到rabbitmq的队列,队列将消息缓存、排序、分发到不同的消费者

  • 轮训分发消息案例:

    1. 抽取连接的步骤抽取到一个工具类,新建生产者类和消费者类,消费者类启动多个线程
    2. 当生产者发送消息时,消息被多个线程(消费者类)平均接收
  • 消息应答:消费者接收消息并处理完成后,会告诉rabbitmq,这时rabbitmq会把消息删除了

  • 自动应答:建议不要使用,若消费者不能即使处理消息,会导致消息丢失

  • 手动应答:自定义应答方法

Channel.basicAck:用于肯定确认,RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了 
Channel.basicNack:用于否定确认
Channel.basicReject:用于否定确认,与 Channel.basicNack 相比少一个参数Multiple
	Multiple为true表示批量应答,即消费者同时处理多个消息,其中一个消息处理完成后,则告诉rabbitmq全处理完了
	Multiple为false表示不批量应答,即消费者同时处理多个消息,其中一个消息处理完成后,则告诉rabbitmq处理完的那个消息

  • 消息自动重新入队,消息会保存在rabbitmq中,直到被消费者处理后、应答后才会删除,当消费者出现异常或者宕机时导致处理失败,rabbitmq会将消息分发给另一个消费者处理

  • 手动应答 + 自动重新入队案例:

    1. 新建一个类作为消息生产者
    2. 新建一个类作为消息消费者:让该线程睡眠1秒后打印出接收到消息,再新建一个消息消费者类:让该线程睡眠30秒后打印出接收的消息,是为了模拟处理复杂的消息;在两个消费者类中设置为手动应答
    3. 测试:生产者发送消息,两个消费者轮训接收消息,当第二个消费者在接收消息时突然关闭服务,这时消息没处理完,又被自动分配到第一个消费者处理
  • 队列持久化:
    在生产者类中设置
    消息应答机制确保了rabbitmq正常的情况下消息不会丢失
    队列设置持久化后,rabbitmq即使宕机或重启后,该队列中的消息依然存在于rabbitmq
    若将一个未持久化的队列设置成持久化后,重启会报错,这时由于该队列已经在rabbitmq中存在,在rabbitmq页面删除该队列,再重启线程,
    这时该队列在rabbitmq显示的持久化
    在rabbitmq控制台可查看设置了队列持久化的消息带有一个大写的D

  • 消息持久化:
    rabbitmq中的队列持久化了,发送的消息也需设置持久化,否则rabbitmq重启后,队列存在,队列中的消息丢失了
    在生产者类中发送消息时添加:MessageProperties.PERSISTENT_TEXT_PLAIN

  • 不公平分发:
    即处理速度快的消费者多分配消息,确保消费者不会出现空闲状态
    在消费者类添加:channel.basicQos(1) # 默认为0表示轮训,1表示不公平分发

  • 预取值:
    即指定消费者处理多少消息
    在消费者类添加:channel.basicQos(5) # 这就表示分发5条,表示该消费者类会一直堆着5条,当该消费者类处理了1条,只剩4条时,又会接收1条消息

  • 参考代码

// 连接rabbitmq工具类
public class RabbitMqUtils {
    //得到一个连接的 channel
    public static Channel getChannel() throws Exception{
    //创建一个连接工厂
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("182.92.234.71");     // rabbitmq的ip和账户密码
    factory.setUsername("admin");
    factory.setPassword("123");
    Connection connection = factory.newConnection();     // 创建连接
    Channel channel = connection.createChannel();
    return channel;    // 返回信道
    }
}

// 生产者
public class Task02 {
    private static final String TASK_QUEUE_NAME = "ack_queue";     // 创建队列
    public static void main(String[] argv) throws Exception {
        try (Channel channel = RabbitMqUtils.getChannel()){      // 获取连接rabbitmq
           /**
            * 生成一个队列
            * 1.队列名称
            * 2.队列持久化
            * 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
            * 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
            * 5.其他参数
            */
            channel.queueDeclare(TASK_QUEUE_NAME, false, false, false, null);
            Scanner sc = new Scanner(System.in);
            System.out.println("请输入信息");
            while (sc.hasNext()) {
                String message = sc.nextLine();    // 从控制台获取消息
               /**
                * 发送一个消息
                * 1.发送到那个交换机
                * 2.路由的 key 是哪个
                * 3.消息持久化
                * 4.发送消息的消息体
                */
                channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8"));    // 发送消息到rabbitmq
                System.out.println("生产者发出消息" + message);
            }
        }
    }
}
    
// 消费者
public class Work03 {
    private static final String ACK_QUEUE_NAME="ack_queue";
    public static void main(String[] args) throws Exception{
        Channel channel = RabbitMqUtils.getChannel();
        System.out.println("C1 等待接收消息处理时间较短");
        //消息消费的时候如何处理消息
        DeliverCallback deliverCallback=(consumerTag,delivery)->{
            String message= new String(delivery.getBody());
            SleepUtils.sleep(1);
            System.out.println("接收到消息:"+message);
            /**
             * 消息应答的具体处理:
             * 1.消息标记 tag
             * 2.是否批量应答未应答消息
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        // 不公平分发
        //channel.basicQos(1)
        // 预取值
        channel.basicQos(5)
        //采用手动应答
        boolean autoAck=false;
        /**
         * 1.队列名称
         * 2.是否手动应答
         * 3.接收成功的处理
         * 4.接收消息失败的回调
         */
        channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
            System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
        });
    }
}

// 模拟处理时间的睡眠工具类
public class SleepUtils {
    public static void sleep(int second){
        try {
            Thread.sleep(1000*second);
        } catch (InterruptedException _ignored){
            Thread.currentThread().interrupt();
        }
    }
}

posted @ 2021-08-06 16:55  DogLeftover  阅读(82)  评论(0编辑  收藏  举报