Do princípio ao combate real, ensine como usar o RabbitMQ em seu projeto

Olá a todos, meu nome é Lou Tsai.

O artigo do RabbitMQ foi escrito antes, mas o exemplo dado na época foi a versão Demo. Este artigo integra principalmente o RabbitMQ no projeto técnico com base no conhecimento teórico escrito anteriormente.

Não BB, no diretório de artigos:

foto

A seguir, vamos primeiro revisar o conhecimento teórico. Se você já tem esse conhecimento claro, pode pular diretamente para a parte de combate real.

1. Fila de mensagens

1.1 Modo de fila de mensagens

Existem atualmente dois modos principais de filas de mensagens, nomeadamente "modo ponto a ponto" e "modo de publicação/assinatura".

modo ponto a ponto

Uma mensagem específica só pode ser consumida por um consumidor, e vários produtores podem enviar mensagens para a mesma fila de mensagens, mas quando uma mensagem é processada por um mensageiro, a mensagem será bloqueada ou removida da fila e outros consumidores não poderão processar a mensagem .

Deve-se notar que se um consumidor não conseguir processar uma mensagem, o sistema de mensagens geralmente colocará a mensagem de volta na fila para que outros consumidores possam continuar o processamento.

foto

 Se você quiser aprender testes automatizados, recomendo aqui um conjunto de vídeos para você. Este vídeo pode ser considerado o tutorial de teste automatizado número um em toda a rede na estação B. Ao mesmo tempo, o número de pessoas online aumentou atingiu 1.000 e há notas para coletar e compartilhar com você.Dashen Technical Exchange: 798478386   

[Atualizado] A coleção mais detalhada de tutoriais práticos para testes automatizados de interfaces Python ensinados pela estação B (a versão mais recente do combate real)_哔哩哔哩_bilibili [Atualizado] A coleção mais detalhada de tutoriais práticos para testes automatizados de Python interfaces ensinadas pela estação B (combate real) A versão mais recente) tem um total de 200 vídeos, incluindo: 1. Por que a automação de interface deve ser feita na automação de interface, 2. A visão geral da solicitação na automação de interface, 3. Combate de interface em automação de interface, etc. UP hospeda vídeos mais emocionantes, preste atenção à conta UP. https://www.bilibili.com/video/BV17p4y1B77x/?spm_id_from=333.337.search-card.all.click 

modelo de publicação/assinatura

Uma única mensagem pode ser buscada e processada simultaneamente por vários assinantes. Em geral, existem dois tipos de assinaturas:

  • Assinaturas temporárias : essas assinaturas só existem enquanto o consumidor está instalado e funcionando. Depois que o consumidor sai, a assinatura correspondente e as mensagens não processadas são perdidas.

  • Assinatura durável (durável) : Esta assinatura sempre existirá, a menos que seja excluída ativamente. Após a saída do consumidor, o sistema de mensagens continuará a manter a assinatura e as mensagens subsequentes poderão continuar a ser processadas.

foto

1.2 Características do RabbitMQ

  • Roteamento de mensagens (suporte) : RabbitMQ pode suportar diferentes tipos de roteamento de mensagens por meio de diferentes switches;

  • Mensagens ordenadas (não suportadas) : Ao consumir mensagens, se o consumo falhar, as mensagens serão colocadas de volta na fila e depois consumidas novamente, o que fará com que as mensagens fiquem fora de ordem;

  • Tempo de atraso da mensagem (muito bom) : através da fila de atraso, você pode especificar o tempo de atraso da mensagem, o tempo de expiração TTL, etc.;

  • Tratamento tolerante a falhas (muito bom) : Lida com falhas de processamento de mensagens com novas tentativas de entrega e Dead Letter Exchange (DLX);

  • Escalonamento (geral) : O escalonamento não é muito inteligente, porque mesmo que seja escalonado, ainda há apenas uma fila mestre, e apenas esta fila mestre pode resistir à carga, então entendo que o escalonamento do RabbitMQ é muito fraco (entendimento pessoal) .

  • Persistência (não muito boa) : Mensagens sem consumo podem suportar persistência.Isso é para garantir que as mensagens possam ser restauradas quando a máquina estiver inoperante, mas as mensagens consumidas serão excluídas imediatamente, porque o RabbitMQ não foi projetado para armazenar dados históricos.

  • Retrocesso de mensagens (suporte) : como as mensagens não suportam armazenamento permanente, o retrocesso naturalmente não é suportado.

  • Alto rendimento (médio) : Como todas as solicitações são executadas na fila mestre no final, seu design faz com que o desempenho de uma única máquina não atinja o padrão de nível 100.000.

