国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁(yè) > 營(yíng)銷資訊 > 網(wǎng)站運(yùn)營(yíng) > 請(qǐng)問(wèn),如何設(shè)計(jì)一個(gè)網(wǎng)站后臺(tái)管理系統(tǒng)?

請(qǐng)問(wèn),如何設(shè)計(jì)一個(gè)網(wǎng)站后臺(tái)管理系統(tǒng)?

時(shí)間:2024-02-12 17:25:01 | 來(lái)源:網(wǎng)站運(yùn)營(yíng)

時(shí)間:2024-02-12 17:25:01 來(lái)源:網(wǎng)站運(yùn)營(yíng)

請(qǐng)問(wèn),如何設(shè)計(jì)一個(gè)網(wǎng)站后臺(tái)管理系統(tǒng)?:
“N 高 N 可”,高性能、高并發(fā)、高可用、高可靠、可擴(kuò)展、可維護(hù)、可用性等是后臺(tái)開(kāi)發(fā)耳熟能詳?shù)脑~了,它們中有些詞在大部分情況下表達(dá)相近意思。本序列文章旨在探討和總結(jié)后臺(tái)架構(gòu)設(shè)計(jì)中常用的技術(shù)和方法,并歸納成一套方法論。

前言

本文主要探討和總結(jié)服務(wù)架構(gòu)設(shè)計(jì)中高性能的技術(shù)和方法,如下圖的思維導(dǎo)圖所示,左邊部分主要偏向于編程應(yīng)用,右邊部分偏向于組件應(yīng)用,文章將按圖中的內(nèi)容展開(kāi)。

高性能思維導(dǎo)圖

1 無(wú)鎖化

大多數(shù)情況下,多線程處理可以提高并發(fā)性能,但如果對(duì)共享資源的處理不當(dāng),嚴(yán)重的鎖競(jìng)爭(zhēng)也會(huì)導(dǎo)致性能的下降。面對(duì)這種情況,有些場(chǎng)景采用了無(wú)鎖化設(shè)計(jì),特別是在底層框架上。無(wú)鎖化主要有兩種實(shí)現(xiàn),串行無(wú)鎖和數(shù)據(jù)結(jié)構(gòu)無(wú)鎖。

1.1 串行無(wú)鎖

無(wú)鎖串行最簡(jiǎn)單的實(shí)現(xiàn)方式可能就是單線程模型了,如 redis/Nginx 都采用了這種方式。在網(wǎng)絡(luò)編程模型中,常規(guī)的方式是主線程負(fù)責(zé)處理 I/O 事件,并將讀到的數(shù)據(jù)壓入隊(duì)列,工作線程則從隊(duì)列中取出數(shù)據(jù)進(jìn)行處理,這種半同步/半異步模型需要對(duì)隊(duì)列進(jìn)行加鎖,如下圖所示:

單Reactor多線程模型
上圖的模式可以改成無(wú)鎖串行的形式,當(dāng) MainReactor accept 一個(gè)新連接之后從眾多的 SubReactor 選取一個(gè)進(jìn)行注冊(cè),通過(guò)創(chuàng)建一個(gè) Channel 與 I/O 線程進(jìn)行綁定,此后該連接的讀寫(xiě)都在同一個(gè)線程執(zhí)行,無(wú)需進(jìn)行同步。

主從Reactor職責(zé)鏈模型

1.2 結(jié)構(gòu)無(wú)鎖

利用硬件支持的原子操作可以實(shí)現(xiàn)無(wú)鎖的數(shù)據(jù)結(jié)構(gòu),很多語(yǔ)言都提供 CAS 原子操作(如 go 中的 atomic 包和 C++11 中的 atomic 庫(kù)),可以用于實(shí)現(xiàn)無(wú)鎖隊(duì)列。我們以一個(gè)簡(jiǎn)單的線程安全單鏈表的插入操作來(lái)看下無(wú)鎖編程和普通加鎖的區(qū)別。

template<typename T>struct Node{ Node(const T &value) : data(value) { } T data; Node *next = nullptr;};有鎖鏈表 WithLockList:

