activemmq知識梳理學習

一. 初步安裝使用

ActiveMQ 的官網 : http://activemq.apache.org

ActiveMQ
擴展出:
API 接受發送
MQ 的高可用
MQ 的集羣容錯配置
MQ 的持久化
延時發送
簽收機制
Spring/SpringBoot 整合

// MQ 都需要滿足的技術

MQ  :   消息中間件/消息隊列   

爲什麼要使用 MQ ?
解決了耦合調用、異步模型、抵禦洪峯流量,保護了主業務,消峯。

在linux  的opt 目錄下上傳 mq 的壓縮包,(使用vmware-tools 上傳的)
並且將壓縮包放到  /myactivemq 下  

直接進入myactivemq 的 文件下的activemq 下的 bin 目錄,使用 ./activemq  start  命令啓動  

檢查activemq 是否啓動的三種方法:   也是三種查看後臺進程的方法

ps -ef|grep activemq|grep -v grep // grep -v grep 可以不讓顯示grep 本來的信息

netstat -anp|grep 61616 // activemq 的默認後臺端口是61616

lsof -i:61616

讓啓動的日誌信息不在控制檯打印,而放到專門的文件中:

./activemq start > /myactivemq/myrunmq.log

二 . 部署和代碼嘗試

1. 部署在linux 上的acvtiveMQ 要可以通過前臺windows 的頁面訪問,必須把linux 的IP和 windows的 IP 地址配置到同一個網關下 。這種情況一般都是修改 linux 的IP 地址,修改網卡文件對應的IP 地址  
修改linux 的ip 地址:

cd /etc/sysconfig/network-scripts

vi ifcfg-eth0

這是修改之後的網卡文件配置,IP 地址爲:192.168.17.3  (因爲我的windows 的IP 地址爲192.168.17.1,將他們配置在了同一個網關下)



配置成功後 ,可以用 windows ping  linux , linux  ping  windows  ,當全部ping 通後,可以使用圖形化界面訪問activeMQ
// ActiveMQ 的前臺端口爲 8161  , 提供控制檯服務       後臺端口爲61616 ,提供 JMS 服務 

 // 192.168.17.3   爲 linux 的IP 地址, 使用 IP+端口 訪問了ActiveMQ , 登陸之後的樣子如上。(能訪問成功首先得在linux 上啓動activeMQ 的服務),首次登錄的默認賬戶密碼爲    賬號:admin   密碼:admin 
訪問不到的坑: 1   可能是你的linux 和 windows 沒有在一個網關下 
                 2   可能你windows 的防火牆或者 linux 的防火牆沒有關掉(是的,先得關掉防火牆)
                 3   你忘記啓動activemq 的服務了 
                  4  你啓動失敗了,可能是你得java 環境沒配好,必須是jdk 8 或者以上

JMS :  Java  消息中間件的服務接口規範,activemq 之上是 mq  , 而 mq 之上是JMS 定義的消息規範 。 activemq 是mq 技術的一種理論實現(與之相類似的實現還有 Kafka  RabbitMQ  RockitMQ  ),而 JMS 是更上一級的規範。


在點對點的消息傳遞時,目的地稱爲 隊列   queue   
在發佈訂閱消息傳遞中,目的地稱爲 主題   topic 


2. demo 初試   一個簡單的生產者消費者   
生產者:

public class JmsProduce {
// linux 上部署的activemq 的 IP 地址 + activemq 的端口號
public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String QUEUE_NAME = "queue01";

public static void main(String[] args) throws  Exception{

    // 1 按照給定的url創建連接工程,這個構造器採用默認的用戶名密碼
    ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
    // 2 通過連接工廠連接 connection  和 啓動
    javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
    //  啓動
    connection.start();
    // 3 創建回話  session
    // 兩個參數,第一個事務, 第二個簽收
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    // 4 創建目的地 (兩種 : 隊列/主題   這裏用隊列)
    Queue queue = session.createQueue(QUEUE_NAME);
    // 5 創建消息的生產者
    MessageProducer messageProducer = session.createProducer(queue);
    // 6 通過messageProducer 生產 3 條 消息發送到消息隊列中
    for (int i = 1; i < 4 ; i++) {
        // 7  創建字消息
        TextMessage textMessage = session.createTextMessage("msg--" + i);
        // 8  通過messageProducer發佈消息
        messageProducer.send(textMessage);
    }
    // 9 關閉資源
    messageProducer.close();
    session.close();
    connection.close();

    System.out.println("  **** 消息發送到MQ完成 ****");
}

}