2. Um estudo preliminar sobre o princípio do RabbitMQ

Lançado em 2007, RabbitMQ é um sistema de enfileiramento de mensagens de código aberto desenvolvido na linguagem Erlang e implementado com base no protocolo AMQP.

2.1 Conceitos básicos

Quando se trata de RabbitMQ, é preciso mencionar o protocolo AMQP. O protocolo AMQP é um protocolo binário com recursos modernos. É um protocolo avançado de enfileiramento de mensagens padrão da camada de aplicativo que fornece serviços de mensagens unificadas.É um padrão aberto para protocolos da camada de aplicativo e é projetado para middleware orientado a mensagens.

Primeiro entenda vários conceitos importantes do protocolo AMQP:

  • Servidor: Receba a conexão do cliente e implemente o serviço da entidade AMQP.

  • Conexão: conexão, conexão de rede entre aplicação e servidor, conexão TCP.

  • Canal: As operações de canal, leitura e gravação de mensagens são executadas no canal. Um cliente pode estabelecer múltiplos canais, cada canal representando uma tarefa de sessão.

  • Mensagem: Mensagem, os dados transmitidos entre a aplicação e o servidor, a mensagem pode ser muito simples ou complexa. Consiste em Propriedades e Corpo. Propriedades é a embalagem externa, que pode modificar a mensagem, como prioridade da mensagem, atraso e outros recursos avançados; Corpo é o conteúdo do corpo da mensagem.

  • Host Virtual: host virtual para isolamento lógico. Pode haver várias trocas e filas em um host virtual e não pode haver trocas ou filas com o mesmo nome no mesmo host virtual.

  • Exchange: A exchange recebe mensagens e as encaminha para uma ou mais filas de acordo com regras de roteamento. Se a rota não estiver disponível, devolva-a ao produtor ou descarte-a diretamente. Existem quatro tipos de switches comumente usados ​​pelo RabbitMQ: direto, tópico, fanout e cabeçalhos, que serão descritos em detalhes posteriormente.

  • Vinculação: ligação, conexão virtual entre a troca e a fila de mensagens, a ligação pode conter um ou mais RoutingKey.

  • RoutingKey: Chave de roteamento. Quando o produtor envia uma mensagem ao switch, ele enviará um RoutingKey para especificar as regras de roteamento, para que o switch saiba para qual fila enviar a mensagem. A chave de roteamento geralmente é uma string separada por ".", como "com.rabbitmq".

  • Fila: Fila de mensagens, usada para armazenar mensagens para consumo dos consumidores.

2.2 Princípio de funcionamento

O modelo do protocolo AMQP consiste em três partes: produtor, consumidor e servidor. O processo de execução é o seguinte:

  1. O produtor se conecta ao servidor, estabelece uma conexão e abre um canal.

  2. Os produtores declaram trocas e filas, definem propriedades relacionadas e vinculam trocas e filas por meio de chaves de roteamento.

  3. Os consumidores também precisam realizar operações como estabelecer uma conexão e abrir um canal para receber mensagens.

  4. O produtor envia uma mensagem ao host virtual no servidor.

  5. O switch no host virtual seleciona regras de roteamento de acordo com a chave de roteamento e as envia para diferentes filas de mensagens.

  6. Os consumidores que assinam a fila de mensagens podem receber a mensagem e consumi-la.

foto

2.3 Interruptores comumente usados

Existem quatro tipos de opções comumente usadas pelo RabbitMQ: direto, tópico, fanout e cabeçalhos:

  • Troca Direta: Como você pode ver no texto, troca direta significa que a troca precisa estar vinculada a uma fila, exigindo que a mensagem corresponda exatamente a uma chave de roteamento específica. Simplificando, é um envio ponto a ponto, um para um.

foto

  • Fanout Exchange: Este tipo de troca requer filas vinculadas à troca. Uma mensagem enviada para uma exchange é encaminhada para todas as filas vinculadas a essa exchange. Muito parecido com a transmissão de sub-rede, cada host na sub-rede recebe uma cópia da mensagem. Simplificando, é publicar e assinar.