template<typename T>class WithLockList{ mutex mtx; Node<T> *head;public: void pushFront(const T &value) { auto *node = new Node<T>(value); lock_guard<mutex> lock(mtx); //① node->next = head; head = node; }};無(wú)鎖鏈表 LockFreeList:

template<typename T>class LockFreeList{ atomic<Node<T> *> head;public: void pushFront(const T &value) { auto *node = new Node<T>(value); node->next = head.load(); while(!head.compare_exchange_weak(node->next, node)); //② }};從代碼可以看出,在有鎖版本中 ① 進(jìn)行了加鎖。在無(wú)鎖版本中,② 使用了原子 CAS 操作 compare_exchange_weak,該函數(shù)如果存儲(chǔ)成功則返回 true,同時(shí)為了防止偽失?。丛贾档扔谄谕禃r(shí)也不一定存儲(chǔ)成功,主要發(fā)生在缺少單條比較交換指令的硬件機(jī)器上),通常將 CAS 放在循環(huán)中。

下面對(duì)有鎖和無(wú)鎖版本進(jìn)行簡(jiǎn)單的性能比較,分別執(zhí)行 1000,000 次 push 操作。測(cè)試代碼如下:

int main(){ const int SIZE = 1000000; //有鎖測(cè)試 auto start = chrono::steady_clock::now(); WithLockList<int> wlList; for(int i = 0; i < SIZE; ++i) { wlList.pushFront(i); } auto end = chrono::steady_clock::now(); chrono::duration<double, std::micro> micro = end - start; cout << "with lock list costs micro:" << micro.count() << endl; //無(wú)鎖測(cè)試 start = chrono::steady_clock::now(); LockFreeList<int> lfList; for(int i = 0; i < SIZE; ++i) { lfList.pushFront(i); } end = chrono::steady_clock::now(); micro = end - start; cout << "free lock list costs micro:" << micro.count() << endl; return 0;}三次輸出如下,可以看出無(wú)鎖版本有鎖版本性能高一些。with lock list costs micro:548118 free lock list costs micro:491570 with lock list costs micro:556037 free lock list costs micro:476045 with lock list costs micro:557451 free lock list costs micro:481470

2 零拷貝

這里的拷貝指的是數(shù)據(jù)在內(nèi)核緩沖區(qū)和應(yīng)用程序緩沖區(qū)直接的傳輸,并非指進(jìn)程空間中的內(nèi)存拷貝(當(dāng)然這方面也可以實(shí)現(xiàn)零拷貝,如傳引用和 C++中 move 操作)。現(xiàn)在假設(shè)我們有個(gè)服務(wù),提供用戶下載某個(gè)文件,當(dāng)請(qǐng)求到來(lái)時(shí),我們把服務(wù)器磁盤(pán)上的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)中,這個(gè)流程偽代碼如下:

filefd = open(...); //打開(kāi)文件sockfd = socket(...); //打開(kāi)socketbuffer = new buffer(...); //創(chuàng)建bufferread(filefd, buffer); //從文件內(nèi)容讀到buffer中write(sockfd, buffer); //將buffer中的內(nèi)容發(fā)送到網(wǎng)絡(luò)數(shù)據(jù)拷貝流程如下圖:

普通讀寫(xiě)
上圖中綠色箭頭表示 DMA copy,DMA(Direct Memory Access)即直接存儲(chǔ)器存取,是一種快速傳送數(shù)據(jù)的機(jī)制,指外部設(shè)備不通過(guò) CPU 而直接與系統(tǒng)內(nèi)存交換數(shù)據(jù)的接口技術(shù)。紅色箭頭表示 CPU copy。即使在有 DMA 技術(shù)的情況下還是存在 4 次拷貝,DMA copy 和 CPU copy 各 2 次。

【文章福利】另外小編還整理了一些C/C++后臺(tái)開(kāi)發(fā)教學(xué)視頻,相關(guān)面試題,后臺(tái)學(xué)習(xí)路線圖免費(fèi)分享,需要的可以自行添加:Q群:720209036 點(diǎn)擊加入~ 群文件共享

小編強(qiáng)力推薦C++后臺(tái)開(kāi)發(fā)免費(fèi)學(xué)習(xí)地址:

2.1 內(nèi)存映射

內(nèi)存映射將用戶空間的一段內(nèi)存區(qū)域映射到內(nèi)核空間,用戶對(duì)這段內(nèi)存區(qū)域的修改可以直接反映到內(nèi)核空間,同樣,內(nèi)核空間對(duì)這段區(qū)域的修改也直接反映用戶空間,簡(jiǎn)單來(lái)說(shuō)就是用戶空間共享這個(gè)內(nèi)核緩沖區(qū)。

使用內(nèi)存映射來(lái)改寫(xiě)后的偽代碼如下:

filefd = open(...); //打開(kāi)文件sockfd = socket(...); //打開(kāi)socketbuffer = mmap(filefd); //將文件映射到進(jìn)程空間write(sockfd, buffer); //將buffer中的內(nèi)容發(fā)送到網(wǎng)絡(luò)使用內(nèi)存映射后數(shù)據(jù)拷貝流如下圖所示:

內(nèi)存映射
從圖中可以看出,采用內(nèi)存映射后數(shù)據(jù)拷貝減少為 3 次,不再經(jīng)過(guò)應(yīng)用程序直接將內(nèi)核緩沖區(qū)中的數(shù)據(jù)拷貝到 Socket 緩沖區(qū)中。RocketMQ 為了消息存儲(chǔ)高性能,就使用了內(nèi)存映射機(jī)制,將存儲(chǔ)文件分割成多個(gè)大小固定的文件,基于內(nèi)存映射執(zhí)行順序?qū)憽?br>

2.2 零拷貝

零拷貝就是一種避免 CPU 將數(shù)據(jù)從一塊存儲(chǔ)拷貝到另外一塊存儲(chǔ),從而有效地提高數(shù)據(jù)傳輸效率的技術(shù)。Linux 內(nèi)核 2.4 以后,支持帶有 DMA 收集拷貝功能的傳輸,將內(nèi)核頁(yè)緩存中的數(shù)據(jù)直接打包發(fā)到網(wǎng)絡(luò)上,偽代碼如下:

filefd = open(...); //打開(kāi)文件sockfd = socket(...); //打開(kāi)socketsendfile(sockfd, filefd); //將文件內(nèi)容發(fā)送到網(wǎng)絡(luò)使用零拷貝后流程如下圖:

零拷貝
零拷貝的步驟為:1)DMA 將數(shù)據(jù)拷貝到 DMA 引擎的內(nèi)核緩沖區(qū)中;2)將數(shù)據(jù)的位置和長(zhǎng)度的信息的描述符加到套接字緩沖區(qū);3)DMA 引擎直接將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳遞到協(xié)議引擎;

可以看出,零拷貝并非真正的沒(méi)有拷貝,還是有 2 次內(nèi)核緩沖區(qū)的 DMA 拷貝,只是消除了內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的 CPU 拷貝。Linux 中主要的零拷貝函數(shù)有 sendfile、splice、tee 等。下圖是來(lái)住 IBM 官網(wǎng)上普通傳輸和零拷貝傳輸?shù)男阅軐?duì)比,可以看出零拷貝比普通傳輸快了 3 倍左右,Kafka 也使用零拷貝技術(shù)。

普通讀寫(xiě)和零拷貝性能對(duì)比

3 序列化

當(dāng)將數(shù)據(jù)寫(xiě)入文件、發(fā)送到網(wǎng)絡(luò)、寫(xiě)入到存儲(chǔ)時(shí)通常需要序列化(serialization)技術(shù),從其讀取時(shí)需要進(jìn)行反序列化(deserialization),又稱編碼(encode)和解碼(decode)。序列化作為傳輸數(shù)據(jù)的表示形式,與網(wǎng)絡(luò)框架和通信協(xié)議是解耦的。如網(wǎng)絡(luò)框架 taf 支持 jce、json 和自定義序列化,HTTP 協(xié)議支持 XML、JSON 和流媒體傳輸?shù)取?br>
序列化的方式很多,作為數(shù)據(jù)傳輸和存儲(chǔ)的基礎(chǔ),如何選擇合適的序列化方式尤其重要。

3.1 分類

通常而言,序列化技術(shù)可以大致分為以下三種類型:

3.2 性能指標(biāo)

衡量序列化/反序列化主要有三個(gè)指標(biāo):1)序列化之后的字節(jié)大??;2)序列化/反序列化的速度;3)CPU 和內(nèi)存消耗;

下圖是一些常見(jiàn)的序列化框架性能對(duì)比:

序列化和反序列化速度對(duì)比
序列化字節(jié)占用對(duì)比
可以看出 Protobuf 無(wú)論是在序列化速度上還是字節(jié)占比上可以說(shuō)是完爆同行。不過(guò)人外有人,天外有天,聽(tīng)說(shuō) FlatBuffer 比 Protobuf 更加無(wú)敵,下圖是來(lái)自 Google 的 FlatBuffer 和其他序列化性能對(duì)比,光看圖中數(shù)據(jù) FB 貌似秒殺 PB 的存在。

FlatBuffer性能對(duì)比

3.3 選型考量

在設(shè)計(jì)和選擇序列化技術(shù)時(shí),要進(jìn)行多方面的考量,主要有以下幾個(gè)方面:1)性能:CPU 和字節(jié)占用大小是序列化的主要開(kāi)銷。在基礎(chǔ)的 RPC 通信、存儲(chǔ)系統(tǒng)和高并發(fā)業(yè)務(wù)上應(yīng)該選擇高性能高壓縮的二進(jìn)制序列化。一些內(nèi)部服務(wù)、請(qǐng)求較少 Web 的應(yīng)用可以采用文本的 JSON,瀏覽器直接內(nèi)置支持 JSON。2)易用性:豐富數(shù)據(jù)結(jié)構(gòu)和輔助工具能提高易用性,減少業(yè)務(wù)代碼的開(kāi)發(fā)量。現(xiàn)在很多序列化框架都支持 List、Map 等多種結(jié)構(gòu)和可讀的打印。3)通用性:現(xiàn)代的服務(wù)往往涉及多語(yǔ)言、多平臺(tái),能否支持跨平臺(tái)跨語(yǔ)言的互通是序列化選型的基本條件。4)兼容性:現(xiàn)代的服務(wù)都是快速迭代和升級(jí),一個(gè)好的序列化框架應(yīng)該有良好的向前兼容性,支持字段的增減和修改等。5)擴(kuò)展性:序列化框架能否低門(mén)檻的支持自定義的格式有時(shí)候也是一個(gè)比較重要的考慮因素。

