整理秒殺系統的面試必備!!!

分佈式環境的秒殺系統

如果在簡歷中使用的是秒殺項目,那麼在面對面試官時請做足準備。

應用場景

商城系統需要一個秒殺系統來提高購買量。秒殺活動通常都會伴隨有高併發的情況。爲了支撐高併發情況下的流量衝擊,我們需要設計一個分佈式的秒殺系統。

技術棧

在這裏插入圖片描述

數據庫設計

用戶信息表,用戶密碼錶,商品表,庫存表,訂單信息表
秒殺信息表(字段如下:)
字段 信息

字段 描述
id 自增主鍵
promo_name 秒殺名稱
start_time 開始時間
end_time 結束時間
promo_item_price 秒殺價格
item_id 關聯商品ID

踩坑:Mysql5.7無法給DataTime類型一個默認的初始值 0000-00 解決:面向百度解決!

項目模塊設計

在這裏插入圖片描述

簡單模塊說明及問題

用戶註冊 用戶入庫操作,將用戶信息存入。密碼進行MD5加密
用戶登陸 通過用戶名和密碼進行驗證登陸。並存儲用戶信息到Map中

問題:部署到分佈式環境的時候會發生已經登陸的用戶會丟失登陸信息的情況
解決:

  1. 查看本地的項目,登陸多次並未發現問題。猜測是服務器環境的問題。
  2. 對服務器環境和本地環境進行對比。發現服務器的環境上使用2臺服務器部署項目,而我們的登陸卻只需要在其中一個服務器的內存上進行存儲。所以訪問到另一臺服務器的請求時便無法獲取到內存的數據。
  3. 所以需要藉助一種2個臺服務器都能訪問到的中間件(Redis)進行用戶信息的存儲。客戶端攜帶着一個uuid來對登陸請求進行判定

核心模塊詳細說明

商品模塊
秒殺系統下的商品由於需要大量的被訪問可能導致數據庫的壓力過大。使用分級緩存來解決這一問題。如下圖:
在這裏插入圖片描述

當一個客戶端請求訪問商品信息的時候,需要先到本服務器的內存中查找,然後到緩存中進行查找。最後進入數據庫查找。找到後,將信息分別存入緩存和內存。

問題: 本地項目啓動後在數據庫修改了圖片的鏈接後,刷新頁面發現不能顯示修改後的圖片
解決: 這個想到了緩存的問題很快就想明白了,緩存未進行刷新。算是間接的發現了一個緩存刷新問題。在對緩存的數據進行操作時,一定要對所有的緩存進行一個同步的操作。否則會導致數據異常。

訂單模塊
訂單模塊需要對訂單進行一個入庫操作。訂單的注意點就是判定商品是否以秒殺價格拍下的商品。 秒殺下單操作對庫存有一個要求:庫存由下單判定減少還是由支付判定減少。

入庫操作的問題:
訂單號如何保證有意義且唯一?
解決:UUID?沒有任何格式,Pass。使用時間戳+隨機碼的形式。但是萬一同時隨機到相同的數字怎麼辦?最好是整一個像ID主鍵這種的自增序列。又有時間戳還又可以保證唯一性。這裏想了下在內存設置一個增長鍵。使用加鎖來保證增長鍵的原子性。
但是在分佈式下會產生如下問題:
訂單ID和登陸信息遇到了相同的問題,當時考慮了一下也可以藉助緩存進行。但是使用數據庫設置自增字段更穩定,還有很好的通用性。這樣其他需要自增序列的ID也可以使用。這裏設計了一個自增信息表,字段如下:

字段 描述
name 增長序列名稱
current_value 當前起始值
step 增長長度

我們通過對錶的操作來生成一個序列,並且通過name字段獲取自增信息。可以每天對current_value 進行歸0操作保證每天增長長度。
這裏有一個未解決的問題:就是如果當天增加的訂單數非常多那麼會產生一個非常長的訂單號,並且訂單號的長度通常都固定,一旦超出將會產生一系列問題。

解決併發情況超賣問題:實際訂單數大於庫存數
解決: 還是數據的原子性問題,在執行庫存查詢前進行加鎖,這裏可以使用悲觀鎖的思想,利用mysql的select for update 加鎖機制,悲觀鎖非常像syconized關鍵字,一旦當前的請求訪問到數據,其他的請求就要被阻塞。這裏順便了解了下樂觀鎖思想,發現在這種高併發的情況下,真正的加鎖是一種效率很低的方式。採用樂觀鎖的思想對數據庫進行一個表的版本控制是一個不錯的選擇。在更新前必須保證更新的版本號和查詢到的版本號一致,否則更新失敗。

