基于Docker的RabbitMQ学习

1.安装

docker拉取镜像:
docker pull rabbitmq
运行镜像:
docker run -dit --name Myrabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq
其中5672为应用访问端口,15672为web端口,如果是阿里云需要在阿里云开放安全组。
之后发现ip:15672仍然不能访问,执行如下两条命令即可。
docker exec -i -t rabbitmq容器id /bin/bash
rabbitmq-plugins enable rabbitmq_management

2.使用

2.1 逻辑结构

在这里插入图片描述

2.2 工作模式

消息通信由消息生产者(Producer)消息消费者(Consumer) 共同完成。

2.2.1 简单模式

一个队列只有一个消费者。
在这里插入图片描述
此时消费者相当于一个监听器,只要发现队列中有消息,消费者就进行消费。而可以有多个生产者同时进行生产。

2.2.2 工作模式

在这里插入图片描述
多个消费者监听同一个队列,但多个消费者中只有一个消费者会成功的消费消息。

2.2.3 订阅模式

在这里插入图片描述

一个交换机绑定多个消息队列,每个消息队列有一个消费者进行监听,消息生产者发送的消息可以被每个消费者接收。此时交换机工作模式为fanout。

2.2.4 路由模式

在这里插入图片描述
一个交换机绑定多个消息队列,每个消息队列都有自己唯一的key,每个消息队列都有一个消费者进行监听,消息可以进入指定队列中。此时交换机工作模式为redirect。

2.3 普通maven项目中使用rabbitmq

为了避免重复操作,抽取MQUtils工具类。

public static Connection getConnection() throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("ip");
        factory.setPort(端口(5672));
        factory.setVirtualHost("主机名");
        factory.setUsername("用户名");
        factory.setPassword("密码");
        Connection connection = factory.newConnection();
        return connection;
    }

2.3.1 简单模式

在这里插入图片描述

生产者:

public static void main(String[] args) throws IOException, TimeoutException {
        String msg="hello sgl";
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        //参数1,交换机名称,不需要为""
        //参数2,队列名称
        //参数3,消息属性,不需要为null
        //参数4,消息内容
        channel.basicPublish("","queue1",null,msg.getBytes());
        System.out.println(msg);
        channel.close();
        connection.close();
    }

消费者:

public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println(msg);
            }
        };
        channel.basicConsume("queue1",true,consumer);
    }

生产者进行生产后
在这里插入图片描述
消费者进行消费

idea控制台打印
在这里插入图片描述
在这里插入图片描述

2.3.2 工作模式

在这里插入图片描述

创建如图所示结构:
在这里插入图片描述
其中生产者与消费者的具体代码与简单模式中的相同。
首先运行两个消费者,使得两个消费者进行等待,然后运行生产者,两个消费者都有可能对这条消息进行消费,观察消息被哪个消费者进行消费。
运行两次,一次被Consumer1消费,一次被Consumer2消费。
在这里插入图片描述
在这里插入图片描述

2.3.3 订阅模式

在这里插入图片描述

生产者:

public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        String msg = scanner.nextLine();
        //String msg="hello sgl";
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        //参数1,交换机名称,不需要为""
        //参数2,队列名称
        //参数3,消息属性,不需要为null
        //参数4,消息内容
        channel.basicPublish("ex1","",null,msg.getBytes());
        System.out.println("生产消息"+msg);
        channel.close();
        connection.close();
    }

消费者1:

public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println("Consumer1接收"+msg);
            }
        };
        channel.basicConsume("queue3",true,consumer);
    }

消费者2:

public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println("Consumer2接收"+msg);
            }
        };
        channel.basicConsume("queue4",true,consumer);
    }

运行结果:
两个Consumer均进行了消息的消费。

2.3.4 路由模式

在这里插入图片描述
生产者:

public static void main(String[] args) throws IOException, TimeoutException {
        System.out.println("请输入消息:");
        Scanner scanner = new Scanner(System.in);
        String msg = scanner.nextLine();
        //String msg="hello sgl";
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        //参数1,交换机名称,不需要为""
        //参数2,交换机名称为空,为队列名,名称不为空,为key值
        //参数3,消息属性,不需要为null
        //参数4,消息内容
        if (msg.startsWith("a")) {
            channel.basicPublish("ex2", "a", null, msg.getBytes());
        }else if (msg.startsWith("b")){
            channel.basicPublish("ex2", "b", null, msg.getBytes());
        }
        System.out.println("生产消息"+msg);
        channel.close();
        connection.close();
    }

消费者1:

public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println("Consumer1接收"+msg);
            }
        };
        channel.basicConsume("queue5",true,consumer);
    }

消费者2:

public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println("Consumer2接收"+msg);
            }
        };
        channel.basicConsume("queue6",true,consumer);
    }

运行结果:
消费者1消费aaa,消费者2消费bbb。

2.4 SpringBoot项目中使用rabbitmq

2.4.1 生产者

添加依赖
在这里插入图片描述
配置yml
在这里插入图片描述
Service层