4 池子化

池化恐怕是最常用的一種技術(shù)了,其本質(zhì)就是通過(guò)創(chuàng)建池子來(lái)提高對(duì)象復(fù)用,減少重復(fù)創(chuàng)建、銷毀的開(kāi)銷。常用的池化技術(shù)有內(nèi)存池、線程池、連接池、對(duì)象池等。

4.1 內(nèi)存池

我們都知道,在 C/C++中分別使用 malloc/free 和 new/delete 進(jìn)行內(nèi)存的分配,其底層調(diào)用系統(tǒng)調(diào)用 sbrk/brk。頻繁的調(diào)用系統(tǒng)調(diào)用分配釋放內(nèi)存不但影響性能還容易造成內(nèi)存碎片,內(nèi)存池技術(shù)旨在解決這些問(wèn)題。正是這些原因,C/C++中的內(nèi)存操作并不是直接調(diào)用系統(tǒng)調(diào)用,而是已經(jīng)實(shí)現(xiàn)了自己的一套內(nèi)存管理,malloc 的實(shí)現(xiàn)主要有三大實(shí)現(xiàn)。

1)ptmalloc:glibc 的實(shí)現(xiàn)。

2)tcmalloc:Google 的實(shí)現(xiàn)。

3)jemalloc:Facebook 的實(shí)現(xiàn)。

下面是來(lái)自網(wǎng)上的三種 malloc 的比較圖,tcmalloc 和 jemalloc 性能差不多,ptmalloc 的性能不如兩者,我們可以根據(jù)需要選用更適合的 malloc,如 redis 和 mysl 都可以指定使用哪個(gè) malloc。至于三者的實(shí)現(xiàn)和差異,可以網(wǎng)上查閱。

內(nèi)存分配器性能對(duì)比
雖然標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)在操作系統(tǒng)內(nèi)存管理的基礎(chǔ)上再加了一層內(nèi)存管理,但應(yīng)用程序通常也會(huì)實(shí)現(xiàn)自己特定的內(nèi)存池,如為了引用計(jì)數(shù)或者專門(mén)用于小對(duì)象分配。所以看起來(lái)內(nèi)存管理一般分為三個(gè)層次。

內(nèi)存管理三個(gè)層次

4.2 線程池

線程創(chuàng)建是需要分配資源的,這存在一定的開(kāi)銷,如果我們一個(gè)任務(wù)就創(chuàng)建一個(gè)線程去處理,這必然會(huì)影響系統(tǒng)的性能。線程池的可以限制線程的創(chuàng)建數(shù)量并重復(fù)使用,從而提高系統(tǒng)的性能。

線程池可以分類或者分組,不同的任務(wù)可以使用不同的線程組,可以進(jìn)行隔離以免互相影響。對(duì)于分類,可以分為核心和非核心,核心線程池一直存在不會(huì)被回收,非核心可能對(duì)空閑一段時(shí)間后的線程進(jìn)行回收,從而節(jié)省系統(tǒng)資源,等到需要時(shí)在按需創(chuàng)建放入池子中。

4.3 連接池

常用的連接池有數(shù)據(jù)庫(kù)連接池、redis 連接池、TCP 連接池等等,其主要目的是通過(guò)復(fù)用來(lái)減少創(chuàng)建和釋放連接的開(kāi)銷。連接池實(shí)現(xiàn)通常需要考慮以下幾個(gè)問(wèn)題:

1)初始化:?jiǎn)?dòng)即初始化和惰性初始化。啟動(dòng)初始化可以減少一些加鎖操作和需要時(shí)可直接使用,缺點(diǎn)是可能造成服務(wù)啟動(dòng)緩慢或者啟動(dòng)后沒(méi)有任務(wù)處理,造成資源浪費(fèi)。惰性初始化是真正有需要的時(shí)候再去創(chuàng)建,這種方式可能有助于減少資源占用,但是如果面對(duì)突發(fā)的任務(wù)請(qǐng)求,然后瞬間去創(chuàng)建一堆連接,可能會(huì)造成系統(tǒng)響應(yīng)慢或者響應(yīng)失敗,通常我們會(huì)采用啟動(dòng)即初始化的方式。

2)連接數(shù)目:權(quán)衡所需的連接數(shù),連接數(shù)太少則可能造成任務(wù)處理緩慢,太多不但使任務(wù)處理慢還會(huì)過(guò)度消耗系統(tǒng)資源。

3)連接取出:當(dāng)連接池已經(jīng)無(wú)可用連接時(shí),是一直等待直到有可用連接還是分配一個(gè)新的臨時(shí)連接。

4)連接放入:當(dāng)連接使用完畢且連接池未滿時(shí),將連接放入連接池(包括 3 中創(chuàng)建的臨時(shí)連接),否則關(guān)閉。

5)連接檢測(cè):長(zhǎng)時(shí)間空閑連接和失效連接需要關(guān)閉并從連接池移除。常用的檢測(cè)方法有:使用時(shí)檢測(cè)和定期檢測(cè)。

4.4 對(duì)象池

嚴(yán)格來(lái)說(shuō),各種池都是對(duì)象池模式的應(yīng)用,包括前面的這三哥們。對(duì)象池跟各種池一樣,也是緩存一些對(duì)象從而避免大量創(chuàng)建同一個(gè)類型的對(duì)象,同時(shí)限制了實(shí)例的個(gè)數(shù)。如 redis 中 0-9999 整數(shù)對(duì)象就通過(guò)采用對(duì)象池進(jìn)行共享。在游戲開(kāi)發(fā)中對(duì)象池模式經(jīng)常使用,如進(jìn)入地圖時(shí)怪物和 NPC 的出現(xiàn)并不是每次都是重新創(chuàng)建,而是從對(duì)象池中取出。

5 并發(fā)化

5.1 請(qǐng)求并發(fā)

如果一個(gè)任務(wù)需要處理多個(gè)子任務(wù),可以將沒(méi)有依賴關(guān)系的子任務(wù)并發(fā)化,這種場(chǎng)景在后臺(tái)開(kāi)發(fā)很常見(jiàn)。如一個(gè)請(qǐng)求需要查詢 3 個(gè)數(shù)據(jù),分別耗時(shí) T1、T2、T3,如果串行調(diào)用總耗時(shí) T=T1+T2+T3。對(duì)三個(gè)任務(wù)執(zhí)行并發(fā),總耗時(shí) T=max(T1,T 2,T3)。同理,寫(xiě)操作也如此。對(duì)于同種請(qǐng)求,還可以同時(shí)進(jìn)行批量合并,減少 RPC 調(diào)用次數(shù)。

5.2 冗余請(qǐng)求

冗余請(qǐng)求指的是同時(shí)向后端服務(wù)發(fā)送多個(gè)同樣的請(qǐng)求,誰(shuí)響應(yīng)快就是使用誰(shuí),其他的則丟棄。這種策略縮短了客戶端的等待時(shí)間,但也使整個(gè)系統(tǒng)調(diào)用量猛增,一般適用于初始化或者請(qǐng)求少的場(chǎng)景。公司 WNS 的跑馬模塊其實(shí)就是這種機(jī)制,跑馬模塊為了快速建立長(zhǎng)連接同時(shí)向后臺(tái)多個(gè) ip/port 發(fā)起請(qǐng)求,誰(shuí)快就用誰(shuí),這在弱網(wǎng)的移動(dòng)設(shè)備上特別有用,如果使用等待超時(shí)再重試的機(jī)制,無(wú)疑將大大增加用戶的等待時(shí)間。

