Java Spring boot 整合RabbitMQ(二):工作佇列(Work queues)-B2B2C小程式電子商務

gung123發表於2020-03-05

現在,我們將傳送一些字串,把這些字串當作複雜的任務。我們並沒有一個真實的複雜任務,類似於圖片大小被調整或 pdf 檔案被渲染,所以我們透過 sleep () 方法來模擬這種情況。我們在字串中加上點號(.)來表示任務的複雜程度,一個點(.)將會耗時 1 秒鐘。比如 “Hello…” 就會耗時 3 秒鐘。


如果您尚未設定專案,請參閱第一個教程中的設定。我們將遵循與第一個教程相同的模式:建立一個包(tut2)並建立 Tut2Config、Tut2Receiver 和 Tut2Sender。


程式碼整合

首先建立一個新的包(tut2),我們將在這裡放置我們的三個類。在配置類 Tut2Config 中,我們設定了兩個配置檔案 ——tut2 和 work-queues。我們利用 Spring 來將佇列 Queue 暴露為一個 bean。我們配置消費者,並定義兩個 bean 以對應於上圖中的工作程式 receiver1 和 receiver2。

配置類

@Profile({"tut2", "work-queues"})
@Configuration
public class Tut2Config {
    @Bean
    public Queue queue() {
        return new Queue("work-queues");
    }
    /**
     * 定義兩個消費者,並且給了他們不同的標識
     */
  @Profile ("receiver")
    private static class ReceiverConfig {
        @Bean
        public Tut2Receiver receiver1() {
            return new Tut2Receiver(1);
        }
        @Bean
        public Tut2Receiver receiver2() {
            return new Tut2Receiver(2);
        }
    }
    @Profile("sender")
     @Bean
   public Tut2Sender sender() {
        return new Tut2Sender();
    }
}

生產者
我們簡單修改一下生產者的程式碼,以新增點號(.)的方式來人為的增加該任務的時長,字串中的每個點號(.)都會增加 1s 的耗時。

public class Tut2Sender {
 @Autowired
 private AmqpTemplate template;
 @Autowired   
 private Queue queue;
    int dots = 0;
    int count = 0;
   @Scheduled(fixedDelay = 1000, initialDelay = 500)
    public void send(){
        StringBuilder builder = new StringBuilder("Hello");
        if (dots++ == 3) {
            dots = 1;
        }
        for (int i = 0; i < dots; i++) {
            builder.append('.');
        }
        builder.append(Integer.toString(++count));
        String message = builder.toString();
        template.convertAndSend(queue.getName(), message);
        System.out.println(" [x] Sent '" + message + "'");
    }
}

消費者
我們的消費者 Tut2Receiver 透過 doWork () 方法模擬了一個耗時的虛假任務,它需要為訊息體中每一個點號(.)模擬 1 秒鐘的操作。並且我們為消費者增加了一個例項編號,以知道是哪個例項消費了訊息和處理的時長。

@RebbitListener(queues = "work-queues")
public class Tut2Receiver {
    private int instance;
    public Tut2Receiver(int instance) {
        this.instance = instance;
    }
    @RabbitHandler
    public void receive(String in) throws InterruptedException {
        StopWatch watch = new StopWatch();
        watch.start();
        System.out.println("instance " + this.instance +
                " [x] Received '" + in + "'");
        doWork(in);
        watch.stop();
        System.out.println("instance " + this.instance +
                " [x] Done in " + watch.getTotalTimeSeconds() + "s");
    }
    private void doWork(String in) throws InterruptedException {
        for (char ch : in.toCharArray()) {
            if (ch == '.') {
                Thread.sleep(1000);
            }
        }
    }
}

執行

maven 編譯


mvn clean package -Dmaven.test.skip=true

執行


java -jar target/rabbitmq-tutorial-0.0.1-SNAPSHOT.jar --spring.profiles.active=tut2,sender --tutorial.client.duration=60000

java -jar target/rabbitmq-tutorial-0.0.1-SNAPSHOT.jar --spring.profiles.active=tut2,receiver --tutorial.client.duration=60000

輸出


// Sender

Ready … running for 10000ms

[x] Sent ‘Hello.1’

[x] Sent ‘Hello…2’

[x] Sent ‘Hello…3’

