關於分散式商城的專案講解
我們這個專案是基於SOA的架構來實現的。採用的是dubbo中介軟體來實現表現層跟服務層之間的通訊。
我們專案分為前臺後臺,前臺提供內容展示,商品展示,商品搜尋,購物車,訂單等,支付等模組,後臺提供管理商品,內容管理,訂單管理等模組。
下面我給您具體介紹一下。
首先進入我們的網站首頁:最上面是我們的網站的logo,搜尋框,下面左邊是測分類欄對商品進行分類,輪中間是輪播圖廣告位,跟著下面是新聞公告欄,再下面是一些熱門商品的展示。這上面內容都是動態的展示出來的,所以我們要有一個後臺來管理這些內容。
我介紹下我參與的模組:
比如說廣告位的展示吧,我們後臺要管理這些內容,在後臺頁面最左邊就會有一個網站內容管理模組,內容模組包含內容分類管理,內容管理。當我們點選內容分類,在右邊就會顯示所有商品的一個分類,在點選子節點,比如說我們這裡的廣告位,就會非同步載入資料的顯示查出廣告內容。我們再在這個基礎上對廣告進行增刪改查。考慮高併發量,為了避免頻繁的與資料庫互動,我們將資料存在了redis中,當在首頁中點選廣告時,我們將不再直接去資料庫中查詢資料,而是先判斷快取中是否有資料,如果有直接返回資料,如果沒有就去查資料庫並將資料快取在redis中,另外我們修改內容後,會將原來的快取刪除掉,來同步資料庫。
後臺模組還有商品管理模組,這一塊是對商品的列表展示,以及增刪改查操作;新增商品的話,在商品新增介面錄入商品資訊。其中圖片儲存考慮到數量比較多,採用的是分散式檔案儲存系統(FastDFS),圖片多了可以搭建叢集,圖片的訪問我使用了Nginx的Http伺服器能力,對圖片的訪問都交給圖片伺服器;
然後我們再來說下首頁的其他部分,最上面有個搜尋框,當用戶在首頁沒有看到想要的商品時,可以有針對性的進行搜尋,使用了solr技術,根據IK分詞器,對查詢的關鍵字進行查詢,首先建立一個SolrQuery物件作為商品搜尋的查詢條件,設定分頁條件,指定預設的搜尋域,設定高亮,執行查詢,計算出總頁數和總條數,返回一個QueryResponse結果集,在將結果集迴圈遍歷新增到自己定義的集合裡面,返回給頁面,這樣我們就搜尋到我們想要的一些商品。
這裡有一個問題就是索引庫要同步,當我們新增商品的時候,我們使用了一個訊息中介軟體rabbitmq來同步索引庫,新增商品時,傳送訊息。在搜尋模組中,接收訊息,取商品id,根據商品id查詢資料庫,建立一SolrInputDocument物件,使用SolrServer物件寫入索引庫。
當用戶搜尋到自己想要的商品時,就會去點選那個商品圖片或者名稱進入商品詳情頁面。商品詳情展示要查兩個表,一個是商品表,一個是商品描述表,當訪問量很大時,如熱門商品,就會頻繁的跟資料庫互動,為了減輕資料庫的壓力,考慮使用快取。熱門商品訪問量比較大,需要做快取,普通商品卻需求不大。要區別對待,我們考慮到使用記錄訪問量來記錄訪問次數,來區別熱門商品,針對的做快取,但這樣操作麻煩。最終我們採用設定redis過期時間來處理這個問題, 熱門商品訪問比較深多,過期了又會存進去。
不過我們為了進一步減少資料庫的壓力,商品詳情頁採用了FreeMarker模板引擎技術,生成一個靜態化頁面。這樣能減輕伺服器的壓力。當我們新增商品的時候,我們使用了一個訊息中介軟體rabbitmq傳送訊息,取商品id。
將生產的靜態頁面放到Nginx靜態伺服器上,並且將css,js,png等準備好的靜態資源放在靜態資源伺服器,然後在使用Nginx負載均衡進行請求分發,靜態資源訪問交給靜態資源伺服器處理,tomcat只需要處理頁面的動態請求,在使用keepalived對Nginx搭建叢集實現了Nginx的高可用;
我再講下購物車模組,當客戶檢視商品詳情後,想購買了,就會加入購物車。為了減少使用者流失量,在不登陸的情況下,可以把購物車資訊寫入cookie,這樣使用者體驗好。讀寫cookie抽取出TokenUtil,使用@Cookie進行獲取。加入時先判斷商品id在商品列表中是否存在。如果存在,商品數量相加。不存在,根據商品id查詢商品資訊,把商品新增到購車列表,把購車商品列表寫入cookie。如果登入的話將購物車直接寫入mysql資料庫中,如果沒有登入的話,將購物車資訊寫入redis中快取起來,等使用者登入後將快取中的購物車資訊加入到mysql資料庫中,然後清除快取。展示購物車列表時,也是需要判斷是否登入,如果登入從資料庫中將購物車資訊讀出來,如果沒有登入,從cookie中取商品列表,商品列表傳遞給頁面。修改商品數量時,頁面傳送一個非同步請求,後臺接收兩個引數商品id和數量,再從cookie或者mysql中取商品列表,遍歷商品列表找到對應商品,更新商品數量,把商品列表寫入cookie/mysql。
在使用Cookie的時候出現了跨域問題,使用Jsonp解決,並且在SSO發現了Cookie的域名問題,使用setDomain()使用二級域名解決;還有就是多個方法的判斷登入狀況,考慮到訂單,支付模組還需要多次進行判斷,使用了AOP的思想;通過自定義註解@IsLogin在Controller層方法上新增該註解,實現方式的環繞增強,在進入方法之前通過註解value屬性的true/false判斷是否強制登入,該註解的作用有通過新增該註解,然後獲取cookie值如果cookie中有使用者資訊的token那就從redis中獲取使用者資訊並通過反射給方法上User物件賦值,在不強制登入的方法中通過對user物件的判斷進行登入和未登入的不同操作,如果該註解value設定為true就要求強制登入如果沒有登入的的話就直接跳到登入頁面,這時就有一個問題,為了提高使用者體驗,使用者被強制登入後需要回到之前的頁面,通過頁面location.href獲得頁面的地址當做returnUrl引數拼接然後在帶到服務端返回到指定的頁面,並且通過Encoding方法對引數的拼接做了處理保證搜尋時頁面也回到對應的搜尋也,關鍵字也不改變;
最後我講一下訂單模組,購物車完成後要生成訂單,訂單中有配送資訊,因此我們必須要求使用者登入從而獲得資料庫中使用者的地址。我們使用的是單點登入,使用redis模擬Session,實現Session的統一管理,這樣使用者只需要登入一次就可以訪問所有相互信任的應用。使用者登入成功後,生成token,相當key, 把使用者物件value存入redis,模擬Session的過期時間。一般半個小時。最後把token寫入cookie中(Cookie需要跨域)。
使用者登入後,如果登出登入訊息就將使用者資訊的Cookie設定過期時間為0,並且回到登入頁
當點選生成訂單時候,就通過AOP+自定義註解實現強制登入然後跳轉到訂單頁面,訂單頁面使用了儲存過程實現新增預設地址的功能,我們需要保證地址新增如果是預設地址的時候同時將資料庫中的預設地址設為非預設;使用者到了訂單頁面後就不能選擇購物車商品的數量了,然後就是下單操作了,當用戶點選付款後,根據使用者選擇,直接接入支付寶,然後通過非同步的等待支付寶訊息修改訂單狀態,並且如果訂單取消還需要對商品進行回庫操作
訂單的操作涉及到了多張表的操作,使用了spring的事務管理
最後是秒殺工程,這是一個常見的商城功能,但也是一個比較麻煩的場景,面對高併發的場景,考慮到對秒殺商品的高速讀寫,使用了redis作為資料快取伺服器,對秒殺商品的庫存進行快取,用商品id關聯做為key,頁面使用js的setInterverl定時器+時間伺服器對秒殺商品進行倒計時以及搶購按鈕做控制
對於秒殺的操作都放在redis中進行,大致邏輯就是首先使用redis+lua指令碼實現秒殺整體流程,保證面對高併發的情況下不會出現超買,漏賣的情況,lua指令碼先獲得需要購買的商品id和商品數量,然後在獲取庫存,判斷庫存是否足夠,足夠才繼續往下執行扣減庫存,快取訂單資訊返回執行結果;在service中執行前快取lua指令碼然後通過evalSha()傳參執行,返回值分三種情況,如果秒殺成功就會生成指定的訂單,然後在將購物車資訊清空,如果秒殺失敗就直接返回然後跳轉到抱歉頁面,還有就是庫存為0的情況考慮到有些使用者可能只是生成了訂單,並沒有付款,訂單關閉時間為半個小時,通過rabbitmq的延遲佇列實現了定時功能,對訂單訊息進行監聽如果訂單訊息在半小時之內沒有被消費,將變成死信訊息進入死信佇列然後觸發訂單關閉,將訂單狀態設定為關閉並且將秒殺商品加回redis中;這個時候秒殺結束,伺服器的壓力比較小,我們可以直接將redis中的訂單資訊寫回資料庫中,然後情況redis相關資料;