Pub-Sub vs Message Queues

Trong hướng dẫn này, chúng ta sẽ xem xét việc sử dụng hàng đợi tin nhắn và nhà xuất bản/người đăng ký. Đây là những mẫu phổ biến được sử dụng trong các hệ thống phân tán để hai hoặc nhiều dịch vụ giao tiếp với nhau.

Pub-Sub vs Message Queues

Đối với hướng dẫn này, tất cả các ví dụ sẽ được hiển thị bằng cách sử dụng trình môi giới tin nhắn RabbitMQ, vì vậy trước tiên hãy làm theo hướng dẫn của RabbitMQ để thiết lập và chạy cục bộ. Để tìm hiểu sâu hơn về RabbitMQ, hãy xem hướng dẫn khác của chúng tôi .

Lưu ý: có nhiều lựa chọn thay thế cho RabbitMQ có thể được sử dụng cho các ví dụ tương tự trong hướng dẫn này, chẳng hạn như Kafka , Google Cloud Pub-Sub và Amazon SQS để đặt tên nhưng chỉ một số ít.

1. Hàng đợi tin nhắn là gì?

Hãy bắt đầu bằng cách nhìn vào hàng đợi tin nhắn . Hàng đợi tin nhắn bao gồm một dịch vụ xuất bản và nhiều dịch vụ tiêu dùng giao tiếp qua hàng đợi. Giao tiếp này thường là một cách mà nhà xuất bản sẽ đưa ra mệnh lệnh cho người tiêu dùng. Dịch vụ xuất bản thường sẽ đưa tin nhắn vào hàng đợi hoặc trao đổi và một dịch vụ tiêu dùng sẽ sử dụng tin nhắn này và thực hiện hành động dựa trên tin nhắn này.
Hãy xem xét trao đổi sau đây:

1-1

Từ đó, chúng ta có thể thấy dịch vụ Nhà xuất bản đang đưa thông báo 'm n+1' vào hàng đợi. Ngoài ra, chúng ta cũng có thể thấy nhiều tin nhắn đã tồn tại trên hàng đợi đang chờ được sử dụng. Ở phía bên phải, chúng tôi có 2 dịch vụ tiêu thụ 'A' và 'B' đang nghe hàng đợi tin nhắn.

Bây giờ chúng ta hãy xem xét trao đổi tương tự sau một thời gian:

2-1

Đầu tiên chúng ta có thể thấy tin nhắn của Nhà xuất bản đã được đẩy xuống cuối hàng đợi. Tiếp theo, phần quan trọng cần xem xét là phía bên phải của hình ảnh. Chúng ta có thể thấy rằng người tiêu dùng 'A' đã đọc tin nhắn 'm 1' và do đó, nó không còn có sẵn trong hàng đợi để dịch vụ 'B' khác sử dụng.

1.1. Nơi sử dụng hàng đợi tin nhắn

Hàng đợi tin nhắn thường được sử dụng khi chúng ta muốn ủy thác công việc từ một dịch vụ. Khi làm như vậy, chúng tôi muốn đảm bảo rằng công việc chỉ được thực hiện một lần.

Việc sử dụng hàng đợi tin nhắn rất phổ biến trong kiến ​​trúc vi dịch vụ và trong khi phát triển các ứng dụng dựa trên đám mây hoặc không có máy chủ vì nó cho phép chúng tôi mở rộng quy mô ứng dụng của mình theo chiều ngang dựa trên tải.

Ví dụ: nếu có nhiều tin nhắn trên hàng đợi đang chờ xử lý, chúng tôi có thể tạo ra nhiều dịch vụ tiêu dùng nghe cùng một hàng đợi tin nhắn và xử lý luồng tin nhắn. Sau khi tin nhắn đã được xử lý, các dịch vụ có thể bị tắt khi lưu lượng truy cập ở mức tối thiểu để tiết kiệm chi phí vận hành.

1.2. Ví dụ sử dụng RabbitMQ

Chúng ta hãy đi qua một ví dụ cho rõ ràng. Ví dụ của chúng ta sẽ có dạng một nhà hàng pizza. Hãy tưởng tượng mọi người có thể đặt pizza thông qua một ứng dụng và các đầu bếp tại tiệm bánh pizza sẽ nhận đơn đặt hàng khi họ bước vào. Trong ví dụ này, khách hàng là nhà xuất bản của chúng tôi và (các) đầu bếp là người tiêu dùng của chúng tôi.
Đầu tiên, hãy xác định hàng đợi của chúng tôi:

