九章算法電商秒殺系統 - Spring項目實戰2021

愛共享 愛生活 加油 2021

百度網盤javascript

提取碼:qhhv css

優化的點

  • 穩定不太變化的數據,減小數據查詢
  • redis解決超賣
  • rabbitMQ削峯和限流
  • nginx負載均衡
  • 解決負載均衡多個api之間session不一樣步的問題
  • nginx靜態資源緩存
  • nginx資源壓縮
  • cdn內容分發
  • 流量防刷以及反爬蟲

1.穩定數據優化方式

問題分析:電商和咱們平時app中,90% 以上都是數據讀取操做,在海量數下,數據庫最後可能成爲高併發的瓶頸,所以減小數據庫交互是首先須要考慮的事情。可是要區分靜態數據和動態數據。長期不變的數據,好比商品詳情,圖片,介紹等適合作緩存,可是評論就不適合作緩存。html

解決方案1:reids緩存

查詢一次數據庫後,把商品詳情,圖片,介紹等放入redis,而後下次redis有的話直接從reids查詢,避免再次查數據庫。
具體切換到 step2-靜態數據優化 分支java

git checkout step2-靜態數據優化

安裝redisnginx

docker pull redis;
docker run -d --name redis1 -p 6379:6379 redis --requirepass "123456"

安裝完redis,對應在yml中修改本身的redis配置,訪問下面地址,查看redis,就會發現redis中緩存了商品相關信息,具體實現,查看代碼,很簡單。git

http://localhost:8080/good?gid=2000

解決方案2:html頁面靜態化

頁面靜態化是指把動態頁面,jsp,freemarker等爲每個商品生成一個html靜態頁面。動態數據例如評論就在html頁面用ajax異步獲取。這樣,就直接訪問html避免了頁面數據查詢,解析的過程。web

邏輯流程
  • 首先把全部商品生成固定html,而後編寫定時器,把每5分鐘更新過的的商品從新查詢出來,從新生成固定的html頁面
  • 對於例如評論等動態數據,用ajax動態獲取
  • 由於是靜態頁面,須要使用nginx作頁面轉發
操做流程
  • 切換到 step2-靜態數據優化 分支,啓動項目
  • 執行http://localhost:8080/doStatic,修改doStatic方法中放靜態文件的目錄。並把static中layui的包拷到生成的html目錄,要引入。goods.ftl中路徑寫的./
  • 啓動nginx,腳本以下。
  • 訪問http://localhost/page/2000.html 就能出現頁面。

總結:http://localhost/page/2000.html這裏的流程
是首先去容器內部/usr/share/nginx/html/去找html文件,而後實際上是訪問到了掛載在本地生成html的目錄,而後html裏面發起ajax請求去獲取評論,實際是經過nginx把請求經過upstream轉發到本地。ajax

docker安裝nginx,指令以下:
--- nginx
docker pull nginx;
docker run -d --name nginx -p 80:80 -v /Users/along/websoft/nginx:/etc/nginx/conf.d -v /Users/along/Desktop/template:/usr/share/nginx/html nginx

上面啓動腳本意思是:映射80端口,而後掛載配置文件目錄和放html的目錄到nginx裏面
啓動腳本和html位置在要和上面啓動命令中的一致。