以及在頁面上的顯示:

與之相對應的消息消費者(處理消息的系統)代碼及運行
public class JmsConsumer {
public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String QUEUE_NAME = "queue01"; // 1對1 的隊列

public static void main(String[] args) throws Exception{
    // 1 按照給定的url創建連接工程,這個構造器採用默認的用戶名密碼
    ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
    // 2 通過連接工廠連接 connection  和 啓動
    javax.jms.Connection connection = activeMQConnectionFactory.createConnection();
    //  啓動
    connection.start();
    // 3 創建回話  session
    // 兩個參數,第一個事務, 第二個簽收
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    // 4 創建目的地 (兩種 : 隊列/主題   這裏用隊列)
    Queue queue = session.createQueue(QUEUE_NAME);
    // 5 創建消息的消費者
    MessageConsumer messageConsumer = session.createConsumer(queue);
    while(true){
        // 這裏是 TextMessage 是因爲消息發送者是 TextMessage , 接受處理的
        // 也應該是這個類型的消息
        TextMessage message = (TextMessage)messageConsumer.receive();
        if (null != message){
            System.out.println("****消費者的消息:"+message.getText());
        }else {
            break;
        }
    }
    messageConsumer.close();
    session.close();
    connection.close();
}

}

                                                  這個代表有一個消息消費者處理消息,並且處理了三條消息

 // 通過監聽的方式來消費消息

// 通過異步非阻塞的方式消費消息
// 通過messageConsumer 的setMessageListener 註冊一個監聽器,
// 當有消息發送來時,系統自動調用MessageListener 的 onMessage 方法處理消息
messageConsumer.setMessageListener(new MessageListener() { // 可以用監聽器替換之前的同步receive 方法
public void onMessage(Message message) {
if (null != message && message instanceof TextMessage){
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("****消費者的消息:"+textMessage.getText());
}catch (JMSException e) {
e.printStackTrace();
}
}
}
});

這裏的一點經驗: activemq 好像自帶負載均衡,當先啓動兩個隊列(Queue)的消費者時,在啓動生產者發出消息,此時的消息平均的被兩個消費者消費。 並且消費者不會消費已經被消費的消息(即爲已經出隊的消息)
但是當有多個主題(Topic)訂閱者時,發佈者發佈的消息,每個訂閱者都會接收所有的消息。topic 更像是被廣播的消息,但是缺點是不能接受已經發送過的消息。

先要有訂閱者,生產者纔有意義。

三 . JMS
1.JAVAEE 是一套使用Java 進行企業級開發的13 個核心規範工業標準 , 包括:
JDBC 數據庫連接
JNDI Java的命名和目錄接口
EJB
RMI 遠程方法調用
Java IDL 接口定義語言
JSP
Servlet
XML
JMS Java 消息服務
JTA
JTS
JavaMail
JAF

JMS 部件
JMS provider
JMS producer
JMS consumer
JMS message
含義
實現JMS 的消息中間件,也就是MQ服務器
消息生產者,創建和發送消息的客戶端
消息消費者,接收和處理消息的客戶端
JMS 消息,分爲消息頭、消息屬性、消息體
5 個主要的消息頭
消息頭
JMSDestination
JMSDeliveryMode
JMSExpiration
JMSPriority
JMSMessageId
含義
頭在哪兒
是持久還是非持久
過期時間,默認永久
優先級,默認是4 有0~9 ,5-9 是緊急的,0-4 是普通的
唯一的消息ID

消息體;封裝具體的消息數據
5 種消息體格式:
5種消息體
TextMessage
Mapmessage
BytesMessage
StreamMessage
ObjectMessage
含義
普通字符串消息,包含一個String
Map 類型的消息, k-> String v -> Java 基本類型
二進制數組消息,包含一個byte[]
Java 數據流消息,用標準流操作來順序的填充讀取
對象消息,包含一個可序列化的Java 對象
發送和接收的消息類型必須一致

消息屬性:識別、去重、重點標註

  1. 如何保證消息的可靠性
    JMS 可靠性:Persistent 持久性 、 事務 、 Acknowledge 簽收
    2.1 持久化
    // 在隊列爲目的地的時候持久化消息
    messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

