Explicação detalhada do modo de publicação e assinatura Publish/Subscribe do RabbitMQ

Já faz muito tempo, amigos, e hoje vou compartilhar produtos secos com vocês novamente. Sabemos que o RabbitMQ possui modo simples, modo de fila de trabalho, modo de assinatura de publicação, modo de roteamento, modo de tópico, modo de chamada de procedimento remoto, modo de confirmação do editor, etc. Com tantos modelos, pode ser difícil absorvê-los todos de uma vez.Hoje, o professor Yuan apresenta principalmente o conteúdo relevante do modelo de publicação/assinatura Publicar/Assinar.

SpringBoot integra o middleware RabbitMQ para realizar o serviço de mensagens, que gira principalmente em torno de três partes: middleware personalizado, o remetente da mensagem envia mensagens e os consumidores da mensagem recebem mensagens. Entre eles, customizar middleware é um trabalho complicado e deve ser pré-customizado.

A seguir, é apresentado o cenário de envio de notificação por e-mail e notificação por SMS ao mesmo tempo após o registro bem-sucedido do usuário como exemplo, usando métodos baseados em API, baseados em configuração e baseados em anotação, respectivamente, para realizar a integração do modo de trabalho Publicar/Assinar .

Abordagem baseada em API

O método baseado em API refere-se ao uso da classe de gerenciamento de API AmqpAdmin fornecida pela estrutura Spring para personalizar o componente de envio de mensagens e enviar a mensagem. Este método de customização de componentes de envio de mensagens é basicamente o mesmo que a implementação de operações de componentes por meio do painel correspondente na interface visual RabbitMQ. Ele usa a identidade do administrador para declarar manualmente o switch, fila, chave de roteamento, etc. e, em seguida, monte a fila de mensagens. Ela é chamada pelo programa aplicativo para realizar o serviço de mensagens. Abaixo explicaremos e demonstraremos essa abordagem baseada em API.

1. Use AmqpAdmin para personalizar componentes de envio de mensagens

Primeiro abrimos a classe de teste Chapter08ApplicationTests do projeto Chapter08 e apresentamos os componentes de mensagem exigidos pela classe de gerenciamento AmqpAdmin para personalizar o modo de trabalho Publicação/Assinatura na classe de teste.

package com.ytx;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Chapter08ApplicationTests {
    @Autowired
    private AmqpAdmin amqpAdmin;

    @Test
    void contextLoads() {
    }

    /** 使用AmqpAdmin管理员API定制消息组件 */
    @Test
    public void amqpAdmin() {
        // 1.定义fanout类型的交换器
        amqpAdmin.declareExchange(new FanoutExchange("fanout_exchange"));
        // 2.定义两个默认持久化队列,分别处理email和sms
        amqpAdmin.declareQueue(new Queue("fanout_queue_email"));
        amqpAdmin.declareQueue(new Queue("fanout_queue_sms"));
        // 3.将队列分别与交换器进行绑定
        amqpAdmin.declareBinding(new Binding("fanout_queue_email", Binding.DestinationType.QUEUE, "fanout_exchange", "", null));
        amqpAdmin.declareBinding(new Binding("fanout_queue_sms", Binding.DestinationType.QUEUE, "fanout_exchange", "", null));
    }
}

Execute o método de teste de unidade acima amqpAdmin() para verificar o efeito de personalização do componente de mensagem RabbitMQ. Após a execução bem-sucedida do método de teste de unidade, visualize o efeito por meio do painel Exchanges da página de gerenciamento visual do RabbitMQ.

foto

Como pode ser visto na figura acima, no painel Exchanges da página de gerenciamento visual do RabbitMQ, um novo switch chamado fanout_exchange aparece (os outros 7 switches são integrados pelo RabbitMQ) e seu tipo é o tipo de fanout que definimos. Podemos clicar na troca fanout_exchange para visualizá-la.

foto

