1 ActiveMQ介紹
1.1 什麼是ActiveMQ
ActiveMQ是Apache出品的一個消息中間件,常用的消息對列主要有ActiveMQ,RabbitMQ,Kafka等,隊列的主要作用是消除高併發訪問高峯,加快網站的響應速度。在實際應用中常用的使用場景。異步處理,應用解耦,流量削鋒和消息通訊四個場景
1.2 JMS介紹
JMS的全稱是Java Message Service,即Java消息服務。用於在兩個應用程序之間,或分佈式系統中發送消息,進行異步通信。
它主要用於在生產者和消費者之間進行消息傳遞,生產者負責產生消息,而消費者負責接收消息。把它應用到實際的業務需求中的話我們可以在特定的時候利用生產者生成一消息,並進行發送,對應的消費者在接收到對應的消息後去完成對應的業務邏輯。
對於消息的傳遞有兩種類型:
-
一種是點對點的,即一個生產者和一個消費者一一對應;
- 一個生產者向一個特定的隊列發佈消息,一個消費者從該隊列中讀取消息。這裏,生產者知道消費者的隊列,並直接將消息發送到消費者的隊列。這種模式被概括爲:只有一個消費者將獲得消息。生產者不需要在接收者消費該消息期間處於運行狀態,接收者也同樣不需要在消息發送時處於運行狀態。每一個成功處理的消息都由接收者簽收。
-
另一種是發佈/訂閱模式,即一個生產者產生消息並進行發送後,可以由多個消費者進行接收。
- 支持向一個特定的消息主題發佈消息。0或多個訂閱者可能對接收來自特定消息主題的消息感興趣。這種模式被概括爲:多個消費者可以獲得消息.在發佈者和訂閱者之間存在時間依賴性。發佈者需要建立一個訂閱(subscription),以便客戶能夠購訂閱。訂閱者必須保持持續的活動狀態以接收消息。
2 ActiveMQ的安裝
2.1 下載
進入http://activemq.apache.org/下載ActiveMQ
2.2 安裝
安裝步驟:
第一步:安裝jdk,需要jdk1.7以上版本
第二步:解壓縮activeMQ的壓縮包。
第三步:進入bin目錄。
啓動:[root@localhost bin]# ./activemq start
停止:[root@localhost bin]# ./activemq stop
注意:如果ActiveMQ整合spring使用不要使用activemq-all-5.12.0.jar包。建議使用5.11.2
第四步:訪問後臺管理。
用戶名:admin
密碼:admin
3 常用對象介紹
- ConnectionFactory 接口(連接工廠)
用戶用來創建到JMS提供者的連接的被管對象。JMS客戶通過可移植的接口訪問連接,這樣當下層的實現改變時,代碼不需要進行修改。管理員在JNDI名字空間中配置連接工廠,這樣,JMS客戶才能夠查找到它們。根據消息類型的不同,用戶將使用隊列連接工廠,或者主題連接工廠。
- Connection 接口(連接)
連接代表了應用程序和消息服務器之間的通信鏈路。在獲得了連接工廠後,就可以創建一個與JMS提供者的連接。根據不同的連接類型,連接允許用戶創建會話,以發送和接收隊列和主題到目標。
- Destination 接口(目標)
目標是一個包裝了消息目標標識符的被管對象,消息目標是指消息發佈和接收的地點,或者是隊列,或者是主題。JMS管理員創建這些對象,然後用戶通過JNDI發現它們。和連接工廠一樣,管理員可以創建兩種類型的目標,點對點模型的隊列,以及發佈者/訂閱者模型的主題。
- MessageConsumer 接口(消息消費者)
由會話創建的對象,用於接收發送到目標的消息。消費者可以同步地(阻塞模式),或異步(非阻塞)接收隊列和主題類型的消息。
- MessageProducer 接口(消息生產者)
由會話創建的對象,用於發送消息到目標。用戶可以創建某個目標的發送者,也可以創建一個通用的發送者,在發送消息時指定目標。
- Message 接口(消息)
是在消費者和生產者之間傳送的對象,也就是說從一個應用程序創送到另一個應用程序。一個消息有三個主要部分:
- Session 接口(會話)
表示一個單線程的上下文,用於發送和接收消息。由於會話是單線程的,所以消息是連續的,就是說消息是按照發送的順序一個一個接收的。會話的好處是它支持事務。如果用戶選擇了事務支持,會話上下文將保存一組消息,直到事務被提交才發送這些消息。在提交事務之前,用戶可以使用回滾操作取消這些消息。一個會話允許用戶創建消息生產者來發送消息,創建消息消費者來接收消息。
4 消息的使用
- 添加依賴
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.11.2</version>
</dependency>
1:queue
- 創建生產者
@Test
public void testQueueProducer() throws Exception {
// 第一步:創建ConnectionFactory對象,需要指定服務端ip及端口號。
//brokerURL服務器的ip及端口號
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.168:61616");
// 第二步:使用ConnectionFactory對象創建一個Connection對象。
Connection connection = connectionFactory.createConnection();
// 第三步:開啓連接,調用Connection對象的start方法。
connection.start();
// 第四步:使用Connection對象創建一個Session對象。
//第一個參數:是否開啓事務。true:開啓事務,第二個參數忽略。
//第二個參數:當第一個參數爲false時,纔有意義。消息的應答模式。1、自動應答2、手動應答。一般是自動應答。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 第五步:使用Session對象創建一個Destination對象(topic、queue),此處創建一個Queue對象。
//參數:隊列的名稱。
Queue queue = session.createQueue("test-queue");
// 第六步:使用Session對象創建一個Producer對象。
MessageProducer producer = session.createProducer(queue);
// 第七步:創建一個Message對象,創建一個TextMessage對象。
/*TextMessage message = new ActiveMQTextMessage();
message.setText("hello activeMq,this is my first test.");*/
TextMessage textMessage = session.createTextMessage("hello activeMq,this is my first test.");
// 第八步:使用Producer對象發送消息。
producer.send(textMessage);
// 第九步:關閉資源。
producer.close();
session.close();
connection.close();
}
- 創建消費者
publicclass QueueConsumer {
public static void main(String[]args) {
//創建一連接工廠
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.168:61616");
try {
//創建一個連接
Connection connection = connectionFactory.createConnection();
//打開連接
connection.start();
//創建一個回話
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//創建一個目的地Destination
Queue queue = session.createQueue("test-queue");
//創建一個消費者
MessageConsumer consumer = session.createConsumer(queue);
while(true) {
//設置接收者接收消息的時間,爲了便於測試,這裏定爲100s
Messagemessage =consumer.receive(100000);
if (message !=null) {
System.out.println(message);
}else {
//超時結束
break;
}
}
consumer.close();
session.close();
connection.close();
} catch (Exceptione) {
e.printStackTrace();
}
}
}
2:Topic
- 創建生產者
@Test
public void testTopicProducer() throws Exception {
// 第一步:創建ConnectionFactory對象,需要指定服務端ip及端口號。
// brokerURL服務器的ip及端口號
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.168:61616");
// 第二步:使用ConnectionFactory對象創建一個Connection對象。
Connection connection = connectionFactory.createConnection();
// 第三步:開啓連接,調用Connection對象的start方法。
connection.start();
// 第四步:使用Connection對象創建一個Session對象。
// 第一個參數:是否開啓事務。true:開啓事務,第二個參數忽略。
// 第二個參數:當第一個參數爲false時,纔有意義。消息的應答模式。1、自動應答2、手動應答。一般是自動應答。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 第五步:使用Session對象創建一個Destination對象(topic、queue),此處創建一個topic對象。
// 參數:話題的名稱。
Topic topic = session.createTopic("test-topic");
// 第六步:使用Session對象創建一個Producer對象。
MessageProducer producer = session.createProducer(topic);
// 第七步:創建一個Message對象,創建一個TextMessage對象。
/*
* TextMessage message = new ActiveMQTextMessage(); message.setText(
* "hello activeMq,this is my first test.");
*/
TextMessage textMessage = session.createTextMessage("hello activeMq,this is my topic test");
// 第八步:使用Producer對象發送消息。
producer.send(textMessage);
// 第九步:關閉資源。
producer.close();
session.close();
connection.close();
}
- 創建消費者
@Test
public void testTopicConsumer() throws Exception {
// 第一步:創建一個ConnectionFactory對象。
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.25.168:61616");
// 第二步:從ConnectionFactory對象中獲得一個Connection對象。
Connection connection = connectionFactory.createConnection();
// 第三步:開啓連接。調用Connection對象的start方法。
connection.start();
// 第四步:使用Connection對象創建一個Session對象。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 第五步:使用Session對象創建一個Destination對象。和發送端保持一致topic,並且話題的名稱一致。
Topic topic = session.createTopic("test-topic");
// 第六步:使用Session對象創建一個Consumer對象。
MessageConsumer consumer = session.createConsumer(topic);
// 第七步:接收消息。
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
String text = null;
// 取消息的內容
text = textMessage.getText();
// 第八步:打印消息。
System.out.println(text);
} catch (JMSException e) {
e.printStackTrace();
}
}
});
System.out.println("topic的消費端03。。。。。");
// 等待鍵盤輸入
System.in.read();
// 第九步:關閉資源
consumer.close();
session.close();
connection.close();
}
5 ActiveMQ整合Spring
- 引用相關的jar包。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
-
配置Activemq整合spring。配置ConnectionFactory
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"> <!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 --> <bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="tcp://192.168.25.168:61616" /> </bean> <!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory --> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory --> <property name="targetConnectionFactory" ref="targetConnectionFactory" /> </bean> </beans>
-
配置生產者
使用JMSTemplate對象。發送消息。
- 在spring容器中配置Destination。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.168:61616" />
</bean>
<!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
<!-- 配置生產者 -->
<!-- Spring提供的JMS工具類,它可以進行消息發送、接收等 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<property name="connectionFactory" ref="connectionFactory" />
</bean>
<!--這個是隊列目的地,點對點的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--這個是主題目的地,一對多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
</beans>
- 代碼測試
- 生產者
@Test
public void testQueueProducer() throws Exception {
// 第一步:初始化一個spring容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-activemq.xml");
// 第二步:從容器中獲得JMSTemplate對象。
JmsTemplate jmsTemplate = applicationContext.getBean(JmsTemplate.class);
// 第三步:從容器中獲得一個Destination對象
Queue queue = (Queue) applicationContext.getBean("queueDestination");
// 第四步:使用JMSTemplate對象發送消息,需要知道Destination
jmsTemplate.send(queue, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
TextMessage textMessage = session.createTextMessage("spring activemq test");
return textMessage;
}
});
}
-----消費者
public class MyMessageListener implements MessageListener {
@Override
public void onMessage(Message message) {
try {
TextMessage textMessage = (TextMessage) message;
//取消息內容
String text = textMessage.getText();
System.out.println(text);
} catch (JMSException e) {
e.printStackTrace();
}
- 配置spring和Activemq整合
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供 -->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.168:61616" />
</bean>
<!-- Spring用於管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory"
class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目標ConnectionFactory對應真實的可以產生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory" />
</bean>
<!--這個是隊列目的地,點對點的 -->
<bean id="queueDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg>
<value>spring-queue</value>
</constructor-arg>
</bean>
<!--這個是主題目的地,一對多的 -->
<bean id="topicDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic" />
</bean>
<!-- 接收消息 -->
<!-- 配置監聽器 -->
<bean id="myMessageListener" class="com.taotao.search.listener.MyMessageListener" />
<!-- 消息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="queueDestination" />
<property name="messageListener" ref="myMessageListener" />
</bean>
</beans>
Kafka
Kafka是一個分佈式流處理平臺有四個核心API
- Producer API —Producer 消息的生產者
- Consumer API ---- —Producer 消息的消費者
- Stream API
- Connector API
Consumer Group
包含一個或者多個Consumer,同一個消息只會被同一個消費組中一個消費者實例處理
不同的消費組可以同時消費同一條數據
Kafka架構圖
-
topic: 主題表示某一種類別的消息(訂閱的一個服務)Kafka流中記錄都是以Topic作爲一個分類去管理。
partition: 分區, 一種類別的消息會根據自定義的分片數量進行hash分片,沒一個分片中會按照時間戳的順序維持 一個隊列。
-
Record: Kafka流中的一則消息一般包含key、value、time stamp
Kafka是通過Topic對Record做分類管理,通常一個Topic會有0~n個訂閱者訂閱。一旦在訂閱的Topic下產生新的Record,所有的訂閱者都將消費該消息。對於每一個Topic底層Kafka集羣都會以分區的形式存儲
Kafka將按照’log.retention.hours’配置保留所有發佈過來的Record無論該Record是否被消費。Record記錄在規定的時間內 ”log.retention.hours”配置時間(默認1周168小時)是可以被消費者消費的,一旦操過規定時間內沒有消費者消費Kafka中的消息,該消息會被系統自動清除。由於kafka的分佈式磁盤存儲,將數據長時間保留在磁盤中是沒有太大問題的。
事實上kafka會記錄每一個消費者消費的消息的offset偏移量,該偏移量是由客戶端所控制的。正常情況下Consumer消費一則消息後對應該消費的者的偏移量會自動加1,但事實上消費者可以自由的控制offset偏移量來實現消息的重複消費,各個消費者之間相互不影響。
所有消息在Kafka集羣中按照分區存儲目的是爲了針對海量數據的存儲,一個Topic表示用戶所關心的一類消息,Topic可以有很多的分區,因此無需擔心海量日誌的存儲,其次每個分區運行在獨立的機器上(broker),因此也會從某種意義上提升系統的並行度。
-
**leader:**Leader負責分區所有讀取和寫入的節點。每個節點隨機選擇的分區中的領導者。
Kafka消息的日誌數據是通過分區的形式分佈在各個server之上,每個服務器負責接收和處理用戶請求。每一個分區都會在集羣節點上設置副本,每個副本都有一個Leader和0~n個follower。Leader負責接收讀寫請求,所有的Follower負責備份主機的數據。如果leader宕機了,其中的Follower會自動的做選舉,選舉出Leader。每一臺服務器在集羣中都會扮演着其中某一個Partition數據的Leader也會扮演着其他Partition數據的Follower的角色。
生產者
消息的生產者負責生產消息到他們選擇的Topic中去,由生產者決定消息存儲到哪一個Topic中的哪一個Partition當中,可以在生產者那邊定義一個負載均衡用於實現消息發送的負載均衡策略,如何實現負載均衡後續章節再進行討論。
消費者
消費者是通過group的概念對自己做標記。因爲不同的Group消費者之間相互獨立。如果隸屬於一個Group中有多個消費者,則消息會在多個消費者之間負載均衡的形式被髮送給組內的消費者;如果不是在一個組內的消費者之間是廣播形式發送。
- Consumer Group 消費組
同一組內的消費者之間是通過對分區日誌數據做負載均衡,kafka通過均分原則(“fair share”)將分區的數據分配給每一組內的消費者(一般消費者的數目不應該操過分區的數目)。如果一個組內的有消費者退出了,kafka會自動的原本屬於該消費者的分區在動態分分配給組內其他消費者。