// 隊列爲目的地的非持久化消息
messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
持久化的消息,服務器宕機後消息依舊存在,只是沒有入隊,當服務器再次啓動,消息任就會被消費。
但是非持久化的消息,服務器宕機後消息永遠丟失。 而當你沒有註明是否是持久化還是非持久化時,默認是持久化的消息。

對於目的地爲主題(topic)來說,默認就是非持久化的,讓主題的訂閱支持化的意義在於:對於訂閱了公衆號的人來說,當用戶手機關機,在開機後任就可以接受到關注公衆號之前發送的消息。
代碼實現:持久化topic 的消費者
…… // 前面代碼相同,不復制了
Topic topic = session.createTopic(TOPIC_NAME);
TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"remark…");

     // 5 發佈訂閱
    connection.start();

    Message message = topicSubscriber.receive();// 一直等
     while (null != message){
         TextMessage textMessage = (TextMessage)message;
         System.out.println(" 收到的持久化 topic :"+textMessage.getText());
         message = topicSubscriber.receive(3000L);    // 等1秒後meesage 爲空,跳出循環,控制檯關閉
     }

……

持久化生產者
……

    MessageProducer messageProducer = session.createProducer(topic);
    // 6 通過messageProducer 生產 3 條 消息發送到消息隊列中

    // 設置持久化topic 在啓動
    messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT); 
    connection.start();
    for (int i = 1; i < 4 ; i++) {
        // 7  創建字消息
        TextMessage textMessage = session.createTextMessage("topic_name--" + i);
        // 8  通過messageProducer發佈消息
        messageProducer.send(textMessage);

        MapMessage mapMessage = session.createMapMessage();
        //    mapMessage.setString("k1","v1");
        //     messageProducer.send(mapMessage);
    }
    // 9 關閉資源
  …… 

當生產者啓動後:

消息被消費,並且: (因爲我在receive方法中設置瞭如果接收到消息後3秒還沒有消息就離線,也也可以設置永久存活)

2.2 事務
createSession的第一個參數爲true 爲開啓事務,開啓事務之後必須在將消息提交,纔可以在隊列中看到消息
Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
提交:
session.commit();

事務開啓的意義在於,如果對於多條必須同批次傳輸的消息,可以使用事務,如果一條傳輸失敗,可以將事務回滾,再次傳輸,保證數據的完整性。
對於消息消費者來說,開啓事務的話,可以避免消息被多次消費,以及後臺和服務器數據的不一致性。舉個栗子:
如果消息消費的 createSession 設置爲 ture ,但是沒有 commit ,此時就會造成非常嚴重的後果,那就是在後臺看來消息已經被消費,但是對於服務器來說並沒有接收到消息被消費,此時就有可能被多次消費。

2.3 Acknowledge 簽收 (俗稱ack)
非事務 :
Session.AUTO_ACKNOWLEDGE 自動簽收,默認

Session.CLIENT_ACKNOWLEDGE 手動簽收
手動簽收需要acknowledge
textMessage.aacknowledge();

而對於開啓事務時,設置手動簽收和自動簽收沒有多大的意義,都默認自動簽收,也就是說事務的優先級更高一些。
Session session = connection.createSession(true,Session.AUTO_ACKNOWLEDGE);
//Session session = connection.createSession(true,Session.CLIENT_ACKNOWLEDGE); // 也是自動簽收

    ……

session.commit();
但是開啓事務沒有commit 任就會重複消費

小知識: broker
broker 就是實現了用代碼形式啓動 ActiveMQ 將 MQ 內嵌到 Java 代碼中,可以隨時啓動,節省資源,提高了可靠性。
就是將 MQ 服務器作爲了 Java 對象

使用多個配置文件啓動 activemq
cp activemq.xml activemq02.xml

// 以active02 啓動mq 服務器
./activemq start xbean:file:/myactivemq/apache-activemq-5.15.9/conf/activemq02.xml

把小型 activemq 服務器嵌入到 java 代碼: 不在使用linux 的服務器
需要的包:
com.fasterxml.jackson.core jackson-databind 2.9.5
代碼實現:
public class Embebroker {
public static void main(String[] args) throws Exception {
// broker 服務
BrokerService brokerService = new BrokerService();
// 把小型 activemq 服務器嵌入到 java 代碼
brokerService.setUseJmx(true);
// 原本的是 192.…… 是linux 上的服務器,而這裏是本地windows 的小型mq 服務器
brokerService.addConnector("tcp://localhost:61616");
brokerService.start();
}
}

