Rabbitmq的实现方式

1.Rabbitmq的实现方式

文章目录

  • 1.Rabbitmq的实现方式
    • 1.1第一种-基本模式(直连)
      • 1.1.1概述
      • 1.1.2开发工具类
      • 1.1.3开发生产者
      • 1.1.4开发消费者
    • 1.2第二种-工作队列模型
      • 1.2.1概述
      • 1.2.2开发生产者
      • 1.2.3开发消费者
    • 1.3第三种-fanout模型
      • 1.3.1开发消费者
      • 1.3.2开发消费者
    • 1.4第四种-发布/订阅模型之routing
      • 1.4.1开发消费者
      • 1.4.2消费者
    • 1.5发布/订阅模型之topics
      • 1.5.1生产者
      • 1.5.2消费者
    • 1.6springboot整合rabbitmq的五种模型
      • 1.6.1新建项目导入依赖
      • 1.6.2springboot-第一种模型-直连
        • 开发生产者
        • 开发消费者
        • 测试功能
      • 1.6.3springboot-第二种模型-工作队列
        • 开发生产者
        • 开发生产者
        • 测试
      • 1.6.4springboot-第三种模型-fanout
        • 开发生产者
        • 开发消费者
        • 测试
      • 1.6.5springboot-第四种模型-发布/订阅模型-routing
        • 开发生产者
        • 开发消费者
        • 运行结果
      • 1.6.6springboot-第五种模型-发布/订阅模型-topics
        • 开发生产者
        • 开发消费者
        • 测试
    • 1.7RabbitMQ的应用场景
      • 1.7.1传统方式存在的问题
      • 1.7.2解耦
      • 1.7.3削峰
      • 1.7.3引入消息队列的优缺点

1.1第一种-基本模式(直连)

步骤
1.添加虚拟机名
Rabbitmq的实现方式_第1张图片Rabbitmq的实现方式_第2张图片2.开发程序

1.1.1概述

生产者发送一个消息到队列中,消费者从队列取出消息。
Rabbitmq的实现方式_第3张图片

1.1.2开发工具类

public class RabbitmqUtils {
    private static final ConnectionFactory connectionFactory;
    static {
        //创建连接mq的连接工厂对象,重量级对象,类加载时创建一次即可
        connectionFactory = new ConnectionFactory();
        //设置连接rabbitmq的主机
        connectionFactory.setHost("192.168.200.130");
        //设置端口号
        connectionFactory.setPort(5672);
        //设置连接的虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的用户名和密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");
    }

