秒殺系統——整體流程

技術點:Thymeleaf    springboot  jsr303 mybatis rabbitmq   redis  druid

總體流程:

                

 

大併發的瓶頸:數據庫。所以我們處理大併發的出發點就是怎麼減少對數據庫的訪問,現在能夠想到的就是加各種各樣的緩存來減少對數據庫的訪問。比如頁面緩存,url緩存,對象緩存,redis緩存等等

優化的方向:緩存優化,消息隊列

 

登錄模塊:

  

 

 

秒殺模塊:

          

 

 

頁面優化技術:

     

 

 

 

接口優化:

   

 

 

安全優化:

   

 

 

爲什麼明文密碼要進行兩次md5?

   1.用戶/客戶端:pass = md5(明文+固定salt)

  2.服務端:pass = md5(用戶輸入+隨機salt)

        如果用戶登錄時不做任何處理密碼是明文的,那麼密碼就會在網絡上傳輸,假如被別人截取到了,那麼就能得到用戶的密碼,所以要對用戶的密碼進行一次md5,然後加密後的密碼再發送給服務端,服務端接收到用戶端傳過來的經過md5的密碼後要再對密碼隨機生成一個salt,然後和用戶的md5的密碼進行一個拼裝,再做一次md5,然後把再次md5後的密碼和salt一起寫入數據庫中。主要是基於安全方面的考慮,第一次md5是爲了防止用戶的密碼在網絡中進行傳輸,第二次md5是因爲假如數據庫的數據出現了泄露,爲了避免這個時候用戶的密碼被通過反MD5推出來,所以就再進行了一次md5,雙重保險

 

分佈式session?

   首先登錄成功後會爲用戶生成一個token,我這裏用的是uuid,然後會將token存儲在redis裏也就是緩存中,因此就實現了分佈式session

 

數據庫設計?

  商品表      秒殺商品表          訂單表      秒殺訂單表

  設置秒殺商品表而不是直接在商品表後面增加一次字段是因爲秒殺系統很有可能不只一次,而是有很多次,所以假如是在商品表裏的話,以後這樣的活動越來越多,那麼表就越來越難以維護,所以就新起了一個秒殺商品表

 

 

壓力測試?

    當併發量達到1000時,qps是80左右(也就是說1000個請求進來,這個時候只有80多個請求能夠訪問成功),因爲此時我做的就是查詢商品,查詢商品是直接去的mysql查詢的,所以mysql的性能成了瓶頸。我在進行壓測的時候同時運行了top指令,然後看到cpu的絕大部分都是使用在了mysql上,所以可以確定qps低的原因就是mysql的原因。然後我查詢用戶信息時發現qps達到了800,性能提高了10倍左右,原因就是用戶所需要的token是存放在redis裏的,由此說明了redis的速度比mysql快,性能更強,此時是同一個用戶發出多次請求

 redis的壓測:redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000       也就是100個併發連接,100000個請求,經過壓測發現1.6s左右可以完成100000個請求,qps達到了60000多

  最開始:當線程數爲5000,循環數10次時(也就是一共50000),訪問商品列表時qps爲1300左右。

                    然後開始壓秒殺接口,5000個併發,跑了10次(一共50000),這個時候qps爲1300左右。

 

超賣問題?

   在上面的高併發情況下進行秒殺發現了數據庫的庫存變爲負數了,也就是出現了超賣的情況。超賣的原因就是在判斷庫存的時候假如這個時候只有1個商品,但是兩個線程同時到達,這個時候進行判斷因爲庫存爲1大於0,所以兩個線程都認爲自己可以繼續進行秒殺,然後假如兩個線程都沒有創建訂單,那麼就都會秒殺成功,減庫存,所以就出現了超賣現象

 

