RabbitMQ

初始MQ

同步调用

同步调用的优点:

时效性较强,可以立即得到结果

同步调用的问题:

耦合度高:每次加入新的需求,都要修改原来的代码

性能和吞吐能力下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间之和。

有额外的资源消耗:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源

有级联失败问题:如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个微服务群故障

异步调用

异步通信的优点:  

        耦合度低

        吞吐量提升

        故障隔离

        流量削峰

异步通信的缺点:

        依赖于Broker的可靠性、安全性、吞吐能力

        架构复杂了,业务没有明显的流程线,不好追踪管理


有哪些MQ?

RabbitMQ

下载安装

通过命令安装RabbitMQ:

 # 通过docker拉取镜像
docker pull rabbitmq:3-management

 # 运行容器
docker run \
 -e RABBITMQ_DEFAULT_USER=mymq \
 -e RABBITMQ_DEFAULT_PASS=123456 \
 --name mq \
 --hostname mq1 \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3-management

成功运行RabbitMQ后,通过浏览器输入192.168.153.100:15672访问RabbitMQ,登录后如下:

 在user界面可以新增用户与虚拟主机

 建议每个用户单独配置一个虚拟主机,彼此隔离

RabbitMQ中的几个概念:

        channel:操作MQ的工具

        exchange:路由消息到队列中

        queue:缓存消息

        virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组


 helloworld案例

导入资料中的案例,部分代码如下:

public class ConsumerTest {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.153.100");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("mymq");
        factory.setPassword("123456");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
    }
}

 进行调试运行,切换到RabbitMQ页面,可以看到connection,channel和queue都已被成功创建

 

 

 控制台输出如下:

 总结

基本消息队列的消息发送流程:

1.建立connection

2.创建channel

3.利用channel声明队列

4.利用channel向队列发送消息

基本消息队列的消息接收流程:

1.建立connection

2.创建channel

3.利用channel声明队列

4.定义consumer的消费行为handleDelivery()

5.利用channel将消费者与队列绑定


SpringAMQP

入门案例

1.引入依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.配置RabbMQ

在publisher中的application.yml中写入以下配置

spring:
  rabbitmq:
    host: 192.168.153.100 #rabbitMQ的ip地址
    port: 5672 #端口
    username: mymq
    password: 123456
    virtual-host: "/"

3.建立测试方法

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableRabbit
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() {
        String queueName = "simple.queue";
        String message = "hello spring amqp";
        rabbitTemplate.convertAndSend(queueName,message);
    }
}

提示,这个方法不能自己创建队列,所以需要提前创建一个队列,才能接收到消息,可以运行之前的publisher方法

 4.编写consumer配置

在consumer中的application.yml中写入以下配置

spring:
  rabbitmq:
    host: 192.168.153.100 #rabbitMQ的ip地址
    port: 5672 #端口
    username: mymq
    password: 123456
    virtual-host: "/"

5.编写消费逻辑

在consumer中新建一个listener类

@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者接收到simple.queue的消息:【" + msg + "】");
    }
}

启动后成功在控制台中接收到消息

总结

SpringAMQP如何发送消息?

        引入amqp的starter依赖

        配置RabbitMQ地址

        利用RabbitTemplate的convertAndSend方法

 SpringAMQP如何接收消息?

        引入amqp的starter依赖

        配置RabbitMQ地址

        定义类,添加@Component注解

        类中声明方法,添加@RabbitListener注解,方法参数就时消息

        注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能


WorkQueue工作队列

模拟WorkQueue,实现一个队列绑定多个消费者

1.在publisher服务中添加一个测试方法,循环发送50条消息到simple.queue队列

@Test
    public void testSendMessage2WorkQueue() throws InterruptedException {
        String queueName = "simple.queue";
        String message = "hello spring amqp__";
        for (int i = 1; i <= 50; i++) {
            rabbitTemplate.convertAndSend(queueName,message + i);
            Thread.sleep(20);
        }

    }

2.在consumer服务中添加一个消费者,也监听simple.queue:

@RabbitListener(queues = "simple.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalDateTime.now());
        Thread.sleep(20);
    }

    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2......接收到消息:【" + msg + "】" + LocalDateTime.now());
        Thread.sleep(200);
    }