Como pode ser visto na figura acima, a página de detalhes da troca fanout_exchange exibe as informações específicas da troca, bem como as duas filas de mensagens fanout_queue_email e fanout_queue_sms vinculadas a ela, que são consistentes com as regras de ligação definidas no programa. Alterne para a página do painel Filas para visualizar as informações da fila de mensagens geradas de forma personalizada.

foto

Como pode ser visto na figura acima, as informações da fila de mensagens customizadas são exibidas na página do painel Fila de filas, que é consistente com a fila de mensagens customizadas do programa. Podemos clicar no nome da fila de mensagens para visualizar os detalhes de cada fila.

Através das operações acima, pode-se descobrir que as funções personalizadas do trocador de componentes de mensagens e da fila são fornecidas na página de gerenciamento. Usando o componente API do administrador AmqpAdmin fornecido pela estrutura Spring no programa, a essência da personalização do componente de mensagem é a mesma que personalizar manualmente o componente de mensagem na página de gerenciamento.

2. O remetente da mensagem envia a mensagem

Após concluir a customização do componente de mensagem, crie um remetente de mensagem para enviar mensagens para a fila de mensagens. Ao enviar uma mensagem, podemos usar uma classe de entidade para passar a mensagem e precisamos criar um objeto de classe de entidade antecipadamente.

Primeiro, crie um pacote chamado com.cy.domain no projeto Chapter08 e crie uma classe de entidade User neste pacote.

package com.ytx.domain;

/** 发布消息的实体类可以通过实现Serializable序列化接口进行发布 */
public class User {
    private Integer id;
    private String username;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}

Em segundo lugar, na classe de teste do projeto Chapter08ApplicationTests, usamos a classe de modelo RabbitTemplate fornecida pela estrutura Spring para implementar o envio de mensagens.

@Autowired
private RabbitTemplate rabbitTemplate;

/** 1.Publish/Subscribe工作模式消息发送端 */
@Test
public void subPublisher() {
    User user = new User();
    user.setId(1);
    user.setUsername("小明");
    rabbitTemplate.convertAndSend("fanout_exchange", "", user);
}

No código acima, primeiro usamos a anotação @Autowired para introduzir o objeto componente RabbitTemplate gerenciado pelo middleware de mensagem e, em seguida, usamos o método convertAndSend(String exchange, String routingKey, Object object) da classe de ferramenta de modelo para publicar a mensagem. O primeiro parâmetro neste método indica a exchange que envia a mensagem, e o valor deste parâmetro deve ser consistente com o nome da exchange previamente customizado; o segundo parâmetro indica a chave de roteamento, pois implementa o modo de trabalho Publish/Subscribe, portanto não precisa ser especificado; O terceiro parâmetro é o conteúdo da mensagem a ser enviada e o tipo de objeto é recebido.

Em seguida, execute o método de teste subPublisher() do envio da mensagem acima, e o efeito de execução do console é mostrado na figura abaixo.

foto

Como pode ser visto na figura acima, uma exceção ocorre no programa ao enviar uma mensagem de objeto de classe de entidade. A partir das informações de exceção "SimpleMessageConverter suporta apenas cargas úteis de String, byte[] e Serializable", pode-se ver que o conversor SimpleMessageConverter é usado por padrão para conversão de mensagens durante o processo de envio de mensagens. Armazenamento, o conversor suporta apenas mensagens serializadas de strings ou objetos de classe de entidade. Porém, a mensagem enviada na classe de teste é a mensagem do objeto da classe de entidade Usuário, portanto ocorre uma exceção.

Se quisermos resolver as exceções mencionadas acima no middleware de mensagens enviando mensagens de classe de entidade, geralmente podemos adotar duas soluções: a primeira é implementar a interface de serialização Serializable que vem com o JDK; a segunda é personalizar outros tipos de conversores de mensagens. Ambos os métodos de implementação são viáveis. Comparado com o segundo método de implementação, o efeito de visualização do primeiro método é fraco após a implementação e a mensagem convertida não pode ser reconhecida, portanto o segundo método é geralmente usado.

