實現RabbitMQ廣播/動態創建隊列
1、背景
在當前實際運用的項目中,遇到了一個需求,所有在線服務器實例都維護着一份JVM本地內存數據(系統運行參數配置);
當系統運行參數配置發生修改,需要觸發所有的實例清空和刷新本地內存數據;
系統之前的解決方案是:應用程序對外提供一個http接口地址,一個實例一個實例進行調用,觸發它們刷新JVM本地內存數據;
2、問題&思考
- 原有的方案由於是單線程,隨着部署實例的個數越來越多,等待執行完成的時間越來越長。
- 考慮到系統中有使用到RabbitMQ,就在想能否通過RabbitMQ實現對所有服務器的廣播呢?
- RabbitMQ中比較適合的消息類型是基於exchange的fanout模式。
- 但是RabbitMQ的消息通知是基於隊列queue來說的,而不是針對客戶端實例來說的。並且1個消息只能被隊列下的1個客戶端消費,這明顯不符合需求。
- 如果需要N個實例客戶端都能同時消費同一條消息,就必須創建每個客戶端的專屬queue,綁定到exchange上,queue的個數與實例數相同。
- 基於這個思路在網上查閱了相關資料,發現可以通過動態創建隊列queue和綁定來實現該需求。
3、解決方案
廢話不多說,直接上代碼
FanoutRabbitConfig.java
import com.wzl.rabbitmqdemo.receiver.MyMessageListener;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.UUID;
/**
* @author administrator
* @date 2020-05-06 20:24
*/
@Configuration
public class FanoutRabbitConfig {
public static final String MY_FANOUTEXCHANGE_NAME = "myFanoutExchange";
//用UUID來生成一個queue名稱,也可以用服務器IP端口作爲queue名稱
public static final String MY_QUEUE_NAME = UUID.randomUUID().toString();
@Autowired
RabbitTemplate rabbitTemplate;
//創建動態queue
@Bean
public Queue myQueue2() {
return new Queue(MY_QUEUE_NAME, true, true, true);
}
//創建Exchange
@Bean
public FanoutExchange fanoutExchange2() {
return new FanoutExchange(MY_FANOUTEXCHANGE_NAME, true, true);
}
//綁定當前queue到Exchange
@Bean
public Binding bindingExchangeMyQueue2() {
return BindingBuilder.bind(myQueue2()).to(fanoutExchange2());
}
//設置消息處理
@Bean
public SimpleMessageListenerContainer mqMessageContainer(MyMessageListener myMessageListener) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(rabbitTemplate.getConnectionFactory());
container.setQueueNames(MY_QUEUE_NAME);
container.setExposeListenerChannel(true);
container.setPrefetchCount(1);//設置每個消費者獲取的最大的消息數量
container.setConcurrentConsumers(1);//消費者個數
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);//設置確認模式爲手工確認
container.setMessageListener(myMessageListener);//消息處理類
return container;
}
}
MyMessageListener.java
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Service;
/**
* @author administrator
* @date 2020-05-06 22:59
*/
@Service
public class MyMessageListener implements ChannelAwareMessageListener {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println("消費消息:" + new String(message.getBody()));
//確認消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}