【Go 語言框架與實現 學習資料】第三屆字節跳動青訓營 - 後端專場
theme: juejin
第三屆字節跳動青訓營講師非常用心給大家整理了課前、中、後的學習內容,同學們自我評估,選擇性查漏補缺,便於大家更好的跟上講師們的節奏,祝大家學習愉快,多多提問交流~
第十三節:深入淺出 RPC 框架
概述
本節課程主要分為四個方面:
- RPC 相關的基本概念
- RPC 框架的分層設計
- 衡量 RPC 框架的一些核心指標
- 字節內部 RPC 框架 Kitex 實踐分享
課前部分主要羅列課程中涉及到的概念。對於不熟悉的概念,同學們可以提前查詢預習;
課中部分主要羅列每一部分的關鍵思路,幫助同學們跟上課程的進度;
課後部分是一些問題,幫助同學們在課後梳理本課程的重點。
課前
RPC 的基本概念
-
RPC的概念模型:User、User-Stub、RPC-Runtime、Server-Stub、Server
-
IDL(Interface Definition Language) 文件
- Thrift
- Protobuf
- 生成代碼
- 編解碼(序列化/反序列化)
-
通信協議
- 應用層協議
-
網絡通信
-
IO 網絡模型
- blocking IO
- unblocking IO
- IO multiplexing
- signal driven IO
- asynchronous IO
-
傳輸層協議
- TCP
- UDP
-
RPC 框架分層設計
-
編解碼層
-
數據格式:
-
語言特定格式
-
文本格式
-
二進制編碼
- TLV 編碼:Thrift 使用 TLV 編碼
- Varint 編碼:Protobuf 使用 Varint 編碼
-
-
選項:
- 兼容性
- 通用型
- 性能
-
-
傳輸協議層
-
消息切分
- 特殊結束符
- 變長協議:length+body
-
協議構造
- 以 Thrift 的 THeader 協議為例講解
-
-
網絡通信層
-
網絡庫
-
核心指標
- 吞吐高
- 延遲低
-
RPC 框架的核心指標
-
穩定性
-
保障策略
- 熔斷
- 限流
- 超時
-
請求成功率
- 負載均衡
- 重試
-
長尾請求
- BackupRequest
-
-
易用性
- 開箱即用
- 周邊工具
- 擴展性
-
觀測性
- Log
- Metric
- Tracing
- 內置觀測性服務
- 高性能
字節內部 Kitex 實踐分享
- Kitex 整體架構
- 自研網絡庫 Netpoll
-
性能優化:
- 網絡庫優化
- 編解碼優化
- 合併部署
課中
基本概念
-
相比本地函數調用,RPC調用需要解決的問題
- 函數映射
- 數據轉換成字節流
- 網絡傳輸
- 一次 RPC 的完整過程
-
RPC 帶來的問題將由 RPC 框架來解決
- 服務宕機如何感知?
- 遇到網絡異常應該如何應對?
- 請求量暴增怎麼處理?
RPC 框架分層設計
編解碼層
-
數據格式
- 語言特定格式:例如 java.io.Serializable
- 文本格式:例如 JSON、XML、CSV 等
- 二進制編碼:常見有 Thrift 的 BinaryProtocol,Protobuf,實現可以有多種形式,例如 TLV 編碼 和 Varint 編碼
-
選型考察點
-
兼容性
-
通用型
-
- 空間開銷
- 時間開銷
-
- 生成代碼和編解碼層相互依賴,框架的編解碼應當具備擴展任意編解碼協議的能力
協議層
-
以 Thrift 的 THeader 協議為例
- LENGTH 字段 32bits,包括數據包剩餘部分的字節大小,不包含 LENGTH 自身長度 - HEADER MAGIC 字段16bits,值為:0x1000,用於標識 協議版本信息,協議解析的時候可以快速校驗 - FLAGS 字段 16bits,為預留字段,暫未使用,默認值為 0x0000 - SEQUENCE NUMBER 字段 32bits,表示數據包的 seqId,可用於多路複用,最好確保單個連接內遞增 - HEADER SIZE 字段 16bits,等於頭部長度字節數/4,頭部長度計算從第14個字節開始計算,一直到 PAYLOAD 前(備註:header 的最大長度為 64K) - PROTOCOL ID 字段 uint8 編碼,取值有: - ProtocolIDBinary = 0 - ProtocolIDCompact = 2 - NUM TRANSFORMS 字段 uint8 編碼,表示 TRANSFORM 個數 - TRANSFORM ID 字段 uint8 編碼,表示壓縮方式 zlib or snappy - INFO ID 字段 uint8 編碼,具體取值參考下文,用於傳遞一些定製的 meta 信息 - PAYLOAD 消息內容
- 協議解析
網絡通信層
- 阻塞 IO 下,耗費一個線程去阻塞在 read(fd) 去等待用足夠多的數據可讀並返回。
- 非阻塞 IO 下,不停對所有 fds 輪詢 read(fd) ,如果讀取到 n <= 0 則下一個循環繼續輪詢。
第一種方式浪費線程(會佔用內存和上下文切換開銷),第二種方式浪費 CPU 做大量無效工作。而基於 IO 多路複用系統調用實現的 Poll 的意義在於將可讀/可寫狀態通知和實際文件操作分開,並支持多個文件描述符通過一個系統調用監聽以提升性能。
網絡庫的核心功能就是去同時監聽大量的文件描述符的狀態變化(通過操作系統調用),並對於不同狀態變更,高效,安全地進行對應的文件操作。
RPC 框架核心指標
穩定性
-
保障策略
- 熔斷
- 限流
- 超時控制
從某種程度上講超時、限流和熔斷也是一種服務降級的手段 。
-
請求成功率
- 負載均衡
- 重試
-
長尾請求
- BackupRequest
易用性
-
開箱即用
- 合理的默認參數選項、豐富的文檔
-
周邊工具
- 生成代碼工具、腳手架工具
擴展性
- Middleware:middleware 會被構造成一個有序調用鏈逐個執行,比如服務發現、路由、負載均衡、超時控制等
- Option:作為初始化參數
- 核心層是支持擴展的:編解碼、協議、網絡傳輸層
- 代碼生成工具也支持插件擴展
觀測性
- 三件套:Log、Metric 和 Tracing
-
內置觀測性服務,用於觀察框架內部狀態
- 當前環境變量
- 配置參數
- 緩存信息
- 內置 pprof 服務用於排查問題
高性能
- 連接池和多路複用:複用連接,減少頻繁建聯帶來的開銷
- 高性能編解碼協議:Thrift、Protobuf、Flatbuffer 和 Cap'n Proto 等
- 高性能網絡庫:Netpoll 和 Netty 等
字節內部 Kitex 實踐分享
- 框架文檔 Kitex
-
自研網絡庫 Netpoll,背景:
a. 原生庫無法感知連接狀態
b. 原生庫存在 goroutine 暴漲的風險
- 擴展性:支持多協議,也支持靈活的自定義協議擴展
-
性能優化,參考 字節跳動 Go RPC 框架 KiteX 性能優化實踐
a. 網絡優化
i. 調度優化 ii. LinkBuffer 減少內存拷貝,從而減少 GC iii. 引入內存池和對象池
b. 編解碼優化
i. Codegen:預計算提前分配內存,inline,SIMD等 ii. JIT:無生產代碼,將編譯過程移到了程序的加載(或首次解析)階段,可以一次性編譯生成對應的 codec 並高效執行
-
合併部署
a. 微服務過微,引入的額外的傳輸和序列化開銷越來越大
b. 將強依賴的服務統計部署,有效減少資源消耗
課後
- 行業內各個流行的 RPC 框架的優劣對比
- 從第三章節 RPC 的核心指標來看,Kitex 還有哪些功能是欠缺或者需要加強的?
- 瞭解微服務的新趨勢 ServiceMesh,以及 RPC 框架和 ServiceMesh 的關係
- 關於 RPC 框架,業界有哪些新的趨勢和概念?
- Netpoll 的優勢在哪?相比其他高性能網絡庫例如 Netty 還有什麼不足?
- Flatbuffer 和 Cap'n Proto 等編解碼協議為什麼高性能?
參考文獻
第十四節:HTTP 框架修煉之道
概述
本節課程主要分為四個方面:
- HTTP 協議相關知識
- HTTP 框架的設計與實現
- HTTP 框架的優化手段
- 企業實踐
課前部分主要羅列課程中涉及到的概念。對於不熟悉的概念,同學們可以提前查詢預習;課後部分是一些問題,幫助同學們在課後梳理本課程的重點。
課前
HTTP 協議
- HTTP 協議出現背景
- HTTP 協議是什麼
- HTTP 協議有什麼
可參考百度百科
嘗試寫一個 hello world 服務器
可嘗試用 gin 寫一個 hello world 程序,達到以下效果
HTTP 框架中常見概念
-
框架路由:根據請求的 URI 選擇對應的處理函數。
-
首先匹配 HTTP 方法
-
靜態路由: 精確匹配註冊的路由,如:/a/b/c、/a/b/d
-
參數路由:
- 命名參數:形如
:name
這類叫做命名參數,命名參數只匹配單個路徑段: -
``` Pattern: /user/:user
/user/gordon match(user = gordon) /user/you match(user = you) /user/gordon/profile no match /user/ no match
- 通配參數:形如 **`*action`**這類叫做通配參數,就像名字所暗示的那樣,它們匹配所有內容。因此,它們必須始終位於模式的末尾: -
Pattern: /src/*filepath/src/ match(filepath = "") /src/somefile.go match(filepath = somefile.go) /src/subdir/somefile.go match(filepath = subdie/somefile.go) ```
- 命名參數:形如
-
路由修復: 如果只註冊了 /a/b,但是訪問的 URI 是 /a/b/,那可以提供自動重定向到 /a/b 能力;同樣,如果只註冊了 /a/b/,但是訪問的 URI 是 /a/b,那可以提供自動重定向到 /a/b/ 能力
-
衝突路由:同時註冊 /a/b 和 /:id/b,並設定優先級。比如:當請求 URI 為 /a/b 時,優先匹配靜態路由 /a/b
-
Golang
- sync.Pool 用法
網絡庫
- C10K Problem
- Select,Poll,Epoll
- Epoll ET、LT 區別
- 字節跳動自研網絡庫 netpoll,netpoll-examples
SIMD
- SIMD 是什麼,可參考維基百科
課後作業
- 為什麼 HTTP 框架做要分層設計?分層設計有哪些優勢與劣勢。
- 現有開源社區 HTTP 框架有哪些優勢與不足。
- 中間件還有沒有其他實現方式?可以用偽代碼説明。
- 完成基於前綴路由樹的註冊與查找功能?可以用偽代碼説明。
- 路由還有沒有其他的實現方式?
第十五節:微服務架構原理與治理實踐
概述
本課程內容主要分為以下4個方面:
-
微服務架構介紹
- 微服務架構的背景由來、架構概覽、基本要素
-
微服務架構原理及特徵
- 微服務架構的基本組件、工作原理、流量特徵
-
核心服務治理功能
- 核心的服務治理功能,包括流量治理、服務均衡、穩定性治理
-
字節跳動服務治理實踐
- 字節跳動在微服務架構穩定性治理中,對請求重試策略的探索及實踐
為了幫助大家更好地預習及理解本節課程,該學員手冊列出了課前、課中、及課後這三個階段所涉及到的專業內容大綱,其中課前部分供同學們提前預習參考,課中部分給出了課程大綱,幫助同學們整理思路,課後部分列出一些擴展性的問題讓同學們進一步延伸思考。
課前
微服務架構介紹
-
系統架構的演進歷史
- 單體架構
- 垂直應用架構
- 分佈式架構
- SOA架構
- 微服務架構
-
微服務架構的三大要素
- 服務治理
- 可觀測性
- 安全
微服務架構原理及特徵
-
微服務架構中的基本概念及組件
- 服務、實例......
-
服務間通信
- RPC、HTTP
- 服務註冊及服務發現
核心服務治理功能
-
服務發佈
- 藍綠部署
- 灰度發佈(金絲雀發佈)
- 流量治理
-
負載均衡
- Round Robin
- Ring Hash
- Random
-
穩定性治理
- 限流
- 熔斷
- 過載保護
- 降級
字節跳動服務治理實踐
- 請求重試的意義
- 請求重試的難點
課中
微服務架構介紹
系統架構的演進歷史
-
單體架構
- All in one process
-
垂直應用架構
- 按照業務線垂直劃分
-
分佈式架構
- 抽出與業務無關的公共模塊
-
SOA架構
- 面向服務
-
微服務架構
- 徹底的服務化
微服務架構概覽
- 網關
- 服務配置和治理
- 鏈路追蹤和監控
微服務架構的三大要素
-
服務治理(本課程內容)
- 服務註冊
- 服務發現
- 負載均衡
- 擴縮容
- 流量治理
- 穩定性治理
-
可觀測性
- 日誌採集
- 日誌分析
- 監控打點
- 監控大盤
- 異常報警
- 鏈路追蹤
-
安全
- 身份驗證
- 認證授權
- 訪問令牌
- 審計
- 傳輸加密
- 黑產攻擊
微服務架構原理及特徵
微服務架構中的基本概念及組件
-
服務
- 一組具有相同邏輯的運行實體
-
實例
- 一個服務中的每個運行實體
-
實例與進程的關係
- 沒有必然對應關係,一般一對一或者一對多
-
常見的實例承載形式
- 進程、VM、k8s pod......
服務間通信
- 微服務之間通過網絡進行通信
- 常見的通信協議包括 HTTP、RPC
服務註冊及服務發現
-
基本問題
- 服務間調用中,如何指定下游服務實例的地址?
-
簡單方案
-
直接指定 ip:port?
- 沒有任何動態能力
- 有多個實例下游實例怎麼辦?
-
使用 DNS?
- 本地 DNS 存在緩存,導致延遲
- DNS 沒有負載均衡
- 不支持服務探活檢查
- DNS 不能指定端口
-
-
服務註冊發現
- 新增一個統一的服務註冊中心,用於存儲服務名到服務實例之間的映射關係
- 舊服務實例下線前,從服務註冊中心刪除該實例,下線流量
- 新服務實例上線後,在服務註冊中心註冊該實例,上線流量
-
微服務流量特徵
- 統一網關入口
- 外網通信多數採用 HTTP,內網通信多數採用 RPC(Thrift, gRPC)
核心服務治理功能
服務發佈
-
何為服務發佈
- 讓一個服務升級運行新的代碼的過程
-
服務發佈難點
- 服務不可用
- 服務抖動
- 服務回滾
-
藍綠部署
- 將服務分成兩個部分,分別先後發佈
- 簡單、穩定
- 但需要兩倍資源
-
灰度發佈(金絲雀發佈)
- 先發布少部分實例,接着逐步增加發布比例
- 不需要增加資源
- 回滾難度大,基礎設施要求高
流量治理
-
流量控制
- 在微服務架構中,可以從各個維度對端到端的流量在鏈路上進行精確控制
-
控制維度
- 地區維度
- 集羣維度
- 實例維度
- 請求維度
負載均衡
- Round Robin
- Random
- Ring Hash
- Least Request
穩定性治理
-
限流
- 限制服務處理的最大 QPS,拒絕過多請求
-
熔斷
- 中斷請求路徑,增加冷卻時間從而讓故障實例嘗試恢復
-
過載保護
- 在負載高的實例中,主動拒絕一部分請求,防止實例被打掛
-
降級
- 服務處理能力不足時,拒絕低級別的請求,只響應線上高優請求
字節跳動服務治理實踐
-
請求重試的意義
-
本地函數調用
- 通常沒有重試意義
-
遠程函數調用
- 網絡抖動、下游負載高、下游機器宕機......
- 重試是有意義的,可以避免偶發性的錯誤,提高 SLA
-
重試的意義
- 降低錯誤率
- 降低長尾延時
- 容忍暫時性錯誤
- 避開下游故障實例
-
-
請求重試的難點
-
冪等性
- POST 請求可以重試嗎?
-
重試風暴
- 隨着調用鏈路的增加,重試次數呈指數級上升
-
超時設置
- 假設調用時間一共1s,經過多少時間開始重試?
-
-
重試策略
-
限制重試比例
- 設定一個重試比例閾值(例如 1%),重試次數佔所有請求比例不超過該閾值
-
防止鏈路重試
- 返回特殊的 status code,表示“請求失敗,但別重試”
-
Hedged Requests
- 對於可能超時(或延時高)的請求,重新向另一個下游實例發送一個相同的請求,並等待先到達的響應
-
-
重試效果驗證
- 字節跳動重試組件能夠極大限制重試發生的鏈路放大效應
課後
- 結合 CAP 等原理,思考微服務架構有哪些缺陷?
- 微服務是否拆分得越“微”越好?為什麼?
- Service Mesh 這一架構是為了解決微服務架構的什麼問題?
- 有沒有可能有這樣一種架構,從開發上線運維體驗上是微服務,但實際運行又類似單體服務?