foto

  • Troca de Tópicos: Se traduzido diretamente, é chamado de troca de tópicos. Se for traduzido a partir do uso acima, pode ser chamado de troca de curinga, o que seria mais apropriado. Essa opção usa curingas para corresponder e rotear para a fila correspondente. Existem dois tipos de curingas: "*" e "#". Ressalta-se que o símbolo “.” deve ser adicionado antes do curinga.

    • * símbolo: Existe e apenas uma palavra corresponde. Por exemplo, a.* pode corresponder a "ab" e "ac", mas não a "abc".

    • # símbolo: Combine uma ou mais palavras. Por exemplo, "rabbit.#" pode corresponder não apenas a "rabbit.ab", "rabbit.a", mas também a "rabbit.abc".

foto

  • Troca de cabeçalhos: esse tipo de opção relativamente não é muito usado. É um pouco diferente dos três tipos acima: seu roteamento não usa routingKey para correspondência de roteamento, mas roteamento combinando o valor da chave transportado no cabeçalho da solicitação. Para criar uma fila, você precisa definir as informações do cabeçalho de ligação. Existem dois modos: correspondência completa e correspondência parcial. Conforme mostrado na figura acima, o switch irá combinar o valor da chave vinculado à fila de acordo com o valor da chave transportado nas informações do cabeçalho enviadas pelo produtor e encaminhar para a fila correspondente.

foto

3. Construção do ambiente RabbitMQ

Como uso um Mac, posso consultar diretamente o site oficial:

https://www.rabbitmq.com/install-homebrew.html

Deve-se notar que deve ser executado primeiro:

brew update

Em seguida, execute:

brew install rabbitmq

Antes de brew update não ser executado, quando brew install RabbitMQ era executado diretamente, vários erros estranhos eram relatados, a maioria dos quais era "403 Forbidde".

Mas quando você executa "brew install coelhomq", outros programas serão instalados automaticamente. Se você instalar o Rabbitmq usando o código fonte, porque iniciar o serviço depende do ambiente erlang, você precisa instalar o erlang manualmente, mas o oficial já fez isso para você com um clique. Não é ótimo instalar automaticamente todos os programas dos quais o Rabbitmq depende!

foto

A saída da execução final bem-sucedida é a seguinte:

foto

Inicie o serviço:

# 启动方式1:后台启动
brew services start rabbitmq
# 启动方式2:当前窗口启动
cd /usr/local/Cellar/rabbitmq/3.8.19
rabbitmq-server

Digite no navegador:

http://localhost:15672/

A interface de gerenciamento de segundo plano do RabbitMQ aparecerá (o nome de usuário e a senha são convidados):

foto

Instalado através do brew, uma linha de comando para fazer isso, realmente perfumado!

4. Integração RabbitMQ

4.1 Trabalho preliminar

Adicionar Conta:

## 添加账号
./rabbitmqctl add_user admin admin
## 添加访问权限
./rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
## 设置超级权限
./rabbitmqctl set_user_tags admin administrator

Pom introduz dependências:

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.5.1</version>
</dependency>

 

4.2 Implementação do código

código principal

Primeiro, conclua um singleton ConnectionFactory, cada máquina tem seu próprio ConnectionFactory para evitar a inicialização todas as vezes (em iterações posteriores, irei removê-lo e transformá-lo em um pool de conexões).

/**
 * @author Louzai
 * @date 2023/5/10
 */
public class RabbitmqUtil {

    /**
     * 每个key都有自己的工厂
     */
    private static Map<String, ConnectionFactory> executors = new ConcurrentHashMap<>();

    /**
     * 初始化一个工厂
     *
     * @param host
     * @param port
     * @param username
     * @param passport
     * @param virtualhost
     * @return
     */
    public static ConnectionFactory init(String host,
                                  Integer port,
                                  String username,
                                  String passport,
                                  String virtualhost) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost(host);
        factory.setPort(port);
        factory.setUsername(username);
        factory.setPassword(passport);
        factory.setVirtualHost(virtualhost);
        return factory;
    }

    /**
     * 工厂单例,每个key都有属于自己的工厂
     *
     * @param key
     * @param host
     * @param port
     * @param username
     * @param passport
     * @param virtualhost
     * @return
     */
    public static ConnectionFactory getOrInitConnectionFactory(String key,
                                                               String host,
                                                               Integer port,
                                                               String username,
                                                               String passport,
                                                               String virtualhost) {
        ConnectionFactory connectionFactory = executors.get(key);
        if (null == connectionFactory) {
            synchronized (RabbitmqUtil.class) {
                connectionFactory = executors.get(key);
                if (null == connectionFactory) {
                    connectionFactory = init(host, port, username, passport, virtualhost);
                    executors.put(key, connectionFactory);
                }
            }
        }
        return connectionFactory;
    }
}