6 異步化

對(duì)于處理耗時(shí)的任務(wù),如果采用同步等待的方式,會(huì)嚴(yán)重降低系統(tǒng)的吞吐量,可以通過(guò)異步化進(jìn)行解決。異步在不同層面概念是有一些差異的,在這里我們不討論異步 I/O。

6.1 調(diào)用異步化

在進(jìn)行一個(gè)耗時(shí)的 RPC 調(diào)用或者任務(wù)處理時(shí),常用的異步化方式如下:

6.2 流程異步化

一個(gè)業(yè)務(wù)流程往往伴隨著調(diào)用鏈路長(zhǎng)、后置依賴多等特點(diǎn),這會(huì)同時(shí)降低系統(tǒng)的可用性和并發(fā)處理能力??梢圆捎脤?duì)非關(guān)鍵依賴進(jìn)行異步化解決。如企鵝電競(jìng)開(kāi)播服務(wù),除了開(kāi)播寫(xiě)節(jié)目存儲(chǔ)以外,還需要將節(jié)目信息同步到神盾推薦平臺(tái)、App 首頁(yè)和二級(jí)頁(yè)等。由于同步到外部都不是開(kāi)播的關(guān)鍵邏輯且對(duì)一致性要求不是很高,可以對(duì)這些后置的同步操作進(jìn)行異步化,寫(xiě)完存儲(chǔ)即向 App 返回響應(yīng),如下圖所示:

企鵝電競(jìng)開(kāi)播流程異步化

7 緩存

從單核 CPU 到分布式系統(tǒng),從前端到后臺(tái),緩存無(wú)處不在。古有朱元璋“緩稱王”而終得天下,今有不論是芯片制造商還是互聯(lián)網(wǎng)公司都同樣采取了“緩稱王”(緩存稱王)的政策才能占據(jù)一席之地。緩存是原始數(shù)據(jù)的一個(gè)復(fù)制集,其本質(zhì)就是空間換時(shí)間,主要是為了解決高并發(fā)讀。

7.1 緩存的使用場(chǎng)景

緩存是空間換時(shí)間的藝術(shù),使用緩存能提高系統(tǒng)的性能?!皠啪齐m好,可不要貪杯”,使用緩存的目的是為了提高性價(jià)比,而不是一上來(lái)就為了所謂的提高性能不計(jì)成本的使用緩存,而是要看場(chǎng)景。

適合使用緩存的場(chǎng)景,以之前參與過(guò)的項(xiàng)目企鵝電競(jìng)為例:

1)一旦生成后基本不會(huì)變化的數(shù)據(jù):如企鵝電競(jìng)的游戲列表,在后臺(tái)創(chuàng)建一個(gè)游戲之后基本很少變化,可直接緩存整個(gè)游戲列表;

2)讀密集型或存在熱點(diǎn)的數(shù)據(jù):典型的就是各種 App 的首頁(yè),如企鵝電競(jìng)首頁(yè)直播列表;

3)計(jì)算代價(jià)大的數(shù)據(jù):如企鵝電競(jìng)的 Top 熱榜視頻,如 7 天榜在每天凌晨根據(jù)各種指標(biāo)計(jì)算好之后緩存排序列表;

4)千人一面的數(shù)據(jù):同樣是企鵝電競(jìng)的 Top 熱榜視頻,除了緩存的整個(gè)排序列表,同時(shí)直接在進(jìn)程內(nèi)按頁(yè)緩存了前 N 頁(yè)數(shù)據(jù)組裝后的最終回包結(jié)果;

不適合使用緩存的場(chǎng)景:

1)寫(xiě)多讀少,更新頻繁

2)對(duì)數(shù)據(jù)一致性要求嚴(yán)格;

7.2 緩存的分類

企鵝電競(jìng)首頁(yè)多級(jí)緩存
整體工作流程如下:

7.3 緩存的模式

關(guān)于緩存的使用,已經(jīng)有人總結(jié)出了一些模式,主要分為 Cache-Aside 和 Cache-As-SoR 兩類。其中 SoR(system-of-record):表示記錄系統(tǒng),即數(shù)據(jù)源,而 Cache 正是 SoR 的復(fù)制集。

Cache-Aside:旁路緩存,這應(yīng)該是最常見(jiàn)的緩存模式了。對(duì)于讀,首先從緩存讀取數(shù)據(jù),如果沒(méi)有命中則回源 SoR 讀取并更新緩存。對(duì)于寫(xiě)操作,先寫(xiě) SoR,再寫(xiě)緩存。這種模式架構(gòu)圖如下:

Cache-Aside結(jié)構(gòu)圖
邏輯代碼:

//讀操作data = Cache.get(key);if(data == NULL){ data = SoR.load(key); Cache.set(key, data);}//寫(xiě)操作if(SoR.save(key, data)){ Cache.set(key, data);}這種模式用起來(lái)簡(jiǎn)單,但對(duì)應(yīng)用層不透明,需要業(yè)務(wù)代碼完成讀寫(xiě)邏輯。同時(shí)對(duì)于寫(xiě)來(lái)說(shuō),寫(xiě)數(shù)據(jù)源和寫(xiě)緩存不是一個(gè)原子操作,可能出現(xiàn)以下情況導(dǎo)致兩者數(shù)據(jù)不一致:

1)在并發(fā)寫(xiě)時(shí),可能出現(xiàn)數(shù)據(jù)不一致。如下圖所示,user1 和 user2 幾乎同時(shí)進(jìn)行讀寫(xiě)。在 t1 時(shí)刻 user1 寫(xiě) db,t2 時(shí)刻 user2 寫(xiě) db,緊接著在 t3 時(shí)刻 user2 寫(xiě)緩存,t4 時(shí)刻 user1 寫(xiě)緩存。這種情況導(dǎo)致 db 是 user2 的數(shù)據(jù),緩存是 user1 的數(shù)據(jù),兩者不一致。

Cache-Aside并發(fā)讀寫(xiě)
2)先寫(xiě)數(shù)據(jù)源成功,但是接著寫(xiě)緩存失敗,兩者數(shù)據(jù)不一致。對(duì)于這兩種情況如果業(yè)務(wù)不能忍受,可簡(jiǎn)單的通過(guò)先 delete 緩存然后再寫(xiě) db 解決,其代價(jià)就是下一次讀請(qǐng)求的 cache miss。

Cache-As-SoR:緩存即數(shù)據(jù)源,該模式把 Cache 當(dāng)作 SoR,所以讀寫(xiě)操作都是針對(duì) Cache,然后 Cache 再將讀寫(xiě)操作委托給 SoR,即 Cache 是一個(gè)代理。如下圖所示:

Cache-As-SoR結(jié)構(gòu)圖
Cache-As-SoR 有三種實(shí)現(xiàn):

1)Read-Through:發(fā)生讀操作時(shí),首先查詢 Cache,如果不命中則再由 Cache 回源到 SoR 即存儲(chǔ)端實(shí)現(xiàn) Cache-Aside 而不是業(yè)務(wù))。