    //获取连接对象
    public static Connection getConnection(){
        try {
            return connectionFactory.newConnection();
        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
        return null;
    }

    //关闭通道和连接
    public static void close(Channel channel, Connection connection){
        if(channel != null){
            try {
                channel.close();
            } catch (IOException | TimeoutException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

1.1.3开发生产者

public class Provider {

    @Test
    public void ConnnetionRabbitMq() throws IOException, TimeoutException {

        //创建对象
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //设置连接的rabbitmq主机
        connectionFactory.setHost("192.168.200.130");
        //设置连接的虚拟主机
        connectionFactory.setVirtualHost("/ems");
        //设置访问虚拟主机的哦用户名和密码
        connectionFactory.setUsername("ems");
        connectionFactory.setPassword("123");

        //获取连接对象
        Connection connection = connectionFactory.newConnection();
        //获取来凝结中通道
        Channel channel = connection.createChannel();
        //参数一:队列名称
        //参数二:是否持久化
        //参数三:是否独占队列
        //参数四:是否在消息后自动删除队列
        //参数五:附加额外参数

        channel.queueDeclare("hello", false, false, false, null);
        channel.basicPublish("", "hello", null, "hello RabbitMq".getBytes());
        channel.close();
        connection.close();
    }
}

1.1.4开发消费者

public class Consumer {
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接对象
            connection = RabbitmqUtils.getConnection();
            //获取通道
            if (connection != null) {
                channel = connection.createChannel();
                //通道绑定对应消息队列
                /*
                 * 参数1 queue:队列名称(不存在自动创建)
                 * 参数2 durable:用来定义队列特性是否需要持久化(为true该队列将在服务器重启后保留下来)
                 * 参数3 exclusive:是否独占队列(为true仅限此连接)
                 * 参数4 autoDelete:是否在消费完成不再使用后自动删除队列
                 * 参数5 arguments:队列的其他属性(构造参数)
                 * */
                channel.queueDeclare("hello", false, false, false, null);

                //消费消息
                DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
                    //获取消息并且处理。此方法类似于事件监听,有消息时会被自动调用
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("message:" + new String(body));  //body即消息体
                    }
                };
                /*
                 * 参数1 queue:队列名称
                 * 参数2 autoAck:开启消息的自动确认机制
                 * 参数3 Consumer callback:消费时的回调接口
                 * */
                channel.basicConsume("hello", true, defaultConsumer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("消费者消费消息完成......");
            //RabbitmqUtils.close(channel,connection);
        }
    }
}

1.2第二种-工作队列模型

1.2.1概述

当消息处理比较耗时的时候,可能生产消息的速度大于消费速度,堆积越来越多无法得到及时的处理,可以使用work模型,让多个消费者绑定在一个队列,共同消费队列中的消息。队列中的消息一旦消费就会消失,因此任务不会被重复执行。
Rabbitmq的实现方式_第4张图片

1.2.2开发生产者

public class Provider {
    public static void main(String[] args) {

        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接对象
            connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道对象
                channel = connection.createChannel();
                //声明队列
                channel.queueDeclare("work",true,false,false,null);

                //生产消息
                String message = "";
                for (int i = 1; i <= 20; i++) {
                    message = "work queues,id:"+i;
                    channel.basicPublish("","work",null,message.getBytes());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            RabbitmqUtils.close(channel,connection);
        }
    }
}

1.2.3开发消费者

public class Consumer1 {
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接
            connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                channel = connection.createChannel();
                //声明队列
                channel.queueDeclare("work",true,false,false,null);

                //消费消息
                channel.basicConsume("work",true,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer-1:"+new String(body));//打印消息
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Consumer2 {
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接
            connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                channel = connection.createChannel();
                //声明队列
                channel.queueDeclare("work",true,false,false,null);

                //消费消息
                channel.basicConsume("work",true,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer-2:"+new String(body));//打印消息
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.3第三种-fanout模型

发布/订阅模式fanout下的消息发送流程:

可以有多个消费者
每个消费者对应一个临时队列(queue)
每个队列都要绑定到交换机(exchange)
生产者只能将消息发送到交换机,交换机来决定发送给哪个队列
交换机把消息发送给绑定过的所有队列
队列的消费者都能拿到消息,实现一条消息被多个消费者消费

Rabbitmq的实现方式_第5张图片

1.3.1开发消费者

public class Provider {
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;

        //获取连接对象
        connection = RabbitmqUtils.getConnection();
        try {
            if (connection != null) {
                //获取通道对象
                channel = connection.createChannel();
                //将通道声明指定交换机 参数1:交换机名称 参数2:交换机类型
                channel.exchangeDeclare("logs","fanout");

                //发布消息(指定交换机)
                String message = "fanout message";
                channel.basicPublish("logs","",null,message.getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            RabbitmqUtils.close(channel,connection);
        }
    }
}

1.3.2开发消费者

public class Consumer1 {
    public static void main(String[] args) {

        try {
            //获取连接
            Connection connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                Channel channel = connection.createChannel();
                //声明交换机
                channel.exchangeDeclare("logs","fanout");
                //临时队列
                String queue = channel.queueDeclare().getQueue();
                //绑定交换机和队列  队列名称 交换机名称 路由键
                channel.queueBind(queue,"logs","");

                //消费消息
                channel.basicConsume(queue,true,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer1: "+new String(body)); //打印消息
                    }
                });
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.4第四种-发布/订阅模型之routing

在Fanout模式中,一条消息会被所有订阅的队列都消费。但在某些场景下,希望不同的消息被不同功能的队列消费。这时需要用到Direct类型的Exchange
可以有选择性的订阅消息。例如,只将严重错误消息定向到日志文件(以节省磁盘空间),同时仍然能够打印所有控制台上的日志消息。

在Direct模型下:
队列与交换机的绑定,不能是任意绑定,而需要指定一个RoutingKey(路由键)
消息的发送方在向Exchange发送消息时,也必须指定消息的RoutingKey
Exchange不再把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断。之后队列的RoutingKey和消息的RoutingKey一致,才会接收消息

Rabbitmq的实现方式_第6张图片

1.4.1开发消费者

public class Provider {
    public static void main(String[] args) {
        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接
            connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                channel = connection.createChannel();
                //通过通道声明交换机 交换机类型为direct
                channel.exchangeDeclare("logs_direct","direct");

                //发送消息
                String routingKey = "info";
                String message = "direct--routingKey,routingKey:"+routingKey;
                //交换机名称 路由键 消息其他属性 消息具体内容
                channel.basicPublish("logs_direct",routingKey,null,message.getBytes());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源
            RabbitmqUtils.close(channel,connection);
        }
    }
}

1.4.2消费者

public class Consumer1 {
    public static void main(String[] args) {
        try {
            //获取连接
            Connection connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                Channel channel = connection.createChannel();
                //通道声明交换机与交换机类型
                channel.exchangeDeclare("logs_direct","direct");
                //临时队列
                String queue = channel.queueDeclare().getQueue();
                //基于route key绑定队列和交换机
                channel.queueBind(queue,"logs_direct","error");

                //消费消息
                channel.basicConsume(queue,true,new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer1:"+new String(body));
                    }
                });
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


public class Consumer2 {
    public static void main(String[] args) {

        try {
            //获取连接
            Connection connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                Channel channel = connection.createChannel();
                //声明交换机与交换机类型
                channel.exchangeDeclare("logs_direct","direct");
                //获取临时队列
                String queue = channel.queueDeclare().getQueue();
                //基于route key绑定队列与交换机
                channel.queueBind(queue,"logs_direct","info");
                channel.queueBind(queue,"logs_direct","error");
                channel.queueBind(queue,"logs_direct","warning");

                //消费消息
                DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer2:"+new String(body));
                    }
                };
                channel.basicConsume(queue,true,defaultConsumer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.5发布/订阅模型之topics

Topic类型的Exchange和Direct相比,都可以根据routingkey把消息路由到不同队列。只不过Topic类型Exchange可以让队列在绑定routingkey时使用通配符。这种routingkey一般由一个或多个单词组成。多个单词之间以"."分隔。例如item.insert
通配符:
*(star):可以代替一个单词
#(hash):可以代替零个或多个单词

Rabbitmq的实现方式_第7张图片

1.5.1生产者

public class Provider {
    public static void main(String[] args) {

        Connection connection = null;
        Channel channel = null;

        try {
            //获取连接
            connection = RabbitmqUtils.getConnection();
            if (connection != null) {
                //获取通道
                channel = connection.createChannel();
                //声明交换机及交换机类型
                channel.exchangeDeclare("topics","topic");

                //发布消息
                String routingKey = "user.save";
                String message = "hello topic,routingKey:"+routingKey;
                channel.basicPublish("topics",routingKey,null,message.getBytes());

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            RabbitmqUtils.close(channel,connection);
        }
    }
}

1.5.2消费者

public class Consumer1 {
    public static void main(String[] args) {
        try {
            //获取连接
            Connection connection = RabbitmqUtils.getConnection();
            //获取通道
            if (connection != null) {
                Channel channel = connection.createChannel();
                //声明交换机与交换机类型
                channel.exchangeDeclare("topics","topic");
                //生成临时队列
                String queue = channel.queueDeclare().getQueue();
                //绑定交换机与队列  使用通配符形式routingKey
                channel.queueBind(queue,"topics","user.*");

                //消费消息
                DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer1:"+new String(body)); //打印消息
                    }
                };
                channel.basicConsume(queue,true,defaultConsumer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Consumer2 {
    public static void main(String[] args) {
        try {
            //获取连接
            Connection connection = RabbitmqUtils.getConnection();
            //获取通道
            if (connection != null) {
                Channel channel = connection.createChannel();
                //声明交换机与交换机类型
                channel.exchangeDeclare("topics","topic");
                //生成临时队列
                String queue = channel.queueDeclare().getQueue();
                //绑定交换机与队列  使用通配符形式routingKey
                channel.queueBind(queue,"topics","user.#");

                //消费消息
                DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
                    @Override
                    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                        System.out.println("Consumer2:"+new String(body)); //打印消息
                    }
                };
                channel.basicConsume(queue,true,defaultConsumer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.6springboot整合rabbitmq的五种模型

1.6.1新建项目导入依赖

创建一个spring boot的项目,导入rabbitmq的依赖

 <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
dependency>

配置yml文件

spring:
  application:
    name: rabbitmq-springboot
  rabbitmq:
    host: 192.168.200.130
    port: 5672
    username: ems
    password: 123
    virtual-host: /ems

Rabbitmq的实现方式_第8张图片

1.6.2springboot-第一种模型-直连

Rabbitmq的实现方式_第9张图片

开发生产者

@SpringBootTest(classes = SpringbootRabbitmqTestApplication.class)
@RunWith(SpringRunner.class)
class SpringbootRabbitmqTestApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //hello world
    @Test
    public  void test() {
        rabbitTemplate.convertAndSend("hello","hello world");
    }

}

注意需要有消费者才会创建队列

开发消费者

@Component
@RabbitListener(queuesToDeclare = @Queue("hello"))
public class HelloConsumer {
    @RabbitHandler
    public void receive(String message){
        System.out.println("message="+message);
    }
}

测试功能

Rabbitmq的实现方式_第10张图片

Rabbitmq的实现方式_第11张图片

1.6.3springboot-第二种模型-工作队列

Rabbitmq的实现方式_第12张图片

开发生产者

 @Test
    public void testWork(){
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("work","hello work");
        }
    }

开发生产者

@Component
public class WorkConsumer {

    //第一个消费者
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive1(String message){
        System.out.println("message1"+message);
    }

    //第二个消费者
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void receive2(String message){
        System.out.println("message2"+message);
    }
}

测试

Rabbitmq的实现方式_第13张图片

1.6.4springboot-第三种模型-fanout

Rabbitmq的实现方式_第14张图片

开发生产者


    //fanout
    @Test
    public void testFanout(){
        rabbitTemplate.convertAndSend("logs","","Fanout模型.....");
    }

开发消费者

@Component
public class FanoutComsumer {
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    exchange = @Exchange(value = "logs",type = "fanout")
            )
    })
    public void comsumer1(String message){
        System.out.println("message1="+message);
    }


    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    exchange = @Exchange(value = "logs",type = "fanout")
            )
    })
    public void comsumer2(String message){
        System.out.println("message2="+message);
    }
}

测试

Rabbitmq的实现方式_第15张图片

1.6.5springboot-第四种模型-发布/订阅模型-routing

Rabbitmq的实现方式_第16张图片

开发生产者

 @Test
    public void testRoute(){
        rabbitTemplate.convertAndSend("directs","info","Route模型.....");
    }

开发消费者

@Component
public class RouteCustomer {
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,  //临时队列
                    exchange = @Exchange(value = "directs",type = "direct"), //交换机名称和类型
                    key = {"info","error","warning"}
            )
    })
    public void consumer1(String message){
        System.out.println("Consumer1:"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,  //临时队列
                    exchange = @Exchange(value = "directs",type = "direct"), //交换机名称和类型
                    key = {"error"}
            )
    })
    public void consumer2(String message){
        System.out.println("Consumer2:"+message);
    }

}

运行结果

Rabbitmq的实现方式_第17张图片

1.6.6springboot-第五种模型-发布/订阅模型-topics

Rabbitmq的实现方式_第18张图片

开发生产者

 //topic
    @Test
    public void testTopic() {

        rabbitTemplate.convertAndSend("topics", "user.hello", "topic模型....");

    }

开发消费者

@Component
public class TopicConsumer {
    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    exchange = @Exchange(name = "topics",type = "topic"),
                    key = {"user.*"}
            )
    })
    public void consumer1(String message){
        System.out.println("Consumer1:"+message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,
                    exchange = @Exchange(name = "topics",type = "topic"),
                    key = {"user.#","order.#"}
            )
    })
    public void consumer2(String message){
        System.out.println("Consumer2:"+message);
    }
}

测试

Rabbitmq的实现方式_第19张图片

1.7RabbitMQ的应用场景

1.7.1传统方式存在的问题

用户注册后,需要发注册邮件和注册短信,传统的做法有两种:串行或并行

串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回客户端。

存在问题:邮件短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西

串行方式:将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回客户端。
存在问题:邮件短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西
Rabbitmq的实现方式_第20张图片

并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册信息。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
Rabbitmq的实现方式_第21张图片引入消息队列,将不是必须的业务逻辑(发送短信/邮件)进行异步处理。改造后的架构如下:
Rabbitmq的实现方式_第22张图片
引入消息队列后,用户的响应时间就等于写入数据库的时间+写入消息队列的时间(可以忽略不计),引入消息队列处理后,响应时间是串行的3倍,是并行的2倍

1.7.2解耦

场景:购物节用户下单后,订单系统需要通知库存系统。
传统做法:订单系统直接调用库存系统的接口
Rabbitmq的实现方式_第23张图片传统模式缺点:

如果订单系统无法访问,订单系统则失败
订单系统与库存系统耦合度高

引入消息队列
Rabbitmq的实现方式_第24张图片
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功

库存系统:订阅下单的消息,获取下单消息,进行库存操作。就算库存系统出现故障,也不会影响正常下单。下单之后,订单系统写入消息队列就不再需要关心后续操作,不会导致消息丢失。实现订单系统和库存系统的应用解耦

1.7.3削峰

场景:秒杀活动,一般因为流量过大,可能导致应用宕机。为了解决此问题,一般在应用前端加入消息队列
Rabbitmq的实现方式_第25张图片
流程:

服务器接收到用户请求后,首先写入到消息队列。加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面。
秒杀业务可根据消息队列中的请求消息,做后续处理

作用:

可以控制活动人数,当请求超过一定阈值时,直接丢弃
可以缓解短时间的高流量压垮应用(应用按自己最大处理能力获取订单)

1.7.3引入消息队列的优缺点

优点:在特殊场景下能获得对应好处

缺点:

系统可用性降低:系统引入的外部依赖越多,越容易挂掉。MQ在系统中充当一个中间人的身份,如果MQ失联,整个系统肯定会出现问题
系统复杂性提高:在使用消息队列的过程中,难免会出现生产者、消费者或MQ宕机不可用的情况。随之而来的问题就是消息重复、消息乱序、消息堆积等问题。而这些问题都需要我们来控制
一致性问题:多个系统进行数据库操作,如果其中一个写库失败,就会导致数据不一致问题。

你可能感兴趣的:(后端学习,java-rabbitmq,rabbitmq,java)