接口優化?

   首先是解決超賣問題,先在sql語句中設置stock>0,這樣首先保證商品不會變爲負數,然後就是爲了避免一個用戶多次秒殺,再在數據庫上加上唯一索引。這兩步做完後就是秒殺的優化了,爲了優化秒殺,我做了這幾步:在秒殺系統啓動時將庫存預緩存到redis中,然後當秒殺請求到達時先在redis中進行預減庫存,這個時候使用了消息隊列rabbitmq來實現異步操作,當用戶的請求到達消息隊列中就返回正在秒殺中,這樣就能夠實現異步操作,同時通過消息隊列避免大量的請求同時到達數據庫導致數據庫承受不住,在等待過程中消費者會判斷是否已經秒殺過了或者是否賣完,如果是上面兩種情況就直接返回秒殺失敗,這裏設置了3個狀態碼,-1就表示秒殺失敗,0就是正在秒殺(也就是客戶端正在做輪詢),1就是秒殺成功。當輪詢到某個客戶端時,纔會真正爲它生成訂單,然後訂單支付以後,mysql庫存纔會減1。我這裏也定義了一個本地標識(map)來減少對redis的訪問,因爲假如我只有10件庫存,然而有10000個請求,那麼其實11個以後的請求都是無效的,假如這10000個請求都去訪問redis,說實話還是會有消耗的,爲了解決這個問題,我設置了一個hashMap來標識,key爲goodsId,value爲boolean類型的變量,初始值爲false,當第一次防線庫存小於0時,就設置boolean爲true,然後後面進行秒殺的請求直接判斷髮現該goods的value爲true,就直接返回了,節約了去訪問redis的時間和消耗。

      但是我發現其實性能只是從800提升到了2400,大概提升了3倍,我自我覺得qps提升有點小,不應該這麼少的,後來通過top命令去查看cpu使用情況,發現java,jmeter,redis,mysql都佔用了很多的cpu,估計是因爲我是單機進行的,所以四核cpu都快被壓榨完了,假如mysql,jmeter,redis是放在不同的機器上,應該qps提升會大更多

 

 

 

安全方面的優化?

  1.秒殺接口地址的隱藏

  2.數學公式驗證碼

  3.接口限流防刷

因爲前端的html的源碼是透明的,可以直接看到源碼,也就是能看到你的url請求,參數,訪問方式都能看到,這個時候就算開始秒殺按鈕變灰了,但是也能夠直接通過輸入真正的秒殺url地址來實現在秒殺時間段外的秒殺,僅僅靠前端是不能避免這種情況發生的,所以前端的校驗不完美,安全校驗依然是主要放在後端進行

  秒殺接口地址的隱藏?

     思路:秒殺開始之前,先去請求接口獲取秒殺地址

                 1.接口改造,帶上PathVariable參數

                 2.添加生成地址的接口

                3.秒殺收到請求,先驗證PathVariable

每次進行一次秒殺都會生成一個不同的秒殺地址,我是通過uuid來生成一個額外的path,然後在秒殺的時候url要帶上這個path,大多數情況下uuid隨機生成的能保證不一樣,然後再將這個path給緩存到redis中去,這樣當用戶訪問的時候就不是直接暴露原來的地址了,而是加上了url後的地址,就實現了秒殺接口地址的隱藏,然後每個用戶想要訪問秒殺接口時都要先從redis中獲得該path,真正秒殺的時候判斷是否有該地址,有的話才能進行下面的秒殺活動

      

數學公式驗證碼?

    思路:點擊秒殺之前,先輸入驗證碼,分散用戶的請求

                   1.添加生成驗證碼的接口

                   2.在獲取秒殺路徑的時候,驗證驗證碼

                   3.ScriptEngine的使用

 

前端的操作:首先驗證碼都給隱藏起來,假如是在秒殺進行中,那麼驗證碼就都給顯示出來,加入秒殺結束了,那麼又隱藏起來

 

 

接口防刷限流?

   思路:對接口做限流

                 將用戶對接口的訪問次數緩存在redis中

                 可以通過攔截器來進行

                 自定義一個註解,通過註解的方式來設置,因爲其實這個限流是與業務無關的但是卻會對業務產生影響的,這裏使用了切面的思想,定義一個AccessLimit註解,然後裏面參數是時間,最大訪問次數,是否需要登錄

               

 

  

  整體完成

 

 

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