private static final String MESSAGE_QUEUE = "pizza-message-queue";

@Bean
public Queue queue() {
    return new Queue(MESSAGE_QUEUE);
}

Sử dụng Spring AMQP, chúng tôi đã tạo một hàng đợi có tên là “pizza-message-queue”. Tiếp theo, hãy xác định nhà xuất bản sẽ đăng thông báo lên hàng đợi mới được xác định của chúng tôi:

public class Publisher {

    private RabbitTemplate rabbitTemplate;
    private String queue;

    public Publisher(RabbitTemplate rabbitTemplate, String queue) {
        this.rabbitTemplate = rabbitTemplate;
        this.queue = queue;
    }

    @PostConstruct
    public void postMessages() {
        rabbitTemplate.convertAndSend(queue, "1 Pepperoni");
        rabbitTemplate.convertAndSend(queue, "3 Margarita");
        rabbitTemplate.convertAndSend(queue, "1 Ham and Pineapple (yuck)");
    }
}

Spring AMQP sẽ tạo cho chúng tôi một Bean RabbitTemplate  có kết nối với sàn giao dịch RabbitMQ của chúng tôi để giảm chi phí cấu hình. Nhà xuất bản của chúng tôi tận dụng điều này bằng cách gửi 3 tin nhắn đến hàng đợi của chúng tôi.

Bây giờ, chúng tôi đã có đơn đặt hàng pizza, chúng tôi cần một ứng dụng dành cho người tiêu dùng riêng biệt. Điều này sẽ đóng vai trò là đầu bếp của chúng tôi trong ví dụ và đọc tin nhắn:

public class Consumer {
    public void receiveOrder(String message) {
        System.out.printf("Order received: %s%n", message);
    }
}

Bây giờ chúng ta hãy tạo MessageListenerAdapter cho hàng đợi của chúng ta để gọi phương thức nhận đơn đặt hàng của Người tiêu dùng bằng cách sử dụng sự phản chiếu:

@Bean
public SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames(MESSAGE_QUEUE);
    container.setMessageListener(listenerAdapter);
    return container;
}

@Bean
public MessageListenerAdapter listenerAdapter(Consumer consumer) {
    return new MessageListenerAdapter(consumer, "receiveOrder");
}

Các tin nhắn được đọc từ hàng đợi bây giờ sẽ được chuyển đến phương thức getOrder của lớp Consumer . Để chạy ứng dụng này, chúng tôi có thể tạo bao nhiêu ứng dụng Tiêu dùng tùy thích để đáp ứng các đơn đặt hàng đến. Ví dụ: nếu 400 đơn đặt hàng pizza được xếp vào hàng đợi thì chúng tôi có thể cần nhiều hơn 1 'đầu bếp' người tiêu dùng, nếu không đơn hàng sẽ bị chậm. Trong trường hợp này, chúng tôi có thể tạo ra 10 phiên bản tiêu dùng để hoàn thành đơn hàng kịp thời.

2. Pub-Sub là gì?

Bây giờ chúng ta đã giải quyết xong hàng đợi tin nhắn, hãy xem xét pub-sub. Ngược lại, đối với hàng đợi tin nhắn, trong kiến ​​trúc pub-sub, chúng tôi muốn tất cả các ứng dụng tiêu dùng (đăng ký) nhận được ít nhất 1 bản sao của tin nhắn mà nhà xuất bản của chúng tôi đăng lên một sàn giao dịch.

Hãy xem xét trao đổi sau đây:

3-1

Ở bên trái, chúng ta có một nhà xuất bản gửi tin nhắn “m n+1” tới một Chủ đề. Chủ đề này sẽ phát thông báo này tới các thuê bao của nó. Những đăng ký này bị ràng buộc vào hàng đợi. Mỗi hàng đợi có một dịch vụ thuê bao nghe đang chờ tin nhắn.

Bây giờ chúng ta hãy xem xét trao đổi tương tự sau một thời gian:

 

4

Cả hai dịch vụ đăng ký đều đang sử dụng “m 1” vì cả hai đều nhận được bản sao của tin nhắn này. Ngoài ra, Chủ đề đang phân phối tin nhắn mới “m n+1” tới tất cả những người đăng ký.

