天天看點

圖文詳解!10大高性能開發核心技術+

程式員經常要面臨的一個問題就是:如何提高程式性能?

這篇文章,我們循序漸進,從記憶體、磁盤I/O、網絡I/O、CPU、緩存、架構、算法等多層次遞進,串聯起高性能開發十大必須掌握的核心技術。

- I/O優化:零拷貝技術 - I/O優化:多路複用技術 - 線程池技術 - 無鎖程式設計技術 - 程序間通信技術 - RPC && 序列化技術 - 資料庫索引技術 - 緩存技術 && 布隆過濾器 - 全文搜尋技術 - 負載均衡技術。

- I/O優化:零拷貝技術 - I/O優化:多路複用技術 - 線程池技術 - 無鎖程式設計技術 - 程序間通信技術 - RPC && 序列化技術 - 資料庫索引技術 - 緩存技術 && 布隆過濾器 - 全文搜尋技術 - 負載均衡技術

準備好了嗎,坐穩了,發車!

首先,我們從最簡單的模型開始。

老闆告訴你,開發一個靜态web伺服器,把磁盤檔案(網頁、圖檔)通過網絡發出去,怎麼做?

你花了兩天時間,撸了一個1.0版本:

  • 主線程進入一個循環,等待連接配接
  • 來一個連接配接就啟動一個工作線程來處理
  • 工作線程中,等待對方請求,然後從磁盤讀檔案、往套接口發送資料,完事兒

上線一天,老闆發現太慢了,大一點的圖檔加載都有卡頓感。讓你優化,這個時候,你需要:

好文推薦:深入了解Intel CPU體系結構【值得收藏!】

Linux核心入門-- likely和unlikely

不看後悔的Linux核心Makefile檔案詳解

I/O優化:零拷貝技術

上面的工作線程,從磁盤讀檔案、再通過網絡發送資料,資料從磁盤到網絡,兜兜轉轉需要拷貝四次,其中CPU親自搬運都需要兩次。

圖文詳解!10大高性能開發核心技術+

零拷貝技術,解放CPU,檔案資料直接從核心發送出去,無需再拷貝到應用程式緩沖區,白白浪費資源。

圖文詳解!10大高性能開發核心技術+

Linux API:

ssize_t sendfile(
  int out_fd, 
  int in_fd, 
  off_t *offset, 
  size_t count
  );
           

函數名字已經把函數的功能解釋的很明顯了:發送檔案。指定要發送的檔案描述符和網絡套接字描述符,一個函數搞定!

用上了零拷貝技術後開發了2.0版本,圖檔加載速度明顯有了提升。不過老闆發現同時通路的人變多了以後,又變慢了,又讓你繼續優化。這個時候,你需要:

I/O優化:多路複用技術

前面的版本中,每個線程都要阻塞在recv等待對方的請求,這來通路的人多了,線程開的就多了,大量線程都在阻塞,系統運轉速度也随之下降。

【文章福利】小編推薦自己的Linux核心技術交流群: 【977878001】整理一些個人覺得比較好得學習書籍、視訊資料共享在群檔案裡面,有需要的可以自行添加哦!!!前100進群領取,額外贈送一份 價值699的核心資料包(含視訊教程、電子書、實戰項目及代碼)
圖文詳解!10大高性能開發核心技術+

核心資料直通車:Linux核心源碼技術學習路線+視訊教程代碼資料

圖文詳解!10大高性能開發核心技術+

https://link.zhihu.com/?target=https%3A//docs.qq.com/doc/DUGZVQk1qWVBHTEl3

學習直通車:Linux核心源碼/記憶體調優/檔案系統/程序管理/裝置驅動/網絡協定棧-學習視訊教程-騰訊課堂是不是學完作業系統原理後覺得紙上談兵不過瘾?是不是面對浩若煙海的Linux核心源代碼迷失在代碼的海洋裡不知所措?這門課可以帶您用理論結合實踐的方法一步一步抓住Linux核心最核心的部分代碼,了解Linux作業系統運作的基本過程及涉及的核心機制。

圖文詳解!10大高性能開發核心技術+

https://ke.qq.com/course/4032547?flowToken=1044374