@Service
public class TestService {
    @Autowired
    private AmqpTemplate amqpTemplate;
    public void sendMsg(String msg){
        if (msg.startsWith("q_")){
            //发送消息到队列
            amqpTemplate.convertAndSend("queue1",msg);
        }else if (msg.startsWith("f_")){
            //发送消息到交换机(订阅交换机)
            amqpTemplate.convertAndSend("ex1","",msg);
        }else if (msg.startsWith("r_")){
            if (msg.startsWith("r_a")){
                //发送消息到交换机(路由交换机)
                amqpTemplate.convertAndSend("ex2","a",msg);
            }else if (msg.startsWith("r_b")){
                amqpTemplate.convertAndSend("ex2","b",msg);
            }
        }
    }
}

2.4.2 消费者

同样添加依赖和配置yml文件,如果消费者和生产者需要同时运行,需要改端口。
Service层:

@Service
//监听queue1的消息
@RabbitListener(queues = "queue1")
public class ReceiveMsgService {
    @RabbitHandler
    public void ReceiveMsg(String msg){
        System.out.println("接收:"+msg);

    }
}

2.4.3 使用RabbitMQ传递对象

使用序列化对象进行传输:

生产者(实体类需实现Serializable接口):

@Service
public class MQService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendGoodsToMQ(Goods goods){
        //消息队列发送字符串、字节数组和序列化对象
        amqpTemplate.convertAndSend("","queue1",goods);
    }
}

消费者(实体类需实现Serializable接口):

@Service
@RabbitListener(queues = "queue1")
public class ReceiveService {
//    @RabbitHandler
//    public void receiveMsg(String msg){
//        System.out.println(msg);
//    }
    @RabbitHandler
    public void receiveMsg(Goods goods){
        System.out.println(goods);
    }
}

使用序列化数组进行传输:

生产者(实体类需实现Serializable接口):

@Service
public class MQService {
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendGoodsToMQ(Goods goods){
        //消息队列发送字符串、字节数组和序列化对象
        //amqpTemplate.convertAndSend("","queue1",goods);
        byte[] bytes = SerializationUtils.serialize(goods);
        amqpTemplate.convertAndSend("","queue1",bytes);
    }
}

消费者(实体类需实现Serializable接口):

@Service
@RabbitListener(queues = "queue1")
public class ReceiveService {
//    @RabbitHandler
//    public void receiveMsg(Goods goods){
//        System.out.println(goods);
//    }
    @RabbitHandler
    public void receiveMsg(byte[] bytes){
        Goods goods= (Goods) SerializationUtils.deserialize(bytes);
        System.out.println(goods);
    }
}
使用json进行传输:

生产者:

public void sendGoodsToMQ(Goods goods) throws JsonProcessingException {
        //消息队列发送字符串、字节数组和序列化对象
        ObjectMapper objectMapper = new ObjectMapper();
        String msg = objectMapper.writeValueAsString(goods);
        amqpTemplate.convertAndSend("","queue1",msg);
    }

消费者:

@RabbitHandler
    public void receiveMsg(String msg) throws JsonProcessingException {
        ObjectMapper m = new ObjectMapper();
        Goods goods = m.readValue(msg, Goods.class);
        System.out.println(goods);
    }

2.5 消息的可靠性

2.5.1 RabbitMQ事务

当在消息发送过程中添加了事务,效率降低几十倍甚至上百倍。

channel.txSelect();//开启事务
try{
	channel.basicPublish("ex4","k1",null,msg.getBytes());
	channel.txCommit();//提交事务
}catch(Execption e){
	channel.txRollback();//事务回滚
}

2.5.2 消息确认和return机制

在这里插入图片描述

消息确认:确认生产者是否成功发送消息到交换机。
return机制:确认到达交换机的数据是否成功分发到队列。

对于普通maven项目

  • 消息确认

b为true发送成功,b为false发送失败。

		channel.confirmSelect();//发送之前开启消息确认
        channel.basicPublish("ex1","queue1",null,msg.getBytes());
        boolean b = channel.waitForConfirms();//接收消息确认
        System.out.println(b);

当批量进行接收时,发送的所有消息中如果有一条消息是失败的,则所有消息发送均失败。抛出异常。

当必须收到确认消息才能进行下一步时,选上面的方式进行确认。如果不需要收到确认消息,就可以进行下一步操作,就可以使用下面的这种方式:监听器异步confirm。

		channel.confirmSelect();//发送之前开启消息确认
		//异步确认
        channel.addConfirmListener(new ConfirmListener() {
            //参数1 long l 返回消息
            //参数2 boolean b 是否为批量confirm
            //不需进行channel和connection的关闭
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("消息成功发送到交换机");
            }

            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("消息发送到交换机失败");
            }
        });
  • return机制
