動機

查漏補缺

ch1: 高性能架構

效能指標

  • throughput
    • TPS(transaction per second)
    • QPS(queries per second)
  • 併發數
    • 同時處理的數目: 同時在線user, 併發連線數…
  • response time
    • amdahl’s law
  • 可靠度指標
    • 無故障時間

指標之間的影響

  • throughput & 併發數
    • (一開始) 併發數上升 => throughput上升
    • (持平) 併發數上升 => throughput不變
      • 系統臨界點
    • (下降) 併發數上升 => throughput下降
      • 系統隨時都會崩潰
  • 併發數 & response time
    • (併發req數目) m/m/1的平均時間公式
      • service的處理rate(mu那一個, 系統可以承受的最大併發數)
      • (一開始) req的數目小於系統可以承受的最大併發數,response time上升不明顯
      • (過了可以承受的最大併發數) response time急速增加
    • 系統中觀察到正在處理的req/thread數目
      • response time大 => 卡在system中的req/thread會比較多!!

ch2: 分流設計

CDN

  • 優點
    • 減少系統併發數
    • 減少response time
    • 減少網路壅塞
  • 原理
    • 多個CDN node,DNS做query時根據使用者位置回傳最近的ip
    • CDN node會變成cache proxy,有就回傳;沒有就relay req到主機

可以把CDN對地址動手腳的方法用到service discovery

service discovery

  • 服務註冊
    • user: 去registry找service的實際位置
    • registry: 維護service的清單
    • server: 要把自己登記到registry
  • 服務規則
    • user: 去registry找service地址的使用規則
    • registry: 放service地址的使用規則,讓user自己判斷要去哪
    • server: 要開發或是營運去調整規則

反向代理

由proxy分配req到不同的server (追求load balance就是load balancer;server的任務都不同就是一般的反向代理)

  • 原理
    • level 4: 只看ip與port做分配
    • level 7: 看req內容作分配,eg: http, ftp…
      • 對外面來說,ip與port都是一樣的!!

ch3: 服務平行設計

  • 叢集: 有很多同樣的server的set,一起提供服務
    • server無狀態
      • 無狀態的節點叢集
      • 一般都是負責query之類沒有狀態的服務
      • 平行喚醒問題
        • 在這個set加入定時寄信功能
          • 結果所有server一起寄信!?
        • 解法: 外部喚醒
          • 打req進來,跑功能,這樣就只會有一台跑
    • server有狀態
      • server自己維護狀態
        • 要與別人share
          • 資訊一致的節點叢集
          • 大家都有自己的狀態,但需要與其他server的狀態同步
          • 一致性問題(讀寫不一致): 我在A改的,在B看不到
            • 一致性的等級
              • 強一致性
                • 2 phases commit
                • 3 phases commit
              • 最終一致性
                • 有retry的msg queue
        • 不要與別人share
          • 單一服務節點叢集
          • user與server是被綁定的!!
          • how to bind?
            • user指定
              • online game的選server
            • 根據位置、user id
            • 先隨便assign,把server位置寫到cookie中
          • 單點失敗
      • server把狀態抽出去
        • 資訊共用的節點叢集
        • 在redis之類的地方紀錄state,像是session
        • redis之類的地方
          • 會是bottleneck
          • 需要concurrent的控制
  • 分散式系統
    • 把server中的(實體/虛擬)元件拆掉,像web server與DB跑在不同host
      • 如果有元件是bottlebeck,可以用叢集拆掉
    • 一致性問題(讀寫不一致): 我在A改的,在B看不到
      • 資訊一致的節點叢集
    • 所有元件是可組合的? 微服務系統

