第 4-5 課:使⽤ Spring Boot 操作 ActiveMQ

消息隊列中間件是分佈式系統中重要的組件,主要解決應⽤耦合、異步消息、流量削鋒等問題,實現⾼性
能、⾼可⽤、可伸縮和最終⼀致性架構,是⼤型分佈式系統不可缺少的中間件。
 
⽬前在⽣產環境中使⽤較多的消息隊列有 ActiveMQRabbitMQZeroMQKafkaMetaMQRocketMQ
等。
特性
  • 異步性:將耗時的同步操作通過以發送消息的⽅式進⾏了異步化處理,減少了同步等待的時間。
  • 松耦合:消息隊列減少了服務之間的耦合性,不同的服務可以通過消息隊列進⾏通信,⽽不⽤關⼼彼此 的實現細節,只要定義好消息的格式就⾏。
  • 分佈式:通過對消費者的橫向擴展,降低了消息隊列阻塞的⻛險,以及單個消費者產⽣單點故障的可能 性(當然消息隊列本身也可以做成分佈式集羣)。
  • 可靠性:消息隊列⼀般會把接收到的消息存儲到本地硬盤上(當消息被處理完之後,存儲信息根據不同 的消息隊列實現,有可能將其刪除),這樣即使應⽤掛掉或者消息隊列本身掛掉,消息也能夠重新加 載。

JMS 規範

JMS Java 消息服務(Java Message Service)應⽤程序接⼝,是⼀個 Java 平臺中關於⾯向消息中間件
MOM)的 API,⽤於在兩個應⽤程序之間,或分佈式系統中發送消息,進⾏異步通信。Java 消息服務是
⼀個與具體平臺⽆關的 API,絕⼤多數 MOM 提供商都對 JMS 提供⽀持。
 
JMS 的消息機制有 2 種模型,⼀種是 Point to Point,表現爲隊列的形式,發送的消息,只能被⼀個接收者取
⾛;另⼀種是 Topic,可以被多個訂閱者訂閱,類似於羣發。
 
ActiveMQ JMS 的⼀個實現。

ActiveMQ 介紹

ActiveMQ Apache 軟件基⾦下的⼀個開源軟件,它遵循 JMS1.1 規範(Java Message Service),是消息
驅動中間件軟件(MOM)。它爲企業消息傳遞提供⾼可⽤、出⾊性能、可擴展、穩定和安全保障。
ActiveMQ 使⽤ Apache 許可協議,因此,任何⼈都可以使⽤和修改它⽽不必反饋任何改變。
 
ActiveMQ 的⽬標是在儘可能多的平臺和語⾔上提供⼀個標準的,消息驅動的應⽤集成。ActiveMQ 實現 JMS
規範並在此之上提供⼤量額外的特性。ActiveMQ ⽀持隊列和訂閱兩種模式的消息發送。
 
Spring Boot 提供了 ActiveMQ 組件 spring-boot-starter-activemq,⽤來⽀持 ActiveMQ Spring Boot 體系
內使⽤,下⾯我們來詳細瞭解如何使⽤。

添加依賴

主要添加組件:spring-boot-starter-activemq
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

配置⽂件

application.properties 中添加配置。
# 基於內存的 ActiveMQ
spring.activemq.in-memory=true
# 不適應連接池
spring.activemq.pool.enabled=false
# 獨⽴安裝的 ActiveMQ
#spring.activemq.broker-url=tcp://192.168.0.1:61616
#spring.activemq.user=admin
#spring.activemq.password=admin
在使⽤ ActiveMQ 時有兩種使⽤⽅式,⼀種是使⽤獨⽴安裝的 ActiveMQ,在⽣產環境推薦使⽤這種;另⼀種
是使⽤基於內存 ActiveMQ ,在調試階段建議使⽤這種⽅式。

