마이크로서비스를 위한 비동기 통신 기술인 RabbitMQ

머리말

MQ의 등장은 마이크로 서비스 모듈 간의 결합 정도를 더욱 감소시키고 동기식 통신에 비해 관련 서비스의 대기 시간을 줄이고 메시지 전달을 보다 변경 가능하고 유연하게 만듭니다.어쨌든 Spring에 의해 통합되는 한 매우 간단
하고 RabbitMQ도 예외는 아닙니다.SpringAMQP를
사용하여 메시지 송수신을 실현하면 연결 매개 변수를 반복적으로 구성할 필요가 없으므로 일부 "하드 코딩" 문제가 해결됩니다. JDBC와 MyBatis의 통합과 매우 유사하다고 할 수 있습니다.
과거에 기본 RabbitMQ를 사용하여 메시지를 보내고 받는 것은 다음과 같습니다.

SpringAMQP를 사용한 후 메시지를 보내고 받는 것은 다음과 같습니다. 이것은 보시다시피
기본 큐(Basic-Queue)입니다 . 종속성을 도입 하고 yml 구성을 작성합니다. 파일, 연결 설정, 생성 채널 작업은 Spring에 의해 수행되었으며 우리가해야 할 일은 도구 클래스를 사용하여 메시지를 보내고 모니터링하는 것입니다. 매우 편리하다고 할 수 있습니다! 다른 시나리오에서는 다른 대기열 모델을 사용해야 합니다.
여기에 이미지 설명 삽입
spring-boot-starter-amqp

1. WorkQueue(작업 대기열)

여기에 이미지 설명 삽입
단일 소비자 상황(단순 대기열)의 경우 생산자가 초당 50개의 메시지를 보내고 소비자가 초당 40개의 메시지를 처리하면 처리할 수 없는 메시지가 초당 10개 더 생겨 과잉 생산되어 메시지가 누적됩니다. 대기열 메모리의 상한에 도달하면 새로 들어오는 메시지를 처리할 수 없으며 폐기됩니다.
메시지 처리 속도를 개선하고 대기열에 메시지가 누적되는 것을 방지하기 위해 대기열을 여러 소비자에게 바인딩할 수 있습니다. 즉, WorkQueue는 일반적으로
여기에 이미지 설명 삽입
콘솔 관찰의 편의를 위해 다음과 같은 방식으로 설계됩니다
.

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

소비자:
가능한 한 실제 시나리오를 시뮬레이션하기 위해(소비자는 메시지를 처리하는 능력이 다릅니다) 두 소비자의 수면 매개변수를 서로 다른 두 시간으로 설정합니다.

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

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

메시지 프리페치 메커니즘

실행 후 콘솔을 관찰한 결과 모든 메시지를 처리하는 데 거의 5초가 걸렸다는 것을 알았습니다 분명히 이런 종류의 효율성은 매우 낮습니다: 메시지를 소비하기 위해 두 소비자를 바인딩하는 속도가 빠르지 않고 느린 이유는 무엇입니까
여기에 이미지 설명 삽입
? 콘솔을 유심히 관찰해보면 50개의 메시지가 고르게 분포되어 있고 두 명의 소비자가 각각 짝수와 홀수의
ID를 가진 메시지를 소비하고 있음을 알 수 있습니다 . 이런 일이? 즉, 소비자가 처리하기 전에 미리 메시지 채널을 얻은 다음 메시지를 하나씩 처리합니다.이 프로세스는 메시지 처리와 격리됩니다! 여전히 이해하지 못하는 사람들이 있다면 네이티브 RabbitMQ를 사용할 때 메시지를 처리하는 방법을 생각해보십시오. 콜백 함수가 실행될 때 메시지가 처리되지 않고 프로그램이 계속 아래쪽으로 실행될 수 있습니다. 그리고 잠시 후에 처리를 시작합니다 Messages (사실 여기도 RabbitMQ의 비동기성을 반영하는 곳이라고 생각합니다 ) 그런 메커니즘의 존재가 비동기성을 보장하는 열쇠입니다. 다음과 같이 처리 효율성을 보장하도록 조정할 수도 있습니다.


여기에 이미지 설명 삽입

listener:
  simple:
    prefetch: 1