Pub sub nên được sử dụng khi chúng tôi cần đảm bảo rằng mỗi người đăng ký sẽ nhận được một bản sao của tin nhắn.

2.1. Ví dụ sử dụng RabbitMQ

Hãy tưởng tượng chúng ta có một trang web về quần áo. Trang web này có thể gửi thông báo đẩy tới người dùng để thông báo cho họ về các giao dịch. Hệ thống của chúng tôi có thể gửi thông báo qua email hoặc cảnh báo bằng văn bản. Trong trường hợp này, trang web là nhà xuất bản của chúng tôi và các dịch vụ cảnh báo bằng văn bản và email là những người đăng ký của chúng tôi.

Đầu tiên, hãy xác định trao đổi chủ đề của chúng ta và liên kết 2 hàng đợi với nó:

private static final String PUB_SUB_TOPIC = "notification-topic";
private static final String PUB_SUB_EMAIL_QUEUE = "email-queue";
private static final String PUB_SUB_TEXT_QUEUE = "text-queue";

@Bean
public Queue emailQueue() {
    return new Queue(PUB_SUB_EMAIL_QUEUE);
}

@Bean
public Queue textQueue() {
    return new Queue(PUB_SUB_TEXT_QUEUE);
}

@Bean
public TopicExchange exchange() {
    return new TopicExchange(PUB_SUB_TOPIC);
}

@Bean
public Binding emailBinding(Queue emailQueue, TopicExchange exchange) {
    return BindingBuilder.bind(emailQueue).to(exchange).with("notification");
}

@Bean
public Binding textBinding(Queue textQueue, TopicExchange exchange) {
    return BindingBuilder.bind(textQueue).to(exchange).with("notification");
}

 

Hiện tại chúng tôi đã liên kết 2 hàng đợi bằng cách sử dụng “thông báo” khóa định tuyến, nghĩa là mọi tin nhắn được đăng về chủ đề có khóa định tuyến này sẽ chuyển đến cả hai hàng đợi. Cập nhật lớp Nhà xuất bản  mà chúng tôi đã tạo trước đó, chúng tôi có thể gửi một số tin nhắn đến sàn giao dịch của mình:

rabbitTemplate.convertAndSend(topic, "notification", "New Deal on T-Shirts: 95% off!");
rabbitTemplate.convertAndSend(topic, "notification", "2 for 1 on all Jeans!");

 

3. So sánh

Như đã đề cập trước đó, cả hàng đợi tin nhắn và mẫu kiến ​​trúc pub-sub đều là một cách tuyệt vời để chia nhỏ một ứng dụng để làm cho nó có khả năng mở rộng theo chiều ngang tốt hơn.

Một lợi ích khác của việc sử dụng hàng đợi pub-sub hoặc hàng đợi tin nhắn là quá trình liên lạc bền hơn các chế độ liên lạc đồng bộ truyền thống. Ví dụ: nếu ứng dụng A giao tiếp với ứng dụng B thông qua lệnh gọi HTTP không đồng bộ thì nếu một trong hai ứng dụng ngừng hoạt động thì dữ liệu sẽ bị mất và yêu cầu phải được thử lại.

Sử dụng hàng đợi tin nhắn nếu một phiên bản ứng dụng tiêu dùng không hoạt động thì một ứng dụng tiêu dùng khác sẽ có thể xử lý tin nhắn thay thế. Sử dụng pub-sub, nếu một người đăng ký không hoạt động thì sau khi khôi phục được các tin nhắn đã bỏ lỡ sẽ có sẵn để sử dụng trong hàng đợi đăng ký.

Cuối cùng, bối cảnh là chìa khóa. Việc chọn sử dụng kiến ​​trúc pub-sub hay hàng đợi tin nhắn phụ thuộc vào việc xác định chính xác cách bạn muốn dịch vụ tiêu dùng hoạt động. Yếu tố quan trọng nhất cần ghi nhớ là hỏi “Có vấn đề gì nếu mọi người tiêu dùng nhận được mọi tin nhắn không? ”

4. Kết luận

Trong hướng dẫn này, chúng ta đã xem xét hàng pub-sub và hàng đợi tin nhắn cũng như một số đặc điểm của từng loại. Tất cả mã được đề cập trong hướng dẫn này có thể được tìm thấy trên GitHub.