rabbitmq-sharding插件使用

RabbitMQ是非常流行的消息中間件,大家都知道通過集羣能夠增大它的吞吐量,那麼針對單個隊列,集羣能增大他的吞吐量嗎?如果不能,我們要怎麼做呢?

南山遠眺

南山遠眺

問題

RabbitMQ是非常流行的消息中間件,大家都知道通過集羣能夠增大它的吞吐量,那麼針對單個隊列,集羣能增大他的吞吐量嗎?如果不能,我們要怎麼做呢?

答案是集羣並不能的增加單個隊列的吞吐量,這是因爲RabbitMQ的普通集羣只是共享元數據信息,達到將整個集羣規模的隊列擴大以增加吞吐量的目的。普通集羣甚至不能保證消息數據的高可用,任意一個broker宕機,都會導致這個broker上的隊列不可用。

而鏡像隊列也僅僅只是保證了實現鏡像複製的隊列的高可用。消費者並不能併發消費複製出來的隊列。

那麼RabbitMQ是否也能提高類似Kafka的topic分區的機制,來加大單個主題隊列的吞吐量呢?

通過使用 RabbitMQ Sharding 插件、Consistent-hash Sharding Exchange 來更加靈活地動態均衡隊列壓力,可以更從容地達到百萬併發的性能。

這裏我重點介紹下,RabbitMQ Sharding 插件,有興趣的夥伴可以自己研究下Consistent-hash Sharding Exchange,兩者的基本思路一致,都是根據Routeing Key的hash值將消息分發到分片隊列中。

原理介紹

官網:https://github.com/rabbitmq/rabbitmq-sharding

rabbitmq sharding插件爲您自動對隊列進行分區,也就是說,一旦您將一個exchange 定義爲sharded,那麼在每個集羣節點上自動創建支持隊列,並在它們之間共享消息。rabbitmq sharding向使用者顯示了一個隊列,但它可能是後臺運行在它後面的多個隊列。rabbitmq sharding插件爲您提供了一個集中的位置,通過向集羣中的其他節點添加隊列,您可以將消息以及跨多個節點的負載平衡發送到該位置。

 

插件安裝

查看當前插件
find / -name rabbitmq-plugins
cd /usr/sbin/
./rabbitmq-plugins list

 

如果有沒有對應的插件,自己下載後複製插件到指定的目錄
手動下載安裝,https://www.rabbitmq.com/community-plugins/

 

RabbitMQ的有些插件沒有集成在初始的安裝中,它們需要額外安裝,這些文件的後綴爲.ez,安裝時需要將.ez文件拷貝到安裝的插件目錄。以下是不同系統中默認安裝的插件目錄路徑:

 


插件安裝完成後可以通過命令sudo rabbitmq-plugins list查看已有插件列表,eg:

 


指定安裝插件命令:
./rabbitmq-plugins enable rabbitmq_sharding

 

說明安裝成功,重啓生效:
service rabbitmq-server restart

隊列分片

1.配置策略

find / -name rabbitmqctl

cd /usr/sbin/

./rabbitmqctl set_policy history-shard    "^history" \
  '{"shards-per-node": 2, "routing-key": "1234"}' \
  --apply-to exchanges

說明:
通過rabbitmqctl set_policy設置新增策略,策略名稱爲history-shard,
匹配規則爲^history,shards-per-node表示每個節點上分片出2個分片隊列,
routing-key爲1234,應用到所有交換器exchanges上去匹配執行。

2、在管理界面,手動創建名稱爲history的交換器,交換器類型選擇x-consistent-hash

 


 

如上圖現實,說明我們的消息分片已經成功。

但是,我們怎麼去消費history交換器下的消息呢?

代碼實戰

1、引入rabbitmq的依賴包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2、生產者

public class ProductTest {
    private static final String EXCHANGE_NAME = "history";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        factory.setHost("wanli");
        factory.setPort(5672);

        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();

        AMQP.BasicProperties.Builder bldr = new AMQP.BasicProperties.Builder();
        for (int i = 0; i < 10000; i++) {
            //第一個參數是交換器名稱,第二個參數是routeing key ,注意這裏的routeing key一定要是隨機的,不然消息都會發送到同一個隊列中
            channel.basicPublish(EXCHANGE_NAME, String.valueOf(i), bldr.build(), "hello".getBytes("UTF-8"));
        }
        channel.waitForConfirmsOrDie(10000);

        TimeUnit.SECONDS.sleep(5);

        channel.close();
        connection.close();
    }
}

消費者向名稱爲history的交換器,發送10000條routeing key爲0~10000,內容爲hello的消息

 

3、消費者

public class ConsumerTest {
    private static final String QUEUE_NAME = "history";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setVirtualHost("/");
        factory.setHost("wanli");
        factory.setPort(5672);
         Connection connection = factory.newConnection();
         Channel channel = connection.createChannel();

        //每次抓取的消息數量
        channel.basicQos(32);
        for (int i = 0; i < 10; i++) {
            Consumer consumer = new MyConsumer(channel);
            channel.basicConsume(QUEUE_NAME,consumer);
        }
        TimeUnit.SECONDS.sleep(120);

        channel.close();
        connection.close();

    }

    private  static class MyConsumer extends DefaultConsumer{
        public MyConsumer(Channel channel) {
            super(channel);
        }
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
            System.out.println("接收到的消息爲:" + new String(body));
            super.getChannel().basicAck(envelope.getDeliveryTag(),false);
        }
    }
}

10個消費者去消費名稱爲history的隊列,消費者均勻分佈在每個隊列上(每個隊列上綁定了5個),每次抓取32個消息去消費。

 

總結

通過rabbitmq-sharding插件,將原本單個隊列history的分成了2個隊列,但是對消費者來說,還是消費的原來的history隊列,而不用管底層實際對應的物理隊列。
極大的提高了單個隊列在大併發下的吞吐量。

感謝每一次關注和點贊

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章