2)Write-Through:稱為穿透寫(xiě)模式,由業(yè)務(wù)先調(diào)用寫(xiě)操作,然后由 Cache 負(fù)責(zé)寫(xiě)緩存和 SoR。

3)Write-Behind:稱為回寫(xiě)模式,發(fā)生寫(xiě)操作時(shí)業(yè)務(wù)只更新緩存并立即返回,然后異步寫(xiě) SoR,這樣可以利用合并寫(xiě)/批量寫(xiě)提高性能。

7.4 緩存的回收策略

在空間有限、低頻熱點(diǎn)訪問(wèn)或者無(wú)主動(dòng)更新通知的情況下,需要對(duì)緩存數(shù)據(jù)進(jìn)行回收,常用的回收策略有以下幾種:

1)基于時(shí)間:基于時(shí)間的策略主要可以分兩種:

2)基于空間:緩存設(shè)置了存儲(chǔ)空間上限,當(dāng)達(dá)到上限時(shí)按照一定的策略移除數(shù)據(jù)。

3)基于容量:緩存設(shè)置了存儲(chǔ)條目上限,當(dāng)達(dá)到上限時(shí)按照一定的策略移除數(shù)據(jù)。

4)基于引用:基于引用計(jì)數(shù)或者強(qiáng)弱引用的一些策略進(jìn)行回收。

緩存的常見(jiàn)回收算法如下:

7.5 緩存的崩潰與修復(fù)

由于在設(shè)計(jì)不足、請(qǐng)求攻擊(并不一定是惡意攻擊)等會(huì)造成一些緩存問(wèn)題,下面列出了常見(jiàn)的緩存問(wèn)題和解決方案。

緩存穿透:大量使用不存在的 key 進(jìn)行查詢時(shí),緩存沒(méi)有命中,這些請(qǐng)求都穿透到后端的存儲(chǔ),最終導(dǎo)致后端存儲(chǔ)壓力過(guò)大甚至被壓垮。這種情況原因一般是存儲(chǔ)中數(shù)據(jù)不存在,主要有兩個(gè)解決辦法。

緩存雪崩:指大量的緩存在某一段時(shí)間內(nèi)集體失效,導(dǎo)致后端存儲(chǔ)負(fù)載瞬間升高甚至被壓垮。通常是以下原因造成:

緩存熱點(diǎn):雖然緩存系統(tǒng)本身性能很高,但也架不住某些熱點(diǎn)數(shù)據(jù)的高并發(fā)訪問(wèn)從而造成緩存服務(wù)本身過(guò)載。假設(shè)一下微博以用戶 id 作為哈希 key,突然有一天志玲姐姐宣布結(jié)婚了,如果她的微博內(nèi)容按照用戶 id 緩存在某個(gè)節(jié)點(diǎn)上,當(dāng)她的萬(wàn)千粉絲查看她的微博時(shí)必然會(huì)壓垮這個(gè)緩存節(jié)點(diǎn),因?yàn)檫@個(gè) key 太熱了。這種情況可以通過(guò)生成多份緩存到不同節(jié)點(diǎn)上,每份緩存的內(nèi)容一樣,減輕單個(gè)節(jié)點(diǎn)訪問(wèn)的壓力。

7.6 緩存的一些好實(shí)踐

1)動(dòng)靜分離:對(duì)于一個(gè)緩存對(duì)象,可能分為很多種屬性,這些屬性中有的是靜態(tài)的,有的是動(dòng)態(tài)的。在緩存的時(shí)候最好采用動(dòng)靜分離的方式。如企鵝電競(jìng)的視頻詳情分為標(biāo)題、時(shí)長(zhǎng)、清晰度、封面 URL、點(diǎn)贊數(shù)、評(píng)論數(shù)等,其中標(biāo)題、時(shí)長(zhǎng)等屬于靜態(tài)屬性,基本不會(huì)改變,而點(diǎn)贊數(shù)、評(píng)論數(shù)經(jīng)常改變,在緩存時(shí)這兩部分開(kāi),以免因?yàn)閯?dòng)態(tài)屬性每次的變更要把整個(gè)視頻緩存拉出來(lái)進(jìn)行更新一遍,成本很高。

2)慎用大對(duì)象:如果緩存對(duì)象過(guò)大,每次讀寫(xiě)開(kāi)銷非常大并且可能會(huì)卡住其他請(qǐng)求,特別是在 redis 這種單線程的架構(gòu)中。典型的情況是將一堆列表掛在某個(gè) value 的字段上或者存儲(chǔ)一個(gè)沒(méi)有邊界的列表,這種情況下需要重新設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)或者分割 value 再由客戶端聚合。

3)過(guò)期設(shè)置:盡量設(shè)置過(guò)期時(shí)間減少臟數(shù)據(jù)和存儲(chǔ)占用,但要注意過(guò)期時(shí)間不能集中在某個(gè)時(shí)間段。

4)超時(shí)設(shè)置:緩存作為加速數(shù)據(jù)訪問(wèn)的手段,通常需要設(shè)置超時(shí)時(shí)間而且超時(shí)時(shí)間不能過(guò)長(zhǎng)(如 100ms 左右),否則會(huì)導(dǎo)致整個(gè)請(qǐng)求超時(shí)連回源訪問(wèn)的機(jī)會(huì)都沒(méi)有。

5)緩存隔離:首先,不同的業(yè)務(wù)使用不同的 key,防止出現(xiàn)沖突或者互相覆蓋。其次,核心和非核心業(yè)務(wù)進(jìn)行通過(guò)不同的緩存實(shí)例進(jìn)行物理上的隔離。

6)失敗降級(jí):使用緩存需要有一定的降級(jí)預(yù)案,緩存通常不是關(guān)鍵邏輯,特別是對(duì)于核心服務(wù),如果緩存部分失效或者失敗,應(yīng)該繼續(xù)回源處理,不應(yīng)該直接中斷返回。

7)容量控制:使用緩存要進(jìn)行容量控制,特別是本地緩存,緩存數(shù)量太多內(nèi)存緊張時(shí)會(huì)頻繁的 swap 存儲(chǔ)空間或 GC 操作,從而降低響應(yīng)速度。

8)業(yè)務(wù)導(dǎo)向:以業(yè)務(wù)為導(dǎo)向,不要為了緩存而緩存。對(duì)性能要求不高或請(qǐng)求量不大,分布式緩存甚至數(shù)據(jù)庫(kù)都足以應(yīng)對(duì)時(shí),就不需要增加本地緩存,否則可能因?yàn)橐霐?shù)據(jù)節(jié)點(diǎn)復(fù)制和冪等處理邏輯反而得不償失。

9)監(jiān)控告警:跟妹紙永遠(yuǎn)是對(duì)的一樣,總不會(huì)錯(cuò)。對(duì)大對(duì)象、慢查詢、內(nèi)存占用等進(jìn)行監(jiān)控。

【文章福利】另外小編還整理了一些C/C++后臺(tái)開(kāi)發(fā)教學(xué)視頻,相關(guān)面試題,后臺(tái)學(xué)習(xí)路線圖免費(fèi)分享,需要的可以自行添加:Q群:720209036 點(diǎn)擊加入~ 群文件共享

小編強(qiáng)力推薦C++后臺(tái)開(kāi)發(fā)免費(fèi)學(xué)習(xí)地址:

8 分片