ch4: 運算併發

  • 多process
    • 可能在不同的cpu跑(平行),也可能在同一顆跑(共時)
    • 具有很強的隔離
      • 像是開兩個server在process
        • 可以兩個指定不同的port,就有兩台獨立的server
    • ctx switch的成本高
      • cache要預熱
      • 在userspace與kernel space之間切換
      • reg的切換
    • IPC不好做
    • sync
      • 使用情境
        • 獨佔 (競爭)
        • 協作: 等對方好了在一起走
        • 獨佔、協作的詳細介紹,去看Parallel Thinking
  • 多thread
    • 可能在不同的cpu跑(平行),也可能在同一顆跑(共時)
    • ctx switch的成本小
      • 都在同一塊記憶體
      • 有locality
    • 使用情境
      • 做非同步 (會block的工作)
      • 處理subtask,之後merge
    • sync
      • 使用情境
        • 獨佔 (競爭)
        • 協作: 等對方好了在一起走
        • 獨佔、協作的詳細介紹,去看Parallel Thinking
  • fiber就是continuation
    • 只能在同一顆跑(共時)
    • 執行fiber就會停下當下的動作,之後執行會回到原本的位置
    • 不會race condition
    • 不會平行是concurrent!!
    • ctx switch的代價很小
    • sync
      • 使用情境
        • 協作: 等對方好了在一起走

ch5: 輸入輸出設計

去看之前的文章

ch6: 資料庫設計與最佳化

  • 設計table
    • 當成設計class,但是只有primitive與reference而已
      • array要轉90度,變成table (NF1)
      • 所有的欄位(primitive)可以是來自
        • 自己本身的屬性
        • 另一個物件展開的結果 (NF2, NF2, BCNF, 反正規化)
      • 去看之前的文章
  • index
    • 故障
      • 一個col做index
        • 上index的column做修改
        • 用錯type去query (string卻用成int,雖然會被cast,但index會失效)
      • 多個col做index
        • (Btree) query沒有包含前面的col(左手邊)
          • index從第一個col開始sort,一直下去,所以做query時需要前面的
        • (hash) 沒有全部的col
      • 對string做index
        • 如果有wildcast, btree與hash都會沒用,要用inverted index
      • btree
        • !=, <>, NOT
        • IN, NOT IN
          • IN => BETWEEN
      • null
        • index沒辦法對null做任何事
          • 用其他值代替!!
    • 不同的engine不同的index (與其他)
      • innoDB會把hash自動換成btree (innoDB沒有做hash)
  • 自建交易
    • 可以undo的action沒差
    • 不能undo的action(對外界有影響)
      • 最多只能有一個
      • 只能在最後一位
  • 資料太多了
    • table分區 (partition)
      • 把一個table(file)分成多個檔案
      • 對於table的操作不變 (在外面來看這個table還是一樣)
      • 可以存到不同的HDD
        • 增加throughput
        • 增加能存的entry數量
        • 可以針對某一區做操作,不影響其他區
      • query最好要把當初做分區的條件放入query
        • 不然要所有分區都跑
    • 分庫
      • 把DB切開
        • table怎麼辦?
          • 分table
    • 分table
      • 不分割(split)table
        • 每個DB都有一些完整的table
      • 分割(split)table
        • 水平分割
          • 把資料放到不同的表
            • [(1,2), (3,4)] => [(1,2)], [(3,4)]
        • 垂直分割
          • 依據col去割,之後用primiary key來認同一個row
            • [(1,2), (3,4)] => [(1,3)], [(2,4)]
      • 原則: 不要跨table
        • 水平分割
          • 一次需要所有col
        • 垂直分割
          • 只要一部份col
      • 對於table的操作要改 (程式要改)
        • table變得不一樣了
          • 路由操作: 要改table的name (現在有多個table)
          • 拼接操作: 要多加join (垂直分割如果需要另一個表的col)
      • 讀寫分離
        • 多個DB分別處理讀與寫
        • 路由操作: 讀與寫要到對的DB
        • 主從複製: 主從要同步!! (一致性問題)
          • 複製的材料
            • log
              • statement: 就是指令 (但遇到調用now_time之類的,就沒辦法反映真實資料到read db上)
              • row: 就是資料
              • mixed: 就是指令+資料
          • 非同步複製 (not reliable)
            • write後立刻return,log非同步的傳
              • write db掛了又沒傳log就gg了
          • 半同步複製
            • write後等log傳到某個db才return
          • 全同步複製
            • write後等log傳到所有db才return
  • DB中介軟體
    • 就是DB的middleware,處理上面的路由操作、拼接操作、主從複製之類的問題
      • eg: MyCat

