一、前言
MQ全稱(chēng)為Message Queue,即消息隊(duì)列,它是一種應(yīng)用程序之間的通信方法,消息隊(duì)列在分布式系統(tǒng)開(kāi)發(fā)中應(yīng)用非常廣泛。開(kāi)發(fā)中消息隊(duì)列通常有如下應(yīng)用場(chǎng)景:
1、任務(wù)異步處理。將不需要同步處理的并且耗時(shí)長(zhǎng)的操作由消息隊(duì)列通知消息接收方進(jìn)行異步處理。提高了應(yīng)用程序的響應(yīng)時(shí)間。
2、應(yīng)用程序解耦合MQ相當(dāng)于一個(gè)中介,生產(chǎn)方通過(guò)MQ與消費(fèi)方交互,它將應(yīng)用程序進(jìn)行解耦合。市場(chǎng)上還有哪些消息隊(duì)列?ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis。我們主要介紹主流的消息中間件,了解每個(gè)MQ的優(yōu)缺點(diǎn),能知曉什么樣的場(chǎng)景下選用合適的MQ。
二、ActiveMQ
2.1介紹
ActiveMQ 是完全基于 JMS 規(guī)范實(shí)現(xiàn)的一個(gè)消息中間件產(chǎn)品。 是 Apache 開(kāi)源基金會(huì)研發(fā)的消息中間件。ActiveMQ主要應(yīng)用在分布式系統(tǒng)架構(gòu)中,幫助構(gòu)建高可用、 高性能、可伸縮的企業(yè)級(jí)面向消息服務(wù)的系統(tǒng)。
2.2什么是JMS
Java 消息服務(wù)(Java Message Service)是 java 平臺(tái)中關(guān)于面向消息中間件的 API,用于在兩個(gè)應(yīng)用程序之間,或者分布式系統(tǒng)中發(fā)送消息,進(jìn)行異步通信。JMS 是一個(gè)與具體平臺(tái)無(wú)關(guān)的 API ,絕大多數(shù) MOM(Message Oriented Middleware)(面向消息中間件)提供商都對(duì) JMS 提供了支持。例如ActiveMQ就是其中一個(gè)實(shí)現(xiàn)。
2.3什么是MOOM
MOM 是面向消息的中間件,使用消息傳送提供者來(lái)協(xié)調(diào)消息傳送操作。MOM 需要提供 API 和管理工具。客戶(hù)端使用 api 調(diào)用,把消息發(fā)送到由提供者管理的目的地。在發(fā)送消息之后,客戶(hù)端會(huì)繼續(xù)執(zhí)行其他工作,并且在接收方收到這個(gè)消息確認(rèn)之前,提供者一直保留該消息。
2.4 JMS 規(guī)范
我們已經(jīng)知道了 JMS 規(guī)范的目的是為了使得 Java 應(yīng)用程序能夠訪問(wèn)現(xiàn)有 MOM (消息中間件)系統(tǒng),形成一套統(tǒng)一的標(biāo)準(zhǔn)規(guī)范,解決不同消息中間件之間的協(xié)作問(wèn)題。在創(chuàng)建 JMS 規(guī)范時(shí),設(shè)計(jì)者希望能夠結(jié)合現(xiàn)有的消息傳送的精髓,比如說(shuō)
不同的消息傳送模式或域,例如點(diǎn)對(duì)點(diǎn)消息傳送和發(fā)布訂閱消息傳送
提供于接收同步和異步消息的工具
對(duì)可靠消息傳送的支持
常見(jiàn)消息格式,例如流、文本和字節(jié)
2.5JMS對(duì)象模型
1)連接工廠。連接工廠(ConnectionFactory)是由管理員創(chuàng)建,并綁定到JNDI樹(shù)中。客戶(hù)端使用JNDI查找連接工廠,然后利用連接工廠創(chuàng)建一個(gè)JMS連接。
2)JMS連接。JMS連接(Connection)表示JMS客戶(hù)端和服務(wù)器端之間的一個(gè)活動(dòng)的連接,是由客戶(hù)端通過(guò)調(diào)用連接工廠的方法建立的。
3)JMS會(huì)話。JMS會(huì)話(Session)表示JMS客戶(hù)與JMS服務(wù)器之間的會(huì)話狀態(tài)。JMS會(huì)話建立在JMS連接上,表示客戶(hù)與服務(wù)器之間的一個(gè)會(huì)話線程。
4)JMS目的。JMS目的(Destination),又稱(chēng)為消息隊(duì)列,是實(shí)際的消息源。
5)JMS生產(chǎn)者和消費(fèi)者。生產(chǎn)者(Message Producer)和消費(fèi)者(Message Consumer)對(duì)象由Session對(duì)象創(chuàng)建,用于發(fā)送和接收消息。
6)JMS消息通常有兩種類(lèi)型:
① 點(diǎn)對(duì)點(diǎn)(Point-to-Point)。在點(diǎn)對(duì)點(diǎn)的消息系統(tǒng)中,消息分發(fā)給一個(gè)單獨(dú)的使用者。點(diǎn)對(duì)點(diǎn)消息往往與隊(duì)列(javax.jms.Queue)相關(guān)聯(lián)。
② 發(fā)布/訂閱(Publish/Subscribe)。發(fā)布/訂閱消息系統(tǒng)支持一個(gè)事件驅(qū)動(dòng)模型,消息生產(chǎn)者和消費(fèi)者都參與消息的傳遞。生產(chǎn)者發(fā)布事件,而使用者訂閱感興趣的事件,并使用事件。該類(lèi)型消息一般與特定的主題(javax.jms.Topic)關(guān)聯(lián)。
2.6安裝ActiveMQ
windows安裝
下載地址:http://activemq.apache.org/activemq-5150-release.html
下載完成后解壓進(jìn)入bin目錄 運(yùn)行 activemq.bat。
如果你遇到如下問(wèn)題,5672端口被占用
可以去修改activemq的conf目錄下的activemq.xml,把a(bǔ)mqp的端口改為其他的,這里改成了5673
再次啟動(dòng):
訪問(wèn)地址:http://127.0.0.1:8161/admin/進(jìn)入后臺(tái)頁(yè)面 初始賬號(hào)密碼 admin admin
Docker安裝ActiveMQ
docker run -d --name activemq -p 61616:61616 -p 8161:8161 webcenter/activemq
2.7ActiveMQ快速入門(mén)
Springboot集成ActiveMQ
導(dǎo)入依賴(lài)
<dependencies>
<!--Springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
<!--ActiveMq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
<version>1.5.0.RELEASE</version>
</dependency>
<!--消息隊(duì)列連接池-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.15.0</version>
</dependency>
</dependencies>
配置MQ
server:
port: 8080
spring:
activemq:
broker-url: tcp://127.0.0.1:61616
user: admin
password: admin
close-timeout: 15s # 在考慮結(jié)束之前等待的時(shí)間
in-memory: true # 默認(rèn)代理URL是否應(yīng)該在內(nèi)存中。如果指定了顯式代理,則忽略此值。
non-blocking-redelivery: false # 是否在回滾回滾消息之前停止消息傳遞。這意味著當(dāng)啟用此命令時(shí),消息順序不會(huì)被保留。
send-timeout: 0 # 等待消息發(fā)送響應(yīng)的時(shí)間。設(shè)置為0等待永遠(yuǎn)。
queue-name: active.queue
topic-name: active.topic.name.model
# packages:
# trust-all: true #不配置此項(xiàng),會(huì)報(bào)錯(cuò)
pool:
enabled: true
max-connections: 10 #連接池最大連接數(shù)
idle-timeout: 30000 #空閑的連接過(guò)期時(shí)間,默認(rèn)為30秒
# jms:
# pub-sub-domain: true #默認(rèn)情況下activemq提供的是queue模式,若要使用topic模式需要配置下面配置
# 是否信任所有包
#spring.activemq.packages.trust-all=
# 要信任的特定包的逗號(hào)分隔列表(當(dāng)不信任所有包時(shí))
#spring.activemq.packages.trusted=
# 當(dāng)連接請(qǐng)求和池滿(mǎn)時(shí)是否阻塞。設(shè)置false會(huì)拋“JMSException異常”。
#spring.activemq.pool.block-if-full=true
# 如果池仍然滿(mǎn),則在拋出異常前阻塞時(shí)間。
#spring.activemq.pool.block-if-full-timeout=-1ms
# 是否在啟動(dòng)時(shí)創(chuàng)建連接。可以在啟動(dòng)時(shí)用于加熱池。
#spring.activemq.pool.create-connection-on-startup=true
# 是否用Pooledconnectionfactory代替普通的ConnectionFactory。
#spring.activemq.pool.enabled=false
# 連接過(guò)期超時(shí)。
#spring.activemq.pool.expiry-timeout=0ms
# 連接空閑超時(shí)
#spring.activemq.pool.idle-timeout=30s
# 連接池最大連接數(shù)
#spring.activemq.pool.max-connections=1
# 每個(gè)連接的有效會(huì)話的最大數(shù)目。
#spring.activemq.pool.maximum-active-session-per-connection=500
# 當(dāng)有"JMSException"時(shí)嘗試重新連接
#spring.activemq.pool.reconnect-on-exception=true
# 在空閑連接清除線程之間運(yùn)行的時(shí)間。當(dāng)為負(fù)數(shù)時(shí),沒(méi)有空閑連接驅(qū)逐線程運(yùn)行。
#spring.activemq.pool.time-between-expiration-check=-1ms
# 是否只使用一個(gè)MessageProducer
#spring.activemq.pool.use-anonymous-producers=true
編寫(xiě)配置類(lèi)
/**
* @author 原
* @date 2020/12/16
* @since 1.0
**/
@Configuration
public class BeanConfig {
@Value("${spring.activemq.broker-url}")
private String brokerUrl;
@Value("${spring.activemq.user}")
private String username;
@Value("${spring.activemq.topic-name}")
private String password;
@Value("${spring.activemq.queue-name}")
private String queueName;
@Value("${spring.activemq.topic-name}")
private String topicName;
@Bean(name = "queue")
public Queue queue() {
return new ActiveMQQueue(queueName);
}
@Bean(name = "topic")
public Topic topic() {
return new ActiveMQTopic(topicName);
}
@Bean
public ConnectionFactory connectionFactory(){
return new ActiveMQConnectionFactory(username, password, brokerUrl);
}
@Bean
public JmsMessagingTemplate jmsMessageTemplate(){
return new JmsMessagingTemplate(connectionFactory());
}
/**
* 在Queue模式中,對(duì)消息的監(jiān)聽(tīng)需要對(duì)containerFactory進(jìn)行配置
* @param connectionFactory
* @return
*/
@Bean("queueListener")
public JmsListenerContainerFactory<?> queueJmsListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(false);
return factory;
}
/**
* 在Topic模式中,對(duì)消息的監(jiān)聽(tīng)需要對(duì)containerFactory進(jìn)行配置
* @param connectionFactory
* @return
*/
@Bean("topicListener")
public JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
return factory;
}
}
編寫(xiě)啟動(dòng)類(lèi)
/**
* @author 原
* @date 2020/12/8
* @since 1.0
**/
@SpringBootApplication
@EnableJms //開(kāi)啟JMS支持
public class DemoApplication {
@Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
@Autowired
private Queue queue;
@Autowired
private Topic topic;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
/**
* 應(yīng)用啟動(dòng)后,會(huì)執(zhí)行該方法
* 會(huì)分別向queue和topic發(fā)送一條消息
*/
@PostConstruct
public void sendMsg(){
jmsMessagingTemplate.convertAndSend(queue,"queue-test");
jmsMessagingTemplate.convertAndSend(topic,"topic-test");
}
}
查看activemq后臺(tái)
active.queue 為隊(duì)列的名稱(chēng)
Number Of Pending Messages 等待消費(fèi)的消息數(shù)量 3是因?yàn)槲易约喊l(fā)了3次
Messages Enqueued 已經(jīng)進(jìn)入隊(duì)列的消息數(shù)量
因?yàn)闆](méi)有消費(fèi)者,消息一直沒(méi)有被消費(fèi)。下面我們編寫(xiě)消費(fèi)者代碼。
/**
* @author 原
* @date 2020/12/16
* @since 1.0
**/
@Component
public class QueueConsumerListener {
@JmsListener(destination = "${spring.activemq.queue-name}",containerFactory = "queueListener")
public void getQueue(String message){
System.out.println("接受queue:"+message);
}
@JmsListener(destination = "${spring.activemq.topic-name}",containerFactory = "topicListener")
public void getTopic(String message){
System.out.println("接受topic:"+message);
}
}
在后臺(tái)發(fā)送一條消息
控制臺(tái)打印
發(fā)送topic消息
控制臺(tái)打印:
但是發(fā)現(xiàn)一個(gè)問(wèn)題是,之前在沒(méi)有消費(fèi)的時(shí)候,有3條queue和一條topic,但是當(dāng)我啟動(dòng)消費(fèi)者時(shí),queue的3條消息被消費(fèi)了,topic確沒(méi)有。這是因?yàn)椋?
topic模式有普通訂閱和持久化訂閱
普通訂閱:在消費(fèi)者啟動(dòng)之前發(fā)送過(guò)來(lái)的消息,消費(fèi)者啟動(dòng)之后不會(huì)去消費(fèi);
持久化訂閱: 在消費(fèi)者啟動(dòng)之前發(fā)送過(guò)來(lái)的消息,消費(fèi)者啟動(dòng)之后會(huì)去消費(fèi);
2.8ActiveMQ原理分析
消息同步發(fā)送與異步發(fā)送
ActiveMQ支持同步、異步兩種發(fā)送模式將消息發(fā)送到broker上。
同步發(fā)送過(guò)程中,發(fā)送者發(fā)送一條消息會(huì)阻塞直到broker反饋一個(gè)確認(rèn)消息,表示消息已經(jīng)被broker處理。
這個(gè)機(jī)制提供了消息的安全性保障,但是由于是阻塞的操作,會(huì)影響到客戶(hù)端消息發(fā)送的性能異步發(fā)送的過(guò)程中,發(fā)送者不需要等待broker提供反饋,所以性能相對(duì)較高。但是可能會(huì)出現(xiàn)消息丟失的情況。
所以使用異步發(fā)送的前提是在某些情況下允許出現(xiàn)數(shù)據(jù)丟失的情況。
默認(rèn)情況下,非持久化消息是異步發(fā)送的,持久化消息并且是在非事務(wù)模式下是同步發(fā)送的。
但是在開(kāi)啟事務(wù)的情況下,消息都是異步發(fā)送。由于異步發(fā)送的效率會(huì)比同步發(fā)送性能更高。所以在發(fā)送持久化消息的時(shí)候,盡量去開(kāi)啟事務(wù)會(huì)話。
消息發(fā)送原理
ProducerWindowSize的含義:
producer每發(fā)送一個(gè)消息,統(tǒng)計(jì)一下發(fā)送的字節(jié)數(shù),當(dāng)字節(jié)數(shù)達(dá)到ProducerWindowSize值時(shí),需要等待broker的確認(rèn),才能繼續(xù)發(fā)送。
代碼在:
ActiveMQSession的1957行主要用來(lái)約束在異步發(fā)送時(shí)producer端允許積壓的(尚未ACK)的消息的大小,且只對(duì)異步發(fā)送有意義。每次發(fā)送消息之后,都將會(huì)導(dǎo)致memoryUsage大小增加(+message.size),當(dāng)broker返回producerAck時(shí),memoryUsage尺寸減少(producerAck.size,此size表示先前發(fā)送消息的大小)。
可以通過(guò)如下2種方式設(shè)置:
? 在brokerUrl中設(shè)置: "tcp://localhost:61616?jms.producerWindowSize=1048576",這種設(shè)置將會(huì)對(duì)所有的
producer生效。
? 在destinationUri中設(shè)置: "test-queue?producer.windowSize=1048576",此參數(shù)只會(huì)對(duì)使用此Destination實(shí)例
的producer失效,將會(huì)覆蓋brokerUrl中的producerWindowSize值。
注意:此值越大,意味著消耗Client端的內(nèi)存就越大。
源碼分析
ActiveMQMessageProducer.send(...)方法
public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, AsyncCallback onComplete) throws JMSException {
checkClosed();//檢查session連接,若已關(guān)閉直接拋出異常
if (destination == null) {//校驗(yàn)發(fā)送消息的目的地是否為空,也就是必須制定queue或者topic信息
if (info.getDestination() == null) {
throw new UnsupportedOperationException("A destination must be specified.");
}
throw new InvalidDestinationException("Don't understand null destinations");
}
//這里做的是封裝Destination
ActiveMQDestination dest;
if (destination.equals(info.getDestination())) {
dest = (ActiveMQDestination)destination;
} else if (info.getDestination() == null) {
dest = ActiveMQDestination.transform(destination);
} else {
throw new UnsupportedOperationException("This producer can only send messages to: " + this.info.getDestination().getPhysicalName());
}
if (dest == null) {
throw new JMSException("No destination specified");
}
//封裝Message
if (transformer != null) {
Message transformedMessage = transformer.producerTransform(session, this, message);
if (transformedMessage != null) {
message = transformedMessage;
}
}
//如果設(shè)置了producerWindow,則需要校驗(yàn)producerWindow大小
if (producerWindow != null) {
try {
producerWindow.waitForSpace();
} catch (InterruptedException e) {
throw new JMSException("Send aborted due to thread interrupt.");
}
}
//發(fā)送消息
this.session.send(this, dest, message, deliveryMode, priority, timeToLive, producerWindow, sendTimeout, onComplete);
//做統(tǒng)計(jì)的
stats.onMessage();
}
ActiveMQSession的send方法
protected void send(ActiveMQMessageProducer producer, ActiveMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive,
MemoryUsage producerWindow, int sendTimeout, AsyncCallback onComplete) throws JMSException {
//校驗(yàn)連接
checkClosed();
//校驗(yàn)發(fā)送目標(biāo)
if (destination.isTemporary() && connection.isDeleted(destination)) {
throw new InvalidDestinationException("Cannot publish to a deleted Destination: " + destination);
}
//互斥鎖,如果一個(gè)session的多個(gè)producer發(fā)送消息到這里,會(huì)保證消息發(fā)送的有序性
synchronized (sendMutex) {
// tell the Broker we are about to start a new transaction
doStartTransaction();
TransactionId txid = transactionContext.getTransactionId();
long sequenceNumber = producer.getMessageSequence();
//Set the "JMS" header fields on the original message, see 1.1 spec section 3.4.11
message.setJMSDeliveryMode(deliveryMode);//設(shè)置是否持久化
long expiration = 0L;
if (!producer.getDisableMessageTimestamp()) {
long timeStamp = System.currentTimeMillis();
message.setJMSTimestamp(timeStamp);
if (timeToLive > 0) {
expiration = timeToLive + timeStamp;
}
}
message.setJMSExpiration(expiration);//消息過(guò)期時(shí)間
message.setJMSPriority(priority);//消息優(yōu)先級(jí)
message.setJMSRedelivered(false);//是否重復(fù)發(fā)送
// transform to our own message format here 統(tǒng)一封裝
ActiveMQMessage msg = ActiveMQMessageTransformation.transformMessage(message, connection);
msg.setDestination(destination);
//設(shè)置消息ID
msg.setMessageId(new MessageId(producer.getProducerInfo().getProducerId(), sequenceNumber));
// Set the message id.
if (msg != message) {//如果消息是經(jīng)過(guò)轉(zhuǎn)化的,則更新原來(lái)的消息id和目的地
message.setJMSMessageID(msg.getMessageId().toString());
// Make sure the JMS destination is set on the foreign messages too.
message.setJMSDestination(destination);
}
//clear the brokerPath in case we are re-sending this message
msg.setBrokerPath(null);
msg.setTransactionId(txid);
if (connection.isCopyMessageOnSend()) {
msg = (ActiveMQMessage)msg.copy();
}
msg.setConnection(connection);
msg.onSend();//把消息屬性和消息體都設(shè)置為只讀,防止被修改
msg.setProducerId(msg.getMessageId().getProducerId());
if (LOG.isTraceEnabled()) {
LOG.trace(getSessionId() + " sending message: " + msg);
}
//如果onComplete沒(méi)有設(shè)置,且發(fā)送超時(shí)時(shí)間小于0,且消息不需要反饋,且連接器不是同步發(fā)送模式,且消息非持久化或者連接器是異步發(fā)送模式
//或者存在事務(wù)id的情況下,走異步發(fā)送,否則走同步發(fā)送
if (onComplete==null && sendTimeout <= 0 && !msg.isResponseRequired() && !connection.isAlwaysSyncSend() && (!msg.isPersistent() || connection.isUseAsyncSend() || txid != null)) {
this.connection.asyncSendPacket(msg);
if (producerWindow != null) {
// Since we defer lots of the marshaling till we hit the
// wire, this might not
// provide and accurate size. We may change over to doing
// more aggressive marshaling,
// to get more accurate sizes.. this is more important once
// users start using producer window
// flow control.
int size = msg.getSize();//異步發(fā)送的情況下,需要設(shè)置producerWindow的大小
producerWindow.increaseUsage(size);
}
} else {
if (sendTimeout > 0 && onComplete==null) {
this.connection.syncSendPacket(msg,sendTimeout);//帶超時(shí)時(shí)間的同步發(fā)送//帶回調(diào)的同步發(fā)送
}else {
this.connection.syncSendPacket(msg, onComplete);//帶回調(diào)的同步發(fā)送
}
}
}
}
看下異步發(fā)送的代碼ActiveMQConnection. asyncSendPacket()
/**
* send a Packet through the Connection - for internal use only
*
* @param command
* @throws JMSException
*/
public void asyncSendPacket(Command command) throws JMSException {
if (isClosed()) {
throw new ConnectionClosedException();
} else {
doAsyncSendPacket(command);
}
}
private void doAsyncSendPacket(Command command) throws JMSException {
try {
this.transport.oneway(command);
} catch (IOException e) {
throw JMSExceptionSupport.create(e);
}
}
再看看transport是個(gè)什么東西?在哪里實(shí)例化的?按照以前看源碼的慣例來(lái)看,它肯定不是一個(gè)單純的對(duì)象。按照以往我看源碼的經(jīng)驗(yàn)來(lái)看,一定是在創(chuàng)建連接的過(guò)程中初始化的。所以我們定位到代碼
//從connection=connectionFactory.createConnection();這行代碼作為入口,一直跟蹤ActiveMQConnectionFactory. createActiveMQConnection這個(gè)方法中。代碼如下
protected ActiveMQConnection createActiveMQConnection(String userName, String password) throws
JMSException {
if (brokerURL == null) {
throw new ConfigurationException("brokerURL not set.");
}
ActiveMQConnection connection = null;
try {
Transport transport = createTransport();//代碼往下看
connection = createActiveMQConnection(transport, factoryStats);
connection.setUserName(userName);
connection.setPassword(password);
//省略后面的代碼
}
//這個(gè)方法就是實(shí)例化Transport的 1.構(gòu)建Broker的URL 2.根據(jù)這個(gè)URL去創(chuàng)建一個(gè)鏈接TransportFactory.connect 默認(rèn)使用的TCP連接
protected Transport createTransport() throws JMSException {
try {
URI connectBrokerUL = brokerURL;
String scheme = brokerURL.getScheme();
if (scheme == null) {
throw new IOException("Transport not scheme specified: [" + brokerURL + "]");
}
if (scheme.equals("auto")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto", "tcp"));
} else if (scheme.equals("auto+ssl")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+ssl", "ssl"));
} else if (scheme.equals("auto+nio")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio", "nio"));
} else if (scheme.equals("auto+nio+ssl")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio+ssl", "nio+ssl"));
}
return TransportFactory.connect(connectBrokerUL);//里面的代碼繼續(xù)往下看
} catch (Exception e) {
throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
}
}
TransportFactory. findTransportFactory**
1.從TRANSPORT_FACTORYS這個(gè)Map集合中,根據(jù)scheme去獲得一個(gè)TransportFactory指定的實(shí)例對(duì)象
2.如果Map集合中不存在,則通過(guò)TRANSPORT_FACTORY_FINDER去找一個(gè)并且構(gòu)建實(shí)例
? 這個(gè)地方又有點(diǎn)類(lèi)似于我們之前所學(xué)過(guò)的SPI的思想吧?他會(huì)從METAINF/services/org/apache/activemq/transport/ 這個(gè)路徑下,根據(jù)URI組裝的scheme去找到匹配class對(duì)象并且
實(shí)例化,所以根據(jù)tcp為key去對(duì)應(yīng)的路徑下可以找到T cpT ransportFactory
//TransportFactory.connect(connectBrokerUL)
public static Transport connect(URI location) throws Exception {
TransportFactory tf = findTransportFactory(location);
return tf.doConnect(location);
}
//findTransportFactory(location)
public static TransportFactory findTransportFactory(URI location) throws IOException {
String scheme = location.getScheme();
if (scheme == null) {
throw new IOException("Transport not scheme specified: [" + location + "]");
}
TransportFactory tf = TRANSPORT_FACTORYS.get(scheme);
if (tf == null) {
// Try to load if from a META-INF property.
try {
tf = (TransportFactory)TRANSPORT_FACTORY_FINDER.newInstance(scheme);
TRANSPORT_FACTORYS.put(scheme, tf);
} catch (Throwable e) {
throw IOExceptionSupport.create("Transport scheme NOT recognized: [" + scheme + "]", e);
}
}
return tf;
}
調(diào)用TransportFactory.doConnect去構(gòu)建一個(gè)連接
public Transport doConnect(URI location) throws Exception {
try {
Map<String, String> options = new HashMap<String, String>(URISupport.parseParameters(location));
if( !options.containsKey("wireFormat.host") ) {
options.put("wireFormat.host", location.getHost());
}
WireFormat wf = createWireFormat(options);
Transport transport = createTransport(location, wf);
Transport rc = configure(transport, wf, options);
//remove auto
IntrospectionSupport.extractProperties(options, "auto.");
if (!options.isEmpty()) {
throw new IllegalArgumentException("Invalid connect parameters: " + options);
}
return rc;
} catch (URISyntaxException e) {
throw IOExceptionSupport.create(e);
}
}
configure
public Transport configure(Transport transport, WireFormat wf, Map options) throws Exception {
//組裝一個(gè)復(fù)合的transport,這里會(huì)包裝兩層,一個(gè)是IactivityMonitor.另一個(gè)是WireFormatNegotiator
transport = compositeConfigure(transport, wf, options);
transport = new MutexTransport(transport);//再做一層包裝,MutexTransport
transport = new ResponseCorrelator(transport);//包裝ResponseCorrelator
return transport;
}
到目前為止,這個(gè)transport實(shí)際上就是一個(gè)調(diào)用鏈了,他的鏈結(jié)構(gòu)為
ResponseCorrelator(MutexT ransport(WireFormatNegotiator(IactivityMonitor(T cpT ransport()))
每一層包裝表示什么意思呢?
ResponseCorrelator 用于實(shí)現(xiàn)異步請(qǐng)求。
MutexT ransport 實(shí)現(xiàn)寫(xiě)鎖,表示同一時(shí)間只允許發(fā)送一個(gè)請(qǐng)求
WireFormatNegotiator 實(shí)現(xiàn)了客戶(hù)端連接broker的時(shí)候先發(fā)送數(shù)據(jù)解析相關(guān)的協(xié)議信息,比如解析版本號(hào),是否
使用緩存等
InactivityMonitor 用于實(shí)現(xiàn)連接成功成功后的心跳檢查機(jī)制,客戶(hù)端每10s發(fā)送一次心跳信息。服務(wù)端每30s讀取
一次心跳信息。
同步發(fā)送和異步發(fā)送的區(qū)別
public Object request(Object command, int timeout) throws IOException {
FutureResponse response = asyncRequest(command, null);
return response.getResult(timeout); // 從future方法阻塞等待返回
}
持久化消息與非持久化消息的存儲(chǔ)消息
正常情況下,非持久化消息是存儲(chǔ)在內(nèi)存中的,持久化消息是存儲(chǔ)在文件中的。能夠存儲(chǔ)的最大消息數(shù)據(jù)在
${ActiveMQ_HOME}/conf/activemq.xml文件中的systemUsage節(jié)點(diǎn)
SystemUsage配置設(shè)置了一些系統(tǒng)內(nèi)存和硬盤(pán)容量
<systemUsage>
<systemUsage>
<memoryUsage>
//該子標(biāo)記設(shè)置整個(gè)ActiveMQ節(jié)點(diǎn)的“可用內(nèi)存限制”。這個(gè)值不能超過(guò)ActiveMQ本身設(shè)置的最大內(nèi)存大小。其中的
percentOfJvmHeap屬性表示百分比。占用70%的堆內(nèi)存
<memoryUsage percentOfJvmHeap="70" />
</memoryUsage>
<storeUsage>
//該標(biāo)記設(shè)置整個(gè)ActiveMQ節(jié)點(diǎn),用于存儲(chǔ)“持久化消息”的“可用磁盤(pán)空間”。該子標(biāo)記的limit屬性必須要進(jìn)行設(shè)置
<storeUsage limit="100 gb"/>
</storeUsage>
<tempUsage>
//一旦ActiveMQ服務(wù)節(jié)點(diǎn)存儲(chǔ)的消息達(dá)到了memoryUsage的限制,非持久化消息就會(huì)被轉(zhuǎn)儲(chǔ)到 temp store區(qū)域,雖然
我們說(shuō)過(guò)非持久化消息不進(jìn)行持久化存儲(chǔ),但是ActiveMQ為了防止“數(shù)據(jù)洪峰”出現(xiàn)時(shí)非持久化消息大量堆積致使內(nèi)存耗
盡的情況出現(xiàn),還是會(huì)將非持久化消息寫(xiě)入到磁盤(pán)的臨時(shí)區(qū)域——temp store。這個(gè)子標(biāo)記就是為了設(shè)置這個(gè)temp
store區(qū)域的“可用磁盤(pán)空間限制”
<tempUsage limit="50 gb"/>
</tempUsage>
</systemUsage>
</systemUsage>
從上面的配置我們需要get到一個(gè)結(jié)論,當(dāng)非持久化消息堆積到一定程度的時(shí)候,也就是內(nèi)存超過(guò)指定的設(shè)置閥值時(shí),ActiveMQ會(huì)將內(nèi)存中的非持久化消息寫(xiě)入到臨時(shí)文件,以便騰出內(nèi)存。但是它和持久化消息的區(qū)別是,重啟之后,持久化消息會(huì)從文件中恢復(fù),非持久化的臨時(shí)文件會(huì)直接刪除
消息的持久化策略分析
消息持久性對(duì)于可靠消息傳遞來(lái)說(shuō)是一種比較好的方法,即時(shí)發(fā)送者和接受者不是同時(shí)在線或者消息中心在發(fā)送者發(fā)送消息后宕機(jī)了,在消息中心重啟后仍然可以將消息發(fā)送出去。消息持久性的原理很簡(jiǎn)單,就是在發(fā)送消息出去后,消息中心首先將消息存儲(chǔ)在本地文件、內(nèi)存或者遠(yuǎn)程數(shù)據(jù)庫(kù),然后把消息發(fā)送給接受者,發(fā)送成功后再把消息從存儲(chǔ)中刪除,失敗則繼續(xù)嘗試。接下來(lái)我們來(lái)了解一下消息在broker上的持久化存儲(chǔ)實(shí)現(xiàn)方式
持久化存儲(chǔ)支持類(lèi)型
ActiveMQ支持多種不同的持久化方式,主要有以下幾種,不過(guò),無(wú)論使用哪種持久化方式,消息的存儲(chǔ)邏輯都是一致的。
? KahaDB存儲(chǔ)(默認(rèn)存儲(chǔ)方式)
? JDBC存儲(chǔ)
? Memory存儲(chǔ)
? LevelDB存儲(chǔ)
? JDBC With ActiveMQ Journal
KahaDB存儲(chǔ)
KahaDB是目前默認(rèn)的存儲(chǔ)方式,可用于任何場(chǎng)景,提高了性能和恢復(fù)能力。消息存儲(chǔ)使用一個(gè)事務(wù)日志和僅僅用一個(gè)索引文件來(lái)存儲(chǔ)它所有的地址。
KahaDB是一個(gè)專(zhuān)門(mén)針對(duì)消息持久化的解決方案,它對(duì)典型的消息使用模式進(jìn)行了優(yōu)化。在Kaha中,數(shù)據(jù)被追加到data logs中。當(dāng)不再需要log文件中的數(shù)據(jù)的時(shí)候,log文件會(huì)被丟棄。
配置方式
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
KahaDB的存儲(chǔ)原理
在data/kahadb這個(gè)目錄下,會(huì)生成四個(gè)文件
? db.data 它是消息的索引文件,本質(zhì)上是B-Tree(B樹(shù)),使用B-Tree作為索引指向db-.log里面存儲(chǔ)的消息
? db.redo 用來(lái)進(jìn)行消息恢復(fù)
? db-.log 存儲(chǔ)消息內(nèi)容。新的數(shù)據(jù)以APPEND的方式追加到日志文件末尾。屬于順序?qū)懭耄虼讼⒋鎯?chǔ)是比較
快的。默認(rèn)是32M,達(dá)到閥值會(huì)自動(dòng)遞增
? lock文件 鎖,表示當(dāng)前獲得kahadb讀寫(xiě)權(quán)限的broker
JDBC存儲(chǔ)
使用JDBC持久化方式,數(shù)據(jù)庫(kù)會(huì)創(chuàng)建3個(gè)表:activemq_msgs,activemq_acks和activemq_lock。
ACTIVEMQ_MSGS 消息表,queue和topic都存在這個(gè)表中
ACTIVEMQ_ACKS 存儲(chǔ)持久訂閱的信息和最后一個(gè)持久訂閱接收的消息ID
ACTIVEMQ_LOCKS 鎖表,用來(lái)確保某一時(shí)刻,只能有一個(gè)ActiveMQ broker實(shí)例來(lái)訪問(wèn)數(shù)據(jù)庫(kù)
JDBC存儲(chǔ)配置
<persistenceAdapter>
<jdbcPersistenceAdapter dataSource="# MySQL-DS " createTablesOnStartup="true" />
</persistenceAdapter>
dataSource指定持久化數(shù)據(jù)庫(kù)的bean,createT ablesOnStartup是否在啟動(dòng)的時(shí)候創(chuàng)建數(shù)據(jù)表,默認(rèn)值是true,這樣每次啟動(dòng)都會(huì)去創(chuàng)建數(shù)據(jù)表了,一般是第一次啟動(dòng)的時(shí)候設(shè)置為true,之后改成false
Mysql持久化Bean配置
<bean id="Mysql-DS" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.11.156:3306/activemq?
relaxAutoCommit=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
LevelDB存儲(chǔ)
LevelDB持久化性能高于KahaDB,雖然目前默認(rèn)的持久化方式仍然是KahaDB。并且,在ActiveMQ 5.9版本提供
了基于LevelDB和Zookeeper的數(shù)據(jù)復(fù)制方式,用于Master-slave方式的首選數(shù)據(jù)復(fù)制方案。
不過(guò),據(jù)ActiveMQ官網(wǎng)對(duì)LevelDB的表述:LevelDB官方建議使用以及不再支持,推薦使用的是KahaDB
<persistenceAdapter>
<levelDBdirectory="activemq-data"/>
</persistenceAdapter>
Memory 消息存儲(chǔ)
基于內(nèi)存的消息存儲(chǔ),內(nèi)存消息存儲(chǔ)主要是存儲(chǔ)所有的持久化的消息在內(nèi)存中。persistent=”false”,表示不設(shè)置持久化存儲(chǔ),直接存儲(chǔ)到內(nèi)存中
<beans>
<broker brokerName="test-broker" persistent="false"
xmlns="http://activemq.apache.org/schema/core">
<transportConnectors>
<transportConnector uri="tcp://localhost:61635"/>
</transportConnectors> </broker>
</beans>
JDBC Message store with ActiveMQ Journal
這種方式克服了JDBC Store的不足,JDBC每次消息過(guò)來(lái),都需要去寫(xiě)庫(kù)和讀庫(kù)。
ActiveMQ Journal,使用高速緩存寫(xiě)入技術(shù),大大提高了性能。
當(dāng)消費(fèi)者的消費(fèi)速度能夠及時(shí)跟上生產(chǎn)者消息的生產(chǎn)速度時(shí),journal文件能夠大大減少需要寫(xiě)入到DB中的消息。
舉個(gè)例子,生產(chǎn)者生產(chǎn)了1000條消息,這1000條消息會(huì)保存到j(luò)ournal文件,如果消費(fèi)者的消費(fèi)速度很快的情況
下,在journal文件還沒(méi)有同步到DB之前,消費(fèi)者已經(jīng)消費(fèi)了90%的以上的消息,那么這個(gè)時(shí)候只需要同步剩余的
10%的消息到DB。
如果消費(fèi)者的消費(fèi)速度很慢,這個(gè)時(shí)候journal文件可以使消息以批量方式寫(xiě)到DB。
? 將原來(lái)的標(biāo)簽注釋掉
? 添加如下標(biāo)簽
<persistenceFactory>
<journalPersistenceAdapterFactory dataSource="#Mysql-DS" dataDirectory="activemqdata"/>
</persistenceFactory>
? 在服務(wù)端循環(huán)發(fā)送消息。可以看到數(shù)據(jù)是延遲同步到數(shù)據(jù)庫(kù)的
消費(fèi)端消費(fèi)東西的原理
我們知道有兩種方法可以接收消息,一種是使用同步阻塞的MessageConsumer#receive方法。另一種是使用消息監(jiān)聽(tīng)器MessageListener。這里需要注意的是,在同一個(gè)session下,這兩者不能同時(shí)工作,也就是說(shuō)不能針對(duì)不同消息采用不同的接收方式。否則會(huì)拋出異常。
至于為什么這么做,最大的原因還是在事務(wù)性會(huì)話中,兩種消費(fèi)模式的事務(wù)不好管控
消費(fèi)流程圖
ActiveMQMessageConsumer.receive消費(fèi)端同步接收消息的源碼入口
public Message receive() throws JMSException {
checkClosed();
checkMessageListener(); //檢查receive和MessageListener是否同時(shí)配置在當(dāng)前的會(huì)話中,同步消費(fèi)不需要設(shè)置MessageListener 否則會(huì)報(bào)錯(cuò)
sendPullCommand(0); //如果PrefetchSizeSize為0并且unconsumerMessage為空,則發(fā)起pull命令
MessageDispatch md = dequeue(-1); //從unconsumerMessage出隊(duì)列獲取消息
if (md == null) {
return null;
}
beforeMessageIsConsumed(md);
afterMessageIsConsumed(md, false); //發(fā)送ack給到broker
return createActiveMQMessage(md);//獲取消息并返回
}
sendPullCommand
發(fā)送pull命令從broker上獲取消息,前提是prefetchSize=0并且unconsumedMessages為空。
unconsumedMessage表示未消費(fèi)的消息,這里面預(yù)讀取的消息大小為prefetchSize的值
protected void sendPullCommand(long timeout) throws JMSException {
clearDeliveredList();
if (info.getCurrentPrefetchSize() == 0 && unconsumedMessages.isEmpty()) {
MessagePull messagePull = new MessagePull();
messagePull.configure(info);
messagePull.setTimeout(timeout);
session.asyncSendPacket(messagePull); //向服務(wù)端異步發(fā)送messagePull指令
}
}
clearDeliveredList
在上面的sendPullCommand方法中,會(huì)先調(diào)用clearDeliveredList方法,主要用來(lái)清理已經(jīng)分發(fā)的消息鏈表deliveredMessages
deliveredMessages,存儲(chǔ)分發(fā)給消費(fèi)者但還未應(yīng)答的消息鏈表
? 如果session是事務(wù)的,則會(huì)遍歷deliveredMessage中的消息放入到previouslyDeliveredMessage中來(lái)做重發(fā)
? 如果session是非事務(wù)的,根據(jù)ACK的模式來(lái)選擇不同的應(yīng)答操作
// async (on next call) clear or track delivered as they may be flagged as duplicates if they arrive again
private void clearDeliveredList() {
if (clearDeliveredList) {
synchronized (deliveredMessages) {
if (clearDeliveredList) {
if (!deliveredMessages.isEmpty()) {
if (session.isTransacted()) {
if (previouslyDeliveredMessages == null) {
previouslyDeliveredMessages = new PreviouslyDeliveredMap<MessageId, Boolean>(session.getTransactionContext().getTransactionId());
}
for (MessageDispatch delivered : deliveredMessages) {
previouslyDeliveredMessages.put(delivered.getMessage().getMessageId(), false);
}
LOG.debug("{} tracking existing transacted {} delivered list ({}) on transport interrupt",
getConsumerId(), previouslyDeliveredMessages.transactionId, deliveredMessages.size());
} else {
if (session.isClientAcknowledge()) {
LOG.debug("{} rolling back delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
// allow redelivery
if (!this.info.isBrowser()) {
for (MessageDispatch md: deliveredMessages) {
this.session.connection.rollbackDuplicate(this, md.getMessage());
}
}
}
LOG.debug("{} clearing delivered list ({}) on transport interrupt", getConsumerId(), deliveredMessages.size());
deliveredMessages.clear();
pendingAck = null;
}
}
clearDeliveredList = false;
}
}
}
}
dequeue
從unconsumedMessage中取出一個(gè)消息,在創(chuàng)建一個(gè)消費(fèi)者時(shí),就會(huì)為這個(gè)消費(fèi)者創(chuàng)建一個(gè)未消費(fèi)的消息道,
這個(gè)通道分為兩種,一種是簡(jiǎn)單優(yōu)先級(jí)隊(duì)列分發(fā)通道SimplePriorityMessageDispatchChannel ;另一種是先進(jìn)先
出的分發(fā)通道FifoMessageDispatchChannel.
至于為什么要存在這樣一個(gè)消息分發(fā)通道,大家可以想象一下,如果消費(fèi)者每次去消費(fèi)完一個(gè)消息以后再broker拿一個(gè)消息,效率是比較低的。所以通過(guò)這樣的設(shè)計(jì)可以允許session能夠一次性將多條消息分發(fā)給一個(gè)消費(fèi)者。
默認(rèn)情況下對(duì)于queue來(lái)說(shuō),prefetchSize的值是1000
beforeMessageIsConsumed
? 這里面主要是做消息消費(fèi)之前的一些準(zhǔn)備工作,如果ACK類(lèi)型不是DUPS_OK_ACKNOWLEDGE或者隊(duì)列模式(簡(jiǎn)單來(lái)說(shuō)就是除了T opic和DupAck這兩種情況),所有的消息先放到deliveredMessages鏈表的開(kāi)頭。并且如果當(dāng)前是事務(wù)類(lèi)型的會(huì)話,則判斷transactedIndividualAck,如果為true,表示單條消息直接返回ack。
? 否則,調(diào)用ackLater,批量應(yīng)答, client端在消費(fèi)消息后暫且不發(fā)送ACK,而是把它緩存下來(lái)(pendingACK),等到這些消息的條數(shù)達(dá)到一定閥值時(shí),只需要通過(guò)一個(gè)ACK指令把它們?nèi)看_認(rèn);這比對(duì)每條消息都逐個(gè)確認(rèn),在性能上要提高很多
private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException {
md.setDeliverySequenceId(session.getNextDeliveryId());
lastDeliveredSequenceId = md.getMessage().getMessageId().getBrokerSequenceId();
if (!isAutoAcknowledgeBatch()) {
synchronized(deliveredMessages) {
deliveredMessages.addFirst(md);
}
if (session.getTransacted()) {
if (transactedIndividualAck) {
immediateIndividualTransactedAck(md);
} else {
ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
}
}
}
}
afterMessageIsConsumed
這個(gè)方法的主要作用是執(zhí)行應(yīng)答操作,這里面做以下幾個(gè)操作
? 如果消息過(guò)期,則返回消息過(guò)期的ack
? 如果是事務(wù)類(lèi)型的會(huì)話,則不做任何處理
? 如果是AUTOACK或者(DUPS_OK_ACK且是隊(duì)列),并且是優(yōu)化ack操作,則走批量確認(rèn)ack
? 如果是DUPS_OK_ACK,則走ackLater邏輯
? 如果是CLIENT_ACK,則執(zhí)行ackLater
private void afterMessageIsConsumed(MessageDispatch md, boolean messageExpired) throws JMSException {
if (unconsumedMessages.isClosed()) {
return;
}
if (messageExpired) {
acknowledge(md, MessageAck.EXPIRED_ACK_TYPE);
stats.getExpiredMessageCount().increment();
} else {
stats.onMessage();
if (session.getTransacted()) {
// Do nothing.
} else if (isAutoAcknowledgeEach()) {
if (deliveryingAcknowledgements.compareAndSet(false, true)) {
synchronized (deliveredMessages) {
if (!deliveredMessages.isEmpty()) {
if (optimizeAcknowledge) {
ackCounter++;
// AMQ-3956 evaluate both expired and normal msgs as
// otherwise consumer may get stalled
if (ackCounter + deliveredCounter >= (info.getPrefetchSize() * .65) || (optimizeAcknowledgeTimeOut > 0 && System.currentTimeMillis() >= (optimizeAckTimestamp + optimizeAcknowledgeTimeOut))) {
MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack != null) {
deliveredMessages.clear();
ackCounter = 0;
session.sendAck(ack);
optimizeAckTimestamp = System.currentTimeMillis();
}
// AMQ-3956 - as further optimization send
// ack for expired msgs when there are any.
// This resets the deliveredCounter to 0 so that
// we won't sent standard acks with every msg just
// because the deliveredCounter just below
// 0.5 * prefetch as used in ackLater()
if (pendingAck != null && deliveredCounter > 0) {
session.sendAck(pendingAck);
pendingAck = null;
deliveredCounter = 0;
}
}
} else {
MessageAck ack = makeAckForAllDeliveredMessages(MessageAck.STANDARD_ACK_TYPE);
if (ack!=null) {
deliveredMessages.clear();
session.sendAck(ack);
}
}
}
}
deliveryingAcknowledgements.set(false);
}
} else if (isAutoAcknowledgeBatch()) {
ackLater(md, MessageAck.STANDARD_ACK_TYPE);
} else if (session.isClientAcknowledge()||session.isIndividualAcknowledge()) {
boolean messageUnackedByConsumer = false;
synchronized (deliveredMessages) {
messageUnackedByConsumer = deliveredMessages.contains(md);
}
if (messageUnackedByConsumer) {
ackLater(md, MessageAck.DELIVERED_ACK_TYPE);
}
}
else {
throw new IllegalStateException("Invalid session state.");
}
}
}
2.9ActiveMQ的優(yōu)缺點(diǎn)
ActiveMQ 采用消息推送方式,所以最適合的場(chǎng)景是默認(rèn)消息都可在短時(shí)間內(nèi)被消費(fèi)。數(shù)據(jù)量越大,查找和消費(fèi)消息就越慢,消息積壓程度與消息速度成反比。
缺點(diǎn)
1.吞吐量低。由于 ActiveMQ 需要建立索引,導(dǎo)致吞吐量下降。這是無(wú)法克服的缺點(diǎn),只要使用完全符合 JMS 規(guī)范的消息中間件,就要接受這個(gè)級(jí)別的TPS。
2.無(wú)分片功能。這是一個(gè)功能缺失,JMS 并沒(méi)有規(guī)定消息中間件的集群、分片機(jī)制。而由于 ActiveMQ 是偉企業(yè)級(jí)開(kāi)發(fā)設(shè)計(jì)的消息中間件,初衷并不是為了處理海量消息和高并發(fā)請(qǐng)求。如果一臺(tái)服務(wù)器不能承受更多消息,則需要橫向拆分。ActiveMQ 官方不提供分片機(jī)制,需要自己實(shí)現(xiàn)。
適用場(chǎng)景
對(duì) TPS 要求比較低的系統(tǒng),可以使用 ActiveMQ 來(lái)實(shí)現(xiàn),一方面比較簡(jiǎn)單,能夠快速上手開(kāi)發(fā),另一方面可控性也比較好,還有比較好的監(jiān)控機(jī)制和界面
不適用的場(chǎng)景
消息量巨大的場(chǎng)景。ActiveMQ 不支持消息自動(dòng)分片機(jī)制,如果消息量巨大,導(dǎo)致一臺(tái)服務(wù)器不能處理全部消息,就需要自己開(kāi)發(fā)消息分片功能。