前言:
前面我們可以利用Solr集群來實現我們的搜索功能,但是有沒有發現在我們每次添加一個新的商品的時候都要重新導入一次索引庫,效率非常低。這就需要我們優化一下我們的方案。我們想到最好就是我們添加商品的時候可以單獨將該商品同步到索引庫。那么我們可以想象有以下幾種方案:
方案一:在taotao-manager中,添加商品的業務邏輯中,添加一個同步索引庫的業務邏輯。
缺點:業務邏輯耦合度高,業務拆分不明確
方案二:業務邏輯在taotao-search中實現,調用服務在taotao-manager實現。業務邏輯分開。
缺點:服務之間的耦合度變高。服務的啟動有先后順序。
方案三:使用消息隊列。MQ是一個消息中間件。如圖
怎么理解消息中間件呢?我們可以把它理解為一個秘書,消息的發布者就是大老板,大老板下午三點要開個會,他只需跟秘書說一聲,下午三點,我要開個會,就行了,老板不用管秘書是怎樣通知各項目經理的,也不用管項目經理要帶什么材料,他所做的只是告訴秘書一聲而已。秘書負責與各個項目經理聯系,告訴各個項目經理應該準備什么。MQ便相當于"秘書"這個角色。當添加一個商品時,商品服務只需要告訴消息中間件MQ,MQ便去通知其它服務做各自該做的事情,比如通知搜索服務去同步索引庫,通知redis服務去同步緩存,通知生成靜態頁面等等。
常見的作為MQ中間件的有:ActiveMQ、RabbitMQ、Kafka。
1.什么是ActiveMQ
ActiveMQ 是Apache出品,最流行的,能力強勁的開源消息總線。ActiveMQ 是一個完全支持JMS1.1和J2EE 1.4規范的 JMS Provider實現,盡管JMS規范出臺已經是很久的事情了,但是JMS在當今的J2EE應用中間仍然扮演著特殊的地位。
主要特點:
- 多種語言和協議編寫客戶端。語言: Java, C, C++, C#, Ruby, Perl, Python, PHP。應用協議: OpenWire,Stomp REST,WS Notification,XMPP,AMQP
- 完全支持JMS1.1和J2EE 1.4規范 (持久化,XA消息,事務)
- 對Spring的支持,ActiveMQ可以很容易內嵌到使用Spring的系統里面去,而且也支持Spring2.0的特性
- 通過了常見J2EE服務器(如 Geronimo,JBoss 4, GlassFish,WebLogic)的測試,其中通過JCA 1.5 resource adaptors的配置,可以讓ActiveMQ可以自動的部署到任何兼容J2EE 1.4 商業服務器上
- 支持多種傳送協議:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
- 支持通過JDBC和journal提供高速的消息持久化
- 從設計上保證了高性能的集群,客戶端-服務器,點對點
- 支持Ajax
- 支持與Axis的整合
- 可以很容易得調用內嵌JMS provider,進行測試
2.ActiveMQ的消息形式
對于消息的傳遞有兩種類型:
一種是點對點的,即一個生產者和一個消費者一一對應;
另一種是發布/訂閱模式,即一個生產者產生消息并進行發送后,可以由多個消費者進行接收。
JMS定義了五種不同的消息正文格式,以及調用的消息類型,允許你發送并接收以一些不同形式的數據,提供現有消息格式的一些級別的兼容性。我們用的最多的就是TextMessage而已。
- StreamMessage -- Java原始值的數據流
- MapMessage--一套名稱-值對
- TextMessage--一個字符串對象
- ObjectMessage--一個序列化的 Java對象
-
BytesMessage--一個字節的數據流
image.png
3.ActiveMQ的安裝
1.進入http://activemq.apache.org/下載ActiveMQ
使用的版本是5.12.0
2.安裝環境
1.需要jdk
2.Linux虛擬機
3.安裝步驟
第一步:先把ActiveMQ的壓縮包上傳到Linux系統
第二步:解壓
第三步:啟動ActiveMQ
[root@localhost bin]# ./activemq start
關閉:
[root@localhost bin]# ./activemq stop
查看狀態:
[root@localhost bin]# ./activemq status
4.進入管理后臺:
(http://192.168.208.40:8161/admin)
用戶名:admin
密碼:admin
有時候我們可能會遇到503錯誤的問題
解決方法:
1、查看機器名
[root@itcast168 bin]# cat /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=itcast168
2、修改host文件
[root@itcast168 bin]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 itcast168
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
[root@itcast168 bin]#
3、重啟Activemq服務
4.測試ActiveMQ
4.1 Queue
4.1.1生產者:生產消息,發送端
1.把jar包添加到依賴中,使用5.11.2版本的包(為什么用這個版本的包?因為5.12的話里面有spring的集成,會影響我們本來的架構)
<!--ActivqMq組件-->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
</dependency>
步驟:
第一步:創建ConnectionFactory對象,需要指定服務端ip及端口號。
第二步:使用ConnectionFactory對象創建一個Connection對象。
第三步:開啟連接,調用Connection對象的start方法。
第四步:使用Connection對象創建一個Session對象。
第五步:使用Session對象創建一個Destination對象(topic、queue),此處創建一個Queue對象。
第六步:使用Session對象創建一個Producer對象。
第七步:創建一個Message對象,創建一個TextMessage對象。
第八步:使用Producer對象發送消息。
第九步:關閉資源。
//測試消息隊列的發送者
@Test
public void testActiveMqProducer() throws Exception{
//1.創建一個連接的工廠對象
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.208.40:61616");
//2.使用ConnectionFactory創建一個Connection對象
Connection connection = connectionFactory.createConnection();
//3.開啟連接
connection.start();
//4.使用Connection對象創建一個Session對象
//第一個參數:是否開啟事務(分布式事務)。true:開啟事務,第二個參數忽略。
//第二個參數:當第一個參數為false時,才有意義。消息的應答模式。1、自動應答2、手動應答。一般是自動應答。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.使用Session對象創建一個Destination對象,(topic、queue),此處創建一個Queue對象
//topic的話就是
// Destination topic = session.createTopic();
Destination queue = session.createQueue("test-queue");
//6.使用Session對象創建一個Producer對象。
MessageProducer producer = session.createProducer(queue);
//7.創建一個Message對象,創建一個TextMessage對象
TextMessage textMessage = session.createTextMessage("hello activeMq,this is my first 1");
//8.使用生產者發送信息
producer.send(textMessage);
//9.關閉資源
producer.close();
session.close();
connection.close();
}
4.1.2 消費者:接收信息
第一步:創建一個ConnectionFactory對象。
第二步:從ConnectionFactory對象中獲得一個Connection對象。
第三步:開啟連接。調用Connection對象的start方法。
第四步:使用Connection對象創建一個Session對象。
第五步:使用Session對象創建一個Destination對象。和發送端保持一致queue,并且隊列的名稱一致。
第六步:使用Session對象創建一個Consumer對象。
第七步:接收消息。
第八步:打印消息。
第九步:關閉資源
//測試消息的接受者
@Test
public void testQueueConsumer() throws Exception{
//1.創建一個連接的工廠對象
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.208.40:61616");
//2.使用ConnectionFactory創建一個Connection對象
Connection connection = connectionFactory.createConnection();
//3.開啟連接
connection.start();
//4.使用Connection對象創建一個Session對象
//第一個參數:是否開啟事務(分布式事務)。true:開啟事務,第二個參數忽略。
//第二個參數:當第一個參數為false時,才有意義。消息的應答模式。1、自動應答2、手動應答。一般是自動應答。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.使用Session對象創建一個Destination對象,(topic、queue),此處創建一個Queue對象
Destination queue = session.createQueue("test-queue");
//6.使用Session對象創建一個Consumer對象。
MessageConsumer consumer = session.createConsumer(queue);
//7.接收消息
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.in.read();
//9.關閉資源
consumer.close();
session.close();
connection.close();
}
運行生產者
運行消費者
我們可以觀察到數目發生了變化
4.2 Topic
4.2.1 Producer
消費者:接收消息。
第一步:創建一個ConnectionFactory對象。
第二步:從ConnectionFactory對象中獲得一個Connection對象。
第三步:開啟連接。調用Connection對象的start方法。
第四步:使用Connection對象創建一個Session對象。
第五步:使用Session對象創建一個Destination對象。和發送端保持一致topic,并且話題的名稱一致。
第六步:使用Session對象創建一個Consumer對象。
第七步:接收消息。
第八步:打印消息。
第九步:關閉資源
@Test
public void testTopicProducer() throws JMSException{
//1.創建一個連接工廠對象ConnectionFactory對象。需要指定mq服務的ip及端口號。注意參數brokerURL的開頭是
//tcp://而不是我們通常的http://,端口是61616而不是我們訪問activemq后臺管理頁面所使用的8161
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.208.40:61616");
//2.使用ConnectionFactory創建一個連接Connection對象
Connection connection = connectionFactory.createConnection();
//3.開啟連接。調用Connection對象的start方法
connection.start();
//4.使用Connection對象創建一個Session對象
//第一個參數是是否開啟事務,一般不使用分布式事務,因為它特別消耗性能,而且顧客體驗特別差,現在互聯網的
//做法是保證數據的最終一致(也就是允許暫時數據不一致),比如顧客下單購買東西,一旦訂單生成完就立刻響應給用戶
//下單成功。至于下單后一系列的操作,比如通知會計記賬、通知物流發貨、商品數量同步等等都先不用管,只需要
//發送一條消息到消息隊列,消息隊列來告知各模塊進行相應的操作,一次告知不行就兩次,直到完成所有相關操作為止,這
//也就做到了數據的最終一致性。如果第一個參數為true,那么第二個參數將會被忽略掉。如果第一個參數為false,那么
//第二個參數為消息的應答模式,常見的有手動和自動兩種模式,我們一般使用自動模式。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.使用Session對象創建一個Destination對象,兩種形式queue、topic。現在我們使用topic
//參數就是消息隊列的名稱
Topic topic = session.createTopic("test-topic");
//6.使用Session對象創建一個Producer對象
MessageProducer producer = session.createProducer(topic);
//7.創建一個TextMessage對象
//有兩種方式,第一種方式:
// TextMessage textMessage = new ActiveMQTextMessage();
// textMessage.setText("hello,activemq!!!");
//第二種方式:
TextMessage textMessage = session.createTextMessage("hello,activemq topic");
//8.發送消息
producer.send(textMessage);
//9.關閉資源
producer.close();
session.close();
connection.close();
}
4.2.2 消費者
@Test
public void testTopicConsumer() throws Exception{
//1.創建一個連接工廠對象ConnectionFactory對象。需要指定mq服務的ip及端口號。注意參數brokerURL的開頭是
//tcp://而不是我們通常的http://,端口是61616而不是我們訪問activemq后臺管理頁面所使用的8161
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://192.168.156.30:61616");
//2.使用ConnectionFactory創建一個連接Connection對象
Connection connection = connectionFactory.createConnection();
//3.開啟連接。調用Connection對象的start方法
connection.start();
//4.使用Connection對象創建一個Session對象
//第一個參數是是否開啟事務,一般不使用分布式事務,因為它特別消耗性能,而且顧客體驗特別差,現在互聯網的
//做法是保證數據的最終一致(也就是允許暫時數據不一致),比如顧客下單購買東西,一旦訂單生成完就立刻響應給用戶
//下單成功。至于下單后一系列的操作,比如通知會計記賬、通知物流發貨、商品數量同步等等都先不用管,只需要
//發送一條消息到消息隊列,消息隊列來告知各模塊進行相應的操作,一次告知不行就兩次,直到完成所有相關操作為止,這
//也就做到了數據的最終一致性。如果第一個參數為true,那么第二個參數將會被忽略掉。如果第一個參數為false,那么
//第二個參數為消息的應答模式,常見的有手動和自動兩種模式,我們一般使用自動模式。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.使用Session對象創建一個Destination對象,兩種形式queue、topic。現在我們使用queue
//參數就是消息隊列的名稱
Topic topic = session.createTopic("test-topic");
//6.使用Session對象創建一個Consumer對象
MessageConsumer consumer = session.createConsumer(topic);
//7.向Consumer對象中設置一個MessageListener對象,用來接收消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage) {
TextMessage textMessage = (TextMessage)message;
try {
String text = textMessage.getText();
System.out.println(text);
} catch (JMSException e) {
e.printStackTrace();
}
}
}
});
//8.程序等待接收用戶結束操作
//程序自己并不知道什么時候有消息,也不知道什么時候不再發送消息了,這就需要手動干預,
//當我們想停止接收消息時,可以在控制臺輸入任意鍵,然后回車即可結束接收操作(也可以直接按回車)。
System.out.println("topic消費者1111。。。。。");
System.in.read();
//9.關閉資源
consumer.close();
session.close();
connection.close();
}
topic形式的話會存在一個問題就是不能持久化,也就是如果消息發送者發送消息的時候,如果沒有消費者運行的話,它將無法消費者條消息。(即就算消費者沒有及時消費的話,就算再啟動也無法獲得生產者傳過來的消息)。如何解決的話我們可以考慮將數據保存在磁盤這樣就不會丟失了。