Obtenha RabbitmqClient:

/**
 * @author Louzai
 * @date 2023/5/10
 */
@Component
public class RabbitmqClient {

    @Autowired
    private RabbitmqProperties rabbitmqProperties;

    /**
     * 创建一个工厂
     * @param key
     * @return
     */
    public ConnectionFactory getConnectionFactory(String key) {
        String host = rabbitmqProperties.getHost();
        Integer port = rabbitmqProperties.getPort();
        String userName = rabbitmqProperties.getUsername();
        String password = rabbitmqProperties.getPassport();
        String virtualhost = rabbitmqProperties.getVirtualhost();
        return RabbitmqUtil.getOrInitConnectionFactory(key, host, port, userName,password, virtualhost);
    }
}

foco! Bata no quadro-negro! ! ! Aqui está a lógica central do RabbmitMQ.

O tipo de switch que usamos é o Direct Exchange. Esse switch precisa estar vinculado a uma fila e exige que a mensagem corresponda exatamente a uma chave de roteamento específica. Simplificando, é um envio ponto a ponto, um para um.

Quanto ao motivo de não utilizarmos o modo de transmissão e troca de tópico, porque o cenário de utilização da escola técnica é o envio de uma única mensagem, e o modo de envio e consumo ponto a ponto pode atender plenamente às nossas necessidades.

Os três métodos a seguir são muito simples:

  • Enviar mensagem: obter fábrica -> criar link -> criar canal -> declarar switch -> enviar mensagem -> fechar link;

  • Consumir mensagens: obter fábrica -> criar link -> criar canal -> determinar fila de mensagens -> vincular fila ao switch -> aceitar e consumir mensagens;

  • Modo perpétuo de mensagem de consumo: consome mensagens RabbitMQ em modo sem bloqueio.

@Component
public class RabbitmqServiceImpl implements RabbitmqService {

    @Autowired
    private RabbitmqClient rabbitmqClient;

    @Autowired
    private NotifyService notifyService;

    @Override
    public void publishMsg(String exchange,
                           BuiltinExchangeType exchangeType,
                           String toutingKey,
                           String message) throws IOException, TimeoutException {
        ConnectionFactory factory = rabbitmqClient.getConnectionFactory(toutingKey);

        // TODO: 这种并发量起不来,需要改造成连接池

        //创建连接
        Connection connection = factory.newConnection();
        //创建消息通道
        Channel channel = connection.createChannel();

        // 声明exchange中的消息为可持久化,不自动删除
        channel.exchangeDeclare(exchange, exchangeType, true, false, null);

        // 发布消息
        channel.basicPublish(exchange, toutingKey, null, message.getBytes());

        System.out.println("Publish msg:" + message);
        channel.close();
        connection.close();
    }

    @Override
    public void consumerMsg(String exchange,
                            String queue,
                            String routingKey) throws IOException, TimeoutException {
        ConnectionFactory factory = rabbitmqClient.getConnectionFactory(routingKey);

        // TODO: 这种并发量起不来,需要改造成连接池

        //创建连接
        Connection connection = factory.newConnection();
        //创建消息信道
        final Channel channel = connection.createChannel();
        //消息队列
        channel.queueDeclare(queue, true, false, false, null);
        //绑定队列到交换机
        channel.queueBind(queue, exchange, routingKey);

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Consumer msg:" + message);

                // 获取Rabbitmq消息,并保存到DB
                // 说明:这里仅作为示例,如果有多种类型的消息,可以根据消息判定,简单的用 if...else 处理,复杂的用工厂 + 策略模式
                notifyService.saveArticleNotify(JsonUtil.toObj(message, UserFootDO.class), NotifyTypeEnum.PRAISE);

                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 取消自动ack
        channel.basicConsume(queue, false, consumer);
    }

    @Override
    public void processConsumerMsg() {
        System.out.println("Begin to processConsumerMsg.");

        Integer stepTotal = 1;
        Integer step = 0;

        // TODO: 这种方式非常 Low,后续会改造成阻塞 I/O 模式
        while (true) {
            step ++;
            try {
                System.out.println("processConsumerMsg cycle.");
                consumerMsg(CommonConstants.EXCHANGE_NAME_DIRECT, CommonConstants.QUERE_NAME_PRAISE,
                        CommonConstants.QUERE_KEY_PRAISE);
                if (step.equals(stepTotal)) {
                    Thread.sleep(10000);
                    step = 0;
                }
            } catch (Exception e) {

            }
        }
    }
}