分片即將一個(gè)較大的部分分成多個(gè)較小的部分,在這里我們分為數(shù)據(jù)分片和任務(wù)分片。對(duì)于數(shù)據(jù)分片,在本文將不同系統(tǒng)的拆分技術(shù)術(shù)語(yǔ)(如 region、shard、vnode、partition)等統(tǒng)稱為分片。分片可以說(shuō)是一箭三雕的技術(shù),將一個(gè)大數(shù)據(jù)集分散在更多節(jié)點(diǎn)上,單點(diǎn)的讀寫(xiě)負(fù)載隨之也分散到了多個(gè)節(jié)點(diǎn)上,同時(shí)還提高了擴(kuò)展性和可用性。

數(shù)據(jù)分片,小到編程語(yǔ)言標(biāo)準(zhǔn)庫(kù)里的集合,大到分布式中間件,無(wú)所不在。如我曾經(jīng)寫(xiě)過(guò)一個(gè)線程安全的容器以放置各種對(duì)象時(shí),為了減少鎖爭(zhēng)用,對(duì)容器進(jìn)行了分段,每個(gè)分段一個(gè)鎖,按照哈?;蛘呷∧?duì)象放置到某個(gè)分段中,如 Java 中的 ConcurrentHashMap 也采取了分段的機(jī)制。分布式消息中間件 Kafka 中對(duì) topic 也分成了多個(gè) partition,每個(gè) partition 互相獨(dú)立可以比并發(fā)讀寫(xiě)。

8.1 分片策略

進(jìn)行分片時(shí),要盡量均勻的將數(shù)據(jù)分布在所有節(jié)點(diǎn)上以平攤負(fù)載。如果分布不均,會(huì)導(dǎo)致傾斜使得整個(gè)系統(tǒng)性能的下降。常見(jiàn)的分片策略如下:

區(qū)間分片
隨機(jī)分片

8.2 二級(jí)索引

二級(jí)索引通常用來(lái)加速特定值的查找,不能唯一標(biāo)識(shí)一條記錄,使用二級(jí)索引需要二次查找。關(guān)系型數(shù)據(jù)庫(kù)和一些 K-V 數(shù)據(jù)庫(kù)都支持二級(jí)索引,如 mysql 中的輔助索引(非聚簇索引),ES 倒排索引通過(guò) term 找到文檔。

本地索引
全局索引

8.3 路由策略

路由策略決定如何將數(shù)據(jù)請(qǐng)求發(fā)送到指定的節(jié)點(diǎn),包括分片調(diào)整后的路由。通常有三種方式:客戶端路由、代理路由和集群路由。

Memcache客戶端路由
CMEM接入層路由
CKV+集群路由
以上三種路由方式都各優(yōu)缺點(diǎn),客戶端路由實(shí)現(xiàn)相對(duì)簡(jiǎn)單但對(duì)業(yè)務(wù)入侵較強(qiáng)。代理層路由對(duì)業(yè)務(wù)透明,但增加了一層網(wǎng)絡(luò)傳輸,對(duì)性能有一定影響,同時(shí)在部署維護(hù)上也相對(duì)復(fù)雜。集群路由對(duì)業(yè)務(wù)透明,且比代理路由少了一層結(jié)構(gòu),節(jié)約成本,但實(shí)現(xiàn)更復(fù)雜,且不合理的策略會(huì)增加多次網(wǎng)絡(luò)傳輸。

8.4 動(dòng)態(tài)平衡

在學(xué)習(xí)平衡二叉樹(shù)和紅黑樹(shù)的時(shí)候我們都知道,由于數(shù)據(jù)的插入刪除會(huì)破壞其平衡性。為了保持樹(shù)的平衡,在插入刪除后我們會(huì)通過(guò)左旋右旋動(dòng)態(tài)調(diào)整樹(shù)的高度以保持再平衡。在分布式數(shù)據(jù)存儲(chǔ)也同樣需要再平衡,只不過(guò)引起不平衡的因素更多了,主要有以下幾個(gè)方面:

1)讀寫(xiě)負(fù)載增加,需要更多 CPU;

2)數(shù)據(jù)規(guī)模增加,需要更多磁盤(pán)和內(nèi)存;

3)數(shù)據(jù)節(jié)點(diǎn)故障,需要其他節(jié)點(diǎn)接替;

業(yè)界和公司很多產(chǎn)品也都支持動(dòng)態(tài)平衡調(diào)整,如 redis cluster 的 resharding,HDFS/kafka 的 rebalance。常見(jiàn)的方式如下:

固定分區(qū)再平衡

8.5 分庫(kù)分表

當(dāng)數(shù)據(jù)庫(kù)的單表/單機(jī)數(shù)據(jù)量很大時(shí),會(huì)造成性能瓶頸,為了分散數(shù)據(jù)庫(kù)的壓力,提高讀寫(xiě)性能,需要采取分而治之的策略進(jìn)行分庫(kù)分表。通常,在以下情況下需要進(jìn)行分庫(kù)分表:

1)單表的數(shù)據(jù)量達(dá)到了一定的量級(jí)(如 mysql 一般為千萬(wàn)級(jí)),讀寫(xiě)的性能會(huì)下降。這時(shí)索引也會(huì)很大,性能不佳,需要分解單表。

2)數(shù)據(jù)庫(kù)吞吐量達(dá)到瓶頸,需要增加更多數(shù)據(jù)庫(kù)實(shí)例來(lái)分擔(dān)數(shù)據(jù)讀寫(xiě)壓力。

分庫(kù)分表按照特定的條件將數(shù)據(jù)分散到多個(gè)數(shù)據(jù)庫(kù)和表中,分為垂直切分和水平切分兩種模式。

垂直切分
優(yōu)點(diǎn):

1)切分規(guī)則清晰,業(yè)務(wù)劃分明確;

2)可以按照業(yè)務(wù)的類型、重要程度進(jìn)行成本管理,擴(kuò)展也方便;

3)數(shù)據(jù)維護(hù)簡(jiǎn)單;

缺點(diǎn):

1)不同表分到了不同的庫(kù)中,無(wú)法使用表連接 Join。不過(guò)在實(shí)際的業(yè)務(wù)設(shè)計(jì)中,也基本不會(huì)用到 join 操作,一般都會(huì)建立映射表通過(guò)兩次查詢或者寫(xiě)時(shí)構(gòu)造好數(shù)據(jù)存到性能更高的存儲(chǔ)系統(tǒng)中。

2)事務(wù)處理復(fù)雜,原本在事務(wù)中操作同一個(gè)庫(kù)的不同表不再支持。如直播結(jié)束時(shí)更新直播節(jié)目同時(shí)生成一個(gè)直播的點(diǎn)播回放在分庫(kù)之后就不能在一個(gè)事物中完成,這時(shí)可以采用柔性事務(wù)或者其他分布式事物方案。

8.6 任務(wù)分片

記得小時(shí)候發(fā)新書(shū),老師抱了一堆堆的新書(shū)到教室,然后找?guī)讉€(gè)同學(xué)一起分發(fā)下去,有的發(fā)語(yǔ)文,有的發(fā)數(shù)學(xué),有的發(fā)自然,這就是一種任務(wù)分片。車間中的流水線,經(jīng)過(guò)每道工序的并行后最終合成最終的產(chǎn)品,也是一種任務(wù)分片。