四. Spring / SpringBoot 整合 ActiveMQ

  1. 對 Spring 的整合
    1.1 所需jar 包

org.springframework spring-jms 4.3.23.RELEASE
org.apache.activemq activemq-pool 5.15.9

org.springframework spring-core 4.3.23.RELEASE
org.springframework spring-context 4.3.23.RELEASE
1.2 寫xml 文件 (applicationContext.xml)

<context:commponent-scan base-package="com.at.activemq"/>
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory"  destroy-method="stop">
    <property name="connectionFactory">
        <bean class="org.apache.activemq.ActiveMQConnectionFactory">
            <property name="brokerURL" value="tcp://192.168.17.3:61616"></property>
        </bean>
    </property>
    <property name="maxConnections" value="100"></property>
</bean>

<!-- 隊列目的地 -->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
    <constructor-arg index="0" value="spring-active-queue"></constructor-arg>
</bean>


<!--  jms 的工具類 -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
    <property name="connectionFactory" ref="jmsFactory"/>
    <property name="defaultDestination" ref="destinationQueue"/>
    <property name="messageConverter">
        <bean class="org.springframework.jms.support.converter.SimpleMessageConverter"/>
    </property>
</bean>

1.3 編寫代碼:
@Service
public class SpringMQ_producer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
SpringMQ_producer producer = (SpringMQ_producer) ctx.getBean("springMQ_Producer");
producer.jmsTemplate.send((session) -> {
TextMessage textMessage = session.createTextMessage("spring 和 activemq 的整合");
return textMessage;
});
System.out.println(" *** send task over ***");
}
}

@Service
public class Spring_MQConsummer {
@Autowired
private JmsTemplate jmsTemplate;
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
Spring_MQConsummer sm = (Spring_MQConsummer)ac.getBean("spring_MQConsummer");

    String s = (String) sm.jmsTemplate.receiveAndConvert();
    System.out.println(" *** 消費者消息"+s);
}

}

並且可以在spring 中設置監聽器,不用啓動消費者,就可以自動監聽到消息,並處理

  1. Spring Boot 整合 ActiveMQ
    2.1 建立boot 項目,配置 pom.xml 配置 application.yml 配置 bean
    2.2 編寫生產者 編寫啓動類 測試類

    按鍵觸發消息和定時發送消息的業務代碼:
    // 調用一次一個信息發出
    public void produceMessage(){
    jmsMessagingTemplate.convertAndSend(queue,"****"+ UUID.randomUUID().toString().substring(0,6));
    }

// 帶定時投遞的業務方法
@Scheduled(fixedDelay = 3000) // 每3秒自動調用
public void produceMessageScheduled(){
jmsMessagingTemplate.convertAndSend(queue,"** scheduled **"+ UUID.randomUUID().toString().substring(0,6));
System.out.println(" produceMessage send ok ");
}

對於消息消費者,在以前使用單獨的監聽器類,編寫監聽器代碼,但是在spring boot 中,使用註解 JmsListener 即可:
@Component
public class Queue_consummer {

@JmsListener(destination = "${myqueue}")     // 註解監聽  
public void receive(TextMessage textMessage) throws  Exception{
    System.out.println(" ***  消費者收到消息  ***"+textMessage.getText());
}

}

這些是之前(隊列)消息發送者發送的消息

2.3 編寫消費者項目
2.4 編寫主題的消息生產者和消費者項目,運行demo
代碼地址:https://github.com/elstic/ActiveMQ

五. 協議

  1. ActiveMQ 支持的協議有 TCP 、 UDP、NIO、SSL、HTTP(S) 、VM
    這是activemq 的activemq.xml 中配置文件設置協議的地方





默認是使用 openwire 也就是 tcp 連接
默認的Broker 配置,TCP 的Client 監聽端口 61616 ,在網絡上傳輸數據,必須序列化數據,消息是通過一個 write protocol 來序列化爲字節流。默認情況 ActiveMQ 會把 wire protocol 叫做 Open Wire ,它的目的是促使網絡上的效率和數據快速交互 。