ch7: 快取設計

  • cache for reading
    • cache會花費到的時間
      • 寫入cache時
        • 生成key時
          • hash function
          • 比較key
        • 寫入mem時
          • 寫mem
            • 寫入什麼?
              • 序列化物件
                • 讀寫時都要經過序列化
              • 原本的物件
                • 不用反序列化
                • 如果存的是reference?
                  • 會發生race condition!!
                  • 解法: 存序列化物件 或是 deep copy
      • 讀cache時
        • 生成key時
          • hash function
          • 比較key
        • 讀mem時
            • 讀mem
          • 沒中
            • 跑原本
    • cache update
      • 被動update
        • 時效性更新
          • 有過期時間
      • 主動update
        • cache aside
          • read: 正常的讀,找不到去後面拉資料
            • 去後面拉資料的時候要不要把cache刪了?
                • 如果寫入中,有另一個read
                  • 就會拉到舊的值到cache!!
                • 但寫入完成後,雙方資料就不一致了
              • 不刪 (good)
                • read時,如果莫名其妙被block住
                • 之後這之間有write完成
                • read就會拿到舊的值
                  • 但這不太可能,只要
                    • read夠快
                    • 確保不會被preemptive
                  • 就不會發生
          • write: 直接往後方寫,不直接更新cache
        • write through
          • read/write: 都透過cache (同步)
            • cache變成單點失敗的點
        • write behind
          • 改write through的write成非同步
            • 如果cache掛了
              • 可能沒寫到!!
    • cache clear
      • 時效性
        • 訂個過期時間
          • 時間到就自己不見
          • 讓另一個thread去清
      • 固定pool數量
        • FIFO
        • LRU
      • 非強引用
        • GC的延伸,有ref到的就是有用的obj,所以不能刪,但沒有就是可以刪
          • 這樣obj只有刪與不刪
        • 但有的情況是在mem吃緊時可以刪
          • 所以ref要有所區分
        • ref的分類
          • 強引用: 一般的ref
          • 軟引用: 會在mem不足時回收
          • 弱引用: 不管mem夠不夠只要被gc就會被回收
          • 虛引用: gc會當成看不到他
    • cache 的問題
      • cache穿透
        • 如果連後面都沒有結果?
          • 沒辦法寫到cache
          • cache根本沒用!!
            • 解法: 丟空值
              • 像是linux的dentry(查詢路徑的node)遇到不存在的中間點(資料夾或是file)
              • dentry會被create,但是標註(state)為不存在
      • cache擊穿
        • 高頻率被access的資料被清掉?
          • 所有req都往後了!!
          • 會發生在
            • cache aside (因為沒有一致的保證,throught是強一致,behind是弱一致(最終一致的下一階))
              • 時效性清理
              • FIFO清理
      • cache預熱
        • 需要req讓資料慢慢變多
        • 兩個點
          • 如果一次來大量req
            • cache擊穿
          • 如果是 時效性清理 與 長時間沒有req
            • 反覆預熱
    • cache的位置
      • cache越前面越好
      • 常見位置
        • client side
          • browser的
            • localstorage & sessionstorage
            • indexdb & web sql
            • application cache
        • 靜態cache
          • CDN
          • reverse proxy
        • 服務cache
          • 在stateful的服務中
            • 具有一定通用return的服務(function)做cache
        • DB的cache
  • cache for writing
    • cache在前面cache所有write的資料,之後再由cache去write到db (限流)
      • 平滑write,消去大量的同樣的write req
    • 總體而言
      • 比起原本的write多了寫入與讀取cache的成本
      • 但是對於user而言,只有cache的response time (比較短!!)
    • cache要在後方有資源時才開始寫入,不然後面會爆