任務(wù)分片將一個(gè)任務(wù)分成多個(gè)子任務(wù)并行處理,加速任務(wù)的執(zhí)行,通常涉及到數(shù)據(jù)分片,如歸并排序首先將數(shù)據(jù)分成多個(gè)子序列,先對(duì)每個(gè)子序列排序,最終合成一個(gè)有序序列。在大數(shù)據(jù)處理中,Map/Reduce 就是數(shù)據(jù)分片和任務(wù)分片的經(jīng)典結(jié)合。

9 存儲(chǔ)

任何一個(gè)系統(tǒng),從單核 CPU 到分布式,從前端到后臺(tái),要實(shí)現(xiàn)各式各樣的功能和邏輯,只有讀和寫(xiě)兩種操作。而每個(gè)系統(tǒng)的業(yè)務(wù)特性可能都不一樣,有的側(cè)重讀、有的側(cè)重寫(xiě),有的兩者兼?zhèn)洌竟?jié)主要探討在不同業(yè)務(wù)場(chǎng)景下存儲(chǔ)讀寫(xiě)的一些方法論。

9.1 讀寫(xiě)分離

大多數(shù)業(yè)務(wù)都是讀多寫(xiě)少,為了提高系統(tǒng)處理能力,可以采用讀寫(xiě)分離的方式將主節(jié)點(diǎn)用于寫(xiě),從節(jié)點(diǎn)用于讀,如下圖所示。

讀寫(xiě)分離架構(gòu)
讀寫(xiě)分離架構(gòu)有以下幾個(gè)特點(diǎn):1)數(shù)據(jù)庫(kù)服務(wù)為主從架構(gòu),可以為一主一從或者一主多從;2)主節(jié)點(diǎn)負(fù)責(zé)寫(xiě)操作,從節(jié)點(diǎn)負(fù)責(zé)讀操作;3)主節(jié)點(diǎn)將數(shù)據(jù)復(fù)制到從節(jié)點(diǎn);基于基本架構(gòu),可以變種出多種讀寫(xiě)分離的架構(gòu),如主-主-從、主-從-從。主從節(jié)點(diǎn)也可以是不同的存儲(chǔ),如 mysql+redis。

讀寫(xiě)分離的主從架構(gòu)一般采用異步復(fù)制,會(huì)存在數(shù)據(jù)復(fù)制延遲的問(wèn)題,適用于對(duì)數(shù)據(jù)一致性要求不高的業(yè)務(wù)。可采用以下幾個(gè)方式盡量避免復(fù)制滯后帶來(lái)的問(wèn)題。

1)寫(xiě)后讀一致性:即讀自己的寫(xiě),適用于用戶寫(xiě)操作后要求實(shí)時(shí)看到更新。典型的場(chǎng)景是,用戶注冊(cè)賬號(hào)或者修改賬戶密碼后,緊接著登錄,此時(shí)如果讀請(qǐng)求發(fā)送到從節(jié)點(diǎn),由于數(shù)據(jù)可能還沒(méi)同步完成,用戶登錄失敗,這是不可接受的。針對(duì)這種情況,可以將自己的讀請(qǐng)求發(fā)送到主節(jié)點(diǎn)上,查看其他用戶信息的請(qǐng)求依然發(fā)送到從節(jié)點(diǎn)。

2)二次讀取:優(yōu)先讀取從節(jié)點(diǎn),如果讀取失敗或者跟蹤的更新時(shí)間小于某個(gè)閥值,則再?gòu)闹鞴?jié)點(diǎn)讀取。

3)關(guān)鍵業(yè)務(wù)讀寫(xiě)主節(jié)點(diǎn),非關(guān)鍵業(yè)務(wù)讀寫(xiě)分離。

4)單調(diào)讀:保證用戶的讀請(qǐng)求都發(fā)到同一個(gè)從節(jié)點(diǎn),避免出現(xiàn)回滾的現(xiàn)象。如用戶在 M 主節(jié)點(diǎn)更新信息后,數(shù)據(jù)很快同步到了從節(jié)點(diǎn) S1,用戶查詢時(shí)請(qǐng)求發(fā)往 S1,看到了更新的信息。接著用戶再一次查詢,此時(shí)請(qǐng)求發(fā)到數(shù)據(jù)同步?jīng)]有完成的從節(jié)點(diǎn) S2,用戶看到的現(xiàn)象是剛才的更新的信息又消失了,即以為數(shù)據(jù)回滾了。

9.2 動(dòng)靜分離

動(dòng)靜分離將經(jīng)常更新的數(shù)據(jù)和更新頻率低的數(shù)據(jù)進(jìn)行分離。最常見(jiàn)于 CDN,一個(gè)網(wǎng)頁(yè)通常分為靜態(tài)資源(圖片/js/css 等)和動(dòng)態(tài)資源(JSP、PHP 等),采取動(dòng)靜分離的方式將靜態(tài)資源緩存在 CDN 邊緣節(jié)點(diǎn)上,只需請(qǐng)求動(dòng)態(tài)資源即可,減少網(wǎng)絡(luò)傳輸和服務(wù)負(fù)載。

在數(shù)據(jù)庫(kù)和 KV 存儲(chǔ)上也可以采取動(dòng)態(tài)分離的方式,如 7.6 提到的點(diǎn)播視頻緩存的動(dòng)靜分離。在數(shù)據(jù)庫(kù)中,動(dòng)靜分離更像是一種垂直切分,將動(dòng)態(tài)和靜態(tài)的字段分別存儲(chǔ)在不同的庫(kù)表中,減小數(shù)據(jù)庫(kù)鎖的粒度,同時(shí)可以分配不同的數(shù)據(jù)庫(kù)資源來(lái)合理提升利用率。

9.3 冷熱分離

冷熱分離可以說(shuō)是每個(gè)存儲(chǔ)產(chǎn)品和海量業(yè)務(wù)的必備功能,Mysql、ElasticSearch、CMEM、Grocery 等都直接或間接支持冷熱分離。將熱數(shù)據(jù)放到性能更好的存儲(chǔ)設(shè)備上,冷數(shù)據(jù)下沉到廉價(jià)的磁盤(pán),從而節(jié)約成本。企鵝電競(jìng)為了節(jié)省在騰訊云成本,直播回放按照主播粉絲數(shù)和時(shí)間等條件也采用了冷熱分離,下圖是 ES 冷熱分離的一個(gè)實(shí)現(xiàn)架構(gòu)圖。

ES冷熱分離架構(gòu)圖

9.4 重寫(xiě)輕讀

重寫(xiě)輕度個(gè)人理解可能有兩個(gè)含義:1)關(guān)鍵寫(xiě),降低讀的關(guān)鍵性,如異步復(fù)制,保證主節(jié)點(diǎn)寫(xiě)成功即可,從節(jié)點(diǎn)的讀可容忍同步延遲。2)寫(xiě)重邏輯,讀輕邏輯,將計(jì)算的邏輯從讀轉(zhuǎn)移到寫(xiě)。適用于讀請(qǐng)求的時(shí)候還要進(jìn)行計(jì)算的場(chǎng)景,常見(jiàn)的如排行榜是在寫(xiě)的時(shí)候構(gòu)建而不是在讀請(qǐng)求的時(shí)候再構(gòu)建。