這個時候,你需要多路複用技術,使用select模型,将所有等待(accept、recv)都放在主線程裡,工作線程不需要再等待。

圖文詳解!10大高性能開發核心技術+

過了一段時間之後,網站通路的人越來越多了,就連select也開始有點應接不暇,老闆繼續讓你優化性能。

這個時候,你需要更新多路複用模型為epoll。

select有三弊,epoll有三優。

  • select底層采用數組來管理套接字描述符,同時管理的數量有上限,一般不超過幾千個,epoll使用樹和連結清單來管理,同時管理數量可以很大。
  • select不會告訴你到底哪個套接字來了消息,你需要一個個去詢問。epoll直接告訴你誰來了消息,不用輪詢。
  • select進行系統調用時還需要把套接字清單在使用者空間和核心空間來回拷貝,循環中調用select時簡直浪費。epoll統一在核心管理套接字描述符,無需來回拷貝。

用上了epoll多路複用技術,開發了3.0版本,你的網站能同時處理很多使用者請求了。

但是貪心的老闆還不滿足,不舍得更新硬體伺服器,卻讓你進一步提高伺服器的吞吐量。你研究後發現,之前的方案中,工作線程總是用到才建立,用完就關閉,大量請求來的時候,線程不斷建立、關閉、建立、關閉,開銷挺大的。這個時候,你需要:

線程池技術

我們可以在程式一開始啟動後就批量啟動一波工作線程,而不是在有請求來的時候才去建立,使用一個公共的任務隊列,請求來臨時,向隊列中投遞任務,各個工作線程統一從隊列中不斷取出任務來處理,這就是線程池技術。

圖文詳解!10大高性能開發核心技術+

多線程技術的使用一定程度提升了伺服器的并發能力,但同時,多個線程之間為了資料同步,常常需要使用互斥體、信号、條件變量等手段來同步多個線程。這些重量級的同步手段往往會導緻線程在使用者态/核心态多次切換,系統調用,線程切換都是不小的開銷。

線上程池技術中,提到了一個公共的任務隊列,各個工作線程需要從中提取任務進行處理,這裡就涉及到多個工作線程對這個公共隊列的同步操作。

有沒有一些輕量級的方案來實作多線程安全的通路資料呢?這個時候,你需要:

無鎖程式設計技術

多線程并發程式設計中,遇到公共資料時就需要進行線程同步。而這裡的同步又可以分為阻塞型同步和非阻塞型同步。

阻塞型同步好了解,我們常用的互斥體、信号、條件變量等這些作業系統提供的機制都屬于阻塞型同步,其本質都是要加“鎖”。

圖文詳解!10大高性能開發核心技術+

與之對應的非阻塞型同步就是在無鎖的情況下實作同步,目前有三類技術方案:

  • Wait-free
  • Lock-free
  • Obstruction-free

三類技術方案都是通過一定的算法和技術手段來實作不用阻塞等待而實作同步,這其中又以Lock-free最為應用廣泛。

Lock-free能夠廣泛應用得益于目前主流的CPU都提供了原子級别的read-modify-write原語,這就是著名的CAS(Compare-And-Swap)操作。在Intel x86系列處理器上,就是cmpxchg系列指令。

// 通過CAS操作實作Lock-free
do {
  ...
} while(!CAS(ptr,old_data,new_data ))
           

我們常常見到的無鎖隊列、無鎖連結清單、無鎖HashMap等資料結構,其無鎖的核心大都來源于此。在日常開發中,恰當的運用無鎖化程式設計技術,可以有效地降低多線程阻塞和切換帶來的額外開銷,提升性能。

伺服器上線了一段時間,發現服務經常崩潰異常,排查發現是工作線程代碼bug,一崩潰整個服務都不可用了。于是你決定把工作線程和主線程拆開到不同的程序中,工作線程崩潰不能影響整體的服務。這個時候出現了多程序,你需要:

程序間通信技術

提起程序間通信,你能想到的是什麼?

  • 管道
  • 命名管道
  • socket
  • 消息隊列
  • 信号
  • 信号量
  • 共享記憶體

以上各種程序間通信的方式詳細介紹和比較