--- nginx 腳本
#由於容器內部須要訪問到外部的接口,因此須要在容器內容用ip+端口訪問本地接口
upstream seckill {
    server 192.168.5.3:8080;    
}
server {
    listen       80;
    server_name localhost;
    #把接口轉發到本地起的項目,個人是192.168.5.3:8080;  
    location / {
    proxy_pass http://seckill;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    #頁面轉發地址是容器內部地址  
    location /page {
       index index.html;
       alias /usr/share/nginx/html/;
    }
}
監聽80端口,訪問/放轉發到本地接口,訪問page的時候,定位到nginx的html放置目錄,而後以前掛載過這個目錄,就能訪問到本機的掛載目錄。

兩種方案總結:redis

  • 方案一把靜態數據存入到redis,操做簡單。可是仍是須要付出解析動態頁面的時間。
  • 方案二很是複雜,須要生成全部html,耗費空間,可是理論上,直接加載html是最快的。

2.redis解決秒殺超賣

現象分析

使用redis以前的通常作法是,查詢數據庫,若是剩餘大於0,就搶到商品,並把剩餘數量減一,並更新數據庫。算法

int num = goodDao.selectRate(num) ; 
if(num>0){
    //處理生成訂單邏輯
    num = num -1; 
    //更新數據
}

可是很明顯,查詢數量和更新數量以前是有時差的,有的請求執行到了生成訂單邏輯,東西搶到了,可是沒來得及更新數據庫,可是有的請求在這時獲取了剩餘數量,那麼就會超賣。
選取redis由於它是單線程模型,內存存儲,官網宣稱支持10w qps。並且天生分佈式支持。

解決方案:

邏輯流程

  • 每隔5秒定時器掃秒殺表t_promotion_seckill
  • 若是掃到開始秒殺,就構建一個商品的list隊列,key爲seckill:count:秒殺id ,有幾個商品,就往list裏面初始化幾個數據。
  • 當用戶點擊了秒殺按鈕就left pop出商品,並把搶到商品的用戶存到一個redis set中。彈出一個商品,就存一個userId.

操做流程:

  • 切換代碼分支到 step3-redis解決超賣
  • 相關新增代碼在定時器和GoodController的doOrder方法。
  • 定時器會掃到 t_promotion_seckill , 若是掃不到,請查看sql,修改數據的狀態爲0

3.rabbitMQ削峯和限流

現象分析

在搶購之後,每每會發起存數據庫,支付等操做,可是很明顯,以前的從redis中取商品,並添加中獎人到redis這個操做是很快的,可是訂單入庫,支付等操做是很慢的。假設處理取商品,存中獎人每秒能夠處理1000,可是處理訂單,支付等一秒處理100個,那麼,若是是一連串操做,很明顯,後面瓶頸,形成前面操做等待。嚴重的狀況,因爲支付過慢崩潰,致使前面一系列流程所有崩潰。
總而言之就是前臺業務處理能力和後臺業務能力不對稱所致使的

解決方案 - 使用rabbitMQ消息隊列

由於能夠分離業務,前臺業務處理快,儘管處理,而後後面生成訂單,支付等在mq中按本身能處理的量異步處理。
rabbitMQ是使用Erlang開發,也有本身的虛擬機,編譯後處處運行,並且編寫分佈式應用簡單。

邏輯流程

  • 當redis取出商品,添加中獎id後,往rabbitMQ隊列發送一條消息,這裏就是消息生成者
  • 而後在消息消費者那裏執行訂單入庫等邏輯

操做流程

  • 切換分支到
  • docker啓動一個rabbitMQ服務,並在rabbitMQ管理界面建立一個exchange-order的交換機,而且建立一個queue-order的隊列,把他們綁定
  • 在yml編寫rabbitMQ相關配置
  • 在PromotionSecKillService 編寫消息生產者
  • 在service編寫OrderConsumer消費者邏輯

docker起rabbitMQ

docker pull rabbitmq;
docker run -d -p 15672:15672  -p  5672:5672  
-e RABBITMQ_DEFAULT_USER=admin 
-e RABBITMQ_DEFAULT_PASS=admin 
--name rabbitmq 
rabbitmq

用admin和admin打開loaclhost:5672.

4.nginx負載均衡

LoadBalance:負載均衡,把任務分配到多個服務器上進行處理,進而提升效率,縮短執行時間。

當一臺機器性能到達極限的狀況下,橫向拓展機器的架構是很是好的,能在不改動代碼的狀況下成倍的提高效率。

邏輯流程:

  • nginx裏面配置各個服務地址,請求同一個api的時候,按必定的策略分發請求到不一樣服務。
  • 在docker啓動nginx

操做流程

  • 切換代碼到 step5-nginx負載均衡 分支,其中新增了一個 LBController 測試負載均衡的控制器。
  • 而後在啓動設置裏面設置 -Dserver.port=8081,-Dserver.port=8082,-Dserver.port=8083三個配置,啓動三個服務。
  • 而後啓動nginx
  • 調用 localhost/LB 這個地址,會發現,請求到不一樣的端口。

nginx的分發策略

  • Round-Robin,輪詢調度算法,當配置未指明的時候,默認就是輪詢算法。就想聽歌列表循環。
  • least-connected,下一次連接將被連接到活躍連接最少的機器上
  • ip-hash,利用IP地址哈希進行分配 。固定ip的請求分發到固定ip的服務
  • weight-balancing,按照權重分配,也就是權重越大,分到的請求越大。能力越大,責任越大。

我比較喜歡使用權重策略,基本都是根據服務器性能來分配。

nginx的配置以及啓動腳本。

#nginx.conf,配置中的server是本地ip,使用權重的策略
upstream lb {
    #least_conn ; 最少鏈接
    #ip hash ; ip策略
    #權重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

nginx在docker中的啓動腳本和上面相同。

5.解決負載均衡多個服務之間session不一樣的問題

問題分析

在部署的多個服務中若是使用session傳遞數據,可能存在獲取不到的狀況,由於假設在8081中執行登錄,session只會存在8081這個服務中,可是下次請求分發到8082或者8083就獲取不到session了。

解決方式

  • 利用redis存儲session的內容。
  • org.springframework.session 這個jar包能夠解決問題。只須要引入這個包,並在啓動類加上 @EnableRedisHttpSession 註解,無需修改任何代碼。

操做流程。

  • 切換代碼到 step6-解決各個服務間session不一樣步問題 分支
  • 和上一步負載均衡同樣啓動三個服務,開啓nginx
  • 首先把啓動類上的 @EnableRedisHttpSession 註釋掉,而後先請求 localhost/login?u=along,而後在屢次請求localhost/check發現,有時候能獲取到session,有時不能獲取到。
  • 而後把註解打開,再執行上面的操做,發現,每次都能獲取到session,查看redis,發現,redis多了session的信息。

6.nginx靜態資源緩存

現象分析

像js,css,圖片,字體等靜態資源,他們都是一個個的url,每次訪問的tamcat都會進行url解析和綁定。可是上線之後,它們幾乎是不會修改的。因此在高併發的狀況下很浪費資源。

處理方案

nginx區分靜態資源和接口url,把url接口送日後端服務器處理,靜態資源在nginx端緩存處理。
主要是修改nginx的配置,配置以下

#在nginx容器內的/home下面定義緩存文件夾。並設定2層目錄,緩存大小以及過時時間。
proxy_temp_path /home/nginx-temp;
proxy_cache_path /home/nginx-cache levels=1:2 keys_zone=lb-cache:100m inactive=7d max_size=20g;

upstream lb {
    #least_conn ; 最少鏈接
    #ip hash ; ip策略
    #權重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
#用正則匹配靜態資源,~*是不區分大小寫。
location ~* \.(gif|jpg|css|png|js|woff|html)(.*){
       proxy_pass http://lb;
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       #使用上面定義的緩存地址
       proxy_cache lb-cache;
       proxy_cache_valid 200 302 24h;
       proxy_cache_valid 301 5d;
       proxy_cache_valid any 5m;
       expires 90d;
    }
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

而後重啓nginx就行了,訪問之後,進入容器

docker exec -it nginx /bin/bash 
cd /home/nginx-cache 
若是上面的文件夾出現了兩層目錄的靜態文件,說明設置成功。

7.nginx資源壓縮

現象分析

當靜態資源被壓縮後,下降了後端tomcat的壓力,可是在實際開發過程當中,大量的壓力不在於處理速度,而是在於帶寬。帶寬決定了下載的總速度,在總速度不變的狀況下,資源傳到瀏覽器的大小約小越好。越快下載完資源,卡頓的越少。

解釋一下帶寬和下載速度的關係把

帶寬是網絡中某一點到另外一點所能經過的"最高數據率",也就是瞬時最高的下載速度。
關於帶寬和下載速度:首先了解一下存儲的單位換算

  • 1GB = 1024MB
  • 1MB = 1024KB
  • 1KB = 1024B
  • 1B = 8bits
    網絡傳輸是以bits爲單位,以1M帶寬爲例,也就是1M bits,也就是瞬時下載速度作多爲:
1M = 10^6;
bits換成B除以8,B換成kb除以1024
1M bits/s = 1000000bits/s = 125000 B/s ≈ 128kb/s

解決方案

nginx將靜態資源打包成gzip傳給瀏覽器,而後瀏覽器解壓縮gizp(這一步瀏覽器自動完成)獲取到資源文件。最少能較少30%內存,最多減小80%的內存損耗。

nginx配置

#打開gzip配置
gzip on;
gzip_min_length 1k;
gzip_types text/plain application/javascript text/css application/x-javascript font/woff;
#禁止IE 1-6 
gzip_disable "MSIE [1-6]\.";
gzip_buffers 32 4k;
#壓縮級別1-9,選1就好了,大小差異不大
gzip_comp_level 1;

#在nginx容器內的/home下面定義緩存文件夾。並設定2層目錄,緩存大小以及過時時間。
proxy_temp_path /home/nginx-temp;
proxy_cache_path /home/nginx-cache levels=1:2 keys_zone=lb-cache:100m inactive=7d max_size=20g;

upstream lb {
    #least_conn ; 最少鏈接
    #ip hash ; ip策略
    #權重
    server 192.168.5.3:8081 weight=4;
    server 192.168.5.3:8082 weight=1;
    server 192.168.5.3:8083 weight=2;
}
#用正則匹配靜態資源,~*是不區分大小寫。
location ~* \.(gif|jpg|css|png|js|woff|html)(.*){
       proxy_pass http://lb;
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       #使用上面定義的緩存地址
       proxy_cache lb-cache;
       proxy_cache_valid 200 302 24h;
       proxy_cache_valid 301 5d;
       proxy_cache_valid any 5m;
       expires 90d;
    }
location / {
    proxy_pass http://lb;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

在docker重啓nginx。訪問頁面,當看到瀏覽器,檢查看到css,js等的response header中,編碼爲gzip。就說明設置成功了。

8.cdn內容分發

現象分析

對靜態文件完成了gzip壓縮,可是對圖片等已經被壓縮過的資源gzip傳輸效果並很差,因此對圖片的帶寬消問題並無解決。

解決方案

圖片能夠考慮使用第三方cdn解決帶寬問題。

cdn簡介

cdn全稱Content dekivery network,就是內容分發網絡。
例如中國很大,北京和深圳就隔者2000多千米,若是北京訪問深圳的網絡,必然延時很高。cnd就是把內容放在一臺中心服務器上,而後各地有不少節點服務器,中心服務器會把內容同步到各地的節點服務器,而後就進訪問資源。

優勢

  • 就近訪問,下降網絡延時
  • 把對帶寬的壓力轉移到了第三方
  • 數據多備份,一個服務掛了能夠切換到另外一個,就就是高可用。

使用

能夠買阿里雲,騰訊雲,七牛的cdn服務。
主要流程是買服務,下載客戶端,把圖片等資源上傳到客戶端,自動同步,而後爲oss配置cdn,進行域名解析。就能經過域名訪問到圖片,最後就去修改項目中靜態資源的路徑。

9.流量防刷以及反爬蟲

流量防刷

有些人會惡意一直訪問網站,甚至用腳本去刷頁面。
因此在必定時間內只容許一個用戶訪問固定的次數,一旦超過了就不容許訪問。

爬蟲

會有人用爬蟲,爬網站,利用超連接分析內容,提取網站的重要信息,不只拷貝走了數據,並且,還很是消耗服務器資源。

解決方案

  • 在代碼中,編寫攔截器
  • 對每一個用戶,記錄ip對應的訪問次數,在redis中用ip+請求頭信息做爲key,存1分鐘內訪問次數,每次訪問count++
  • 若是超過設置的訪問次數,就再也不容許訪問。
  • 每一分鐘清空一次key
操做流程
  • 切換代碼到step7-流量防刷與反爬蟲分支
  • 啓動nginx和三個端口分別啓動項目
  • 訪問localhost/lb,並屢次刷新
  • 觀察redis是否會有key,以及是否會返回禁用的信息
總結

終於寫完啦。這篇文章主要是對代碼和網絡等幾個層面作了優化,主要使用了nginx,redis,rabbitMQ等熱門工具,但願對各位有幫助,若是有問題,歡迎指正,一塊兒討論。

相關文章
相關標籤/搜索