在微博、朋友圈等社交產(chǎn)品場(chǎng)景中都有類似關(guān)注或朋友的功能。以朋友圈模擬為例(具體我也不知道朋友圈是怎么做的),如果用戶進(jìn)入朋友圈時(shí)看到的朋友消息列表是在請(qǐng)求的時(shí)候遍歷其朋友的新消息再按時(shí)間排序組裝出來(lái)的,這顯然很難滿足朋友圈這么大的海量請(qǐng)求。可以采取重寫(xiě)輕讀的方式,在發(fā)朋友圈的時(shí)候就把列表構(gòu)造好,然后直接讀就可以了。

仿照 Actor 模型,為用戶建立一個(gè)信箱,用戶發(fā)朋友圈后寫(xiě)完自己的信箱就返回,然后異步的將消息推送到其朋友的信箱,這樣朋友讀取他的信箱時(shí)就是其朋友圈的消息列表,如下圖所示:

重寫(xiě)輕讀流程
上圖僅僅是為了展示重寫(xiě)輕度的思路,在實(shí)際應(yīng)用中還有些其他問(wèn)題。如:1)寫(xiě)擴(kuò)散:這是個(gè)寫(xiě)擴(kuò)散的行為,如果一個(gè)大戶的朋友很多,這寫(xiě)擴(kuò)散的代價(jià)也是很大的,而且可能有些人萬(wàn)年不看朋友圈甚至屏蔽了朋友。需要采取一些其他的策略,如朋友數(shù)在某個(gè)范圍內(nèi)是才采取這種方式,數(shù)量太多采取推拉結(jié)合和分析一些活躍指標(biāo)等。2)信箱容量:一般來(lái)說(shuō)查看朋友圈不會(huì)不斷的往下翻頁(yè)查看,這時(shí)候應(yīng)該限制信箱存儲(chǔ)條目數(shù),超出的條目從其他存儲(chǔ)查詢。

9.5 數(shù)據(jù)異構(gòu)

數(shù)據(jù)異構(gòu)主要是按照不同的維度建立索引關(guān)系以加速查詢。如京東、天貓等網(wǎng)上商城,一般按照訂單號(hào)進(jìn)行了分庫(kù)分表。由于訂單號(hào)不在同一個(gè)表中,要查詢一個(gè)買(mǎi)家或者商家的訂單列表,就需要查詢所有分庫(kù)然后進(jìn)行數(shù)據(jù)聚合。可以采取構(gòu)建異構(gòu)索引,在生成訂單的時(shí)同時(shí)創(chuàng)建買(mǎi)家和商家到訂單的索引表,這個(gè)表可以按照用戶 id 進(jìn)行分庫(kù)分表。

10 隊(duì)列

在系統(tǒng)應(yīng)用中,不是所有的任務(wù)和請(qǐng)求必須實(shí)時(shí)處理,很多時(shí)候數(shù)據(jù)也不需要強(qiáng)一致性而只需保持最終一致性,有時(shí)候我們也不需要知道系統(tǒng)模塊間的依賴,在這些場(chǎng)景下隊(duì)列技術(shù)大有可為。

10.1 應(yīng)用場(chǎng)景

隊(duì)列的應(yīng)用場(chǎng)景很廣泛,總結(jié)起來(lái)主要有以下幾個(gè)方面:

基于MQ的分布式柔性事務(wù)
其核心原理和流程是:

1)分布式事務(wù)發(fā)起方在執(zhí)行第一個(gè)本地事務(wù)前,向 MQ 發(fā)送一條事務(wù)消息并保存到服務(wù)端,MQ 消費(fèi)者無(wú)法感知和消費(fèi)該消息 ①②。

2)事務(wù)消息發(fā)送成功后開(kāi)始進(jìn)行單機(jī)事務(wù)操作 ③:

a)如果本地事務(wù)執(zhí)行成功,則將 MQ 服務(wù)端的事務(wù)消息更新為正常狀態(tài) ④;

b)如果本地事務(wù)執(zhí)行時(shí)因?yàn)殄礄C(jī)或者網(wǎng)絡(luò)問(wèn)題沒(méi)有及時(shí)向 MQ 服務(wù)端反饋,則之前的事務(wù)消息會(huì)一直保存在 MQ。MQ 服務(wù)端會(huì)對(duì)事務(wù)消息進(jìn)行定期掃描,如果發(fā)現(xiàn)有消息保存時(shí)間超過(guò)了一定的時(shí)間閥值,則向 MQ 生產(chǎn)端發(fā)送檢查事務(wù)執(zhí)行狀態(tài)的請(qǐng)求 ⑤;

c)檢查本地事務(wù)結(jié)果后 ⑥,如果事務(wù)執(zhí)行成功,則將之前保存的事務(wù)消息更新為正常狀態(tài),否則告知 MQ 服務(wù)端進(jìn)行丟棄;

3)消費(fèi)者獲取到事務(wù)消息設(shè)置為正常狀態(tài)后,則執(zhí)行第二個(gè)本地事務(wù) ⑧。如果執(zhí)行失敗則通知 MQ 發(fā)送方對(duì)第一個(gè)本地事務(wù)進(jìn)行回滾或正向補(bǔ)償。

10.2 應(yīng)用分類

緩沖隊(duì)列
在大數(shù)據(jù)日志系統(tǒng)中,通常需要在日志采集系統(tǒng)和日志解析系統(tǒng)之間增加日志緩沖隊(duì)列,以防止解析系統(tǒng)高負(fù)載時(shí)阻塞采集系統(tǒng)甚至造成日志丟棄,同時(shí)便于各自升級(jí)維護(hù)。下圖天機(jī)閣數(shù)據(jù)采集系統(tǒng)中,就采用 Kafka 作為日志緩沖隊(duì)列。

天機(jī)閣數(shù)據(jù)采集系統(tǒng)
TAF請(qǐng)求接收隊(duì)列
常用消息隊(duì)列

總結(jié)

本文探討和總結(jié)了后臺(tái)開(kāi)發(fā)設(shè)計(jì)高性能服務(wù)的常用方法和技術(shù),并通過(guò)思維導(dǎo)圖總結(jié)了成一套方法論。當(dāng)然這不是高性能的全部,甚至只是鳳毛菱角。每個(gè)具體的領(lǐng)域都有自己的高性能之道,如網(wǎng)絡(luò)編程的 I/O 模型和 C10K 問(wèn)題,業(yè)務(wù)邏輯的數(shù)據(jù)結(jié)構(gòu)和算法設(shè)計(jì),各種中間件的參數(shù)調(diào)優(yōu)等。文中也描述了一些項(xiàng)目的實(shí)踐,如有不合理的地方或者有更好的解決方案,請(qǐng)各位同仁賜教。

參考資料

推薦一個(gè)零聲教育C/C++后臺(tái)開(kāi)發(fā)的免費(fèi)公開(kāi)課程,個(gè)人覺(jué)得老師講得不錯(cuò),分享給大家:C/C++后臺(tái)開(kāi)發(fā)高級(jí)架構(gòu)師,內(nèi)容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK等技術(shù)內(nèi)容,立即學(xué)習(xí)

原文:后臺(tái)服務(wù)架構(gòu)高性能設(shè)計(jì)之道

關(guān)鍵詞:后臺(tái),管理,系統(tǒng),設(shè)計(jì),請(qǐng)問(wèn)

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點(diǎn)擊下載Chrome瀏覽器
關(guān)閉