运行后发现,工作效率较慢的消费者2拿到了和消费者1一样多的消息,导致整体消息处理完成时间过长。

原因是消息预取机制的存在。这个机制让消费者1和消费者2提前拿到了一样多的消息。

我们可以在配置中修改最大消息预取数来提高工作队列的效率

spring:
  rabbitmq:
    host: 192.168.153.100 #rabbitMQ的ip地址
    port: 5672 #端口
    username: mymq
    password: 123456
    virtual-host: "/"
    listener:
      simple:
        prefetch: 1

将最大预取数改为1,就可以实现效率高的消费者1多拿到消息,而消费者2少拿到消息了。


FanoutExchange

 1.声明队列,交换机并互相绑定

新建一个FanoutConfig类

@Configuration
public class FanoutConfig {
    //my.fanout
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("my.fanout");
    }

    //fanout.queue1
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    //绑定队列1到交换机
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

    //fanout.queue2
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    //绑定队列2到交换机
    @Bean
    public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

2.编写消费者方法

编写两个消费者方法分别监听两个队列

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg){
    System.out.println("消费者接收到fanout.queue1的消息:【" + msg + "】");
}

@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg){
    System.out.println("消费者接收到fanout.queue2的消息:【" + msg + "】");
}

3.编写测试方法发送消息

    @Test
    public void testSendFanoutExchange() {
        //交换机名称
        String exchangeName = "my.fanout";
        //消息
        String msg = "hello every one!";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,"",msg);
    }

然后启动程序,可在RabbitMQ Management中看到fanout交换机已成功绑定两个队列

在控制台也可看到如下结果

 总结


DirectExchange

1.实现directexchange

在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2, 并利用@RabbitListener声明Exchange、Queue、RoutingKey

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "my.direct", type = ExchangeTypes.DIRECT),
            key = {"red","blue"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "my.direct", type = ExchangeTypes.DIRECT),
            key = {"red","yellow"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
    }

2.编写测试方法

    @Test
    public void testSendDirectExchange() {
        //交换机名称
        String exchangeName = "my.direct";
        //消息
        String msg = "hello red!";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,"red",msg);
    }

3.查看结果

在rabbit网站的exchange中已经能看到my.direct

 绑定关系如下

 启动测试方法后控制台如下:

 总结


TopicExchange

 

 1.声明listener

在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2, 并利用@RabbitListener声明Exchange、Queue、RoutingKey

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC),
            key = "china.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"),
            exchange = @Exchange(name = "my.topic", type = ExchangeTypes.TOPIC),
            key = "#.news"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
    }

2.编写测试方法

    @Test
    public void testSendTopicExchange() {
        //交换机名称
        String exchangeName = "my.topic";
        //消息
        String msg = "今天天气不错!";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName,"china.weather",msg);
    }

3.结果

网站中队列绑定成功

 启动测试方法后控制台输出如下

 


消息转换器

在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。这就是消息转换器的作用

下面测试一下发送object类型的消息

在consumer中声明一个队列

    @Bean
    public Queue objectQueue(){
        return new Queue("object.queue");
    }

然后在publisher中发送消息以测试

    @Test
    public void testSendObjectQueue() {
        Map<String, Object> msg = new HashMap<>();
        msg.put("name", "123");
        msg.put("age", 16);
        rabbitTemplate.convertAndSend("object.queue",msg);
    }

 rabbitmq management查看消息如下:

 出现这种情况的原因是因为Spring的对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。

如果要修改只需要定义一个MessageConverter 类型的Bean即可。推荐用JSON方式序列化,步骤如下:

引入依赖:

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>

在publisher服务中声明MessageConverter:

    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

在consumer中也同样声明MessageConverter

然后定义一个消费者,监听object.queue队列并消费消息:

    @RabbitListener(queues = "object.queue")
    public void listenObjectQueue(Map<String, Object> msg) {
        System.out.println("收到消息:【" + msg + "】");
    }

最后启动服务,在控制台接收消息如下:

 说明修改成功

总结

SpringAMQP中消息的序列化和反序列化是怎么实现的?

        利用MessageConverter实现的,默认是JDK的序列化

        注意发送方与接收方必须使用相同的MessageConverter

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值