使用tcp 的一些優化方案:
tcp://hostname:port?key=value
它的參數詳情參考:http://activemq.apache.org/tcp-transport-reference

  1. NIO 協議爲activeMQ 提供更好的性能
    適合NIO 使用的場景:
    1 當有大量的Client 連接到Broker 上 , 使用NIO 比使用 tcp 需要更少的線程數量,所以使用 NIO
    2 可能對於 Broker 有一個很遲鈍的網絡傳輸, NIO 的性能高於 TCP
    連接形式:
    nio://hostname:port?key=value

各種協議對比 : http://activemq.apache.org/configuring-version-5-transports.html

修改 activemq.xml 使之支持 NIO 協議:






而使用 NIO 協議,代碼修改量極小,只需同時將消息生產者和消費者的 URL 修改即可:
//public static final String ACTIVEMQ_URL = "tcp://192.168.17.3:61616";
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61618";
修改之後即可正確運行

  1. NIO 增強
    URI 格式以 nio 開頭,表示這個端口使用 tcp 協議爲基礎的NIO 網絡 IO 模型,但這樣設置讓它只支持 tcp 、 nio 的連接協議。如何讓它支持多種協議?

    Starting with version 5.13.0, ActiveMQ supports wire format protocol detection. OpenWire, STOMP, AMQP, and MQTT can be automatically detected. This allows one transport to be shared for all 4 types of clients.
    使用 : auto+nio+ssl
    官網介紹 : http://activemq.apache.org/auto
    使用 auto 的方式就相當於四合一協議 : STOMP AMQP MQTT TCP NIO

auto 就像是一個網絡協議的適配器,可以自動檢測協議的類型,並作出匹配

配置文件修改:
……


連接:

消息發送成功

同樣代碼只需修改 URI
public static final String ACTIVEMQ_URL = "nio://192.168.17.3:61608";

對於 NIO 和 tcp 的代碼相同,但不代表使用其他協議代碼相同,因爲底層配置不同,其他協議如果使用需要去修改代碼

六. ActiveMQ 的可持久化
將MQ 收到的消息存儲到文件、硬盤、數據庫 等、 則叫MQ 的持久化,這樣即使服務器宕機,消息在本地還是有,任就可以訪問到。
官網 : http://activemq.apache.org/persistence
ActiveMQ 支持的消息持久化機制: 帶賦值功能的 LeavelDB 、 KahaDB 、 AMQ 、 JDBC
持久化就是高可用的機制,即使服務器宕機了,消息也不會丟失

AMQ 是文件存儲形式,寫入快、易恢復 默認 32M 在 ActiveMQ 5.3 之後不再適用
KahaDB : 5.4 之後基於日誌文件的持久化插件,默認持久化插件,提高了性能和恢復能力
KahaDB 的屬性配置 : http://activemq.apache.org/kahadb
它使用一個事務日誌和 索引文件來存儲所有的地址

db-<數字>.log 存儲數據,一個存滿會再次創建 db-2 db-3 …… ,當不會有引用到數據文件的內容時,文件會被刪除或歸檔
db.data 是一個BTree 索引,索引了消息數據記錄的消息,是消息索引文件,它作爲索引指向了 db-.log 裏的消息
一點題外話:就像mysql 數據庫,新建一張表,就有這個表對應的 .MYD 文件,作爲它的數據文件,就有一個 .MYI 作爲索引文件。
db.free 存儲空閒頁 ID 有時會被清除
db.redo 當 KahaDB 消息存儲在強制退出後啓動,用於恢復 BTree 索引
lock 顧名思義就是鎖
四類文件+一把鎖 ==》 KahaDB

LeavelDB : 希望作爲以後的存儲引擎,5.8 以後引進,也是基於文件的本地數據存儲形式,但是比 KahaDB 更快
它比KahaDB 更快的原因是她不使用BTree 索引,而是使用本身自帶的 LeavelDB 索引
題外話:爲什麼LeavelDB 更快,並且5.8 以後就支持,爲什麼還是默認 KahaDB 引擎,因爲 activemq 官網本身沒有定論,LeavelDB 之後又出了可複製的LeavelDB 比LeavelDB 更性能更優越,但需要基於 Zookeeper 所以這些官方還沒有定論,任就使用 KahaDB

JDBC : 有一部分數據會真是的存儲到數據庫中
使用JDBC 的持久化,
①修改配置文件,默認 kahaDB
修改之前:

