RabbitMQ - 1

一. MQ 基本概念

MQ:Message Queue 消息队列,用于分布式系统中传送消息。

优势:

  • 应用解耦(提高系统容错性和可维护性);
  • 异步提速(提升用户体验及系统吞吐量);
  • 削峰填谷(提高系统稳定性:削弱峰值请求,改成低峰慢慢处理消息)。

劣势

  • 引入外部依赖,系统可用性降低;(MQ宕机对多个系统产生影响,如何保证MQ的高可用?)
  • 系统复杂性提高;(如何保证消息没有被重复消费,如何处理消息丢失的情况,如何保证消息传递的顺序性?)
  • 数据一致性问题。(多个系统同时消费MQ中的消息,如果其中某个系统处理失败,如何保证数据的一致性?)

使用MQ需要满足的条件:

  • 生产者不需要从消费者获得数据处理的反馈;
  • 允许短暂的数据不一致性;
  • 引入MQ得到的性能提升大于维护MQ的成本。

常见的MQ产品:
除了下图的几款常用的MQ产品,还可以直接使用 redis 充当MQ。
在这里插入图片描述

二. RabbitMQ 简介

1. rabbitmq 简介

AMQP(高级消息队列协议)是一种网络协议,基于此协议可以实现客户端与消息中间件间的消息传递,而与客户端与中间件本身产品类型无关。RabbitMQ 是基于 AMQP 协议使用 Erlang 语言开发的一款消息队列产品。

AMQP 中规定的角色类型:
Exchange:交换机,根据 Routes 处理消息的分发;
Queue:消息队列。
在这里插入图片描述
RabbitMQ 架构:
在这里插入图片描述

2. Ubuntu 安装 RabbitMq

1. 安装 rabbitmq-server

开始费了好大的劲用 rpm 装,出了很多错,最后直接下面一句搞定:

sudo apt install rabbitmq-server

如果报错:dpkg: error processing package,可能是dpkg管理的包信息出现了损坏,通过sudo apt -f install进行修复:

sudo mv /var/lib/dpkg/info/ /var/lib/dpkg/info_old/
sudo mkdir /var/lib/dpkg/info/
sudo apt-get update
sudo apt-get -f install
sudo mv /var/lib/dpkg/info/* /var/lib/dpkg/info_old/
sudo rm -rf /var/lib/dpkg/info
sudo mv /var/lib/dpkg/info_old/ /var/lib/dpkg/info/

rabbitmq 启动命令:

rabbitmq-server -detached #后台启动(推荐)
rabbitmqctl stop # 停止服务

service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停止服务
service rabbitmq-server restart # 重启服务

2. 添加用户并赋权

在rabbitMQ中添加用户:

rabbitmqctl add_user zhangsan password

设置用户为管理员:

rabbitmqctl set_user_tags zhangsan administrator

为用户设置读写等权限:

rabbitmqctl set_permissions -p '/' zhangsan '.' '.' '.'

注意:添加完用户需要重启 rabbitmq.

3. 启动 rabbitmq 管理控制台

rabbitmq-plugins enable rabbitmq_management

启动控制台后,即可通过 web 页面访问:http://192.xxx.xxx.xxx:15672/,默认端口15672,用户即是刚才添加的用户。

3. rabbitMq 工作模式

1. 简单模式

一对一,单个生产者,单个消费者。
在这里插入图片描述

- 生产者代码

/**
 * 简单模式,无交换机
 */
public class Producer {

    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("192.168.xxx.xxx");
        factory.setPort(5672);
        factory.setVirtualHost("/rabbitVirtualHost");
        factory.setUsername("xxx");
        factory.setPassword("xxx");
        //3. 创建连接
        Connection connection = factory.newConnection();
        //4. 创建channel
        Channel channel = connection.createChannel();

        //5. 创建队列
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         * 1. 队列名
         * 2. 可持久化,mq宕机后会保存队列
         * 3. 是否独占:只有一个消费者可以监听该队列
         * 4. 是否自动删除:当没有消费者时自动删除队列
         * 5. 参数
         */
        channel.queueDeclare("simpleRabbitmq", true, false, false, null);