對于本地程序間需要高頻次的大量資料互動,首推共享記憶體這種方案。

現代作業系統普遍采用了基于虛拟記憶體的管理方案,在這種記憶體管理方式之下,各個程序之間進行了強制隔離。程式代碼中使用的記憶體位址均是一個虛拟位址,由作業系統的記憶體管理算法提前配置設定映射到對應的實體記憶體頁面,CPU在執行代碼指令時,對通路到的記憶體位址再進行實時的轉換翻譯。

圖文詳解!10大高性能開發核心技術+

從上圖可以看出,不同程序之中,雖然是同一個記憶體位址,最終在作業系統和CPU的配合下,實際存儲資料的記憶體頁面卻是不同的。

而共享記憶體這種程序間通信方案的核心在于:如果讓同一個實體記憶體頁面映射到兩個程序位址空間中,雙方不是就可以直接讀寫,而無需拷貝了嗎?

圖文詳解!10大高性能開發核心技術+

當然,共享記憶體隻是最終的資料傳輸載體,雙方要實作通信還得借助信号、信号量等其他通知機制。

用上了高性能的共享記憶體通信機制,多個服務程序之間就可以愉快的工作了,即便有工作程序出現Crash,整個服務也不至于癱瘓。

不久,老闆增加需求了,不再滿足于隻能提供靜态網頁浏覽了,需要能夠實作動态互動。這一次老闆還算良心,給你加了一台硬體伺服器。

于是你用Java/PHP/Python等語言搞了一套web開發架構,單獨起了一個服務,用來提供動态網頁支援,和原來等靜态内容伺服器配合工作。

這個時候你發現,靜态服務和動态服務之間經常需要通信。

一開始你用基于HTTP的RESTful接口在伺服器之間通信,後來發現用JSON格式傳輸資料效率低下,你需要更高效的通信方案。

這個時候你需要:

RPC && 序列化技術

什麼是RPC技術?

RPC全稱Remote Procedure Call,遠端過程調用。我們平時程式設計中,随時都在調用函數,這些函數基本上都位于本地,也就是目前程序某一個位置的代碼塊。但如果要調用的函數不在本地,而在網絡上的某個伺服器上呢?這就是遠端過程調用的來源。

圖文詳解!10大高性能開發核心技術+

從圖中可以看出,通過網絡進行功能調用,涉及參數的打包解包、網絡的傳輸、結果的打包解包等工作。而其中對資料進行打包和解包就需要依賴序列化技術來完成。

什麼是序列化技術?

圖文詳解!10大高性能開發核心技術+

序列化簡單來說,是将記憶體中的對象轉換成可以傳輸和存儲的資料,而這個過程的逆向操作就是反序列化。序列化 && 反序列化技術可以實作将記憶體對象在本地和遠端計算機上搬運。好比把大象關進冰箱門分三步:

  • 将本地記憶體對象編碼成資料流
  • 通過網絡傳輸上述資料流
  • 将收到的資料流在記憶體中建構出對象

序列化技術有很多免費開源的架構,衡量一個序列化架構的名額有這麼幾個:

  • 是否支援跨語言使用,能支援哪些語言
  • 是否隻是單純的序列化功能,包不包含RPC架構
  • 序列化傳輸性能
  • 擴充支援能力(資料對象增删字段後,前後的相容性)
  • 是否支援動态解析(動态解析是指不需要提前編譯,根據拿到的資料格式定義檔案立即就能解析)

下面流行的三大序列化架構protobuf、thrift、avro的對比:

ProtoBuf:

廠商

:Google

支援語言

:C++、Java、Python等

動态性支援

:較差,一般需要提前編譯

是否包含RPC

:否

簡介

:ProtoBuf是谷歌出品的序列化架構,成熟穩定,性能強勁,很多大廠都在使用。自身隻是一個序列化架構,不包含RPC功能,不過可以與同是Google出品的GPRC架構一起配套使用,作為後端RPC服務開發的黃金搭檔。

圖文詳解!10大高性能開發核心技術+

缺點是對動态性支援較弱,不過在更新版本中這一現象有待改善。總體來說,ProtoBuf都是一款非常值得推薦的序列化架構。

Thrift

