動機
查漏補缺
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會比較多!!
- (併發req數目) m/m/1的平均時間公式
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進來,跑功能,這樣就只會有一台跑
- 在這個set加入定時寄信功能
- 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中
- user指定
- 單點失敗
- 要與別人share
- server把狀態抽出去
- 資訊共用的節點叢集
- 在redis之類的地方紀錄state,像是session
- redis之類的地方
- 會是bottleneck
- 需要concurrent的控制
- server自己維護狀態
- server無狀態
- 分散式系統
- 把server中的(實體/虛擬)元件拆掉,像web server與DB跑在不同host
- 如果有元件是bottlebeck,可以用叢集拆掉
- 一致性問題(讀寫不一致): 我在A改的,在B看不到
- 同資訊一致的節點叢集
- 所有元件是可組合的? 微服務系統
- 把server中的(實體/虛擬)元件拆掉,像web server與DB跑在不同host
ch4: 運算併發
- 多process
- 可能在不同的cpu跑(平行),也可能在同一顆跑(共時)
- 具有很強的隔離
- 像是開兩個server在process
- 可以兩個指定不同的port,就有兩台獨立的server
- 像是開兩個server在process
- 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, 反正規化)
- 去看之前的文章
- 當成設計class,但是只有primitive與reference而已
- index
- 故障
- 一個col做index
- 上index的column做修改
- 用錯type去query (string卻用成int,雖然會被cast,但index會失效)
- 多個col做index
- (Btree) query沒有包含前面的col(左手邊)
- index從第一個col開始sort,一直下去,所以做query時需要前面的
- (hash) 沒有全部的col
- (Btree) query沒有包含前面的col(左手邊)
- 對string做index
- 如果有wildcast, btree與hash都會沒用,要用inverted index
- btree
!=
,<>
,NOT
IN
,NOT IN
IN
=>BETWEEN
- null
- index沒辦法對null做任何事
- 用其他值代替!!
- index沒辦法對null做任何事
- 一個col做index
- 不同的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怎麼辦?
- 把DB切開
- 分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)]
- 依據col去割,之後用primiary key來認同一個row
- 水平分割
- 原則: 不要跨table
- 水平分割
- 一次需要所有col
- 垂直分割
- 只要一部份col
- 水平分割
- 對於table的操作要改 (程式要改)
- table變得不一樣了
- 路由操作: 要改table的name (現在有多個table)
- 拼接操作: 要多加join (垂直分割如果需要另一個表的col)
- table變得不一樣了
- 讀寫分離
- 多個DB分別處理讀與寫
- 路由操作: 讀與寫要到對的DB
- 主從複製: 主從要同步!! (一致性問題)
- 複製的材料
- log
- statement: 就是指令 (但遇到調用now_time之類的,就沒辦法反映真實資料到read db上)
- row: 就是資料
- mixed: 就是指令+資料
- log
- 非同步複製 (not reliable)
- write後立刻return,log非同步的傳
- write db掛了又沒傳log就gg了
- write後立刻return,log非同步的傳
- 半同步複製
- write後等log傳到某個db才return
- 全同步複製
- write後等log傳到所有db才return
- 複製的材料
- 不分割(split)table
- table分區 (partition)
- DB中介軟體
- 就是DB的middleware,處理上面的路由操作、拼接操作、主從複製之類的問題
- eg: MyCat
- 就是DB的middleware,處理上面的路由操作、拼接操作、主從複製之類的問題
ch7: 快取設計
- cache for reading
- cache會花費到的時間
- 寫入cache時
- 生成key時
- hash function
- 比較key
- 寫入mem時
- 寫mem
- 寫入什麼?
- 序列化物件
- 讀寫時都要經過序列化
- 原本的物件
- 不用反序列化
- 如果存的是reference?
- 會發生race condition!!
- 解法: 存序列化物件 或是 deep copy
- 序列化物件
- 寫入什麼?
- 寫mem
- 生成key時
- 讀cache時
- 生成key時
- hash function
- 比較key
- 讀mem時
- 中
- 讀mem
- 沒中
- 跑原本
- 中
- 生成key時
- 寫入cache時
- cache update
- 被動update
- 時效性更新
- 有過期時間
- 時效性更新
- 主動update
- cache aside
- read: 正常的讀,找不到去後面拉資料
- 去後面拉資料的時候要不要把cache刪了?
- 刪
- 如果寫入中,有另一個read
- 就會拉到舊的值到cache!!
- 但寫入完成後,雙方資料就不一致了
- 如果寫入中,有另一個read
- 不刪 (good)
- read時,如果莫名其妙被block住
- 之後這之間有write完成
- read就會拿到舊的值
- 但這不太可能,只要
- read夠快
- 確保不會被preemptive
- 就不會發生
- 但這不太可能,只要
- 刪
- 去後面拉資料的時候要不要把cache刪了?
- write: 直接往後方寫,不直接更新cache
- read: 正常的讀,找不到去後面拉資料
- write through
- read/write: 都透過cache (同步)
- cache變成單點失敗的點
- read/write: 都透過cache (同步)
- write behind
- 改write through的write成非同步
- 如果cache掛了
- 可能沒寫到!!
- 如果cache掛了
- 改write through的write成非同步
- cache aside
- 被動update
- cache clear
- 時效性
- 訂個過期時間
- 時間到就自己不見
- 讓另一個thread去清
- 訂個過期時間
- 固定pool數量
- FIFO
- LRU
- 非強引用
- GC的延伸,有ref到的就是有用的obj,所以不能刪,但沒有就是可以刪
- 這樣obj只有刪與不刪
- 但有的情況是在mem吃緊時可以刪
- 所以ref要有所區分
- ref的分類
- 強引用: 一般的ref
- 軟引用: 會在mem不足時回收
- 弱引用: 不管mem夠不夠只要被gc就會被回收
- 虛引用: gc會當成看不到他
- GC的延伸,有ref到的就是有用的obj,所以不能刪,但沒有就是可以刪
- 時效性
- cache 的問題
- cache穿透
- 如果連後面都沒有結果?
- 沒辦法寫到cache
- cache根本沒用!!
- 解法: 丟空值
- 像是linux的dentry(查詢路徑的node)遇到不存在的中間點(資料夾或是file)
- dentry會被create,但是標註(state)為不存在
- 解法: 丟空值
- 如果連後面都沒有結果?
- cache擊穿
- 高頻率被access的資料被清掉?
- 所有req都往後了!!
- 會發生在
- cache aside (因為沒有一致的保證,throught是強一致,behind是弱一致(最終一致的下一階))
- 時效性清理
- FIFO清理
- cache aside (因為沒有一致的保證,throught是強一致,behind是弱一致(最終一致的下一階))
- 高頻率被access的資料被清掉?
- cache預熱
- 需要req讓資料慢慢變多
- 兩個點
- 如果一次來大量req
- cache擊穿
- 如果是 時效性清理 與 長時間沒有req
- 反覆預熱
- 如果一次來大量req
- cache穿透
- cache的位置
- cache越前面越好
- 常見位置
- client side
- browser的
- localstorage & sessionstorage
- indexdb & web sql
- application cache
- browser的
- 靜態cache
- CDN
- reverse proxy
- 服務cache
- 在stateful的服務中
- 具有一定通用return的服務(function)做cache
- 在stateful的服務中
- DB的cache
- client side
- cache會花費到的時間
- cache for writing
- cache在前面cache所有write的資料,之後再由cache去write到db (限流)
- 平滑write,消去大量的同樣的write req
- 總體而言
- 比起原本的write多了寫入與讀取cache的成本
- 但是對於user而言,只有cache的response time (比較短!!)
- cache要在後方有資源時才開始寫入,不然後面會爆
- cache在前面cache所有write的資料,之後再由cache去write到db (限流)
ch8: 可靠性設計
- module串接方式
- 串聯:
m1 -> m2 -> m3 ...
- 並聯:
- m/m/n的圖
- 只能處理不response的server
- 可以容錯到只要有一台是正確的就好
- m/m/n的圖
- 容錯
- m/m/n的圖 + 裁決器(有過半數的相同response就當成是對的response)
- 可以處理有惡意(亂回答)與不response的server
- 拜占庭容錯!!
- 可以容錯到至少有一半+1台有一樣的response就好
- 可以處理有惡意(亂回答)與不response的server
- m/m/n的圖 + 裁決器(有過半數的相同response就當成是對的response)
- 串聯:
- 可靠性設計
- 消除單點依賴
- 把串聯轉成並聯
- 叢集
- 相等式: 並聯
- 主從式: 一台服務、其他backup
ch9: 應用保護
- 故障等級
- 所有user都能用所有service
- 部分user都能用所有service
- 部分user都能用部分service
- 不能提供service但可以恢復
- 系統crash但不影響其他系統
- 系統crash且影響到其他系統
- 隔離
- 用thread pool(或是semaphore去替代),去包服務
- 每次invoke就是用thread跑
- 如果沒有thread可以用了(像是所有thread都因為server掛了,而block),也繼續跑
- 用thread pool(或是semaphore去替代),去包服務
- 限流/恢復
- tc的qdisc
- algo
- 時間窗
- 一段時間中只放最多幾個req進來
- 如果有大量的req在開始計時時,一次出現
- 後面來看根本沒限到流 (還是一次很多)
- 從system來看
- 會有一堆沒辦法服務到
- 後面的服務空轉
- 如果有大量的req在開始計時時,一次出現
- 一段時間中只放最多幾個req進來
- 漏桶
- 把時間窗的時間變小,一放一個req,剩下用queue去存
- 現在整個流平順了
- 但是如果queue不夠大
- 從system來看
- 會有一堆沒辦法服務到
- 後面的服務空轉
- 從system來看
- 把時間窗的時間變小,一放一個req,剩下用queue去存
- token bucket
- 每個單位時間生一個token,1個token放一個req
- token可以存!!
- 避免服務空轉
- token可以存!!
- 如果不調整token的總數,放著讓它長
- 如果有大量的req在開始計時時,一次出現
- 後面來看根本沒限到流 (還是一次很多)
- 如果有大量的req在開始計時時,一次出現
- 每個單位時間生一個token,1個token放一個req
- 時間窗
- 兩個差在?
- 限流: 把收的req壓在一定數量
- 恢復: 慢慢把可以收的req往上拉
- 降級/融斷
- (手法) 把複雜的server換成簡單的
- 不直接讀db改讀cache
- 精確結果改成近似結果
- 返回靜態結果 (不跑運算)
- 同步改成非同步
- 停用非必要的功能
- 禁止寫入
- 依據user level做diffSrv
- 降級/融斷依據
- 失敗次數/機率過高
- 限流啟動時
- 手動
- 兩個差在?
- 降級: 以降低response time
- 融斷: 以維持服務繼續
- (手法) 把複雜的server換成簡單的
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次而已
- dom新增/刪除
- 整個網頁就是一棵樹
- lazy loading
- 先載入必要的部分,需要再load其他的
- proload/prefetch
- 比較大的檔案可以先下載
- Reflow & Repaint (最花時間的步驟)
ch11: 架構設計理論
skip
ch12: 高性能架構實踐
skip