ch8: 可靠性設計

  • module串接方式
    • 串聯:
      • m1 -> m2 -> m3 ...
    • 並聯:
      • m/m/n的圖
        • 只能處理不response的server
        • 可以容錯到只要有一台是正確的就好
    • 容錯
      • m/m/n的圖 + 裁決器(有過半數的相同response就當成是對的response)
        • 可以處理有惡意(亂回答)與不response的server
          • 拜占庭容錯!!
        • 可以容錯到至少有一半+1台有一樣的response就好
  • 可靠性設計
    • 消除單點依賴
    • 把串聯轉成並聯
    • 叢集
      • 相等式: 並聯
      • 主從式: 一台服務、其他backup

ch9: 應用保護

  • 故障等級
    • 所有user都能用所有service
    • 部分user都能用所有service
    • 部分user都能用部分service
    • 不能提供service但可以恢復
    • 系統crash但不影響其他系統
    • 系統crash且影響到其他系統
  • 隔離
    • 用thread pool(或是semaphore去替代),去包服務
      • 每次invoke就是用thread跑
      • 如果沒有thread可以用了(像是所有thread都因為server掛了,而block),也繼續跑
  • 限流/恢復
    • tc的qdisc
    • algo
      • 時間窗
        • 一段時間中只放最多幾個req進來
          • 如果有大量的req在開始計時時,一次出現
            • 後面來看根本沒限到流 (還是一次很多)
            • 從system來看
              • 會有一堆沒辦法服務到
              • 後面的服務空轉
      • 漏桶
        • 把時間窗的時間變小,一放一個req,剩下用queue去存
          • 現在整個流平順了
          • 但是如果queue不夠大
            • 從system來看
              • 會有一堆沒辦法服務到
              • 後面的服務空轉
      • token bucket
        • 每個單位時間生一個token,1個token放一個req
          • token可以存!!
            • 避免服務空轉
        • 如果不調整token的總數,放著讓它長
          • 如果有大量的req在開始計時時,一次出現
            • 後面來看根本沒限到流 (還是一次很多)
    • 兩個差在?
      • 限流: 把收的req壓在一定數量
      • 恢復: 慢慢把可以收的req往上拉
  • 降級/融斷
    • (手法) 把複雜的server換成簡單的
      • 不直接讀db改讀cache
      • 精確結果改成近似結果
      • 返回靜態結果 (不跑運算)
      • 同步改成非同步
      • 停用非必要的功能
      • 禁止寫入
      • 依據user level做diffSrv
    • 降級/融斷依據
      • 失敗次數/機率過高
      • 限流啟動時
      • 手動
    • 兩個差在?
      • 降級: 以降低response time
      • 融斷: 以維持服務繼續

ch10: 前端高性能

  • 資源下載
    • 資源壓縮
      • content-encoding
    • 減少req
      • 資源合併
        • sprite圖
      • keep-alive與polling與server push
    • 資源快取
      • Etag等等
  • redner最佳化
    • Reflow & Repaint (最花時間的步驟)
      • 整個網頁就是一棵樹
        • 最後要排版(reflow)
        • 畫到畫面上(repaint)
      • 讓影響範圍變小
      • 觸發方式
        • dom新增/刪除
          • => 直接改dom的內容
          • => 讓frontend framework代勞
        • dom大小/定位方式/邊距/pesudo class改變狀態
          • => 保持上面的不變
          • (要常常變動) => 把parent node設成display: none,從一開始就沒有在tree中
            • 這樣reflow就會是1次而已
    • lazy loading
      • 先載入必要的部分,需要再load其他的
    • proload/prefetch
      • 比較大的檔案可以先下載

ch11: 架構設計理論

skip

ch12: 高性能架構實踐

skip