        //6. 发送消息
        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 参数:
         * 1. 交换机名称,简单模式使用默认的交换机,名称为空字符串
         * 2. 路由键,使用默认路由时路由键与队列名称保持一致
         * 3. 参数
         * 4. 消息内容
         */
        String msg = "hello rabbitmq";
        channel.basicPublish("", "simpleRabbitmq", null, msg.getBytes());

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

开始在 java 建立连接的时候一直连接不上,报 IOException、EOF 啥的错。我是在本地windows上安装了 Unbuntu 虚拟机,rabbitmq 安装在虚拟机上,程序在本地运行,相当于远程连接。百度都是说什么端口错了需要连5672、用户不能用默认的 guest、用户需要添加权限啥的…… 这些我都是正确的,还有人说需要开放 5672 端口,但是我 telnet 是通的,说明已经开放了。后来终于看到一个博客上说

除了要配置访问权限以外,还需要将运行环境配置修改下:

vi /etc/rabbitmq/rabbitmq-env.conf

添加:

NODE_IP_ADDRESS=192.168.xxx.xxx ## 注意这个地方写啥,java连接的ip就应该写啥
NODE_PORT=5672

配置完以后重启 rabbitmq,终于连接成功了,泪奔,卡了太久了。。。

- 消费者代码

public class SimpleConsumer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("192.168.xxx.xxx");
        factory.setPort(5672);
        factory.setVirtualHost("/rabbitVirtualHost");
        factory.setUsername("xxx");
        factory.setPassword("xxx");
        //3. 创建连接
        Connection connection = factory.newConnection();
        //4. 创建channel
        Channel channel = connection.createChannel();

        //5. 声明队列
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         * 1. 队列名
         * 2. 可持久化,mq宕机后会保存队列
         * 3. 是否独占:只有一个消费者可以监听该队列
         * 4. 是否自动删除:当没有消费者时自动删除队列
         * 5. 参数
         */
        channel.queueDeclare("simpleRabbitmq", true, false, false, null);

        //6. 发送消息
        Consumer consumer = new DefaultConsumer(channel) {
            /**
             * 收到消息后执行该方法
             * @param consumerTag
             * @param envelope 获取某些信息
             * @param properties 配置
             * @param body
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                super.handleDelivery(consumerTag, envelope, properties, body);
                System.out.println("consumerTag: " + consumerTag);
                System.out.println("exchange: " + envelope.getExchange());
                System.out.println("routingKey: " + envelope.getRoutingKey());
                System.out.println("properties: " + properties);
                System.out.println("body: " + new String(body));
            }
        };
        /**
         * basicConsumer(String queue, boolean autoAck, Consumer callback)
         * 参数:
         * 1. 队列名
         * 2. 是否自动发送回执
         * 3. 回调对象
         */
        channel.basicConsume("simpleRabbitmq", true, consumer);
    }
}

2. 工作队列
在这里插入图片描述

一对多,多个消费者消费同一个队列中的消息,每个消息只能被其中一个消费者消费。适用于消息过多,单个消费者处理压力过大的情况。

代码实现上与简单模式相同,只需要启动两个消费者监听同一个队列即可。

3. 发布订阅(广播模式)

简单模式和工作队列模式每个消息都只会被一个消费者消费。发布订阅模式可以实现同一个消息被多个消费者消费。增加 exchange 和 队列绑定。

BuiltinExchangeType.FANOUT

在这里插入图片描述

  • 发布者代码
public class PubSubProducer {
    public static void main(String[] args) throws IOException, TimeoutException {
        //1. 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2. 设置参数
        factory.setHost("192.168.xxx.xxx");
        factory.setPort(5672);
        factory.setVirtualHost("/rabbitVirtualHost");
        factory.setUsername("xxx");
        factory.setPassword("xxx");
        //3. 创建连接
        Connection connection = factory.newConnection();
        //4. 创建channel
        Channel channel = connection.createChannel();

        //5. 创建队列
        /**
         * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments)
         * 参数:
         * 1. 队列名
         * 2. 可持久化,mq宕机后会保存队列
         * 3. 是否独占:只有一个消费者可以监听该队列
         * 4. 是否自动删除:当没有消费者时自动删除队列
         * 5. 参数
         */
        String queue1 = "fanoutQueue1";
        String queue2 = "fanoutQueue2";
        channel.queueDeclare(queue1, true, false, false, null);
        channel.queueDeclare(queue2, true, false, false, null);

        //6. 创建交换机
        /**
         * DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments)
         * 参数:
         * 1. 交换机名称
         * 2. 交换机类型
         * 3. 是否可持久化
         * 4. 是否自动删除
         * 5. 是否内部使用
         * 6. 参数
         */
        String exchangeName = "fanoutExchange";
        channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT, true, false, false, null);