隊列(Queue

隊列發送的消息,只能被⼀個消費者接收。

創建隊列

@Configuration
public class MqConfig {
 @Bean
 public Queue queue() {
 return new ActiveMQQueue("neo.queue");
 }
}
使⽤ @Confifiguration 註解在項⽬啓動時,定義了⼀個隊列 queue 命名爲:neo.queue

消息⽣產者

創建⼀個消息的⽣產者:
@Component
public class Producer{
 @Autowired
 private JmsMessagingTemplate jmsMessagingTemplate;
 @Autowired
 private Queue queue;
 public void sendQueue(String msg) {
 System.out.println("send queue msg :"+msg);
 this.jmsMessagingTemplate.convertAndSend(this.queue, msg);
 }
}
JmsMessagingTemplate Spring 提供發送消息的⼯具類,使⽤ JmsMessagingTemplate 和創建好的
queue 對消息進⾏發送。

消息消費者

@Component
public class Consumer {
 @JmsListener(destination = "neo.queue")
 public void receiveQueue(String text) {
 System.out.println("Consumer queue msg : "+text);
 }
}
使⽤註解 @JmsListener(destination = "neo.queue"),表示此⽅法監控了名爲 neo.queue 的隊列。當隊列
neo.queue 中有消息發送時會觸發此⽅法的執⾏,text 爲消息內容。

測試

創建 SampleActiveMqTests 測試類,注⼊創建好的消息⽣產者。
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleActiveMqTests {
 @Autowired
 private Producer producer;
 @Rule
 public OutputCapture outputCapture = new OutputCapture();
}
OutputCapture Spring Boot 提供的⼀個測試類,它能捕獲 System.out System.err 的輸出,我們可以利
⽤這個特性來判斷程序中的輸出是否執⾏。
 
@Test
public void sendSimpleQueueMessage() throws InterruptedException {
 this.producer.sendQueue("Test queue message");
 Thread.sleep(1000L);
 assertThat(this.outputCapture.toString().contains("Test queue")).isTrue();
}
創建測試⽅式,使⽤ producer 發送消息,爲了保證容器可以接收到消息,讓測試⽅法等待 1 秒,最後使⽤
outputCapture 判斷是否執⾏成功。

測試多消費者

上⾯的案例只是⼀個⽣產者⼀個消費者,我們在模擬⼀個⽣產者和多個消費者隊列的執⾏情況。我們複製上
⾯的消費者 Consumer 重新命名爲 Consumer2,並且將輸出內容加上 2 的關鍵字,如下:
@Component
public class Consumer2 {
 @JmsListener(destination = "neo.queue")
 public void receiveQueue(String text) {
 System.out.println("Consumer2 queue msg : "+text);
 }
}
在剛纔的測試類中添加⼀個 send100QueueMessage() ⽅法,模式發送 100 條消息時,兩個消費者是如何消
費消息的。
 
@Test
public void send100QueueMessage() throws InterruptedException {
 for (int i=0;i<100;i++){
 this.producer.sendQueue("Test queue message"+i);
 }
 Thread.sleep(1000L);
}
控制檯輸出結果:
Consumer queue msg : Test queue message0
Consumer2 queue msg : Test queue message1
Consumer queue msg : Test queue message2
Consumer2 queue msg : Test queue message3
...
根據控制檯輸出的消息可以看出,當有多個消費者監聽⼀個隊列時,消費者會⾃動均衡負載的接收消息,並
且每個消息只能有⼀個消費者所接收。GitChat
 
注意:控制檯輸出 javax.jms.JMSException: peer (vm://localhost#1) stopped. 報錯信息可以忽略,這
Info 級別的錯誤,是 ActiveMQ 的⼀個 bug

⼴播(Topic

⼴播發送的消息,可以被多個消費者接收。

創建 Topic

@Configuration
public class MqConfig {
 @Bean
 public Topic topic() {
 return new ActiveMQTopic("neo.topic");
 }
}
使⽤ @Confifiguration 註解在項⽬啓動時,定義了⼀個⼴播 Topic 命名爲:neo.topic

消息⽣產者

創建⼀個消息的⽣產者:
@Component
public class Producer{
 @Autowired
 private JmsMessagingTemplate jmsMessagingTemplate;
 @Autowired
 private Topic topic;
 public void sendTopic(String msg) {
 System.out.println("send topic msg :"+msg);
 this.jmsMessagingTemplate.convertAndSend(this.topic, msg);
 }
}
和上⾯的⽣產者對⽐只是 convertAndSend() ⽅法傳⼊的第⼀個參數變成了 Topic

消息消費者

@Component
public class Consumer {
 @JmsListener(destination = "neo.topic")
 public void receiveTopic(String text) {
 System.out.println("Consumer topic msg : "+text);
 }
}
消費者也沒有變化,只是監聽的名改爲上⾯的 neo.topic,因爲模擬多個消費者,複製⼀份 Consumer 命名爲
Consumer2,代碼相同在輸出中標明來⾃ Consumer2

測試

創建 SampleActiveMqTests 測試類,注⼊創建好的消息⽣產者。
@Test
public void sendSimpleTopicMessage() throws InterruptedException {
 this.producer.sendTopic("Test Topic message");
 Thread.sleep(1000L);
}
測試⽅法執⾏成功後,會看到控制檯輸出信息,如下:
send topic msg :Test Topic message
Consumer topic msg : Test Topic message
Consumer2 topic msg : Test Topic message
可以看出兩個消費者都收到了發送的消息,從⽽驗證⼴播(Topic)是⼀個發送者多個消費者的模式。
同時⽀持隊列(Queue)和⼴播(Topic
 
Spring Boot 集成 ActiveMQ 的項⽬默認只⽀持隊列或者⼴播中的⼀種,通過配置項 spring.jms.pub-sub
domain 的值來控制,true 爲⼴播模式,false 爲隊列模式,默認情況下⽀持隊列模式。
如果需要在同⼀項⽬中既⽀持隊列模式也⽀持⼴播模式,可以通過 DefaultJmsListenerContainerFactory
建⾃定義的 JmsListenerContainerFactory 實例,之後在 @JmsListener 註解中通過 containerFactory 屬性引
⽤它。
 
分別創建兩個⾃定義的 JmsListenerContainerFactory 實例,通過 pubSubDomain 來控制是⽀持隊列模式還
是⼴播模式。GitChat
 
@Configuration
@EnableJms
public class ActiveMQConfig {
 @Bean("queueListenerFactory")
 public JmsListenerContainerFactory<?> queueListenerFactory(ConnectionFactory c
onnectionFactory) {
 DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContain
erFactory();
 factory.setConnectionFactory(connectionFactory);
 factory.setPubSubDomain(false);
 return factory;
 }
 @Bean("topicListenerFactory")
 public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory c
onnectionFactory) {
 DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContain
erFactory();
 factory.setConnectionFactory(connectionFactory);
 factory.setPubSubDomain(true);
 return factory;
 }
}
然後在消費者接收的⽅法中,指明使⽤ containerFactory 接收消息。
@Component
public class Consumer {
 @JmsListener(destination = "neo.queue", containerFactory = "queueListenerFacto
ry")
 public void receiveQueue(String text) {
 System.out.println("Consumer queue msg : "+text);
 }
 @JmsListener(destination = "neo.topic", containerFactory = "topicListenerFacto
ry")
 public void receiveTopic(String text) {
 System.out.println("Consumer topic msg : "+text);
 }
}
改造完成之後,再次執⾏隊列和⼴播的測試⽅法,就會發現項⽬同時⽀持了兩種類型的消息收發。

總結

消息中間件⼴泛應⽤在⼤型互聯⽹架構中,利⽤消息中間件隊列和⼴播各⾃的特性可以⽀持很多業務,⽐如
羣發發送短信、給單個⽤戶發送郵件等。ActiveMQ 是⼀款⾮常流⾏的消息中間件,它的特點是部署簡單、使
⽤⽅便,⽐較適合中⼩型團隊。Spring Boot 提供了集成 ActiveMQ 對應的組件,在 Spring Boot 中使⽤
ActiveMQ 只需要添加相關注解即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章