廠商

:Facebook

支援語言

:C++、Java、Python、PHP、C#、Go、JavaScript等

動态性支援

:差

是否包含RPC

:是

簡介

:這是一個由Facebook出品的RPC架構,本身内含二進制序列化方案,但Thrift本身的RPC和資料序列化是解耦的,你甚至可以選擇XML、JSON等自定義的資料格式。在國内同樣有一批大廠在使用,性能方面和ProtoBuf不分伯仲。缺點和ProtoBuf一樣,對動态解析的支援不太友好。

Avro

支援語言

:C、C++、Java、Python、C#等

動态性支援

:好

是否包含RPC

:是

簡介

:這是一個源自于Hadoop生态中的序列化架構,自帶RPC架構,也可獨立使用。相比前兩位最大的優勢就是支援動态資料解析。

圖文詳解!10大高性能開發核心技術+

為什麼我一直在說這個動态解析功能呢?在之前的一段項目經曆中,軒轅就遇到了三種技術的選型,擺在我們面前的就是這三種方案。需要一個C++開發的服務和一個Java開發的服務能夠進行RPC。

Protobuf和Thrift都需要通過“編譯”将對應的資料協定定義檔案編譯成對應的C++/Java源代碼,然後合入項目中一起編譯,進而進行解析。

當時,Java項目組同學非常強硬的拒絕了這一做法,其理由是這樣編譯出來的強業務型代碼融入他們的業務無關的架構服務,而業務是常變的,這樣做不夠優雅。

最後,經過測試,最終選擇了AVRO作為我們的方案。Java一側隻需要動态加載對應的資料格式檔案,就能對拿到的資料進行解析,并且性能上還不錯。(當然,對于C++一側還是選擇了提前編譯的做法)

自從你的網站支援了動态能力,免不了要和資料庫打交道,但随着使用者的增長,你發現資料庫的查詢速度越來越慢。

這個時候,你需要:

資料庫索引技術

想想你手上有一本數學教材,但是目錄被人給撕掉了,現在要你翻到講三角函數的那一頁,你該怎麼辦?

沒有了目錄,你隻有兩種辦法,要麼一頁一頁的翻,要麼随機翻,直到找到三角函數的那一頁。

對于資料庫也是一樣的道理,如果我們的資料表沒有“目錄”,那要查詢滿足條件的記錄行,就得全表掃描,那可就惱火了。是以為了加快查詢速度,得給資料表也設定目錄,在資料庫領域中,這就是索引。

一般情況下,資料表都會有多個字段,那根據不同的字段也就可以設立不同的索引。

索引的分類

  • 主鍵索引
  • 聚集索引
  • 非聚集索引

主鍵我們都知道,是唯一辨別一條資料記錄的字段(也存在多個字段一起來唯一辨別資料記錄的聯合主鍵),那與之對應的就是主鍵索引了。

聚集索引是指索引的邏輯順序與表記錄的實體存儲順序一緻的索引,一般情況下主鍵索引就符合這個定義,是以一般來說主鍵索引也是聚集索引。但是,這不是絕對的,在不同的資料庫中,或者在同一個資料庫下的不同存儲引擎中還是有不同。

聚集索引的葉子節點直接存儲了資料,也是資料節點,而非聚集索引的葉子節點沒有存儲實際的資料,需要二次查詢。

索引的實作原理

索引的實作主要有三種:

  • B+樹
  • 哈希表
  • 位圖

其中,B+樹用的最多,其特點是樹的節點衆多,相較于二叉樹,這是一棵多叉樹,是一個扁平的胖樹,減少樹的深度有利于減少磁盤I/O次數,适宜資料庫的存儲特點。

圖文詳解!10大高性能開發核心技術+

哈希表實作的索引也叫散列索引,通過哈希函數來實作資料的定位。雜湊演算法的特點是速度快,常數階的時間複雜度,但缺點是隻适合準确比對,不适合模糊比對和範圍搜尋。

圖文詳解!10大高性能開發核心技術+

位圖索引相對就少見了。想象這麼一個場景,如果某個字段的取值隻有有限的少數幾種可能,比如性别、省份、血型等等,針對這樣的字段如果用B+樹作為索引的話會出現什麼情況?會出現大量索引值相同的葉子節點,這實際上是一種存儲浪費。