channel.confirmSelect();//发送之前开启消息确认
        //参数1,交换机名称,不需要为""
        //参数2,队列名称
        //(true)表示开启return机制
        //参数3,消息属性,不需要为null
        //参数4,消息内容
        channel.basicPublish("ex2","c",true,null,msg.getBytes());
        channel.addConfirmListener(new ConfirmListener() {
            //参数1 long l 返回消息
            //参数2 boolean b 是否为批量confirm
            //不需进行channel和connection的关闭
            public void handleAck(long l, boolean b) throws IOException {
                System.out.println("消息成功发送到交换机");
            }

            public void handleNack(long l, boolean b) throws IOException {
                System.out.println("消息发送到交换机失败");
            }
        });
            channel.addReturnListener(new ReturnListener() {
                public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
                    //return机制,如果交换机分发消息到队列失败,则会执行此方法 s1和s2分别表示交换机和发送失败的key
                    System.out.println(i);
                    System.out.println(s);
                    System.out.println(s1);
                    System.out.println(s2);
                    System.out.println(new String(bytes));
                }
            });

由于ex2并没有key为c的队列,所以执行失败,执行handleReturn方法。如下图:
在这里插入图片描述

对于SpringBoot项目

在消费端确认(保证每个消息被正确消费,此时才可以删除这个消息)

  • 默认消费端为自动确认,只要消息接收到,客户端就会自动确认,服务器就会移除这个消息。

  • 自动确认存在的问题:我们收到很多消息,自动回复给服务器ack,只有一个消息处理成功,宕机了。发生消息丢失。

  • 综上,应该开启消费者手动确认模式,即使Consumer宕机,消息不会丢失,会重新变为ready,下次consumer上线后重新进行消费。

  • 手动确认消息签收?

#配置文件
spring.rabbitmq.listener.simple.acknowledge-mode=manual
//签收,业务完成后就应该签收
channel.basicAck(deliveryTag,false);
//拒签,业务失败,拒签
channel.basicNack(deliveryTag,false,true);

3. 延迟机制

3.1 延迟队列

消息进入到队列之后,延迟指定的时间才能被消费。
Rabbitmq不支持延迟队列,那么怎么实现消息延迟呢?
通过TTL(Time To Live)特性模拟延迟队列功能。

在创建队列时可以设置RabbitMQ中消息或者队列的过期时间,如果到期仍然没被消费,消息会被销毁。这个消息称为死信,TTL就是消息或者队列的存活时间。RabbitMQ可以分别对队列和消息设置存活时间。一般只设置队列的TTL。

实现延迟队列:A服务将消息发到交换机,指定key1后发到queue1中,设置30分钟过期,由于这个队列为死信队列(消息会过期,无人接收),所以可以设置在队列到期时将消息发送到queue2中,而B服务对queue2进行监听,发现有消息立即进行消费,这样就实现了消息的延迟机制。

在这里插入图片描述

3.2 使用延迟队列实现订单监控

1.创建路由交换机

在这里插入图片描述

2.创建两个队列,其中队列1为死信队列。

在这里插入图片描述

在这里插入图片描述

3.队列绑定交换机。

在这里插入图片描述

4.测试。

生产者

  String msg="hello sgl";
        Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        //参数1,交换机名称,不需要为""
        //参数2,交换机名称为空,为队列名,名称不为空,为key值
        //参数3,消息属性,不需要为null
        //参数4,消息内容
        channel.basicPublish("delay_exchange", "k1", null, msg.getBytes());

        System.out.println("生产消息"+msg);
        channel.close();
        connection.close();

消费者

Connection connection = MQUtils.getConnection();
        Channel channel = connection.createChannel();
        DefaultConsumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //body就是接受的消息数据
                String msg=new String(body);
                System.out.println("Consumer1接收"+msg);
            }
        };
        channel.basicConsume("delay_queue2",true,consumer);

运行结果
运行后生产者立即开始生产消息,当过10s后消费者delay_queue2进行了消息的消费。测试成功。

4. 消息队列使用场景

4.1 解耦

场景说明:用户下单之后,订单系统通知库存系统进行库存的修改。

传统方式

订单系统直接调用库存系统提供的接口,如果库存系统出现故障会导致订单系统失败。
在这里插入图片描述

使用消息队列

进行解耦,如果库存系统出现了问题,消息存在消息队列中,不会被消费。待库存系统恢复正常后再进行消费。
在这里插入图片描述

4.2 异步

场景说明:用户注册成功后,需要发送注册邮件和短信进行提醒。

传统方式

响应时间较长。
在这里插入图片描述

使用消息队列

响应时间减少。
在这里插入图片描述

4.3 消息通信

场景说明:应用系统之间的通信,如聊天室。
在这里插入图片描述

4.4 流量削峰

场景说明:秒杀系统。

大量的请求不会主动请求秒杀业务,而是存在消息队列中(缓存)。当秒杀业务还有能力处理时,从消息队列中取消息进行处理。传统方式会将全部请求直接打到秒杀业务中,当秒杀业务没有能力进行处理时,就会造成系统的崩溃。
在这里插入图片描述

4.5 日志处理

场景说明:系统中大量的日志处理。

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值