教你2種常用的電商高并發處理解決方案

網站架構師面臨的最大挑戰之一就是并發。自Web服務開始以來,并發水平一直在不斷增長,一個主流網站同時服務十萬甚至數百萬用戶,這并不罕見。
就目前應用廣泛的電商系統來說,各種營銷場景的增加,讓電商系統高并發也成為一種必然。為此,本文將給大家帶來2種高并發解決方案,希望能為電商系統實現高并發提供一些靈感。
1、多級緩存
2、Nginx 限流
本文我們將以CRMEB商城為例,了解電商中常見的2種高并發方案
一、多級緩存?
我們以CRMEB Pro版首頁為例
在上圖中,我們可以看到商品的分類變化不大,我們可以把它存儲在緩存中,這樣會大大減輕數據庫的壓力,這種情況我們可以用redis進行緩存,但是有時候電商網站的并發數只靠redis,會使redis的壓力太大。這時我們在這里引入一個概念:多級緩存。
1. 什么是多級緩存
為了解決上述問題,我們在redis的基礎上增加了另一個nginx緩存。這時候用戶訪問我們的網站,會先訪問nginx緩存。如果nginx緩存不存在,他們會再次訪問redis緩存。如果redis也不存在,我們最終會訪問MySQL來獲取數據。這樣一來redis的壓力就大大降低了,然后nginx緩存和redis緩存就形成了多級緩存。
2、多級緩存怎么實現
了解了多級緩存的概念,我們該怎么去實現多級緩存呢?
我們可以使用OpenResty實現,這是一個基于Nginx和Lua的高性能Web平臺,它將Nginx與大量復雜的Lua庫、第三方模塊和大多數依賴項集成在一起。Lua是一種輕量級緊湊腳本語言,用標準C語言編寫,以源代碼的形式開放。它被設計成嵌入在應用程序中,從而為應用程序提供靈活的擴展和定制功能。在實際操作中,我們通常使用Lua腳本來訪問Nginx緩存、Redis緩存和MySQL。流程圖如下:
圖片來自網絡,侵權聯系刪除
但是,在上述過程中,我們會發現一個問題。當Redis緩存已經存在時,數據庫中的數據已經更改。此時所有用戶都訪問緩存的數據,那么如何解決這個問題呢?
3、Redis 緩存同步 MySql 數據
我們可以通過 canal 解決上面的問題。canal 是一個用來監控數據庫數據的變化的工具,可以在Mysql 數據更新時獲取其更新的數據。
解決方案:當Mysql數據發生變化時,我們可以通過canal微服務或者用RocketMQ或Kafka配置MQ模式來改變redis中的緩存數據,使Redis中的緩存數據與Mysql中的數據保持一致。??????
二、Nginx 限流
平常情況下,首頁的并發量會比較大,即使使用多級緩存,當用戶不斷刷新頁面時,或者大量惡意請求首頁,也會對系統造成影響。這時候就需要采用限流方案了。
1、什么是限流
顧名思義,限流就是限制流量。比如你的手機流量包只有1 G的流量,用完了就沒了。這時候我們就可以說我們的流量被限制了,這就叫限流。
常見的限流有漏桶算法
圖片來自網絡,侵權聯系刪除
算法思路:
水(請求)從上方倒入水桶,從水桶下面流出(被處理);
來不及流出的水,儲存在水桶(緩沖器)里,以固定的速率流出;
水桶滿后水溢出(丟棄)。
這個算法的核心是:緩存請求,統一處理,直接丟棄冗余請求。
2、nginx 限流的方式
控制速度
我們先來說第一種,控制速度。Nginx 控制速率主要使用的是漏桶算法,這樣能夠強行保證請求的實時處理速度不會超過設置的閾值。
配置 OpenResty 中 Nginx 的配置文件,使在訪問 ip/test1 地址時進行限流,該地址會訪問一個 Lua 腳本
user root root;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#限流設置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /test1{
#使用限流配置
limit_req zone=contentRateLimit;
content_by_lua_file /root/lua/test1.lua;
}
}
}
配置說明:
binary_remote_addr:是一種key,意思是基于 remote_addr(客戶端IP) 來做限流,binary_ 的目的是壓縮內存占用數。
zone:定義了一個共享內存區域來存儲訪問信息。
contentRateLimit:10m 表示一個大小為10M,名字為 contentRateLimit 的內存區域。1M可以存儲 16000 IP地址的訪問信息,10M可以存儲16W IP地址訪問信息。
Rate:用于設置最大訪問速率,rate=10r/s 表示每秒最多處理10個請求。
Nginx:實際上以毫秒為粒度來跟蹤請求信息,因此 10r/s 實際上是限制每100毫秒處理一個請求。這意味著,自上一個請求處理完后,若后續100毫秒內又有請求到達,將拒絕處理該請求。
限流速度為每秒 10 次請求,如果有10次請求同時到達一個空閑的 nginx,他們都能得到執行嗎?
事實上,漏桶漏出請求是勻速的。10r/s是怎樣勻速的呢?每100ms漏出一個請求。在這個配置中,桶是空的,所有不能實時漏出的請求,會被拒絕掉。所以如果10次請求同時到達,那么只有一個請求能夠得到執行,其它的,都會被拒絕。這不太友好,大部分業務場景下我們希望這10個請求都能得到執行。
處理突發流量
上述情況稱為突發流量,上面的例子僅限于10r/s,如果正常流量突然增加,超出的請求會被拒絕,無法處理突發流量,可以結合 burst 參數使用來解決該問題。
修改上文中的 Nginx 配置文件
server {
listen 80;
server_name localhost;
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
burst 譯為突發、爆發,表示在超過設定的處理速率后能額外處理的請求數,當 rate=10r/s 時,將1s拆成10份,即每100ms可處理1個請求。
此處,burst=12,若同時有12個請求到達,Nginx 會處理第一個請求,剩余11個請求將放入隊列,然后每隔100ms從隊列中獲取一個請求進行處理。若請求數大于12,將拒絕處理多余的請求,直接返回 503。
不過,單獨使用 burst 參數并不實用。假設 burst=50 ,rate依然為10r/s,排隊中的50個請求雖然每100ms會處理一個,但第50個請求卻需要等待 50 * 100ms即 5s,這么長的處理時間自然難以接受。
因此,burst 往往結合 nodelay 一起使用。
繼續修改上文中的 Nginx 配置文件
server {
listen 80;
server_name localhost;
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
平均每秒允許不超過10個請求,突發不超過20個請求,并且處理突發20個請求的時候,沒有延遲,等到完成之后,按照正常的速率處理。
如上兩種配置結合就達到了速率穩定,但突然流量也能正常處理的效果。
控制并發量(連接數)
nginx 還提供了利用連接數限制某一個用戶的ip連接的數量來控制流量。一種是限制固定連接數,第二種是限制每個客戶端IP與服務器的連接數,同時限制與服務器的連接總數。
注意:并非所有連接都被計算在內,只有當服務器正在處理請求并且已經讀取了整個請求頭時,才會計算有效連接。
配置限制固定連接數:
http {
include mime.types;
default_type application/octet-stream;
#限流設置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;
#根據IP地址來限制,存儲內存大小10M
limit_conn_zone $binary_remote_addr zone=addr:1m;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /test2 {
limit_conn addr 2;
content_by_lua_file /root/lua/test2.lua;
}
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
}
limit_conn_zone $binary_remote_addr zone=addr:10m; 表示限制根據用戶的IP地址來顯示,設置存儲地址為的內存大小10M
limit_conn addr 2; 表示同一個地址只允許連接2次。
限制每個客戶端IP與服務器的連接數,同時限制與服務器的連接總數:
http {
include mime.types;
default_type application/octet-stream;
#限流設置
limit_req_zone $binary_remote_addr zone=contentRateLimit:10m rate=10r/s;
#存儲個人請求IP的限流配置
limit_conn_zone $binary_remote_addr zone=perip:10m;
#整個location對應的請求并發容量配置
limit_conn_zone $server_name zone=perserver:100m;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location /test2 {
limit_conn perip 10;#單個客戶端ip與服務器的連接數"10"
limit_conn perserver 100; #限制與服務器的總連接數"100"
content_by_lua_file /root/lua/test2.lua;
}
location /test1 {
limit_req zone=contentRateLimit burst=20 nodelay;
content_by_lua_file /root/lua/test1.lua;
}
}
}
總結:我們在redis的基礎上增加了另一個nginx緩存就能實現多級緩存,使用漏桶算法可以實現限流的效果,再結合 burst 參數使用來解決突發流量的處理。這幾種方法相結合,就能實現電商系統高并發。如果還有不明白之處可以在下方留言,大家一起學習討論。