位圖索引正是基于這一點進行優化,針對字段取值隻有少量有限項,資料表中該列字段出現大量重複時,就是位圖索引一展身手的時機。

所謂位圖,就是Bitmap,其基本思想是對該字段每一個取值建立一個二進制位圖來标記資料表的每一條記錄的該列字段是否是對應取值。

圖文詳解!10大高性能開發核心技術+

索引雖好,但也不可濫用,一方面索引最終是要存儲到磁盤上的,無疑會增加存儲開銷。另外更重要的是,資料表的增删操作一般會伴随對索引的更新,是以對資料庫的寫入速度也是會有一定影響。

你的網站現在通路量越來越大了,同時線上人數大大增長。然而,大量使用者的請求帶來了後端程式對資料庫大量的通路。漸漸的,資料庫的瓶頸開始出現,無法再支援日益增長的使用者量。老闆再一次給你下達了性能提升的任務。

緩存技術 && 布隆過濾器

從實體CPU對記憶體資料的緩存到浏覽器對網頁内容的緩存,緩存技術遍布于計算機世界的每一個角落。

面對目前出現的資料庫瓶頸,同樣可以用緩存技術來解決。

每次通路資料庫都需要資料庫進行查表(當然,資料庫自身也有優化措施),反映到底層就是進行一次或多次的磁盤I/O,但凡涉及I/O的就會慢下來。如果是一些頻繁用到但又不會經常變化的資料,何不将其緩存在記憶體中,不必每一次都要找資料庫要,進而減輕對資料庫對壓力呢?

圖文詳解!10大高性能開發核心技術+

有需求就有市場,有市場就會有産品,以memcached和Redis為代表的記憶體對象緩存系統應運而生。

緩存系統有三個著名的問題:

  • 緩存穿透

    : 緩存設立的目的是為了一定層面上截獲到資料庫存儲層的請求。穿透的意思就在于這個截獲沒有成功,請求最終還是去到了資料庫,緩存沒有産生應有的價值。
  • 緩存擊穿

    : 如果把緩存了解成一面擋在資料庫面前的牆壁,為資料庫“抵禦”查詢請求,所謂擊穿,就是在這面牆壁上打出了一個洞。一般發生在某個熱點資料緩存到期,而此時針對該資料的大量查詢請求來臨,大家一股腦的怼到了資料庫。
  • 緩存雪崩

    : 了解了擊穿,那雪崩就更好了解了。俗話說得好,擊穿是一個人的雪崩,雪崩是一群人的擊穿。如果緩存這堵牆上處處都是洞,那這面牆還如何屹立?吃棗藥丸。

有了緩存系統,我們就可以在向資料庫請求之前,先詢問緩存系統是否有我們需要的資料,如果有且滿足需要,我們就可以省去一次資料庫的查詢,如果沒有,我們再向資料庫請求。

注意,這裡有一個關鍵的問題,如何判斷我們要的資料是不是在緩存系統中呢?

進一步,我們把這個問題抽象出來:如何快速判斷一個資料量很大的集合中是否包含我們指定的資料?

圖文詳解!10大高性能開發核心技術+

這個時候,就是布隆過濾器大顯身手的時候了,它就是為了解決這個問題而誕生的。那布隆過濾器是如何解決這個問題的呢?

先回到上面的問題中來,這其實是一個查找問題,對于查找問題,最常用的解決方案是搜尋樹和哈希表兩種方案。

因為這個問題有兩個關鍵點:快速、資料量很大。樹結構首先得排除,哈希表倒是可以做到常數階的性能,但資料量大了以後,一方面對哈希表的容量要求巨大,另一方面如何設計一個好的雜湊演算法能夠做到如此大量資料的哈希映射也是一個難題。

對于容量的問題,考慮到隻需要判斷對象是否存在,而并非拿到對象,我們可以将哈希表的表項大小設定為1個bit,1表示存在,0表示不存在,這樣大大縮小哈希表的容量。