Em seguida, criamos um pacote chamado com.ytx.config no projeto Chapter08 e criamos uma classe de configuração de mensagem RabbitMQ RabbitMQConfig neste pacote.

package com.ytx.config;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** RabbitMQ消息配置类 */
@Configuration
public class RabbitMQConfig {
    /** 定制JSON格式的消息转换器 */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

Crie uma classe de configuração de mensagem RabbitMQ RabbitMQConfig no código e personalize um componente conversor de mensagem do tipo Jackson2JsonMessageConverter por meio da anotação @Bean na classe de configuração. O valor de retorno deste componente deve ser do tipo MessageConverter.

Execute o método subPublisher() novamente. Após a execução bem-sucedida do método, visualize as informações no painel Filas na página de gerenciamento visual do RabbitMQ.

foto

Como pode ser visto na figura acima, após o envio da mensagem, cada uma das duas filas de mensagens vinculadas no modo de trabalho Publicar/Assinar tem uma mensagem a ser recebida. Como nenhum consumidor de mensagem foi fornecido ainda, a mensagem enviada pelo a classe de teste agora estará temporariamente na fila. Vá para a página de detalhes da fila para visualizar as mensagens.

foto

3. O consumidor da mensagem recebe a mensagem

Crie um pacote chamado com.ytx.service no projeto Chapter08 e crie uma classe de negócios RabbitMQService para receber e processar mensagens para o middleware de mensagens RabbitMQ neste pacote.

package com.ytx.chapter08.service;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/** RabbitMQ消息接收处理的业务类 */
@Service
public class RabbitMQService {
    /** Publish/Subscribe工作模式接收,处理邮件业务 */
    @RabbitListener(queues = "fanout_queue_email")
    public void subConsumerEmail(Message message) {
        byte[] body = message.getBody();
        String msg = new String(body);
        System.out.println("邮件业务接收到消息:" + msg);
    }

    /** Publish/Subscribe工作模式接收,处理短信业务 */
    @RabbitListener(queues = "fanout_queue_sms")
    public void subConsumerSms(Message message) {
        byte[] body = message.getBody();
        String msg = new String(body);
        System.out.println("短信业务接收到消息:" + msg);
    }
}

No código acima, uma classe de processamento de negócios RabbitMQService que recebe e processa mensagens RabbitMQ é criada. Usando a anotação @RabbitListener fornecida pela estrutura Spring nesta classe, podemos ouvir mensagens cujos nomes de fila são fanout_queue_email e fanout_queue_sms. As duas filas para a serem monitorados são os anteriores Especifica a fila de mensagens para enviar e armazenar mensagens.

Deve-se observar que após usar a anotação @RabbitListener para escutar mensagens da fila, uma vez que o serviço inicia e monitora se há uma mensagem na fila especificada (atualmente há uma mensagem idêntica em cada uma das duas filas), o método correspondente à anotação irá recebê-la imediatamente e consumir mensagens da fila. Além disso, no método de recebimento de uma mensagem, o tipo de parâmetro pode ser consistente com o tipo de mensagem enviada, ou utilizar Tipo de objeto e Tipo de mensagem. Se a mensagem for recebida com parâmetros correspondentes ao tipo de mensagem, apenas as informações específicas do corpo da mensagem poderão ser obtidas; se a mensagem for recebida com parâmetros do tipo Objeto ou Mensagem, as informações do parâmetro da mensagem MessageProperties diferentes da mensagem também poderão ser obtidas.

Inicie o projeto Chapter08 e o efeito de consumo de mensagens exibido no console é mostrado na figura abaixo.

foto

Como pode ser visto na figura acima, após o projeto ser iniciado com sucesso, o consumidor de mensagens escuta as duas mensagens na fila de mensagens e as consome respectivamente. Ao mesmo tempo, por meio do painel Filas da página de gerenciamento visual do RabbitMQ para visualizar o status das mensagens da fila, você descobrirá que as mensagens armazenadas nas duas filas foram consumidas. Até agora, foi realizado um caso de negócios completo do modo de trabalho Publicar/Assinar (modo publicar-assinar) de envio de mensagens, armazenamento de middleware de mensagens e consumo de mensagens.

Observe que se a dependência do módulo Spring Web não for introduzida, quando o projeto Chapter08 for iniciado, o consumidor da mensagem reportará o seguinte erro ao receber a mensagem.

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.amqp.support.converter.MessageConverter]: Factory method 'messageConverter' threw exception; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/jackson/databind/ObjectMapper
  at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.25.jar:5.3.25]
  at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.25.jar:5.3.25]
  ... 18 common frames omitted