        //7. 交换机绑定队列
        /**
         * queueBind(String queue, String exchange, String routingKey)
         * 当为广播模式时,路由键为""
         */
        channel.queueBind(queue1, exchangeName, "");
        channel.queueBind(queue2, exchangeName, "");

        //8. 发送消息
        /**
         * basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
         * 参数:
         * 1. 交换机名称,简单模式使用默认的交换机,名称为空字符串
         * 2. 路由键,使用默认路由时路由键与队列名称保持一致
         * 3. 参数
         * 4. 消息内容
         */
        String msg = "rabbitmq test pubsub fanout";
        channel.basicPublish(exchangeName, "", null, msg.getBytes());
        channel.close();
        connection.close();
    }
}

消费者代码与简单模式一样,不赘述。

4. 路由模式
在这里插入图片描述
路由模式相比广播模式的唯一区别是队列绑定交换机需要添加 routing key,发布消息时也需要指定 routing key,只有 routing key 一致的消息才能被监听者接收到。

BuiltinExchangeType.DIRECT

String queue1 = "directQueue1";
String queue2 = "directQueue2";
channel.queueDeclare(queue1, true, false, false, null);
channel.queueDeclare(queue2, true, false, false, null);

String exchangeName = "directExchange";
//采用direct类型
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT, true, false, false, null);

//交换机绑定队列
//队列1绑定
channel.queueBind(queue1, exchangeName, "error");
//队列2绑定
channel.queueBind(queue2, exchangeName, "info");
channel.queueBind(queue2, exchangeName, "warning");
channel.queueBind(queue2, exchangeName, "error");

//发送消息
String msg = "rabbitmq test pubsub routing";
channel.basicPublish(exchangeName, "info", null, msg.getBytes());
channel.basicPublish(exchangeName, "error", null, msg.getBytes());

5. 通配符模式

与路由模式相比,通配符模式允许 routing key 带有通配符,使得路由规则更加灵活。

BuiltinExchangeType.TOPIC

在这里插入图片描述

四. springboot 整合 RabbitMQ

producer:

  1. 引入依赖
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  1. 配置文件
spring:
  rabbitmq:
    host: 192.168.xxx.xxx
    port: 5672
    username: xxx
    password: xxx
    virtual-host: /rabbitVirtualHost
  1. 编写配置类,配置交换机和队列
@Configuration
public class MqConfig {

    public static final String EXCHANGE_NAME = "boot_direct_exchange";
    public static final String QUEUE_NAME = "boot_direct_queue";

    /**
     * 配置交换机
     * @return
     */
    @Bean
    public Exchange bootExchange() {
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    /**
     * 配置队列
     * @return
     */
    @Bean
    public Queue bootQueue() {
        return QueueBuilder.durable(QUEUE_NAME).build();
    }

    /**
     * 队列绑定交换机
     * Qualifier 的作用是声明注入哪个对象,防止多个交换机队列不知道注入哪一个
     * @param exchange
     * @param queue
     * @return
     */
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootExchange") Exchange exchange,
                                     @Qualifier("bootQueue") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange).with("errorLog").noargs();
    }
}
  1. 测试类中编写发送消息:
@SpringBootTest
class BootproducerApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    void contextLoads() {
        rabbitTemplate.convertAndSend(MqConfig.EXCHANGE_NAME,
                "errorLog", "this is test for boot rabbitmq producer");
    }

}

consumer:

  1. 依赖和配置文件同生产者;

  2. 监听队列

@Component
public class MqListener {

    @RabbitListener(queues = "boot_direct_queue")
    public void BootConsumerListener (Message message)  {
        System.out.println(message.getBody().toString());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值