It 鐵人賽 2019

這漫長辛苦的 30 天總於結束囉 ~ 接下來依慣例第 30 天都是總結篇。 這 30 天的過程咱們都在追求『 一個好的系統 』中的其中一個重點 : 性能 性能越高的系統,可以帶給『 公司 』與『 用戶 』雙方都達到愉悅的情況。 咱們先看看第一階段最基本系統的樣子。 單機的優化方向 應用層方面性能優化重點知識 這下面 7 篇文章,應該涵蓋住了應用層性能方面所需要注意的重點,雖然有分 cpu 與 i/o 優化,但是我是覺得也不用分到那麼清楚,只要記得,你是要儘可能的以最少資源來做事情就對囉。 運算與 i/o 是重點,但總結來說就是『 儘可能的以最少資源來做最多的事情 』 30-03 之應用層的運算加速 - 演算法 30-04 之應用層的運算加速 - 並行運算 30-05 之應用層的 I / O 加速 - 零拷貝 ( Zero Copy ) 30-06 之應用層的 I / O 優化 - Stream ( 與一些 IPC 知識 ) 30-07 之應用層的 I/O 優化 - 非阻塞 I/O 模型 Reactor 30-08 之應用層的 I/O 優化 ( 維護性 ) - 協程 Coroutine 30-09之應用層的兩個池 - 進 ( 線 ) 程池與連線池 資料庫層優化重點知識 接下來到一個系統的命脈『 資料庫層 』的性能優化知識。這裡的最大重點在於 :
正文開始 前面幾篇文章中,咱們提到了如何擴展資料庫層級服務,讓它可以接更多的客,但是這些擴展方法中,都有提到一個『 中間件 』來使用,接下後本篇文章中,咱們將介紹其中一種比較常見的中間件 : MyCAT 本篇文章分為以下幾個章節 : MyCAT 基本概念 MyCAT 的各種架構實現配置 使用 Docker 來實現 MyCAT 讀寫分離 MyCAT 基本概念 在資料庫中間件中,事實上分為兩種類型 : proxy smart-client 它們兩個的基本差別如下圖 1 所示,proxy 是一個外部的服務,所有的應用都會透過這個 proxy 服務來操作資料庫。 而 smart-client 概念就是包在應用層中,當成一個 sdk 概念的程式碼。 圖 1 : proxy vs smart-client 而其中 mycat 就是屬於 proxy 的其中一種應用。 ~ 小知識 ~ 現在幾個比較可以說的出名字的中間件有 : proxy : cobar、mycat、mysql-router、atlas、vitess smart-client : 大部份語言有實現簡單版的,而如果是支援比較多功能的有 sharding-jdbc、tddl。 有興趣的友人可以自已查查來比較看看。 這裡問一下,那一種比較好呢 ? 首先咱們先說說 smart-client 的優點 :
正文開始 本篇文章中,咱們要來說說分散式系統中,最麻煩的部份『 分散式事務 』這一塊,接下來咱們來認真的理一下這個鬼。 本篇分為以下幾個章節 : 分散式事務難題 分散式事務的處理方案 : 2 PC 二階段提交( Two-phase Commit ) MySQL XA 事務實現與使用 MySQL XA 問題 - 性能 分散式事務難題 首先咱們都知道資料庫有所謂的『 事務 』機制,比較準備的說是事務這個『 單位 』,它當初建立出來是為了解決所謂的 : 確保『 同一組資料庫業務操作 』可以有正確的結果 它們不會因為某項業務的其中一項操作錯誤了,導致整個資料庫的資料不正確 ( A )。 它們不會因為系統固障而導致原本成功修改的資料消失 ( D )。 它們不會因為並行操作,導致資料產生產生無法預期的結果 ( I )。 總而言之,事務在固障與並行的情況下,不會產生所謂的『 資料不一致性 』 ( C ) 如果事務可以確保上述事情,那就可以說 : 這個事務有 ACID 的特性 然後咱們在以下三篇文章中,咱們有談到,在 mysql 單機事務的情況下,它們用了以下的機制來確保這些機制 : 原子性 A : undo log 持久性 D : redo log 隔離性 I : 鎖 + mvcc 30-15 之資料庫層的難題 - 單機『 故障 』一致性難題
正文開始 上一篇文章中,咱們有提到了兩種資料庫層的擴展方式 : 分庫 分表 其中分表是用來解決單表太大的問題,而接下來本章節要來介紹另一種處理單表太的工具 : 分區表 本篇文章分以下幾個章節 : 分區概念 MySQL 分區的切分類型 分區使用的注意事項 分區概念 分區表的核心概念為 : 將一張大表,根據『 規則 』拆分為『 隱藏 』的小表 觀念和分表事實上完全相同,就差在『 隱藏 』這個字詞上。它們的差異如下 分表 : 分表後,應用層需要修改 sql 操作位置,指定到對應的分表上。 分區 : 分區後,應用層『 不 』需要修改 sql 操作位置,資料庫層會自動幫你處理。 也就是說假設是使用 type 這個欄位來『 分表 』那你在查詢時可能需為變成如下指令 : user 根據 type 拆分成三個表 : 1. user_type_A 表 2. user_type_B 表 3. user_type_C 長 SELECT * FROM user_type_A WHERE name = 'mark'; 而如果是『 分區表 』的話為 :
正文開始 上一篇文章中,咱們介紹了資料庫層的分散的第一個起手式『 讀寫分離 』,這個方案是將寫與讀分散在不同的機器上,正常情況下,大部份的系統使用這種方案就已經可以處理很好了。 但 ! 如果你已經將資料庫層與緩存層的架構都已經建立好,但還是發現有性能貧頸,那接下來才會建議使用幾個方案,因為這些方案沒用好,會衍生出非常多的問題。 本篇文章分為以下幾個章節,這些就是接下來咱們要來學的擴展法。 分庫 分表 分庫與分表的問題 重要 : 使用前注意事項 要使用以下的擴展方法時,先確認你的資料庫是否以下的問題是否有發生。 問題 1 : 單庫太大,導致硬碟空間不夠囉。 問題 2 : 單庫寫入量太大,導致每一次新增或更新性能非常的吃緊,感覺隨時都會上天堂。 問題 3 : 單表資料量太太,導致每一次操作時都非常的慢。 有以上事情發生才開始往接下來的擴展走。 沒事真的別用它們 分庫 它可以解決 問題 1 : 單庫太大,導致硬碟空間不夠囉 與 問題 2 : 單庫寫入量太大,導致每一次新增或更新性能非常的吃緊 首先第一個要介紹的就是分庫,它的基本定義如下 : 將一個大大的資料庫,依據『 規則 』拆分成小的資料庫 其中上述說的規則,在傳統上可以分為以下幾種 : 垂直切分 : 根據『 業務 』來拆分成多個小資料庫 ( 圖 1 所示 )。 水平切分 : 根據『 特性 』來拆分成多個小資料庫,例如地區 ( 圖 2 所示 )。 圖 1 : 垂直切分範例
正文開始 前面的文章我們說明完應用層的分散式架構以後,接下來我們要來思考如果讓『 資料庫層 』做更多的事情。 在正式開始章節之前,我們先來想想看一件事情。 資料庫層可以向應用層一樣加機器,就可以做更多的事情嗎 ? 答案為是或不是,這個就取決於使用者的能力,因為假設你沒處理好,不但有可能性能下降,而且導致錯誤百出,它不像應用層那麼簡單的主要原因在於 : 它有狀態的,因為它有儲資料,所有會有一致性問題。 應用層在進行分散式時,基本上都是處於無狀態狀況,所以在進行多台機器時,事實上我們不太需要考慮什麼資料一致的問題,而資料庫則否,當多台時,就要面臨到所謂的資料一致性問題。 接下來的文章與章節我們將要來細說,資料庫層如何的使用分散式架構來讓它做更多的事情,並且有更高的可用性,以及它接下來要面對的種種問題。 本篇文章中,咱們將要先來談談,第一種資料庫層的分散式架構方案『 讀寫分離架構 』: 它適用於讀多寫少情況 本篇文章共分為以下幾個章節 : 讀寫分離架構概念 MySQL 的讀寫分離架構實現 可能面臨問題探討 這個分散技術基本上應該是資料庫層分散的第一個起手式,單完成這個架構就已經可以處理不少的事情囉。 資料庫層的讀寫分離架構 基本架構 讀寫分離最簡單的就是所有寫入的都寫入到一台服務,讀取時讀取一台服務,然後你們之間會進行資料同步。 然後在實務上,咱們通常都是會搭配主從架構 ( master-slave ) 來進行讀寫分離。主從架構本來存在的目的是為了可用,就也是如果 master 壞掉了,咱們還有 slave 有資料。 master : 主要用寫的服務,會與 slave 進行資料同步。 slave : 主要用來讀的服務 圖 2 : master-slave 實現讀寫概念圖 讀進化型架構 之前咱們有提過,現在大部份的系統基本上應該是讀大於寫入,所以如果這時只有一台讀,也是有可能會讓它壓力很大,所以這時會變成如圖 3 所示,加個幾台讀機,這種架構被稱為『 一主多從 』。 然後這裡有幾個重點,那就是要如何實現圖 3 的『 分配器 』。 圖 3 : 讀進化型架構圖
正文開始 上一篇文章中,咱們理解了一般 web 系統的擴展方法後,接下來我們來一篇外傳,來說說關於 即時通訊服務 ( IM Instant Messaging Service ) 的擴展。 本篇文章共分以下幾個章節 : IM 服務的先行知識 IM 服務的擴展方案 1 - 負載均衡 IM 服務的擴展方案 2 : IM 服務分配器 在開台之前咱們先來簡單的談談,什麼是即時通訊服務的擴展。 簡單的說就是像 line 一樣可以進行即時的溝通。 傳統上要建立這種類型的系統,通常會使用以下兩種機制來建立雙向的溝通 : IM 服務的先行知識 首先一般 web 應用都是使用 http 單向的來取得資料,也就是 request 然後 response 這種機制,但是在 im 這種服務系統中,場景通常都是 client A 發送訊息,然後 client B 會收到。 IM 服務就是像聊天室例如 Line 這種類型的服務 而通常要實現這個功能目前應該只有兩種機制 : long polling websocket 咱們來簡單用下圖 1 來看一下這兩種運行的差別。
正文開始 前面幾篇的文章中,我們知道如何儘可能的在單機上,可以以最少的資源做最多的事,但是單機一定有它的限制,因此接下來我們要開始正式進入所謂的『 分散式系統 』。 分散式系統不是簡單的增加機器就可以增加效能那麼簡單,它不是簡單的 1 + 1 = 2 的這種概念,有時後 1 + 1 還會小於 2 或小於 1。 最要的原因在於要達成一致性的難度更高,並且維護與管理更複雜,除非你單機真的已經到了極限,不然如果是為了『 性能 』而加機器,那也只是會浪費你更多的時間,不過在實務上有時是為了可用性而加機器那這到還可接受。 本篇文章將分為以下幾個章節 : 應用層擴展基本架構 負載均衡架構優化 擴展後第一個問題 - Session 應用層擴展基本架構 應用層擴展基本上 90 % 都會是長的如下圖 1 架構。 圖 1 : 基本擴展型 基本上會將應用服務變成多台,然後前面加一個負載均衡 ( Load balancing ),每當用戶有請求進來時,會先通過負載均衡服務,然後它會選一台應用服務來將請求送過去。 目前在 web 領域比較常用的負載均衡服務應該是『 nginx 』,它的基本架構就如同咱們前面章節所說的 reactor 架構,所以基本上它可以處理非常多的連線。 30-07 之應用層的 I/O 優化 - 非阻塞 I/O 模型 Reactor 然後 nginx 它有提供以下幾種的分配演算法 : 輪詢 : 也就是所謂的輪流分配,每個能基本上都可以平均的收到。 加權 : 根據應用服務的能力來決定分配,例如機器性能較好的就給他權限較高,差的則給權限較低,這個地方在 nginx 還有很多變化。 ip_hash : 就是同 ip 的都會打到同一台,這個在 socketio 擴展時很重要,如果沒設置,建立 socketio 連線時會打錯台。 url_hash : 打同一個 url 會到同一台。 fair : 簡單的說就是智能的演算法,它會根據頁面大小、加載時間來智慧的選擇應用服務。 負載均衡優化方向 接下來這個章節,咱們要來看看負載均衡的一些優化方向
正文開始 本篇文章中,網路世界最重的協議 http,不只如上圖應用所示只有用戶端那有用到,現階段大部份很多 server 都還是會實用 http 去其它 server 取資料,所以一個系統中,最重要的應用層協議,咱們幾乎可能說是『 Http 』。 本篇文章分為以下幾個章節,事實上也就是所謂的 http 進化史 : HTTP 行前基本知識 HTTP 1.X 的過去式 HTTP 2.0 的現在式 HTTP 3.0 的未來式 網路層的 http 優化事實上沒有啥重點,那就是 : 儘可能將 http 升級更高的版本 但是,為什麼要升級才是這一篇文章的重點。 Http 行前基本知識 在這篇文章正式開始一前,咱們有些前知識要來學習一下,不然下面會有很多東西看不太種。 首先 http 基本上可以說是網路世界的基礎,它當初建立出來的目的是為讓瀏覽器這個應用層的應用使用,然後它在傳輸資料時所使用的協議為『 TCP 』,所以當咱們要建立連線時會進行所謂的『 TCP 三次握手 』如下圖 1 所示 : 圖 1 : tcp 建立連線 ( 三次握手 ) 然後有了這個連線以後,咱們就可以開始進行資料傳輸。 圖 2 : tcp 傳輸資料 最後斷線時就有所謂的四次揮手。 圖 3 : tcp 斷線 ( 四次揮手 )
正文開始 前幾篇文章中,咱們討論完資料庫層的資料緩存以後,接下來咱們要來談談另外兩個緩存 : CDN ( Content Delivery Network ) HTTP 緩存 本篇文章分為以下幾個章節 : CDN 與運行流程 HTTP 緩存與運行流程 CDN 與 HTTP 緩存搞在一起用 這裡先說一下,接下來有一些 cdn 的章節我是直接抓以前我寫的文章來簡單修改一下,不然我還真想不到 cdn 這還要寫什麼。 30-23之 CDN 的說話島 ( AWS CloudFront CDN 實作 ) CDN 與運行流程 在開始理解 CDN 之前,咱們先來說說傳統上一個 client 連線到一個網站的流程。 首先看看下面這張圖 1 所示,這張圖說明了每當一個 client 發送一個請求到 web 網站時,web 網站會回傳 html、css 與 javascript 回來,這裡假設咱們的 web 網站還在台灣,然後回應時間大約在 100 ms 以內 (假設)。 圖 1 : 一個台灣用戶連到台灣網站的時間 然後呢 ~ 這時付你錢的老大叫你將 web 網路架設到美國,因爲免費,然後這時發現回應時間變成 1000 ms 左右,如下圖 2 所示。
正文開始 上一篇文章中咱們已經學習了一些緩存基本的策略,那接下來我們要來理解一下一個重要的主題 : 如果緩存失效的情況,與可能會發生什麼事情呢 ? 基本上緩存失效後的結果,會很慘,尤其是你當初建立緩存時,就是已經為了讀取接近性能臨異值而建立的情況,當緩存一失效,你的資料庫也會瞬間爆掉,然後用戶就不愈悅,你就完了。 圖 1 : 緩存失效圖 而在實務上,緩存失效大至可以分為以下幾類,也就是咱們接下來每個章節 : 緩存失效情況 - 緩存穿透 緩存失效情況 - 緩存雪崩 緩存失效情況 - 緩存服務炸了 緩存失效情況 - 緩存穿透 這個的主要情境如下 : 查詢一個不存在的資料,由於沒命中緩存,因此會一直往 DB 穿,如下圖 2 所示。 圖 2 : 緩存穿透 通常這種情況有可能是前端出了錯,導致一直送不存在的資料,又或是人為刻意,就是要有人想打爆你整個系統。 解法 1 : 硬處理 這種方法就是,當用戶使用 a key 去 redis 找發現沒有 cache,然後再去 db 抓,也發現沒有,然後就將『 這個 a key 是空值 』也寫入到緩存中 ( 可以給它設個到期時間 ),如下程式碼範例。 這樣後面有人在使用這個 key 去打,會在 cache 這被防下來,然後將算後來真的有寫入 a key 時,咱們緩存寫入流程也是會將它處理 ( 詳見前篇文章 )。
正文開始 上一篇咱們基本上已經理解緩存服務 redis 的基本概念後,接下來咱們要進入正題 : 緩存策略 相信不少人應該會覺得這很簡單,不就是將熱資料丟到緩存,然後用戶先優先去緩存取得,沒有則去資料庫拿去嗎 ? 用腦袋想很簡單沒錯,但是難處就在於 : 你要如何確保資料一致性呢 ? 有沒有覺得這名詞很耳熟呢 ? 你只要記好,只要是多個服務,只要是共用資料的,那就一定會碰到它。 什麼樣類型資料適合緩存呢 ? 緩存讀流程 緩存寫策略與難題 緩存寫策略的難題總結 什麼樣類型資料適合緩存呢 ? 在建立緩存時,我們需要先來決定一件重要的事情。 什麼樣的資料需要存放到緩存中呢 ? 基本上適合緩存資料的特點有以下幾點 : 這個資料是常常被使用到的。 這個資料是不常被更新的。 且中如果符合上述兩個情況的那就可以算在『 適合建立緩存的資料 』選項中。 緩存讀流程 讀的基本流程如下 : (1) 用戶往應用服務發送請求。 (2) 應用服務至緩存服務看看是否有緩存。 (3A) 有,則回傳。 (3B) 無,則前往資料庫服務取得資料。 (4) 並將資料回寫入緩存服務。 基本上這種讀的流程比較沒有太大問題與爭論。 圖 1 : 緩存讀取流程 緩存寫策略與難題 緩存策略最大坑在這 比較大的問題在於『 寫 』這裡,因為不同的寫入方式會產生不同的問題,而且這沒有 100% 的完美解,只能有較優但還是有缺點的解。接下來我們來一個一個慢慢看。
正文開始 前面幾篇文章咱們已經學習完了資料層性能相關的知識,而接下來這篇文章,咱們要來學習,如何進一步的讓系統可以做更多的事情。 資料庫單機性能優化到最後,仍然還是逃不過性能的貧頸,但這並不是說單機優化沒有意義,因為單機如果沒有將它優化好,而直接開機器來增加性能,那只能說是拿錢堆起來的性能,而且可能會出問題。 那要如何在增加性能呢 ? 這時通常會使用以下的策略 : 緩存 也就是說架構會變的如上圖 1 所示,在 mysql 前面會多增加一個緩存服務,這個服務我們通常會選擇用 redis。當資料在緩存服務有時直接回傳,沒有則去資料庫服務取得。 圖 1 : 加入緩存服務圖 在開始緩存策略前,咱們要先來研究一下基本的緩存服務『 redis 』。 本篇文章分為以下幾個章節 : Redis 的架構 Redis 的一致性難題處理 Redis 性能使用的要點 Redis 的架構 首先咱們簡單的介紹一下 redis 是啥 ? 簡單的說它算是一種資料庫,redis 是將所有的資料存在『 記憶體 』中,而 mysql 則是將主要資料存在『 硬碟中 』。 它適合當緩存服務的重點就在於儲『 記憶體 』。 而它適合當緩存服務的重點就在於這裡,它將資料儲放在記憶體,因此操作速度非常的快,咱們來簡單複習一下儲硬碟和記憶體取資料的差異,如下圖 2 所示,mysql 讀取資料,基本上要運行 3 次的拷貝,而 redis 則只需要 1 次 ( 每條線就是一次拷貝 )。 圖 2 : redis vs mysql 讀取資料比較
正文開始 上一章節咱們學習到了,在並行情況下 mysql 可能會發生什麼樣的資料不一致問題,並且也學習到了這些問題它又是如何解決。 雖然 innodb 已經儘可能的解決上述這些問題,但是如果要完全解決,性能代價太大,因此後來有了一些折衷方案,這個東西就叫做 : 事務隔離級別 接下來本篇文章將要談談這東西,並且整理一下 innodb 預設的一些鎖的設定。 MySQL 的折衷解法 - 事務隔離級別 預設 RR 級別 - 鎖的操作總結 死鎖的小分析 MySQL 的折衷解法 - 事務隔離級別 資料庫在並行運行時,通常會發生以下幾種問題,並且也探討過這幾種問題的解法 : 更新不一致 : 鎖 髒讀、不可重複讀 : mvcc 幻讀 : mvcc + next-key locking ( 有一些幻讀情境無解 ) 而在 innodb 事實上可以『 完全簡單 』的處理上面幾種現象,解決方法如下圖 5 所示,也就是同一個時間,只能執行同一個事務,如下圖所示。也就是把發生問題的根本原因『 並行 』給移除。 圖 1 : 一致性的根本解法 但這會嚴重的影響到效能的問題,因此才有了『 隔離層級 』這東西。 隔離層級可以讓你決定需要處理到什麼層級的一致性問題 也就是說上面四個問題,它可以讓你根據性能的要求,來決定你要處理幾個問題,處理的越多代表性能越差,處理越少個則代表性能越好,但反之不一致性機率更高。 事務隔離級別 mysql 總共提供以下四個層級,不過比較準確的說,這是所有資料庫共有的層級,性能從高至低排序,而反之資料一致性性由低至高。
正文開始 本篇文章中,咱們要說說另一種資料不一致性產生的場景,那就是 : 『 並行 』產生的不一致性難題 基本上並行所產生的不一致性難題,可以分為以下幾種類型 : 更新不一致 髒讀 不可重讀 幻讀 本篇將會分為以下幾個章節來談談這幾個難題 : 更新不一致難題與解法 - 鎖 髒讀與不可重讀難題與解法 - MVCC 幻讀難題與解法 - MVCC + Next-Lock 鎖 更新不一致難題與解法 - 鎖 這種情境如下圖 1 所示,a 與 b 兩個事務進行更新操作後,事務 a 再看看自已操作的結果,發現自已的更新消失了。這種情境被稱為『 更新不一致問題 』 圖 1 : 更新不一致問題 那這種情境 innodb 它是如何處理呢 ? 它使用鎖來處理 在 innodb 的預設,它會對要『 update、delete 』的『 行加鎖排他鎖 』,不過比較嚴格定義應該是說 : 它會對有用到『 索引 』的『 行 』加『 排他鎖 』,不然會退化成『 表 』鎖
正文開始 前面幾篇文章中,咱們理解完了 mysql 的索引概念與原理,並且理解了在 mysql 中一個查詢的速度與否取決於索引與表的設計。接下來咱們要來理解一些會拖性能後腿的東西。 這個會性能後腿的東西就是 : 一致性難題 在追求高性能的路上,通常一定會面臨到資料一致性問題,而產生的原因通常可簡單的分為以下來個來源 : 故障 併發 接下來本篇文章,咱們來看看 mysql 它是如何處理『 故障 』所引發的一致性問題。 不一致資料產生原因。 MySQL 的解法。 ACID 的小關連。 不一致資料產生原因 原因 1 : 某項操作故障 假設咱們要將 a 帳戶的 1000 元轉到 b 帳戶去,但這時如果在處理 b 帳戶加錢時出錯了,那整個結果就錯誤了,如下圖 1 所示。 圖 1 : 故障導致資料不一致性 在圖 1 中正確的 b 帳戶應該是 1000 元,但是因為加錢失敗了,所以 b 帳戶變成 0 元,這就是產生了『 資料不一致性 』。 原因 2 : 單機故障 第二個產生的原因要先來理解一下,所謂的『 通常 』寫入硬碟的原理。 假設咱們有一個 insert 的指令,要將資料寫入到資料庫中,然後資料庫要將它寫到硬碟中,那它的運行過程如下。
正文開始 本篇文章中咱們將要從『 表 』的角度來儘可能的優化性能。 表設計的幾個小建議 正規與非正規的小戰爭 表設計的幾個小建議 這個章節會給一些建立表時的小建議,雖然這裡優化的點不多,但是每一個地方都做到好,才是專業。 建議 1 : 選擇適當的欄位類型 - 字串 ( 性能 + ) 基本上在 mysql 中有一下文字類型的選擇,適當的選擇類型,可以省下不少資源 : char varchar text blob char vs varchar char 的特點 : 最大 255 byte 不管如何就是使用指定的空間 ex. char(4) 就是就算只有 1 個字元,就是花費 4 byte。 用 char 需要處理空白。 varchar 特色 : 存幾個字就是需花費 n+1 個 byte。 Ex. varchar(4) 假設你儲 1 個字,那就是花費 1 + 1 = 2 byte。 65535 為最大,實際為 65532。 當 varchar 大於一定字數時,會自動轉成 text。 varchar (255+) 轉成 tinytext varchar (500+) 轉成 text varchar (20000+) 轉成 mediumtext 下表為 char 與 varchar 的實際存儲空間比較。
正文開始 前面兩篇文章中,咱們已經學習完索引的核心觀念以後,接下來咱們學學在使用時有那些的優質的方法與注意事項。 30-11 之資料庫層的核心 - 索引結構演化論 B+樹 30-12 之資料庫層的核心 - MySQL 的索引實現 本篇文章分為以下幾個章節 : 索引的重要小觀念 索引的設計流程 索引的使用注意事項 SQL 地雷區 索引的重要小觀念 觀念 1 : 不是索引越多越好 索引不是聖杯,它是雙刃刀,用的好上天堂,用不好下地獄。基本上資料庫的索引幾乎可以影響一個系統的 50% 以上的性能。 索引可以加快查詢速度,但注意它是以空間換取時間。 基本上它使用的資源如下 : 每個索引都會建立一顆 b+ 樹。 每次新增、更新資料時都會改變 b+ 樹。 所以當你索引越多時,你所需要的記憶體與維護索引的 cpu 運算就需要越多。 觀念 2. 懂的使用 Explain 來分析你的 SQL 索引性能解析 explain 這個指令可以讓你知道你下的 sql 語句是否有命中索引。 EXPLAIN SELECT * FROM user_no WHERE name = 'mark'; ===================================================== id: 1 select_type: SIMPLE table: user partitions: NULL type: const possible_keys: PRIMARY key: PRIMARY key_len: 8 ref: const rows: 1 filtered: 100.
正文開始 上一篇文章中,我們理解了 innoDB 索引的基本原理 b+ 樹的,也理解了為什麼 innoDB 要選擇 b+ 樹的原因後,那接下來,我們要來理解,在 innoDB 中『 實際上 』是如何使用 b+ 樹來建立索引機制 ? 本篇文章分為以下幾個章節 : 一張乾淨的表 InnoDB 實際上如何儲呢 ? ( Clustered Index ) 一張自加索引的表 InnoDB 實際上如何儲呢 ? ( Secondary Index ) InnoDB 所提供的索引類型 一張乾淨的表 InnoDB 實際上如何儲呢 ? ( Clustered Index ) 假設我們有以下的 table 表,然後咱們不要手動設什麼索引,那麼 innodb 會如何儲存它呢 ? Id ( PK ) Name age 1 Mark 18 2 Jack 10 3 Ian 36 4 Jiro 30 5 Fucc 27 … … … 10 Fukk 46 表 1 : 範例 首先 innodb 會自動幫你建立一個叫『 clustered Index 』的東東,在 innodb 中,它就是這份資料實際上儲存的結構,請別管它叫索引,它就是你的實際資料,只是它是有規則的儲存結構。
正文開始 接下來咱們要來理解資料庫系統中最核心的問題 : 要如何儲放資料,才能更快速的找到資料呢 ? 而這個東西的技術就是所謂的 : 索引 而在 mysql 中決定如何儲放的是資料庫儲存引擎來決定,而這裡咱們將要從 mysql 預設引擎 innodb 來深入的理解一下。 在 InnoDB 中預設是使用『 B+樹 』來當資料儲放格式,而為什麼要選擇它呢 ? 這就是我們這篇文章的重點。 InnoDB 為什麼選擇 B+ 樹 ? 本篇文章分為以下幾個章節,這也就是 B+ 樹的誕生原由。 二分搜尋 Binary Search 二元搜尋樹 Binary Search Tree 平衡二元搜尋樹 Balanced Binary Search Tree B 樹 B+樹 ( InnoDB 的選擇 ) 二分搜尋 Binary Search 首先我們都知道,資料庫誕生出來的本質就是為了讓我們找東西更快速。 那是和什麼比呢 ? 就是所謂的『 線性搜尋 』,也就是說有 1000 筆資料,你要找到你想要的值,需要一筆一筆的慢慢查,而這種線性搜尋的時間複雜就是 O(n),其中 n 就是你所有的資料量。如下圖 1 所示。
正文開始 前面幾篇文章中,咱們大致上學習完了應用層的一些性能優化的基本知識,接下來咱們要來學學資料庫層的高性能優化方向。 在這裡先說一下一個重點 : 資料庫絕對是一個系統的性能核心,請把優質的 DBA 們當寶來餵食 接下來幾篇資料庫層文章將會以『 MySQL 』來進行說明,雖然不同的資料庫可能實作上會有些不同,但是大致上原理不會差距太大。 開始第一篇文章,咱們將要先理解一些 MySQL 的基本架構,接下來才能理解那些地方可能可以進行性能優化,又是那一些地方可能是會拖後腿,但是又是必要存在的地方,這篇也算是資料庫篇的小目錄。 本篇文章分成以下幾個章節 : MySQL 基本架構。 資料庫層的優化方向。 資料庫層會拖性能後腿,但是又必須處理的地方。 MySQL 基本架構與運行 圖 1 : mysql 的基本架構 基本上分為以下幾個部份 : 應用層 ( 紅色 ) 服務層 ( 綠色 ) 存儲引擎層 ( 藍色 ) 應用層 應用層主要處理兩個工作 : 連接處理任務 權限管理 其中連接處理任務就是每當一個 client 端發送一個請求到 mysql 時,它會在從 thread poll 中分配一個 thread 來處理此請求,然後之後從此 client 來的任務,都會由此 thread 來處理。 mysql 並沒有使用之前說的非阻塞 I/O 模型 ( reactor ) 來處理請求的,原因可能在於,mysql 的任務是屬於 cpu 運算密集與磁碟 I/O 密集任務,因此比較不適合使用 reactor 這種使用 epoll 的模型。
正文開始 接下來咱們要來談談,在應用層中很常提到的兩個池『線程池』與『連線池』,它們兩個在應用層扮演了性能方面什麼樣的角色。 本篇文章分為以下幾個章節 : 什麼是進程池 ? 為什麼需要它呢 ? 進程池的數量設置。 什麼是連線池 ? 為什麼需要它呢 ? 連線池的數量設定。 什麼是進程池 ? 為什麼需要它呢 ? 先說以一下,這裡不一定是指進程 process ,也有可能是指線程 thread,反正都是代表一個池子裡裝了這兩種東西。 接下來咱們以 process 進程為主。 進程 process 咱們已經在前幾章很常看到它,它的基本概念就是 : 作業系統的操作單位且是最小資源分配單位 也就是在某個時間,會以 process 為單位開啟資源,並將 cpu 分配給它來工作。 而所謂的進程池的就是,一個裝了一堆進程的隊列,當你有需要進程時,去裡面拿,而不用在重新建立一個新的。 為什麼需要它呢 ? 先說一下,咱們什麼時後要開多個 process 或 thread 呢 ? 並行運算,也就是多開個 process 或 thread 來幫忙計算。 i/o 處理,在某些情況下,咱們需要開一個 process 或 thread 來處理 i/o。 那為什麼需要進程池呢 ? 可以節省建立與結束 process 所耗費資源 ( cpu 、 mem )。 可以有效的控制 process 的數量,如果一個不注意開太多,就算開時沒爆,你也有可能在上下文切換時爆了。 有了進程池事實上也代表有管理器,那這樣也代表有可能玩定時執行進程或線程 ( Java 就降玩 )。 這裡後我們簡單的複習一下,看看建立進程與結束是要做那些事情。基本上就如下面這張圖一樣,就是不斷的拷貝記憶體或移除記憶體。請別小看這一段工作,事實上要做不少事情的。
正文開始 上一篇文章說明完了非阻塞 I/O 模式核心 reactor,並且它可以幫我們建立 : 異步非阻塞的 I/O 操作。 而接下這篇文章我們將要來說說 coroutine 協程這東西,協程這東西在 I/O 優化佔據什麼地位呢 ? 簡單的說它可以讓我們實現以下的事情 : 可以在應用層實現同步非阻塞的 I/O 操作 接下來我們來深入的探討一下,協程這東西可以解決什麼事情以下現階段有那些東西有在使用這個機制。本篇文章共分以下幾個章節。 coroutine 協程想要解決的問題。 coroutine 協程實現原理。 Golang 的 goroutine。 coroutine 協程想要解決的問題 首先咱們先來看看它想要解決什麼問題呢 ? 協程它想要將異步非阻塞的 I/O 操作變成同步的。 異步非阻塞寫法 首先咱們來看看 reactor 所實現的異步非阻塞的 I/O 操作的寫法,如下程式碼,這一段是使用 php swoole 來打 redis 的模擬碼。然後重點在於以下兩點 : 異步 : callback 機制,也就是下面程式碼裡面的 function。 非阻塞 : 當打這個 I/O 操作時,不會阻塞住整個進程。 <?php $client = new swoole_redis; $client->connect('127.
正文開始 接下來本篇文章,咱們要來說明所謂的『 I/O 』模型。 這個東西我當初看到也有點不太能理解,為什麼需要它,但後來理解以後發覺,你只要知道一個 http 請求 web server 是如何處理的,從 0 至 1,那這樣的話當你完全通了,就知道這為什麼會有這些模式。 本篇文章共分以下幾個章節 : 傳統的 Web Server 請求 I/O 處理與問題 非阻塞 I/O 模式核心 Reactor 模型。 一些常見的問題。 ~ 重要備註 ~ 相信有不少人聽過阻塞、非阻塞、同步、非同步,也相信有些熟悉 linux 的友人,聽過它所提供的一些上述名詞的方法,但這裡要先說明,接下來的說有上述詞語,都是指『 應用層 』的表達,而不是『 業系統層 』的所提供的方法,除非有特別說明才是作業系統的。 傳統的 I/O 處理與問題 一個 I/O 請求進來要如何處理呢 ? 傳統的 I/O 請求處理 咱們這裡以 http server 處理請求時來當範例,如下圖所示,咱們以常見的 PHP + Apache 來看。如下圖 1 所示,每一個 http 請求都需要開啟一個或是使用一個進程來進行處理,這裡不是只有 http 請求會被阻塞,而是所有的 I/O ( ex. 網路請求、檔案讀取 ) 操作正常來說都是會被阻塞的。
正文開始 Stream 這個東東,基本上在每一個語言你都看的到,而今天我們將要深入的來理解它到底是什麼東西,並且它在一些 I/O 操作上可以幫助我們解決什麼事情。 本篇文章將分為以下幾個章節 Stream 是什麼 ? 可以解決什麼事情 ? Stream 在 IPC 通信的原理。 簡單的小範例。 Stream 是什麼 ? 可以解決什麼事情 ? 傳統資料傳輸流程問題 stream 它是一種技術,基本上專門用來傳輸資料用。 咱們先來看看傳統上,咱們如果要從硬碟拿個檔案是如何處理。 基本上流程如下圖 1 所示。 應用程式發送 system call read 某個檔案給作業系統的內核。(用戶切內核) 內核看看內核緩衝區有沒有相關資料(也就是所謂的內核記憶體)。 有,則將內核緩衝區的資料,拷貝到用戶緩衝區(用戶進程記憶體)。 無,則前往硬碟取得。 將硬碟資料拷貝至內核緩衝區。 將內核緩存區資料,拷貝到用戶緩衝區。(內核切成用戶) 然後就可以使用資料了。 圖 1 : 傳統的資料傳輸流程 而重點在於這裡 : 硬碟資料 copy 到內核緩衝區,接下來再從內核緩衝區 copy 到用戶緩衝區。 然後問題出在於 : 如果資料很大會發生什麼事呢 ? 基本上結果就如下圖 2 所示,有可能在內核緩衝或用戶緩衝,就爆掉了,因為記憶體是整份 copy 過去,如果你硬碟資料有 10 gb,那就代表,要將這 10 gb 的資料 copy 到內核緩存,再 copy 到用戶緩存,這時後用戶進程才能拿到它。
正文開始 前二篇文章中,咱們已經學習完運算方面的優化,而接下來幾篇文章,咱們要來說明 I/O 優化這個議題。 I/O 基本上可以分為兩種,『 文件 I/O 』與『 網路 I/O 』,這兩種 I/O 操作原理大同小議,但是優化方式卻有些不同,接下來這一篇文章,算是混合。 本篇文章分為以下幾個章節 : I/O 原理。 零拷貝 I/O 的概念與優化原理。 零拷貝的實現。 零拷貝的語言支援與問題探討。 I/O 原理 先從最基本的來看,何謂 I/O ? I/O ( Input/Output) 通常是指資料在『系統』與『外部裝置』的輸入與輸入,最簡單的例子,抓取硬碟或 USB 資料就是所謂的 I/O 操作。 接下來我們簡單的來看一下,在 linux 操作系統上,所謂的『 從硬碟讀取資料,並結果輸出到網路 』到底是在做什麼。 記憶體層面來看 I/O 流程 記憶體就是作業系統最基本的資料儲放地,接下來我們從記憶體的層面來看,所謂的『從硬碟讀取資料,並將結果輸出到網路』它是如何變化的。 基本『 讀與寫 』流程如下 : 在看流程前先說明一下,等等到看到的所謂『 緩衝區 』 就是指在某個記憶體中,切割一些空間出來,來當緩衝區。像等等看到的內核緩衝區就是在內核的記憶體中,拿一些空間來儲放等等要進來的資料。 接下來就開始看流程。 應用程式發送 system call read 某個檔案給作業系統的內核。(用戶切內核) 內核看看內核緩衝區有沒有相關資料。 有則將內核緩衝區的資料,拷貝到用戶緩衝區。 無則前網硬碟取得。 將硬碟資料拷貝至內核緩存區。 將內核緩衝區資料,拷貝到用戶緩衝區。(內核切成用戶) 在從用戶緩衝區發送 system call write 將用戶記憶體資料拷貝到 socket 緩衝區,這緩衝區也是在內核中。(用戶切內核) 用戶端收到 ack (內核切用戶) 資料飛向世界。 順到說一下,這樣的操作總共會進行 4 次的上下文切換。
正文開始 假設你已經將你的演算法進行了優化,但是這時發現,這一項演算法工作還是需要花到非常多的時間處理,那要怎麼辦呢 ? 假設你所在的機器是多核心 CPU,那這時的確是有解,那就是本篇文章的主題 : 開啟 Process 或 Thread 來幫忙進行運算。 本篇文章就分為以下三個章節: CPU 運算與 process、thread 的關係。 CPU 密集任務處理。 多線程並行處理的淺在問題。 CPU 運算與 Process、Thread 的關係 在實際上理解如何開啟 process 或 thread 前,咱們先從最基本的東西說起。 何謂 process ? 何謂 thread 呢 ? 進程、行程 ( Process ) 首先咱們先來看看 process,基本上在你的電腦中每一個應用 ( ex. line、chrome ) 都是至少是一個 process,然後比較重要的一點在於 : 對作業系統來說,它是資源分配的最小單位,並且同時是個『操作單位』 在 linux 系統上,一個 process 的建立過程如下圖 1 所示 : 母進程執行 fork() 建立一個子進程。 同時間建立子進程的記憶體空間。 圖 1 : 作業系統建立進程的概念圖
正文開始 本篇文章開始,我們將要深入的探討,每一個服務,要如何儘可能的達到高性能呢 ? 這首先第一部份,我們要探討以下主題 : 在應用層,要如何儘可能的使用越少的資源( CPU、Memory ),來做最多的事情呢 ? 而這一題的主要的答案就是不少人面試很排斥的『演算法』。 本篇文章會分為以下幾段 : 一個好與不好的演算法性能差距多大呢 ? 演算法運算時間的分類 演算法優化建議 接下來正文開始。 一個好與不好的演算法性能差距多大呢 ? 一個演算法的效能基本上有兩個東東 : 時間複雜度: 你可以把它想成演算法的運行時間。 空間複雜度: 這個可以想成你這個演算法需要花多少的空間來處理。 上面只是簡單說明它的代表概念,比較實際的運算方法直接去 wiki 看就夠囉。 那麼拉回拉,一個好與不好的演算法性能差距有多大呢 ? 呵 ! 我們以一個最簡單的演算法『費波那契數列』來看看。 首先這是好的程式碼。 // good console.time('time'); function fib (n) { if ( n === 0 || n === 1) { return n; } let a = 0; let res = 1; let temp = 0; for (let i=2; i <= n; i++) { temp = res; res = a + res; a = temp; } return res; } fib(45); console.
接下來咱們會從最基本的開始 : 儘可能的優化單機性能 基本上不少高性能的書籍都是直接跳至分散式架構,但是如果一個開發者連單機都處理不好,我不太相信他開發出來的分散式架構是高性能的。 單機處理的好,才是高性能的前提。 本篇會分為以下幾個章節,來探討單機領域的優化方向,這個方向也就是之後文章的目錄。 單機系統的優化路線 Step 1 (應用服務、資料庫服務)。 單機系統的優化路線 Step 2 (快取服務、CDN 服務)。 一個系統效能的評估指標。 單機系統的優化路線 Step 1 (應用服務、資料庫服務) 單機系統的最基本最基本架構應該長的如下圖 1,就是最簡單的應用服務與資料庫服務,咱們這裡先以最大眾的 web 服務為主 : 應用層服務 資料庫層服務 圖 1 : 最簡單的應用架構。 單機應用層的性能優化方向 基本上單機應用層的優化目標如下 : 以最快與最少的資源來處理請求,並且可以最快的速度將結果回應給客戶,讓客戶於愉悅。 而要完成這件事情,基本上有幾個方向可以研究。 以最少的資源進行運算,這裡比較白話文的來說就,用最少的 CPU 與 Memeory 來完成工作。 I/O 處理,大部份的 Web 應用都是讀取資料庫或啥的,這裡如果沒處理好,你的系統絕對只能做很少的事情。 I/O 的一些加速技術例如 stream、零拷貝、線程、連線池。 不過簡單來說就分兩種『運算』與『I/O』。 圖 2 : 應用服務性能重點。 資料庫層的性能優化方向 資料庫層是的優化目標基本上如上應用層一樣: 以最快與最少的資源來處理請求,並且可以最快的速度將結果回應給客戶,讓客戶於愉悅。 基本上在性能方向會有幾個方向可以研究 :
何謂一個好的系統呢 ? 為什麼會問這個問題呢 ? 因為事實上這個是我原本想要撰寫的主題。咱們工程師在開發系統,所學習的大部份的技術,基本上都是為了追求『建立一個好的系統』,然後我本來想將建立一個好的系統的知識點,建立成一個 30 天的知識集,但就算進行精簡化,也很難進行有深度的探討。 因此後來才有了這 30 天這個主題 : 30天之從 0 至 1 盡可能的建立一個好的系統 (性能基礎篇) 接下來我們來說說,上述的主題『性能』,在一個好的系統中,是那一個部份。 何謂一個好的系統呢 ? 這個主題事實上非常的抽象與盤大,問十個人可能有十種答案,在這裡筆者將來說明心中理想的系統。 一個好的系統我覺得最重要的一點為 : 要有人用 這個是我覺得是最重要的一點,如果沒有人用,除非你裡面的技術是可以改變世界的,不然基本上是沒啥價值性的,不論是科學或是商業價值。 『要有人用』這個基本上咱們搞技術的除非你和老闆是好麻吉,不然事實上很難干涉行銷策略這個層級的事情,所以這一塊咱們先不管。 不過也不代表我們不用學這個領域的知識。 你的知識越廣,就越不會被固定領域知識僵固,並且更能幫助你突然領域的深度。 忘了是誰說的,不過我很喜歡這句話。 不過這句話我覺的有個前提假設 : 不過你要先有一把刀,你才能去開拓世界。 也就是說如果你沒有先在你的領域打個底,那麼你也沒有本事將從其它領域學到的東西,融入你的領域。 某些方面,我覺得學程式語言就如同上面這一句話一樣。 在有人用的情況下,何謂一個好的系統呢 ? 我覺得一個好的系統只有一個重點 : 讓雙方愉悅 這裡雙方是指『用戶』與『公司』。 能夠讓使用者覺得好用,用起來就是愉悅兩字 (用戶愉悅)。 能夠以最少的資源(機器)做最多的事情 (公司愉悅)。 能夠讓開發人員未來花越少的時間在維護 (公司愉悅)。 安全 (用戶與公司都愉悅)。 能夠讓使用者覺得好用,用起來就是愉悅兩字 (用戶愉悅) 畫面讓人覺得愉悅。(設計) 畫面操作讓人覺得愉悅。(UI/UX、性能) 不會一下可以用,一下不能用。(可用性) 總結一句話 :