dica:

No código acima, a anotação @RabbitListener comumente usada no desenvolvimento é usada para monitorar o status da mensagem da fila de nomes especificada. Este método consumirá e processará imediatamente após ouvir a existência de mensagens na fila especificada. Além disso, também podemos usar o método receiverAndConvert(String queueName) da classe de modelo RabbitTemplate para consumir manualmente mensagens na fila especificada.

Abordagem baseada em classe

Com base na classe de configuração, ele fala principalmente sobre o uso da anotação @Configuration fornecida pela estrutura SpringBoot para configurar componentes de envio de mensagens personalizados e enviar mensagens. Vamos explicar e demonstrar essa abordagem baseada em configuração.

Abra a classe de configuração de mensagem RabbitMQ RabbitMQConfig e use o método baseado em classe de configuração para personalizar os componentes relacionados ao envio de mensagens nesta classe de configuração.

package com.ytx.config;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/** RabbitMQ消息配置类 */
@Configuration
public class RabbitMQConfig {
    /** 定制JSON格式的消息转换器 */
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    /** 使用基于配置类的方式定制消息中间件 */
    // 1.定义fanout类型的交换器
    @Bean
    public Exchange fanoutExchange() {
        return ExchangeBuilder.fanoutExchange("fanout_exchange").build();
    }
    // 2.定义两个不同名称的消息队列
    @Bean
    public Queue fanoutQueueEmail() {
        return new Queue("fanout_queue_email");
    }
    @Bean
    public Queue fanoutQueueSms() {
        return new Queue("fanout_queue_sms");
    }
    // 3.将两个不同名称的消息队列与交换器进行绑定
    @Bean
    public Binding bindingEmail() {
        return BindingBuilder.bind(fanoutQueueEmail()).to(fanoutExchange()).with("").noargs();
    }
    @Bean
    public Binding bindingSms() {
        return BindingBuilder.bind(fanoutQueueSms()).to(fanoutExchange()).with("").noargs();
    }
}

No código acima, 3 tipos de componentes Bean são customizados usando a anotação @Bean.Esses 3 componentes representam respectivamente a troca, a fila de mensagens e a ligação entre a fila de mensagens e a troca. A implementação desse componente de mensagem customizada com base na classe de configuração é exatamente a mesma do componente de mensagem customizada baseado em API, exceto que o método de implementação é diferente.

De acordo com as etapas de implementação da integração do serviço de mensagens, após concluir a customização do componente da mensagem, você precisa escrever o remetente e o consumidor da mensagem, e o remetente e o consumidor da mensagem foram implementados no método baseado em API e personalizados com base na classe de configuração O nome do componente de mensagem é igual ao nome dos componentes de envio e consumo de mensagens usados ​​​​no teste anterior, portanto, podemos reutilizá-lo diretamente aqui.

Execute novamente o método de teste do remetente da mensagem subPublisher() e o consumidor da mensagem poderá monitorar e consumir automaticamente as mensagens na fila de mensagens, e o efeito será o mesmo do teste baseado em API.

Abordagem baseada em anotações

O método baseado em anotação refere-se ao uso da anotação @RabbitListener da estrutura Spring para personalizar o componente de envio de mensagens e enviar a mensagem.

