大家好,我是Leo
前段時間介紹了MySQL,Redis的相關技術。大概告一段落了,只能説對這兩塊技術調優,原理有了初步的認識,後續整個技術棧學的差不多的時候會回來做第二版的修訂。
推薦閲讀
3萬字聊聊什麼是Redis(完結篇)
3萬字聊聊什麼是MySQL(初篇)
福利
每1-2週會選取前3名發一些書籍
每個節日會選取前10名發一些書籍
麻煩這三位在我公眾號右下角點擊聯繫我,加一下微信,發波書了
最近不是在學習MySQL,Redis嘛。我們就找幾本經典的書送一下
比如:高性能MySQL,Redis設計與實現,Redis深度歷險,
或者數據結構類的 劍指offer,代碼隨想錄
前言
任何技術的總結都有一個業務背景,簡單介紹一下有助於更好的理解,順便輸出一下自己的學習思想。
之前接的一個跨境電商項目基本需求均已實現。比如購買需求,積分,會員,購物車,用户,廣告,消息通知等。
下面開始考慮一些特價,秒殺,限時,推薦類的功能了。平白無故讓我寫,從0到1肯定不會,自己一點一點從淘寶10年前的技術發展歷程開始研究肯定不划算。問題來了如何上手開發設計一個秒殺系統呢?
當我有這個想法的時候,我也是瘋了。主要有如下幾點
- 老闆沒要求,我自己的事情忙的要死還要給私活白寫一個功能
- 開發一個秒殺至少要了解淘寶的發展歷程,秒殺的設計思想
- 硬件設施的落地實現
- 原有架構的數據保存模式變更
主要還是沒錢支撐我這個想法,畢竟不是花點時間寫代碼就OK的 。所以綜上所述,購買相關秒殺的課程,學習前輩們的思想,再斟酌自己的系統體量進行開發是我接下來要做的事情
學習思想
聊到學習,相信很多人會是下面這個狀態。
當下信息化太爆炸,很多APP,系統根據個人的喜好很容易就被他影響了。我始終認為應該逼自己一把,給自己一些動力。做時間的掌控者,做人生的掌控者。
關於學習的順序,建議一點一點學習當下切合自己能力的技術。很多人基礎還沒過,天天吵着要學微服務,分佈式,大併發。然後在羣裏我問他為啥,他説要拿阿里20K。
20K哪有那麼容易哦,技術沒有捷徑,一步一步才是王道。C#轉Java已經快半年了,我才剛學微服務。我學微服務還是因為我leader開會的時候説後續可能要上微服務我才提前學的。要不然我吃飽了撐的沒事啃微服務這個大骨頭。
<img src="https://pic.imgdb.cn/item/61d8f74f2ab3f51d91af522c.jpg">
秒殺系統的5個原則
收!回到正題!
數據儘量要少
數據儘量要少的話主要有兩點:用户發送請求附帶的數據儘量要少和服務端給客户端返回的數據儘量要少 絕不傳無關緊要的字段。
因為數據在網絡上傳輸需要時間,其次不管是請求數據還是返回數據都需要服務器做處理,而服務器在寫網絡時通常都要做壓縮和字符編碼,這些都非常消耗 CPU,所以減少傳輸的數據量可以顯著減少 CPU 的使用。
第二層含義是完成某些業務,我們從數據庫讀取查值與保存的數據,這個是涉及到與數據庫交互的。調用其他服務會涉及數據的序列化和反序列化,而這也是 CPU 的一大殺手,同樣也會增加延時。而且,數據庫本身也容易成為一個瓶頸,所以和數據庫打交道越少越好,數據越簡單、越小則越好
下面舉一些例子,比如在下單這裏,要根據體量進行抉擇,到底是由前端傳給後端對應的值來做實體類拼接處理,還是前端只傳ID。根據用户的ID找到用户的等級,根據等級再找到這個用户對應的商品價格呢?
還有下列的積分ID,到底是前端傳呢,還是隻傳用户ID,反查積分ID呢?
還有是保存用户的信息到緩存直接進行Redis incr,decr 還是反查一遍呢?如果交給緩存,那麼一致性如何保證? (可以參考我之前Redis的第七篇文章)
上述只是一些思考點,不是最優的方案,代碼還沒優化,只是一階段的開發。
請求數儘量要少
請求數當時我看的時候第一時間沒反應過來,這裏我用圖文的方式介紹。
用户請求的頁面返回後,瀏覽器渲染這個頁面還要包含其他的額外請求,比如説,這個頁面依賴的 CSS/JavaScript、圖片,以及 Ajax 請求等等都定義為“額外請求”,這些額外請求應該儘量少。因為瀏覽器每發出一個請求都多少會有一些消耗,例如建立連接要做三次握手,有的時候有頁面依賴或者連接數限制,一些請求(例如 JavaScript)還需要串行加載等。
如果不同請求的域名不一樣的話,還涉及這些域名的 DNS 解析,可能會耗時更久。所以我們要記住的是,減少請求數可以顯著減少以上這些因素導致的資源消耗。
路徑儘量要短
路徑這裏主要就是關於節點數了。這些節點可以表示為一個系統或者一個新的 Socket 連接(比如代理服務器只是創建一個新的 Socket 連接來轉發請求)。每經過一個節點,一般都會產生一個新的 Socket 連接。縮短請求路徑不僅可以增加可用性,同樣可以有效提升性能(減少中間節點可以減少數據的序列化與反序列化),並減少延時(可以減少網絡傳輸耗時)。
依賴儘量要少
依賴這裏主要就是 秒殺系統跟業務系統不同的是,業務系統追求穩定,秒殺系統在那一刻追求的是效率。這裏也類似CP和AP的抉擇。
如下圖所示,我們可以想成秒殺場景,如果某一塊服務掛了,難道要影響整個系統嘛,肯定是不行的,所以這裏就要注意強依賴,弱依賴的問題。
杜絕單點
這個沒啥説的,萬一出現意外,宕機了,不僅影響使用,有可能數據都會有影響,所以一定要採用多服務,分佈式,集羣思想設計系統。
學習淘寶
淘寶早期秒殺系統架構
快速搭建一個簡單的秒殺系統,只需要把你的商品購買頁面增加一個“定時上架”功能,僅在秒殺開始時才讓用户看到購買按鈕,當商品的庫存賣完了也就結束了。這就是當時第一個版本的秒殺系統實現方式。
體量的增加10w/s
隨着體量的增加,也遇到了很多瓶頸。因此把秒殺模塊獨立成一個秒殺系統。這樣就可以專門對秒殺系統做一些有針對性的優化處理。
在部署上也可以把秒殺系統獨立部署在一個集羣,這樣即可大流量把秒殺模塊幹倒了也不至於影響正常商品購買的機器負載。
在熱點數據比如庫存數量單獨放在一個緩存系統中,以提高讀性能,減少與數據庫的交互。
在安全上,可以增加一下秒殺滑塊,防止有秒殺腳本搶單,影響正常的用户體驗需求。
體量的增加100w/s
在界面上進行一些動靜分離,這樣可以讓用户秒殺時,不需要刷新整個頁面。
在服務端對秒殺商品進行本地緩存,不需要再調用依賴系統的後台服務獲取數據,甚至不需要去公共的緩存集羣中查詢數據,這樣不僅可以減少系統調用,而且能夠避免壓垮公共緩存集羣。
在安全上,增加系統限流保護,防止最壞情況發生。
結尾
架構是一種平衡的藝術,而最好的架構一旦脱離了它所適應的場景,一切都將是空談。這裏所説的幾點都只是一個個方向,你應該儘量往這些方向上去努力,但也要考慮平衡其他因素。
動靜分離
什麼是動靜數據
動靜分離並不是真正的活數據,死數據。所謂“動靜分離”,其實就是把用户請求的數據(如 HTML 頁面)劃分為“動態數據”和“靜態數據”。
簡單來説,“動態數據”和“靜態數據”的主要區別就是看頁面中輸出的數據是否和 URL、瀏覽者、時間、地域相關,以及是否含有 Cookie 等私密數據
- 比如百度的熱搜前十一樣,不管是任何人訪問,他都是一樣的。它是一個典型的靜態數據,但是是動態界面。
- 淘寶首頁的商品,會根據每個人的特徵推送相關感興趣的商品。這個就是動態數據,動態界面(但是不典型很多具有推薦算法的APP都有這類功能)
動態與靜態並不是説數據本身是否動靜,而是數據中是否含有和訪問者相關的個性化數據。
靜態數據優化
理解了動態數據和靜態數據,我估計你們的腦海中就有一個優化的方案了
- 可以把用户的靜態數據緩存到Redis中,或者緩存到離用户最近的地方。比如用户瀏覽器,CDN。
- 直接緩存HTTP連接
- 利用Web服務器處理大併發的靜態文件請求。比如我們常用的nginx,apache
直接緩存HTTP連接我估計很多人不明白,這裏介紹一下。靜態化改造是直接緩存HTTP而不是緩存數據。Web 代理服務器根據請求 URL,直接取出對應的 HTTP 響應頭和響應體然後直接返回,這個響應過程簡單得連 HTTP 協議都不用重新組裝,甚至連 HTTP 請求頭也不需要解析。
動靜分離改造
電商為例(淘寶,京東)
博客為例(掘金,CSDN)
- 我們可以緩存URL,使URL唯一化。可以見上述的天貓,京東,CSDN,掘金。緩存的key就是每一個唯一的一個編號id
- 商品/文章是動態的但是每個用户登錄的信息驗證都是靜態的,所以我們可以把這部分的服務獨立出來。
- 異步化地域因素。詳情頁面上與地域相關的因素做成異步方式獲取,當然你也可以通過動態請求方式獲取,只是這裏通過異步獲取更合適。(無關緊要的數據)
- 分離時間因素。服務端輸出的時間也通過動態請求獲取。
如下兩張參考圖可以理解下商品詳情部分 分離的思想
分離出動態內容之後,我們可以將這些信息 JSON 化(用 JSON 格式組織這些數據),以方便前端獲取。
動態數據優化
- ESI 方案(或者 SSI):即在 Web 代理服務器上做動態內容請求,並將請求插入到靜態頁面中,當用户拿到頁面時已經是一個完整的頁面了。這種方式對服務端性能有些影響,但是用户體驗較好。
- CSI 方案。即單獨發起一個異步 JavaScript 請求,以向服務端獲取動態內容。這種方式服務端性能更佳,但是用户端頁面可能會延時,體驗稍差。
動靜分離的架構方案
- 實體機單機部署;
- 統一 Cache 層;
- 上 CDN。
實體單機部署
這種方案是將虛擬機改為實體機,以增大 Cache 的容量,並且採用了一致性 Hash 分組的方式來提升命中率。這裏將 Cache 分成若干組,是希望能達到命中率和訪問熱點的平衡。Hash 分組越少,緩存的命中率肯定就會越高,但短板是也會使單個商品集中在一個分組中,容易導致 Cache 被擊穿,所以我們應該適當增加多個相同的分組,來平衡訪問熱點和命中率的問題。
優點
- 沒有網絡瓶頸,而且能使用大內存;
- 既能提升命中率,又能減少 Gzip 壓縮;
- 減少 Cache 失效壓力,因為採用定時失效方式,例如只緩存 3 秒鐘,過期即自動失效。
缺點
- 一定程度上也造成了 CPU 的浪費,因為單個的 Java 進程很難用完整個實體機的 CPU。
- 一個實體機上部署了 Java 應用又作為 Cache 來使用,這造成了運維上的高複雜度
如下圖所示,單機部署的架構圖
統一 Cache 層
所謂統一 Cache 層,就是將單機的 Cache 統一分離出來,形成一個單獨的 Cache 集羣。統一 Cache 層是個更理想的可推廣方案,該方案的結構圖如下
優點
- 單獨一個 Cache 層,可以減少多個應用接入時使用 Cache 的成本。這樣接入的應用只要維護自己的 Java 系統就好,不需要單獨維護 Cache,而只關心如何使用即可。
- 統一 Cache 的方案更易於維護,如後面加強監控、配置的自動化,只需要一套解決方案就行,統一起來維護升級也比較方便。
- 可以共享內存,最大化利用內存,不同系統之間的內存可以動態切換,從而能夠有效應對各種攻擊。
缺點
- Cache 層內部交換網絡成為瓶頸;
- 緩存服務器的網卡也會是瓶頸;
- 機器少風險較大,掛掉一台就會影響很大一部分緩存數據。
要解決上面這些問題,可以再對 Cache 做 Hash 分組,即一組 Cache 緩存的內容相同,這樣能夠避免熱點數據過度集中導致新的瓶頸產生。
上CDN
在將整個系統做動靜分離後,我們自然會想到更進一步的方案,就是將 Cache 進一步前移到 CDN 上,因為 CDN 離用户最近,效果會更好。
採用這種方案,主要有三個問題需要解決一下
- 失效問題
- 命中率問題
- 發佈更新問題
失效和Redis有點類似,如果不及時更新緩存的話有可能在那段時間內用户看到的值都是錯位的,如果及時更新的話就需要保證CDN可以在秒級時間內讓分佈在全國各地的Cache同時失效。這個要求還是比較高的。
命中率是緩存的命根。一個緩存系統命中率過低,那和沒有緩存系統沒什麼兩樣反而還要消耗自身性能去查詢緩存系統。
綜上所述,把商品詳情,博客詳情等放到全國所有CDN節點上不太現實,我們可以採用以下方案解決這一不足
- 靠近訪問量比較集中的地區
- 離主站相對較遠;
- 節點到主站間的網絡比較好,而且穩定;
- 節點容量比較大,不會佔用其他 CDN 太多的資源。
- 節點不要太多。
基於上面幾個因素,選擇 CDN 的二級 Cache 比較合適,因為二級 Cache 數量偏少,容量也更大,讓用户的請求先回源的 CDN 的二級 Cache 中,如果沒命中再回源站獲取數據,部署方式如下圖所示:
使用 CDN 的二級 Cache 作為緩存,可以達到和當前服務端靜態化 Cache 類似的命中率,因為節點數不多,Cache 不是很分散,訪問量也比較集中,這樣也就解決了命中率問題,同時能夠給用户最好的訪問體驗,是當前比較理想的一種 CDN 化方案。
除此之外,CDN 化部署方案還有以下幾個特點:
- 把整個頁面緩存在用户瀏覽器中;
- 如果強制刷新整個頁面,也會請求 CDN;
- 實際有效請求,只是用户對“刷新搶寶”按鈕的點擊。
這樣就把 90% 的靜態數據緩存在了用户端或者 CDN 上,當真正秒殺時,用户只需要點擊特殊的“刷新搶寶”按鈕,而不需要刷新整個頁面。這樣一來,系統只是向服務端請求很少的有效數據,而不需要重複請求大量的靜態數據。秒殺的動態數據和普通詳情頁面的動態數據相比更少,性能也提升了 3 倍以上。所以“搶寶”這種設計思路,讓我們不用刷新頁面就能夠很好地請求到服務端最新的動態數據
二八原則
什麼是二八原則
世上上80%的財富掌握在20%的人手中,熱點數據也是一樣。幾千萬的商品,每天有幾百萬商品被幾十萬用户訪問。這類數據就是熱點數據。
在信息化時代,並不是所有的數據都是受人歡迎的,所以在處理系統熱點時間時,我們可以分為兩種情況
- 特定的熱點數據(比如推薦的商品)
- 沒有被推薦的數據,但是後期的發酵變成了熱點數據
從冷靜期到活躍期與活躍期
從不是熱點到變成熱點最典型的例子就是今日頭條,微博這類APP。如果一個新聞事件慢慢發酵,系統內沒有對這個事件緩存的話,有可能不一會系統就宕機了。
所以我們要先發現熱點。熱點分為熱點操作,熱點數據
- 熱點操作 用户在下拉刷新時,想看看自己留意的事件有沒有上熱搜。
- 熱點數據 上了熱搜的事件要不斷被用户訪問。
對系統來説就是 讀請求 和 寫請求 。這兩類的處理方式大相徑庭,不過讀更好處理一些,因為可以減少與數據庫的交互。而寫請求的瓶頸往往在存儲層。後續會展開介紹
熱點數據還分動態熱點數據(由外在因素引發的熱賣商品),靜態熱點數據(商家推薦的商品)
如何發現動態熱點數據?
- 構建一個異步的系統,它可以收集交易鏈路上各個環節中的中間件產品的熱點 Key,如 Nginx、緩存、RPC 服務框架等這些中間件(一些中間件產品本身已經有熱點統計模塊)。
- 將上游系統收集的熱點數據發送到熱點服務枱,然後下游系統(如交易系統)就會知道哪些商品會被頻繁調用,然後做熱點保護。
這是大一點的系統,對小型系統來説。可以通過ELK方案收集用户搜索商品的次數來決定是否為熱點數據
如何處理動態熱點數據?
主要有三種思路
- 優化
- 限制
- 隔離
優化
優化最簡單的方式就是直接把數據緩存了。如果熱點數據做了動靜分離,那麼可以長期緩存靜態數據。但是,緩存熱點數據更多的是“臨時”緩存,即不管是靜態數據還是動態數據,都用一個隊列短暫地緩存數秒鐘,由於隊列長度有限,可以採用 LRU 淘汰算法替換。
限制,限流
限制,限流更多的是一種保護機制,限制的辦法也有很多,例如對被訪問商品的 ID 做一致性 Hash,然後根據 Hash 做分桶,每個分桶設置一個處理隊列,這樣可以把熱點商品限制在一個請求隊列裏,防止因某些熱點商品佔用太多的服務器資源,而使其他請求始終得不到服務器的處理資源。
隔離
秒殺系統設計的第一個原則就是將這種熱點數據隔離出來,不要讓 1% 的請求影響到另外的 99%,隔離出來後也更方便對這 1% 的請求做針對性的優化。
結尾
大概總結了
- 如何設計一個秒殺系統的五大原則
- 動靜分離方案
- 二八原則,熱冷數據的處理方案
我跟大家一樣,都在學習這類系統的設計思想,接下來準備動手幹一部分代碼了。具體的效果可以通過【公眾號】=>【項目經驗】=>【跨境電商】查看
非常歡迎大家關注公眾號【歡少的成長之路】有關後端方面的問題我們在羣內一起討論! 我們下期再見!