修改之後:

②在activemq 的lib 目錄下添加 jdbc 的jar 包 (connector.jar 我使用5.1.41 版本)
③ 修改配置文件 : activemq.xml 使其連接自己windows 上的數據庫,並在本地創建名爲activemq 的數據庫

④ 讓linux 上activemq 可以訪問到 mysql ,之後產生消息。
ActiveMQ 啓動後會自動在 mysql 的activemq 數據庫下創建三張表:activemq_msgs 、activemq_acks、activemq_lock

點對點會在數據庫的數據表 ACTIVEMQ_MSGS 中加入消息的數據,且在點對點時,消息被消費就會從數據庫中刪除
但是對於主題,訂閱方式接受到的消息,會在 ACTIVEMQ_MSGS 存儲消息,即使MQ 服務器下線,並在 ACTIVEMQ_ACKS 中存儲消費者信息 。 並且存儲以 activemq 爲主,當activemq 中的消息被刪除後,數據庫中的也會自動被刪除。

坑:

JDBC 改進: 加入高速緩存機制 Journal
高速緩存在 activemq.xml 中的配置:

七. 多節點集羣

大概流程:

如何保證高可用 ==》 搭建集羣
ZooKeeper + Replicated LevelDB Store ==》
集羣 http://activemq.apache.org/replicated-leveldb-store

                這幅圖的意思就是 當 Master 宕機後,zookeper 監測到沒有心跳信號, 則認爲 master 宕機了,然後選舉機制會從剩下的 Slave 中選出一個作爲 新的 Master 

zookeper : 3.4.9 搭建zookeper 集羣,搭建 activemq 集羣
集羣搭建: 新建 /mq_cluster 將原始的解壓文件複製三個,修改端口 (jetty.xml)

增加IP 到域名的映射(/etc/hosts 文件)

修改 爲相同的borkername

改爲 replica levelDB (3個都配,這裏列舉一個)

改端口 02 節點 =》 61617 03 節點 =》 61618

      想要啓動replica  leavel DB 必須先啓動所有的zookeper 服務,zookeper 的單機僞節點安裝這裏不細說了,主要講zookeper 複製三份後改配置文件,並讓之自動生成  myid  文件,並將zk的端口改爲之前表格中對應的端口 。這是conf 下的配置文件

其具體配置爲:
tickTime=2000
initLimit=10
syncLimit=5
clientPort=2191 // 自行設置
server.1=192.168.17.3:2888:3888
server.2=192.168.17.3:2887:3887
server.3=192.168.17.3:286:3886
dataDir=/zk_server/data/log1 // 自行設置
我設置了三個,此時方便起見可以寫批處理腳本

!/bin/sh // 注意這個必須寫在第一行

cd /zk_server/zk_01/bin
./zkServer.sh start

cd /zk_server/zk_02/bin
./zkServer.sh start

cd /zk_server/zk_03/bin
./zkServer.sh start
編寫這個 zk_batch.sh 之後, 使用
chmod 700 zk_batch.sh
命令即可讓它變爲可執行腳本, ./zk_batch.sh start 即可 (即啓動了三個zk 的服務)
同理寫一個批處理關閉zk 服務的腳本和 批處理開啓mq 服務 關閉 mq 服務的腳本。

完成上述之後連接zk 的一個客戶端
./zkCli.sh -server 127.0.0.1:2191
連接之後:

表示連接成功

 查看我的三個節點:   我的分別是 0…… 3     ……  4     …… 5

查看我的節點狀態
get /activemq/leveldb-stores/00000000003

此次驗證表明 00000003 的節點狀態是master (即爲63631 的那個mq 服務) 而其餘的(00000004 00000005) activemq 的節點是 slave
如此集羣順利搭建成功 !

此次測試表明只有 8161 的端口可以使用 經測試只有 61 可以使用,也就是61 代表的就是master

測試集羣可用性:
首先:

修改代碼
public static final String ACTIVEMQ_URL = "failover:(tcp://192.168.17.3:61616,tcp://192.168.17.3:61617,
tcp://192.168.17.3:61618)?randomize=false";

public static final String QUEUE_NAME = "queue_cluster";

測試:

                                                                    測試通過連接上集羣的 61616 

MQ服務收到三條消息:

消息接收

MQ 服務也將消息出隊

以上代表集羣可以正常使用