Na classe de negócios RabbitMQService para recepção e processamento de mensagens, o método consumidor de mensagens para serviço de correio e processamento de serviço de SMS será anotado, e a anotação @RabbitListener e seus atributos relacionados serão usados ​​para customizar o componente de envio de mensagens.

package com.ytx.chapter08.service;
import com.ytx.chapter08.domain.User;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/** RabbitMQ消息接收处理的业务类 */
@Service
public class RabbitMQService {
    /** Publish/Subscribe工作模式接收,处理邮件业务 */
    /*
    @RabbitListener(queues = "fanout_queue_email")
    public void subConsumerEmail(Message message) {
        byte[] body = message.getBody();
        String msg = new String(body);
        System.out.println("邮件业务接收到消息:" + msg);
    }
    */

    /** Publish/Subscribe工作模式接收,处理短信业务 */
    /*
    @RabbitListener(queues = "fanout_queue_sms")
    public void subConsumerSms(Message message) {
        byte[] body = message.getBody();
        String msg = new String(body);
        System.out.println("短信业务接收到消息:" + msg);
    }
    */

    /** 使用基于注解的方式实现消息服务 */
    // 1.1 Publish/Subscribe工作模式接收,处理邮件业务
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("fanout_queue_email"),
            exchange = @Exchange(value = "fanout_exchange", type = "fanout")))
    public void subConsumerEmailAno(User user) {
        System.out.println("邮件业务接收到消息:" + user);
    }
    // 1.2 Publish/Subscribe工作模式接收,处理短信业务
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue("fanout_queue_sms"),
            exchange = @Exchange(value = "fanout_exchange", type = "fanout")))
    public void subConsumerSmsAno(User user) {
        System.out.println("短信业务接收到消息:" + user);
    }
}

No código acima, os consumidores dos dois componentes da mensagem são customizados usando a anotação @RabbitListener e seus atributos relacionados. Ambos os consumidores recebem e consomem a classe de entidade User. Na anotação @RabbitListener, o atributo de ligação é usado para criar e vincular os componentes de troca e fila de mensagens.Deve-se notar que, para permitir que os consumidores dos dois componentes de mensagem recebam a classe de entidade Usuário, precisamos customizar o exchange. O tipo de switch está definido como fanout. Além disso, a anotação @QueueBinding do atributo de ligação remove os atributos de valor e troca, e o atributo key é usado para personalizar a chave de roteamento routingKey (não necessária no modo de publicação-assinatura atual).

Reinicie o método de teste subPublisher(), o consumidor de mensagens pode monitorar e consumir automaticamente as mensagens na fila de mensagens, e o efeito é o mesmo do teste baseado em API.

Até agora, concluímos os métodos baseados em API, baseados em configuração e baseados em anotações no SpringBoot para realizar a explicação integrada do modo de trabalho Publicação/Assinatura. Entre as três maneiras de implementar serviços de mensagens, o método baseado em API é relativamente simples e intuitivo, mas é fácil de combinar com o código de negócios; o método baseado em configuração é relativamente isolado, fácil de gerenciar de maneira unificada e está em conformidade com a ideia da estrutura Spring Boot; o método baseado em anotações É claro e fácil de gerenciar separadamente, mas também é fácil de combinar com o código de negócios.

No desenvolvimento real, é mais comum usar métodos baseados em configuração e métodos baseados em anotações, enquanto métodos baseados em API são usados ​​ocasionalmente.Claro, você deve fazer escolhas específicas de acordo com a situação real.

Resumir

Hoje, o Sr. Yuan apresentou três filas de mensagens baseadas em API, configuração e anotação, e ensinou a integração e implementação de código do modo Publicar/Assinar. Você deve se concentrar na implementação baseada em anotação. Em relação a outros conteúdos do RabbitMQ, o Sr. Yuan continuará a atualizar em artigos subsequentes, e você está convidado a continuar prestando atenção.

Acho que você gosta

Origin blog.csdn.net/GUDUzhongliang/article/details/132475765
Recomendado
Clasificación