秒殺模塊
首先利用時間段來判定某商品是否處於一個秒殺活動的時間段。對於進入秒殺的商品就進行一個秒殺價格的展示和倒計時的顯示還有下單按鈕的操作。前端獲取到秒殺開始的時間需要對秒殺活動進行一個判定。在秒殺未開始時,執行一次倒計時操作。倒計時操作使用每隔一秒刷新一次頁面來顯示時間。
問題及解決
問題:使用客戶端時間來進行倒計時展示的安全問題
解決:每次刷新都要重新向服務端請求一次秒殺活動時間。
未解決:如果說每一秒都要請求一次服務器那麼是否會對服務器的性能有影響?或許實際情況中是有一臺專用的時間校驗服務器?
踩坑:從Redis存取時間出現了序列化亂的情況
解決:首先想到的是對時間進行一個格式化的存儲。但是考慮到Json存入Redis的情況會很多,如果可以對redis進行一個通用配置就好了,百度發現可以通過配置對redis進行一個通用的json序列化配置,然後對日期格式進行格式化存取配置。

其他問題: 在對一個service測試類測試的時候拋出了空指針的異常
解決: 通常的空指針異常是對象的引用沒有開闢出內存空間。Debug發現測試的service接口拋出了空指針。進入service接口的實現類debug 發現沒有問題。思來想去,去測試類重新檢查,最後發現對要調用的測試接口沒有加入@Autowire註解。一直都想知道Spring框架如何實例化的,於是去查看了BeanFactory的源碼,發現了在Spring實例化前要經歷很多步驟,從實例的調用方法getBean開始到單例模式,原型模式的選擇。默認單例模式的設計也有很多步驟。單例模式下調用的實例化方式有autowireConstructor自動裝配構造方法,這個我猜測就是@Autowire註解底層的實例化部分。其他實例化方式有:無參構造實例化,工廠實例化,Bean實例化。我到這裏只查看了無參構造的底層是使用的反射進行的實例化,解決了困惑。

其他踩坑:使用Nginx服務器搭建:前端ajax請求無法訪問後端
解決:1.排查錯誤原因,前端瀏覽器報錯是未連接到服務器
2.根據錯誤原因可能是:服務器端未啓動,服務器端拒絕連接當前前端服務器的請求 。檢查後發現啓動日誌正常。並且我並未對服務器進行配置。
3.此處一度懷疑是ajax請求出問題了。最後度娘找到了前後端分離出現的跨域問題。Springboot中可以設置一個允許請任何求頭訪問的註解。
4.因爲在每一個controller都寫一次註解過於重複,SpringBoot也可以使用一個通用的跨域配置解決問題。

分佈式環境的部署

項目完成後需要進行一個環境的搭建,先看一下項目架構:
在這裏插入圖片描述
服務器: 1臺 nginx 服務器 ,2臺秒殺服務器,1臺mysql ,1臺Redis
問題:實行過程中啓動2臺1核1G虛擬機CPU佔用就滿了。
解決: 虛擬化技術不止有虛擬機,我認爲容器技術也是一個微型虛擬機。藉助docker這樣的容器可以達到分佈式模擬的效果,對於無力購買服務器的學生黨來說是一個非常不錯的選擇。

分佈式環境模擬容器部署詳細方案

準備:創建一個Centos的虛擬機,安裝一個docker 環境。
秒殺服務器的容器部署
SpringBoot由於是一個內嵌tomcat的框架,可以直接使用命令行啓動,所以我們只需要使用 docker pull 一個java的鏡像下來。將 SpringBoot打包發送到虛擬上進行容器部署。
部署步驟

  1. 將jar包使用 ftp 發送到服務器
  2. 運行一個 java環境的容器
  3. 將容器複製進入容器內部
  4. 進入容器內部
  5. 使用 jar 命令啓動
    2臺秒殺服務器均採用這種方法進行部署

踩坑

打包踩坑:jar 命令打包運行顯示 找不到主類
解決:這樣的問題應該是編譯後找不到main()函數的問題,百度查找看一下SpringBoot的入口函數的配置即可解決問題。
容器踩坑:容器網絡不通
解決:查看《Docker實戰》中的網絡相關知識,重新部署了含有網絡配置的容器。重複執行上述步驟。

Nginx服務器的容器部署

項目的所有請求都發到Nginx服務器,使用Nginx的動態和靜態分離提高訪問效率。而Nginx服務器使用的是反向代理來代理秒殺服務器。負載均衡是使用的輪詢訪問策略配置的,每次請求都是交替代理到秒殺服務器上的。
問題:Nginx的高性能是如何實現的?
解決:瞭解了Nginx的一些進程模型的設計如:epoll模型,master-worker模型。
未解決:
靜態資源訪問拋出異常

Resource interpreted as Stylesheet but transferred with MIME type
text/plain:

百度到Nginx配置了靜態資源,但是沒有效果。後來查到去掉html上的 後就解決了。但是原因未找到。

Mysql服務器和Redis的容器部署

Mysql服務器和Redis只要注意網絡配置就行。
Mysql要將本地sql的發送到虛擬機並複製到容器內部,然後進入容器內部執行sql文件生成數據庫。

網絡相關說明

docker 容器網絡配置後所有的容器會使用一個默認的網段。相當於實際生產環境中的內網。外網訪問Nginx ,其他服務器的訪問使用內網環境。
未解決問題:Nginx安全性問題

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