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

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