[x] Sent ‘Hello.4’

[x] Sent ‘Hello…5’

[x] Sent ‘Hello…6’

[x] Sent ‘Hello.7’

[x] Sent ‘Hello…8’

[x] Sent ‘Hello…9’


// Receiver

Ready … running for 10000ms

instance 1 [x] Received ‘Hello.1’

instance 2 [x] Received ‘Hello…2’

instance 1 [x] Done in 1.005s

instance 1 [x] Received ‘Hello…3’

instance 2 [x] Done in 2.007s

instance 2 [x] Received ‘Hello.4’

instance 2 [x] Done in 1.005s

instance 1 [x] Done in 3.01s

instance 1 [x] Received ‘Hello…5’

instance 2 [x] Received ‘Hello…6’

instance 1 [x] Done in 2.006s

instance 1 [x] Received ‘Hello.7’

instance 1 [x] Done in 1.002s

instance 1 [x] Received ‘Hello…9’

instance 2 [x] Done in 3.01s

instance 2 [x] Received ‘Hello…8’

prefetch

從消費者這端的輸出可以看出來,instance 1 得到的任務編號始終是奇數(Hello.1,Hello…3,Hello…5,Hello.7),而 instance 2 得到的任務編號始終是偶數。瞭解springcloud架構可以加求求:三五三六二四七二五九

如果感覺這次的輸出只是巧合,可以多試幾次或透過 --tutorial.client.duration= 調整時長得到更多的輸出,而結果肯定都是一樣的。


這裡設計的問題就是之前在基礎概念裡講到的排程策略的問題了。要實現公平排程(Fair dispatch)就是設定 prefetch 的值,實現方式有兩種。


全域性設定

在 application.yml 中設定 spring.rabbitmq.listener.simple.prefetch=1 即可,這會影響到本 Spring Boot 應用中所有使用預設 SimpleRabbitListenerContainerFactory 的消費者。


網上很多人說改配置 pring.rabbitmq.listener.prefetc,實測已經無效,應該是版本的問題。我所使用的版本(RabbitMQ:3.7.4,Spring Boot: 2.0.1.RELEASE),除了 spring.rabbitmq.listener.simple.prefetch,還有一個 spring.rabbitmq.listener.direct.prefetch 可以配置。


改了配置後再執行,可以看到 instance 1 可以獲取到”Hello…6”、”Hello…12” 了。


Ready … running for 60000ms

instance 1 [x] Received ‘Hello.1’

instance 2 [x] Received ‘Hello…2’

instance 1 [x] Done in 1.004s

instance 1 [x] Received ‘Hello…3’

instance 2 [x] Done in 2.008s

instance 2 [x] Received ‘Hello.4’

instance 2 [x] Done in 1.004s

instance 2 [x] Received ‘Hello…5’

instance 1 [x] Done in 3.012s

instance 1 [x] Received ‘Hello…6’

instance 2 [x] Done in 2.007s

instance 2 [x] Received ‘Hello.7’

instance 2 [x] Done in 1.004s

instance 2 [x] Received ‘Hello…8’

instance 1 [x] Done in 3.011s

instance 1 [x] Received ‘Hello…9’

instance 2 [x] Done in 2.007s

instance 2 [x] Received ‘Hello.10’

instance 2 [x] Done in 1.006s

instance 2 [x] Received ‘Hello…11’

instance 1 [x] Done in 3.01s

instance 1 [x] Received ‘Hello…12’

特定消費者

上邊是改了全域性的消費者,如果只針對特定的消費者的話,又怎麼處理呢?

我們可以透過自定義 RabbitListenerContainerFactory 來實現。

@Bean
public RabbitListenerContainerFactory<SimpleMessageListenerContainer> prefetchOneRabbitListenerContainerFactory(ConnectionFactory rabbitConnectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(rabbitConnectionFactory);
    factory.setPrefetchCount(1);
    return factory;
}

然後在特定的消費者上指定 containerFactory

@RebbitListener(queues = "hello", containerFactory = "prefetchTenRabbitListenerContainerFactory")
public void receive(String in) {
    System.out.println(" [x] Received '" + in + "'")
}


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952307/viewspace-2678699/,如需轉載,請註明出處,否則將追究法律責任。

相關文章