rabbitMQ實戰生產者-交換機-佇列-消費者細談

MoreJewels發表於2024-06-21

生產者

rabbitmq的配置

建立交換機,建立queue,繫結交換機的routingkey到queue

一,預設的exchange列表

二,將exchange的routingkey繫結到queue

三,生產端關心訊息將發放哪個交換機,哪個routingkey,

也可以用萬用字元(如calc.*,calc.#)匹配相應的routingkey

mq服務匹配exchange,routingkey,到queue

消費者只關心queue

以下是部分程式碼

python連線到mq

# 建立到RabbitMQ的連線
connection_params = pika.ConnectionParameters(
    host=mq['host'],
    port=mq['port'],
    virtual_host='/',
    credentials=pika.PlainCredentials(mq['username'], mq['passwd']),
    channel_max=1024,
    heartbeat=15,
    retry_delay=5,
    connection_attempts=10
)
connection = pika.BlockingConnection(connection_params)
channel = connection.channel()
default_routing_key = mq['routing_key']

python端的生產者

def send_message(message, retry=3, routing_key=default_routing_key):
    global channel
    global connection
    for i in range(retry):
        try:
            if not channel.is_open:
                channel = connection.channel()
            # 傳送訊息到 exchange
            channel.basic_publish(exchange=mq['exchange'], routing_key=routing_key, body=json.dumps(message),
                                  mandatory=True)
            logger.info(f"Sent message to MQ: {message}")
            break  # 如果傳送成功,跳出迴圈
        except pika.exceptions.AMQPConnectionError as e:
            logger.error(f"Failed to send message to MQ: {e}, retrying...")
            connection = pika.BlockingConnection(connection_params)
        except pika.exceptions.ChannelWrongStateError as e:
            logger.error(f"Failed to send message to MQ: {e}, retrying...")
            channel = connection.channel()
        except Exception as e:
            logger.exception("MQ異常")
            if i < retry - 1:  # 如果不是最後一次重試,等待一段時間後繼續嘗試
                time.sleep(2 ** i)

python端的消費者,這個寫的有點複雜了,消費者不需要指定交換機和routing_key

        def start_consuming():
            with lock:
                sub_flag = subscript_dict.get(func.__qualname__)
                if sub_flag:
                    logger.info(f"Function {func.__qualname__} has been subscribed, skipping...")
                    return
                subscript_dict[func.__qualname__] = True

            logger.info(f'process: {os.getpid()} thread: {threading.current_thread().name}, {func.__qualname__} start consuming ...')
            _connection = pika.BlockingConnection(connection_params)
            _channel = _connection.channel()
            # 宣告交換機
            _channel.exchange_declare(exchange=exchange_name, exchange_type='topic', durable=True)

            # 宣告一個排他佇列,名稱由 RabbitMQ 自動生成
            result = _channel.queue_declare(queue='', exclusive=True)
            queue_name = result.method.queue

            # 將佇列繫結到交換機
            _channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key=routing_key)

            # 訂閱佇列的訊息
            _channel.basic_consume(queue=queue_name, on_message_callback=wrapped_callback, auto_ack=True)

            logger.info(f'Waiting for messages in {queue_name}, server: {mq["host"]}:{mq["port"]}, exchange: {exchange_name},'
                        f' routing_key: {routing_key} ...')
            _channel.start_consuming()

        # 在裝飾器中啟動一個執行緒來執行訂閱操作
        thread = threading.Thread(target=start_consuming)
        thread.daemon = True
        thread.start()

        return func  # 返回原始函式以保持簽名不變

  

java端的連線

    @Bean
    @ConfigurationProperties(prefix = "spring.rabbitmq.template")
    public RabbitTemplate calcRabbitTemplate(final ConnectionFactory connectionFactory) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());

        rabbitTemplate.setReturnsCallback(returnedMessage -> log.error("無法路由[訊息:{},回應碼:{},回應資訊:{},交換機:{},路由鍵:{}]",
                returnedMessage.getMessage(), returnedMessage.getReplyCode(), returnedMessage.getReplyText(),
                returnedMessage.getExchange(), returnedMessage.getRoutingKey()));

        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("交換機非同步確認:[相關資料:{},確認情況:{},原因:{}]",
                correlationData, ack, cause));
        return rabbitTemplate;
    }

  

java端的生產者

    
    @Autowired
@Qualifier("calcRabbitTemplate")
private RabbitTemplate rabbitTemplate;

public void sendMessage(Long testId, Integer current) { try { WsCalcDto wsCalcDto = new WsCalcDto(testId, current); WsMessageDto wsMessageDto = new WsMessageDto(wsCalcDto); rabbitTemplate.convertAndSend( mqCalcConfig.getExchangeConfig().getExchangeName(), mqCalcConfig.getRoutingKey(), wsMessageDto, new GorgeMessagePostProcessor(mqCalcConfig.getMessageConfig())); } catch (Exception e) { log.error("傳送訊息到RabbitMQ失敗", e); } }

  

java端的消費者

    @Autowired
    private MqCalcConfig mqCalcConfig;

    @RabbitListener(queues = "queue")
    public void processMessage(Message message, Channel channel) throws Exception {
        try {
            String body = new String(message.getBody(), StandardCharsets.UTF_8);
            log.info("接收RabbitMQ資料, {}", body);
            JSONObject jsonObject = JSON.parseObject(body);

            // ack表示確認訊息,第二個引數為批次ack,multiple:false只確認該delivery_tag的訊息,true確認該delivery_tag的所有訊息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            log.error("訊息消費異常", e);
            // Reject表示拒絕訊息。requeue:false表示被拒絕的訊息是丟棄;true表示重回佇列
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }

相關文章