而對于雜湊演算法的問題,如果我們對雜湊演算法要求低一些,那哈希碰撞的機率就會增加。那一個雜湊演算法容易沖突,那就多弄幾個,多個哈希函數同時沖突的機率就小的多。

布隆過濾器就是基于這樣的設計思路:

圖文詳解!10大高性能開發核心技術+

當設定對應的key-value時,按照一組雜湊演算法的計算,将對應比特位置1。

但當對應的key-value删除時,卻不能将對應的比特位置0,因為保不準其他某個key的某個雜湊演算法也映射到了同一個位置。

也正是因為這樣,引出了布隆過濾器的另外一個重要特點:布隆過濾器判定存在的實際上不一定存在,但判定不存在的則一定不存在。

你們公司網站的内容越來越多了,使用者對于快速全站搜尋的需求日益強烈。這個時候,你需要:

全文搜尋技術

對于一些簡單的查詢需求,傳統的關系型資料庫尚且可以應付。但搜尋需求一旦變得複雜起來,比如根據文章内容關鍵字、多個搜尋條件但邏輯組合等情況下,資料庫就捉襟見肘了,這個時候就需要單獨的索引系統來進行支援。

圖文詳解!10大高性能開發核心技術+

如今行業内廣泛使用的ElasticSearch(簡稱ES)就是一套強大的搜尋引擎。集全文檢索、資料分析、分布式部署等優點于一身,成為企業級搜尋技術的首選。

圖文詳解!10大高性能開發核心技術+

ES使用RESTful接口,使用JSON作為資料傳輸格式,支援多種查詢比對,為各主流語言都提供了SDK,易于上手。

另外,ES常常和另外兩個開源軟體Logstash、Kibana一起,形成一套日志收集、分析、展示的完整解決方案:ELK架構。

圖文詳解!10大高性能開發核心技術+

其中,Logstash負責資料的收集、解析,ElasticSearch負責搜尋,Kibana負責可視化互動,成為不少企業級日志分析管理的鐵三角。

無論我們怎麼優化,一台伺服器的力量終究是有限的。公司業務發展迅猛,原來的伺服器已經不堪重負,于是公司采購了多台伺服器,将原有的服務都部署了多份,以應對日益增長的業務需求。

現在,同一個服務有多個伺服器在提供服務了,需要将使用者的請求均衡的分攤到各個伺服器上,這個時候,你需要:

負載均衡技術

顧名思義,負載均衡意為将負載均勻平衡配置設定到多個業務節點上去。

圖文詳解!10大高性能開發核心技術+

和緩存技術一樣,負載均衡技術同樣存在于計算機世界到各個角落。

按照均衡實作實體,可以分為軟體負載均衡(如LVS、Nginx、HAProxy)和硬體負載均衡(如A10、F5)。

按照網絡層次,可以分為四層負載均衡(基于網絡連接配接)和七層負載均衡(基于應用内容)。

按照均衡政策算法,可以分為輪詢均衡、哈希均衡、權重均衡、随機均衡或者這幾種算法相結合的均衡。

而對于現在遇到等問題,可以使用nginx來實作負載均衡,nginx支援輪詢、權重、IP哈希、最少連接配接數目、最短響應時間等多種方式的負載均衡配置。

輪詢

upstream web-server {
    server 192.168.1.100;
    server 192.168.1.101;
}
           

權重

upstream web-server {
    server 192.168.1.100 weight=1;
    server 192.168.1.101 weight=2;
}
           

IP哈希值

upstream web-server {
    ip_hash;
    server 192.168.1.100 weight=1;
    server 192.168.1.101 weight=2;
}
           

最少連接配接數目

upstream web-server {
    least_conn;
    server 192.168.1.100 weight=1;
    server 192.168.1.101 weight=2;
}
           

最短響應時間

upstream web-server {
    least_conn;
    server 192.168.1.100 weight=1;
    server 192.168.1.101 weight=2;
}
           

總結

高性能是一個永恒的話題,其涉及的技術和知識面其實遠不止上面列出的這些。

從實體硬體CPU、記憶體、硬碟、網卡到軟體層面的通信、緩存、算法、架構每一個環節的優化都是通往高性能的道路。

路漫漫其修遠兮,吾将上下而求索。

繼續閱讀