此時真正的可用性測試:
殺死 8061 端口的進程 !!!

刷新頁面後 8161 端口宕掉,但是 8162 端口又激活了

當 61616 宕機,代碼不變發消息 自動連接到 61617 了

這樣! 集羣的可用性測試成功!

八. 終章:面試經驗
1> 引入消息隊列後 如何保證高可用性
持久化、事務、簽收、 以及帶複製的 Leavel DB + zookeeper 主從集羣搭建
2> 異步投遞 Async send
對於一個慢消費者,使用同步有可能造成堵塞,消息消費較慢時適合用異步發送消息
activemq 支持同步異步 發送的消息,默認異步。當你設定同步發送的方式和 未使用事務的情況下發持久化消息,這時是同步的。
如果沒有使用事務,且發送的是持久化消息,每次發送都會阻塞一個生產者直到 broker 發回一個確認,這樣做保證了消息的安全送達,但是會阻塞客戶端,造成很大延時 。
在高性能要求下,可以使用異步提高producer 的性能。但會消耗較多的client 端內存,也不能完全保證消息發送成功。在 useAsyncSend = true 情況下容忍消息丟失。

// 開啓異步投遞
activeMQConnectionFactory.setUseAsyncSend(true);

url 後面加參數

開啓ActivemqFactury 的Async 爲true

將connection 設Async 爲true

    > 如何在投遞快還可以保證消息丟失 ?

異步發送消息丟失的情況場景是: UseAsyncSend 爲 true 使用 producer(send)持續發送消息,消息不會阻塞,生產者會認爲所有的 send 消息均會被髮送到 MQ ,如果MQ 突然宕機,此時生產者端尚未同步到 MQ 的消息均會丟失 。
故 正確的異步發送方法需要接收回調
同步發送和異步發送的區別就在於 :
同步發送send 不阻塞就代表消息發送成功
異步發送需要接收回執並又客戶端在判斷一次是否發送

在代碼中接收回調的函數 :
activeMQConnectionFactory.setUseAsyncSend(true);
……

for (int i = 1; i < 4 ; i++) {
textMessage = session.createTextMessage("msg--" + i);
textMessage.setJMSMessageID(UUID.randomUUID().toString()+"-- orderr");
String msgid = textMessage.getJMSMessageID();
messageProducer.send(textMessage, new AsyncCallback() {
@Override
public void onSuccess() {
// 發送成功怎麼樣
System.out.println(msgid+"has been successful send ");
}

            @Override
            public void onException(JMSException e
{
                // 發送失敗怎麼樣
                System.out.println(msgid+" has been failure send ");
            }
        });

}

3> 延遲投遞和定時投遞
① 在配置文件中設置定時器開關 爲 true

② 代碼編寫
Java 代碼中封裝的輔助消息類型 ScheduleMessage
可以設置的 常用參數 如下:

long delay = 3 * 1000 ;
long perid = 4 * 1000 ;
int repeat = 7 ;
for (int i = 1; i < 4 ; i++) {
TextMessage textMessage = session.createTextMessage("delay msg--" + i);
// 消息每過 3 秒投遞,每 4 秒重複投遞一次 ,一共重複投遞 7 次
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY,delay);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD,perid);
textMessage.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT,repeat);

messageProducer.send(textMessage);

}

4> ActiveMQ 的消息重試機制 ^_^ 好累啊,快完了,不想手寫了,框一些圖片吧

最多六次還沒發出就會
加入DLQ (死信隊列)

官網介紹 http://activemq.apache.org/redelivery-policy

   >   死信隊列的一些設置 

    修改,當嫌6 次太多,設置爲 3次         // 三次的意思是不計算本來發送的第一次 ,之後再次發送的第三天就被廢棄

RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
redeliveryPolicy.setMaximumRedeliveries(3);
activeMQConnectionFactory.setRedeliveryPolicy(redeliveryPolicy);

在spring 中使用 死信機制

在業務邏輯中,如果一個訂單系統沒有問題,則使用正常的業務隊列,當出現問題,則加入死信隊列 ,此時可以選擇人工干預還是機器處理 。

死信隊列默認是全部共享的,但是也可以設置獨立的死信隊列
獨立的死信隊列配置

5> 如何保證消息不被重複消費,冪等性的問題

項目地址: https://github.com/elstic/ActiveMQ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章