prefetch 매개변수는 소비자가 매번 받는 메시지 수를 확인하는 데 사용되며 다음 메시지 배치는 ​​처리가 완료된 후에만 얻을 수 있습니다. 데이터 prefetch가
설정된 후 소비자는 1초 내에 모든 메시지를 처리합니다
여기에 이미지 설명 삽입
. WorkQueue의 소비자 설정은 "작업에 따른 할당" 전략을 더 완벽하게 구현해야 합니다.
작업 대기열 WorkQueue를 사용한 후 메시지 처리 효율성이 크게 향상되었으며 메시지 누적이 없습니다. .

2.게시&구독(게시-구독)

단순 대기열 및 작업 대기열 모델의 경우 생산자가 메시지를 게시하고 소비자가 메시지를 사용하면 메시지가 삭제됩니다. 이런 식으로 동시에 여러 소비자에게 메시지를 보내는 것은 불가능합니다.
여기에 이미지 설명 삽입
마이크로 서비스 프로젝트의 경우 결제 주문 모델에서는 결제 서비스가 완료되면 SMS 서비스와 주문 서비스에 동시에 알리는 메시지가 전송됩니다... 이를 위해서는 메시지의 높은 가용성이 필요하며 메시지는 서비스가 메시지를 소비한 후에는 파기할 수 없으므로 다른 서비스가 메시지를 수신할 수 없습니다.
동일한 메시지를 여러 소비자에게 보내고 수신하게 하는 방법은 무엇입니까? 게시 및 구독 작업 모델을 사용하여
여기에 이미지 설명 삽입
메시지를 교환을 통해 다른 대기열로 라우팅할 수 있으며 소비자는 해당 구독 대기열에서 메시지를 사용할 수 있습니다.
교환 유형에 따라 게시 전략이 다릅니다.

1. 팬아웃(방송)

SpringAMQPA는 Exchange, Queue 및 바인딩 관계를 선언하기 위한 API를 제공합니다.
여기에 이미지 설명 삽입
따라서 Exchange 인터페이스 아래 구현 클래스를 사용하면 메시지를 바인딩된 각 Queue로 라우팅할 수 있으며 코드는 매우 간단해집니다.Exchange를 선언하고 바인딩하면 Queue는 다음을 수행할 수 있습니다.

@Bean
public FanoutExchange fanoutExchange(){
    
    
    return new FanoutExchange("yu7.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. DirectExchange(라우팅)

DirectExchange는 규칙에 따라 받은 메시지를 지정된 대기열로 라우팅합니다.생산자가 메시지를 게시할 때 지정된 메시지의 RoutingKey는 소비자가 선언한 bindingKey와 일치하여 "정확한 지침"을 달성합니다.구성을 완료
여기에 이미지 설명 삽입
하려면 @RabbitListener 주석을 통해 클릭 한 번으로 완료할 수 있으므로 구성 클래스를 전혀 사용할 필요가 없습니다.

생산자:

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

소비자:

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

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

여기에 이미지 설명 삽입
큐의 bindingKey가 같으면 브로드캐스트 모델이 됩니다.

3.주제 교환(주제)

Topic은 Direct와 매우 유사하며 RoutingKey-BindingKey를 와일드카드 형태로 매칭하여 더 많은 메시지에 대한 타깃 라우팅 및 구독을 할 수 있으며 과거에 여러 BindingKey를 사용하던 상황을 이제 해결할 수 있습니다. 하나만 사용:
#: 0개 이상의 단어를 나타냄
*: 단어를 나타냄
여기에 이미지 설명 삽입

MQ의 장점

1. 낮은 결합도: 새로운 수요가 있을 때마다 해당 구독을 추가하기만 하면 됨
2. 처리량 향상: 각자가 구독한 이벤트를 자체적으로 처리하고 리소스를 해제하기 전에 실행이 완료될 때까지 기다릴 필요가 없음 .
3. Fault isolation: 강한 의존성이 없기 때문에 중간에 특정 링크에 문제가 있어도 전체 프로세스에 영향을 미치지 않음
4. 트래픽 피크 쉐이빙: MQ는 파이프라인과 같습니다. 요청이 오면 줄을 서서 순차적으로 실행

추천

출처blog.csdn.net/weixin_57535055/article/details/128794993