Aqui está apenas um exemplo: se você quiser usá-lo em um ambiente de produção, que problemas você acha que existem?  Você pensa sobre isso primeiro, e eu contarei no final do artigo.

entrada de chamada

Na verdade, usamos o método de chamada assíncrona integrado do Java antes. Para facilitar a verificação, migrei a função de curtidas de artigos para o RabbitMQ. Enquanto for curtida, seguiremos o modo RabbitMQ.

// 点赞消息走 RabbitMQ,其它走 Java 内置消息机制
if (notifyType.equals(NotifyTypeEnum.PRAISE) && rabbitmqProperties.getSwitchFlag()) {
    rabbitmqService.publishMsg(
            CommonConstants.EXCHANGE_NAME_DIRECT,
            BuiltinExchangeType.DIRECT,
            CommonConstants.QUERE_KEY_PRAISE,
            JsonUtil.toStr(foot));
} else {
    Optional.ofNullable(notifyType).ifPresent(notify -> SpringUtil.publishEvent(new NotifyMsgEvent<>(this, notify, foot)));
}

Onde fica a entrada do consumo? Na verdade, quando o programa é iniciado, iniciamos o RabbitMQ para consumo e então todo o processo continua rodando no programa.

@Override
public void run(ApplicationArguments args) {
    // 设置类型转换, 主要用于mybatis读取varchar/json类型数据据,并写入到json格式的实体Entity中
    JacksonTypeHandler.setObjectMapper(new ObjectMapper());
    // 应用启动之后执行
    GlobalViewConfig config = SpringUtil.getBean(GlobalViewConfig.class);
    if (webPort != null) {
        config.setHost("http://127.0.0.1:" + webPort);
    }
    // 启动 RabbitMQ 进行消费
    if (rabbitmqProperties.getSwitchFlag()) {
        taskExecutor.execute(() -> rabbitmqService.processConsumerMsg());
    }
    log.info("启动成功,点击进入首页: {}", config.getHost());
}

4.3 Demonstração

Clicamos no botão "Curtir" várias vezes para acionar o envio da mensagem RammitMQ.

foto

Você também pode ver as mensagens enviadas e consumidas através do log.

foto

Eu confio! Muitos links não fechados. . .

foto

Ainda há muitos canais que não estão fechados. . .

foto

Estima-se que depois de um tempo de funcionamento, toda a memória se esgotará e a máquina travará, como pode quebrar? A resposta é o pool de conexões!

4.4 Ramos de código

Para facilitar a todos o aprendizado do processo de evolução da função, cada módulo abrirá uma ramificação separada, incluindo a versão atualizada posteriormente:

  • Armazém de código: https://github.com/itwanger/paicoding

  • Ramo de código: feature/add_rabbitmq_20230506

Se você precisar executar o RabbitMQ, a configuração a seguir precisará ser alterada para verdadeiro, porque o código padrão é falso.

foto

5 Epílogo

Este artigo permite que todos conheçam os princípios básicos do RabbitMQ e como integrar o RabbitMQ, mas não pode ser usado no ambiente de produção real, mas esta é realmente a primeira versão que escrevi, e é puramente por diversão, porque existem Existem ainda muitos problemas.

Vou listar resumidamente:

  1. Você precisa adicionar um pool de conexões ao Connection, caso contrário a memória continuará a ser consumida e a máquina definitivamente não será capaz de lidar com isso;

  2. O método de consumo do RabbitMQ precisa ser transformado, pois o método while + sleep é muito simples e rude;

  3. Se a tarefa de consumo for interrompida, você precisará de um mecanismo de consumo para reiniciar o RabbitMQ;

  4. Se a máquina desligar, as mensagens dentro do RabbitMQ não poderão ser perdidas após a reinicialização.

Se você também estiver muito interessado nas questões acima, pode basear-se diretamente no branch feature/add_rabbitmq_20230506 e, em seguida, me dar um PR, tecnologia, gosto de aprender enquanto jogo.

Acho que você gosta

Origin blog.csdn.net/m0_73409141/article/details/132540698
Recomendado
Clasificación