電商專案day13(訊息中介軟體解決方案 JMS&ActiveMQ)
今日目標:
配置nginx反向代理
jms入門
spring整合jms
ActiveMQ應用到專案實現上下架同時更改索引庫以及靜態頁面
一.nginx配置專案的反向代理
nginx是什麼?
Nginx (engine x) 是一個高效能的HTTP和反向代理服務,也是一個IMAP/POP3/SMTP服務。Nginx是由伊戈爾·賽索耶夫為俄羅斯訪問量第二的Rambler.ru站點(俄文:Рамблер)開發的,第一個公開版本0.1.0釋出於2004年10月4日。
其將原始碼以類BSD許可證的形式釋出,因它的穩定性、豐富的功能集、示例配置檔案和低系統資源的消耗而聞名。2011年6月1日,nginx 1.0.4釋出。
Nginx是一款輕量級的Web 伺服器/反向代理伺服器及電子郵件(IMAP/POP3)代理伺服器,並在一個BSD-like 協議下發行。其特點是佔有記憶體少,併發能力強,事實上nginx的併發能力確實在同類型的網頁伺服器中表現較好,中國大陸使用nginx網站使用者有:百度、京東、新浪、網易、騰訊、淘寶等
三個非常重要的作用:
1.載入靜態頁面
2.反向代理
3.負載均衡
在這我們在本地進行配置相應的對映地址,不要讓瀏覽器,通過網路搜尋DNS伺服器,配置的如下;
C:\Windows\System32\drivers\etc 這個是window下的目錄
127.0.0.1 search.pinyougou.com
127.0.0.1 shop.pinyougou.com
127.0.0.1 www.pinyougou.com
127.0.0.1 manage.pinyougou.com
127.0.0.1 item.pinyougou.com
127.0.0.1 user.pinyougou.com
127.0.0.1 cart.pinyougou.com
安裝window版的nginx步驟:
1.加壓即用
2.配置核心檔案nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name manage.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:8081;
root admin/;
index admin/index.html;
}
}
server {
listen 80;
server_name shop.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:8082;
root admin/;
index admin/index.html;
}
}
server {
listen 80;
server_name www.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:8083;
root /;
index index.html;
}
}
server {
listen 80;
server_name search.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:8084;
root /;
index search.html;
}
}
server {
listen 80;
server_name user.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:8086;
root /;
index home-index.html;
}
}
server {
listen 80;
server_name cart.pinyougou.com;
location / {
proxy_pass http://127.0.0.1:9107;
root /;
index cart.html;
}
}
server {
listen 80;
server_name item.pinyougou.com;
location / {
root F:\\item;
index index.html;
}
}
}
3.重啟Nginx命令: nginx.exe -s reload
載入過後我們就可以看到我們想看的頁面:原理就是通過我們自己配置的本地對映域名 到nginx上的代理伺服器
反向代理的作用是:
1.承受併發能力高
2.安全性高,不能夠訪問到專案的本地檔案
二.jms入門
我們已經完成了 5 個 web 模組和 4 個服務模組。其中運營商後臺的呼叫關係最多,用到了商家商品服務、廣告內容服務、搜尋服務和頁面生成服務。這種模組之間的依賴也稱之為耦合。而耦合越多,之後的維護工作就越困難。那麼如果改善系統模組呼叫關係、減少模
塊之間的耦合呢?我們接下來就介紹一種解決方案----訊息中介軟體。
1.什麼是訊息中介軟體
訊息中介軟體利用高效可靠的訊息傳遞機制進行平臺無關的資料交流,並基於資料通訊來進行分散式系統的整合。通過提供訊息傳遞和訊息排隊模型,它可以在分散式環境下擴充套件程序間的通訊。對於訊息中介軟體,常見的角色大致也就有 Producer(生產者)、Consumer(消
費者)
常見的訊息中介軟體: ActiveMQ RabbitMQ ZeroMQ Kafka
我們改造系統的呼叫關係:
引入訊息中介軟體的最重要的作用之一:就是模組之間解耦合
2.什麼是jms?
JMS(Java Messaging Service)是 Java 平臺上有關面向訊息中介軟體的技術規範,它便於訊息系統中的 Java 應用程式進行訊息交換,並且通過提供標準的產生、傳送、接收訊息的介面簡化企業應用的開發。
JMS 本身只定義了一系列的介面規範,是一種與廠商無關的 API,用來訪問訊息收發系統。它類似於 JDBC(java Database Connectivity):這裡,JDBC 是可以用來訪問許多不同關係資料庫的 API,而 JMS 則提供同樣與廠商無關的訪問方法,以訪問訊息收發服務。許多廠商目前都支援 JMS,包括 IBM 的 MQSeries、BEA 的 Weblogic JMS service 和 Progress 的SonicMQ,這只是幾個例子。 JMS 使您能夠通過訊息收發服務(有時稱為訊息中介程式或路由器)從一個 JMS 客戶機向另一個 JML 客戶機發送訊息。訊息是 JMS 中的一種型別物件,由兩部分組成:報頭和訊息主體。報頭由路由資訊以及有關該訊息的元資料組成。訊息
主體則攜帶著應用程式的資料或有效負載。
JMS定義五種訊息正文格式 :
.TextMessage--一個字串物件
· MapMessage--一套名稱-值對
· ObjectMessage--一個序列化的 Java 物件
· BytesMessage--一個位元組的資料流
· StreamMessage -- Java 原始值的資料流
生產者消費者模式
JMS訊息傳遞型別有兩種:
A B C
點對點模式:一個生產者生產了訊息,只能被一個消費者消費。
釋出訂閱模式:一個生產者生產了訊息,可以被多個消費者消費。
A B C
3.安裝ActiveMQ
1)將 apache-activemq-5.12.0-bin.tar.gz 上傳至伺服器
(2)解壓此檔案
tar zxvf apache-activemq-5.12.0-bin.tar.gz
(3)為 apache-activemq-5.12.0 目錄賦權
chmod 777 apache-activemq-5.12.0
(4)進入 apache-activemq-5.12.0\bin 目錄
(5)賦與執行許可權
啟動: 進入bin目錄下 ./activemq start
預設使用者名稱和密碼都是:admin
預設埠是:8161
jms底層實現見 day13-1
三.網站前臺與搜尋模組商品詳情模組的系統對接
1.入口網站 通過 搜尋 跳到 搜尋模組 並完成搜尋任務
需求分析:
實現思路:我們通過在protal.html頁面繫結搜尋的keywords關鍵字,給搜尋新增點選事件,攜帶關鍵字到搜尋頁面
注意:在這是通過路由傳參 ,接受的時候我們$location 接受
頁面實現:
indexController.js程式碼:
//完成入口網站的搜尋與搜尋模組的對接
$scope.search=function () {
//angularjs頁面傳參時候要在問號前面新增# 這個路由傳參 ng ngroute
location.href="http://search.pinyougou.com/search.html#?keywords="+$scope.keywords
}
一定要注意:在頁面中傳遞引數要通過angularjs的路由傳參 主要在傳遞引數的前邊新增#
搜尋模組實現:
我們一定要在searchController.js中新增$location
如果不輸入值:
搜尋模組我們接受值通過,通過searchController.js接受,不能通過頁面接收
//基於$location接受入口網站傳過來的搜尋關鍵字,$location.search() 獲取的值為Object物件
var keywords = $location.search()["keywords"];
//判斷是否是空值傳過來的,空值的話是"undefined"字串
if (keywords!="undefined"){
//輸入的搜尋的關鍵字
$scope.searchMap.keywords=keywords;
}else{
//反之沒有輸入關鍵字
$scope.searchMap.keywords="手機";//給一個預設值作為搜尋的關鍵字
}
注意:一定要在封裝物件searchMap的下面,如果不這樣,則會清空keywords的值
還要映入$location
2.搜尋模組到商品詳情頁面的對接
程式碼如下:
因為我們已經做了域名的對映,我們就可以通過id找到相應的頁面
三.spring整合jms
1.建立一個工程
2.匯入配置檔案
點對點模式:
Producer的配置檔案:
<context:component-scan base-package="cn.itcast.demo"></context:component-scan>
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128: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="queueTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="queue_text"/>
</bean>
<!--這個是訂閱模式 文字資訊-->
<bean id="topicTextDestination" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg value="topic_text"/>
</bean>
</beans>
consumer的而配置檔案
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128: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="queueTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="queue_text"/>
</bean>
<!-- 我的監聽類 -->
<bean id="myMessageListener" class="cn.itcast.demo.MyMessageListener"></bean>
<!-- 訊息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--監聽的activeMQ的訊息佇列地址-->
<property name="connectionFactory" ref="connectionFactory" />
<!--要監聽的訊息-->
<property name="destination" ref="queueTextDestination" />
<!--指定監聽類,完成監聽操作-->
<property name="messageListener" ref="myMessageListener" />
</bean>
</beans>
編寫生產者和消費者
@Component
public class QueueProducer {
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private Destination queueTextDestination;
public void sendMsg(){
jmsTemplate.send(queueTextDestination, new MessageCreator() {
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage("這是與spirng整合的第一個訊息");
}
});
}
}
public class MyMessageListener implements MessageListener {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage)message;
try {
System.out.println("接受到訊息"+textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
測試:
//測試生產者
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-activemq-producer.xml")
public class QueueProducerTest {
@Autowired
private QueueProducer queueProducer;
@Test
public void sendMsg(){
queueProducer.sendMsg();
}
}
//測試消費者
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring/applicationContext-activemq-consumer.xml")
public class ConsumerTest {
@Test
public void getMeg(){
while (true){}
}
}
3.釋出訂閱模式
和點對點模式差不多,修改注入
topicTextDestination
四.商品的上下架操作同時執行solr索引和網頁靜態化
1.首先我們通過上下架的操作通過同步到solr庫中,注意我們寫在selllergoods模組中
導包 編寫 配置檔案
//activeMQ的建立訊息
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private Destination addItemSolrTextDestination;
@Autowired
private Destination deleItemSolrTextDestination;
在servier層判斷是否上架或者下架
//只有稽核通過的才能上下架
if ("1".equals(tbGoods.getAuditStatus())){
//判斷是否是上架
if("1".equals(isMarketable)){
//同步商品到索引庫
jmsTemplate.send(addItemSolrTextDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
}else{
//刪除商品到索引庫;
jmsTemplate.send(deleItemSolrTextDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
}
2.在search模組編寫消費者
導包 編寫配置檔案
<!--這個是佇列目的地,點對點的 上架同步索引庫-->
<bean id="addItemSolrTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="add_item_solr"/>
</bean>
<!-- 我的監聽類 -->
<bean id="addItemSolrMessageListener" class="com.pinyougou.search.listener.AddItemSolrMessageListener"></bean>
<!-- 訊息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--監聽的activeMQ的訊息佇列地址-->
<property name="connectionFactory" ref="connectionFactory" />
<!--要監聽的訊息-->
<property name="destination" ref="addItemSolrTextDestination" />
<!--指定監聽類,完成監聽操作-->
<property name="messageListener" ref="addItemSolrTextDestination" />
</bean>
<!--下架-->
<!--這個是佇列目的地,點對點的 下架刪除索引庫資料-->
<bean id="deleItemSolrTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="dele_item_solr"/>
</bean>
<!-- 我的監聽類 -->
<bean id="deleItemSolrMessageListener" class="com.pinyougou.search.listener.AddItemSolrMessageListener"></bean>
<!-- 訊息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--監聽的activeMQ的訊息佇列地址-->
<property name="connectionFactory" ref="connectionFactory" />
<!--要監聽的訊息-->
<property name="destination" ref="deleItemSolrTextDestination" />
<!--指定監聽類,完成監聽操作-->
<property name="messageListener" ref="deleItemSolrTextDestination" />
</bean>
編寫
com.pinyougou.search.listener.AddItemSolrMessageListener
上架
public class AddItemSolrMessageListener implements MessageListener{
@Autowired
private TbItemMapper itemMapper;
@Autowired
private SolrTemplate solrTemplate;
@Override
public void onMessage(Message message) {
//獲取上架商品的id
TextMessage textMessage = (TextMessage)message;
//同步到索引庫
try {
String goodsId = textMessage.getText();
TbItemExample example = new TbItemExample();
TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(Long.parseLong(goodsId));
List<TbItem> itemList = itemMapper.selectByExample(example);
//儲存到索引庫
solrTemplate.saveBeans(itemList);
//提交
solrTemplate.commit();
}catch (Exception e) {
e.printStackTrace();
}
}
}
下架:
public class DeleItemSolrMessageListener implements MessageListener {
@Autowired
private SolrTemplate solrTemplate;
@Override
public void onMessage(Message message) {
//獲取上架商品id
TextMessage textMessage = (TextMessage)message;
try {
String goodsId = textMessage.getText();
//商品下架時,同步刪除索引庫資料
SolrDataQuery query = new SimpleQuery("item_goodsid:"+goodsId);
solrTemplate.delete(query);
solrTemplate.commit();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
3.上下架的同時生成靜態頁面
首先我們配置上下架頁面的生成靜態頁的訊息,
1.配置檔案
<!--這個是佇列目的地,點對點的 上架同步生成靜態頁面-->
<bean id="addItemTextPageDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="add_item_page"/>
</bean>
<!--這個是佇列目的地,點對點的 下架刪除靜態頁面-->
<bean id="deleItemPageTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="dele_item_page"/>
</bean>
2.在sellergoods中編寫判斷是否上架,然後通過通過jmsTemplage傳送訊息
//判斷是否是上架
if("1".equals(isMarketable)){
//同步商品到索引庫
jmsTemplate.send(addItemSolrTextDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
//商家時新增相應的靜態頁面
jmsTemplate.send(addItemTextPageDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
}else{
//刪除商品到索引庫;
jmsTemplate.send(deleItemSolrTextDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
//刪除相應的靜態頁面
jmsTemplate.send(deleItemPageTextDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
return session.createTextMessage(id+"");
}
});
}
3.編寫消費者,
思路分析:通過我們從訊息中介軟體中獲得我們需要的goodsId,然後 通過 itemMapper查詢item表的id 刪除這個id 就可以了
<!-- 真正可以產生Connection的ConnectionFactory,由對應的 JMS服務廠商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.25.128: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="addItemPageTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="add_item_page"/>
</bean>
<!-- 我的監聽類 -->
<bean id="addItemPageMessageListener" class="com.pinyougou.page.listener.AddItemPageMessageListener"></bean>
<!-- 訊息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--監聽的activeMQ訊息佇列連線地址-->
<property name="connectionFactory" ref="connectionFactory" />
<!--要監聽的訊息-->
<property name="destination" ref="addItemPageTextDestination" />
<!--指定監聽類完成監聽操作-->
<property name="messageListener" ref="addItemPageMessageListener" />
</bean>
<!--這個是佇列目的地,點對點的 下架同步刪除靜態頁面-->
<bean id="deleItemPageTextDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="dele_item_page"/>
</bean>
<!-- 我的監聽類 -->
<bean id="deleItemPageMessageListener" class="com.pinyougou.page.listener.DeleItemPageMessageListener"></bean>
<!-- 訊息監聽容器 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<!--監聽的activeMQ訊息佇列連線地址-->
<property name="connectionFactory" ref="connectionFactory" />
<!--要監聽的訊息-->
<property name="destination" ref="deleItemPageTextDestination" />
<!--指定監聽類完成監聽操作-->
<property name="messageListener" ref="deleItemPageMessageListener" />
</bean>
4.編寫監聽器
注意在這之前,一定要匯入頁面靜態化的包,以及springmvc.xml中的
freemarker的模板配置
然後編寫生成靜態頁面的監聽頁面
@Service
@Transactional
public class AddItemPageMessageListener implements MessageListener {
@Autowired
private PageService pageService;
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage)message;
try {
String goodsId = textMessage.getText();
//生成靜態頁面
// 第一步:建立一個 Configuration 物件,直接 new 一個物件。構造方法的引數就是
freemarker.template.Configuration configuration = freeMarkerConfigurer.getConfiguration();
// freemarker 的版本號。
// 第四步:載入一個模板,建立一個模板物件。
Template template = configuration.getTemplate("item.ftl");
// 第五步:建立一個模板使用的資料集,可以是 pojo 也可以是 map。一般是 Map。
Goods goods = pageService.findOne(Long.parseLong(goodsId));
List<TbItem> items = goods.getItemList();
for (TbItem item : items) {
// 在這呢我們不要將組合實體類返回,因為返回還的遍歷,取值,我們直接在這封裝給map就可以了
Map<String, Object> map = new HashMap<>();
map.put("goods", goods);
map.put("item", item);
// 第六步:建立一個 Writer 物件,一般建立一 FileWriter 物件,指定生成的檔名。
Writer out = new FileWriter("F:\\item\\" + item.getId() + ".html");
// 第七步:呼叫模板物件的 process 方法輸出檔案。
template.process(map, out);
// 第八步:關閉流
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
刪除靜態頁面,通過goodsId獲得item的id 然後new File("檔案地址").delete()方法刪除
@Service
@Transactional
public class DeleItemPageMessageListener implements MessageListener {
@Autowired
private TbItemMapper itemMapper;
@Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage)message;
try {
//獲得商品id
String goodsId = textMessage.getText();
//在這我們通過生成靜態頁面的id序號刪除頁面
TbItemExample example = new TbItemExample();
TbItemExample.Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(Long.parseLong(goodsId));
//獲得item表的資訊
List<TbItem> itemList = itemMapper.selectByExample(example);
//迴圈刪除
for (TbItem item : itemList) {
new File("F:\\item\\"+item.getId()+".html").delete();
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
五.總結
activeMQ的三個非常重要的作用:
1.專案應用間解耦合
2.非同步通訊
3.流量削峰