《字節跳動-段瀟涵-深入淺出 Node.js RPC.pdf》由會員分享,可在線閱讀,更多相關《字節跳動-段瀟涵-深入淺出 Node.js RPC.pdf(41頁珍藏版)》請在三個皮匠報告上搜索。
1、深淺出 Node.js RPC段瀟涵 Node.js 研發程師個簡介段瀟涵在字節負責 Node.js Infra 程向的作,主要致于建設 Node.js 相關的基建:公司內框架,適配公司基建的 Node.js 基礎庫、私有 NPM 倉庫等。錄 RPC 簡介 Node.js RPC 技術架構 RPC 性能優化過程 Q&ARPC 簡介 RPC 是什么 RPC 業界案 RPC 常場景RPC 是什么RPC 是 Remote Procedure Call 的簡稱,是種通的絡調式,的是能像在本地調法樣調不同服務的法,具備跨語、性能的特點,泛應于后端服務之間的通信??蛻舳朔斩朔瞻l現代碼成序列化代碼調代碼
2、協議層接定義語(IDL)序列化/反序列化傳輸層HTTPTCP RPC 業界案 DUBBO(阿 B2B 開源)主要 protobuf 協議,也持 hessian 等協議;基于 TCP 連接;SOFA-RPC(螞蟻開源,源于阿淘系的 hsf)主要 hessian,也持 protobuf 協議;基于 TCP 連接;GRPC(Google)使 ProtoBuf 協議;基于 HTTP2;THRIFT(Meta)使 Thrift 協議;基于 TCP 連接;RPC 常場景RPC 常在微服務架構中,撐各服務間通信,與 HTTP 相的些優勢 更快且更省空間 更強的契約Node.js RPC 技術架構 Handl
3、e ConfigCenter Middleware Connection ProtocolNode.js RPC 技術架構RPC 的架構設計中主要有五個概念(在 Client/Server 中通),分別是:Handle,ConfigCenter,Middleware,Connection,Protocol。Node.js RPC 技術架構-HandleHandle 是 Client/Server 的下級模型,是 RPC Client 調或者 RPC Server 被觸發調的,封裝了次調/被調的執過程,通過中間件的洋蔥模型將調及觸發調的流程進組合。IDL 中的每個 method,都會實例化個 H
4、andle 實例。Node.js RPC 技術架構-ConfigCenter顧名思義,ConfigCenter 就是配置中的模型,于管理 RPC 的動態配置,如:超時配置、失敗重試策略、安全策略、壓測開關 等等,ConfigCenter 分成了兩個版本 v1 和 v2,兩個版本讀取源不同:V1:基于 etcd 實現;V2:基于 service mesh 實現配置的覆蓋優先級:應配置 配置中 默認配置。Node.js RPC 技術架構-MiddlewareRPC 中的中間件跟 Koa.js 是相似的設計,差異是有 initContext、execute、releaseContext 三個階段,于
5、各個中間件對上下做初始化操作、執、對上下做釋放操作。RPC 通過中間件能持了次請求/響應中的 trace、metrics、失敗重試、熔斷等能。Node.js RPC 技術架構-ConnectionConnection 是連接的抽象模型,具體能在 Client 和 Server 中有些許不同,主要對 socket 做了層封裝,提供 API 進數據的寫或者讀取,也提供了讀寫加鎖、緩沖寫、連接池等能,主要分成以下種類型:tcp-consul:consul 服務發現的 tcp connection 對象(本地或者未開 mesh 出流量);tcp-mesh:固定連 mesh 的 tcp connecti
6、on 對象;tcp-native:讀取 servers 配置進硬負載,mesh 環境下效;http-mesh:連 mesh 的 http connection 對象;http-native:讀取 servers 配置進硬負載;Node.js RPC 技術架構-Connection對下游 RPC 服務發起個請求的時候,會通過連接池分配個空閑的連接(如果沒有則創建個),然后再使該連接進通信,此時這個連接會被占。此時如果再對同個服務發起同個請求,將會重新分配個新的連接。Node.js RPC 技術架構-Connection Timeout:總超時 Connect Timeout:連接超時 Read/
7、Write Timeout:讀寫超時 Access Timeout:Socket 單次連接超時Timeout ConnectTimeout+Read/Write TimeoutConnect Timeout Access Timeout*重試次數Node.js RPC 技術架構-Protocol正如與之間對話需要雙都會的語才能交流,RPC 之所以能夠跨語通信,核也是家有個共同的語在對數據進編解碼,這也就是 IDL,Protocol 則是 RPC 中對 IDL 的編解碼(序列化)模型,主要分成兩種類型。分別是 Header Protocol 和 Payload Protocol,其中 Heade
8、r Protocol 是可選的。Header Protocol(可選)Payload ProtocolNode.js RPC 技術架構-Protocol/寫const payload=Hello World;buf.writeInt32BE(Buffer.byteLength(payload);buf.write(payload,4,utf-8);/讀const len=buf.readInt32BE();const receivePayload=buf.slice(4,len+4).toString(utf-8);Node.js RPC 技術架構-ProtocolNode.js RPC 技術
9、架構-Protocol header protocol(必須)ttheadermeshheader(僅 client 在 mesh 場景到)framed payload protocol(必須)thriftNode.js RPC 技術架構-Protocol+-+|4Byte|2Byte|+-+|Length|HEADER MAGIC|+-+屬于開源 Thrift 中 TFramedTransport 規范中的幀頭信息,較簡單,就只是個具有 4 個字節的 Payload 度信息。FramedNode.js RPC 技術架構-Protocol 0 1 2 3 4 5 6 7 8 9 a b c d
10、 e f 0 1 2 3 4 5 6 7 8 9 a b c d e f+-+|HEADER MAGIC|HEADER SIZE|+-+|HEADER MAP SIZE|HEADER MAP.|+-+|PAYLOAD|+-+屬于 TTHeader 的簡化協議,于當調請求不帶 TTHeader 的時候,Mesh 為了保證能在響應中帶上 rip 等信息設計的個協議,只會出現在了 Mesh 出流量的響應中。MeshHeaderNode.js RPC 技術架構-ProtocolTTHeader內部基于 fbthrift 的 THeader 設計的更適合字節 Mesh 場景的 Header,包含了量信息
11、,除了有度這種基本信息外,還有如 Mesh 版本、LogID、超時配置等等配置。0 1 2 3 4 5 6 7 8 9 a b c d e f 0 1 2 3 4 5 6 7 8 9 a b c d e f+-+|0|LENGTH|+-+|0|HEADER MAGIC|FLAGS|+-+|SEQUENCE NUMBER|+-+|0|Header Size|.+-Header is of variable size:(and starts at offset 14)+-+|PROTOCOL ID|NUM TRANSFORMS.|TRANSFORM 0 ID(uint8)|+-+|TRANSFOR
12、M 0 DATA.+-+|.|+-+|INFO 0 ID(uint8)|INFO 0 DATA.+-+|.|+-+|PAYLOAD|+-+Node.js RPC 技術架構-ProtocolThrfit 是 Payload 前持的唯個協議類型,在這個協議中也分成消息頭信息和消息體。(跟 Header Protocol 不同,消息頭是必須的)Node.js RPC 技術架構-Protocol消息頭信息的規范如下(消息頭的規范其實分成兩種:strict 和 strict,這只講 strict,strict 于舊版本的規范)+-+-+-+-+-+-+-+-+-+.+-+-+-+-+-+|1vvvvvv
13、vvvvvvvvv|unused|00000mmm|name length|name|seq id|+-+-+-+-+-+-+-+-+-+.+-+-+-+-+-+頭兩個字節的 15 位 vvvvvvvvvvvvvvv 是版本號,位的 1 是固定的;第個字節 unused 那個是沒到的占位字節;第三個字節中的 mmm 是消息類型,前 5 位固定是 0,消息類型枚舉為:1:Call(請求)2:Reply(響應)3:Exception(異常)4:Oneway(響應請求)第四到第個字節是 name length,也就是再后那個 name 的度信息;第九到第 n 個字節是 name,度不固定,內容就是
14、MethodName;最后四個字節是 seqId,于控制消息順序。let seqId=0;const name=Ping;buf.writeUInt16BE(0 x8001);/1000000000000001buf.writeUInt8(0);buf.writeUInt8(1);/Callbuf.writeInt32BE(name.length);buf.writeString(name);buf.writeInt32BE(seqId);Node.js RPC 技術架構-Protocol消息體則持復雜的數據類型,不同的數據類型有不同的數據結構:簡單數據類型數據類型類型標識(8位)編號(16位
15、)值bool2個字節的值(true:1,false:0)byte3個字節值double4個字節值i166兩個字節值i328四個字節值i6410個字節值復合數據類型數據類型類型標識(8位)編號(16位)值(度+值)string11四個字節數據度+數據的值struct12嵌套數據+個字節停符(0)map13個字節的key類型+個字節的val類型+四個字節的數據度+數據的值(key+val)set14個字節的val類型+四個字節的數據度+數據的值list15個字節的val類型+四個字節的數據度+數據的值Node.js RPC 技術架構-ProtocolThrift 的序列化會先將 IDL 轉換成 A
16、ST 結構,再遞歸遍歷 AST 結構并且根據當前節點類型,按照表格要求的進制結構進編碼。struct PingRequest 1:string ping 2:i32 i32 3:i64 i64 name:PingRequest,fields:id:1,type:string,name:ping ,id:2,type:i32,name:i32 ,id:3,type:i64,name:i64 /encodebuf.writeInt8(12);/Struct 標識buf.writeInt16BE(0);/idstructs.fields.forEach(field=const value=dataf
17、ield.name;if(field.type=string)buf.writeInt8(11);/String 標識 buf.writeInt16BE(field.id);/id buf.writeInt32BE(Buffer.byteLength(value)buf.write(value);else if(field.type=i32)buf.writeInt8(8);/I32 標識 buf.writeInt16BE(field.id);/id buf.writeInt32BE(value);else if(field.type=i64)buf.writeInt8(10);/I64 標識
18、 buf.writeInt16BE(field.id);/id buf.writeInt32BE(value);else /.等其他類型 );buf.writeInt8(0);/struct 結束標識Node.js RPC 技術架構-ProtocolRPC 性能優化過程 懶加載 預編譯 同步序列化RPC 性能優化過程-出現的內存問題 IDL 量(近 3000 Handle);為了性能和錯誤提示、AST/DataType(序列化封裝對象)常駐內存;RPC 性能優化過程-懶加載思路constructor(command:ThriftCommand,fn:Function_,service:Serv
19、ice,document:Document,compileCache:Map )this.method=fn.name.value;this.service=service.name.value;this.oneway=fn.oneway=true?true:false;mand=command;this.initRequest=()=astToRequest(command,fn,document,compileCache);this.initResponse=()=astToResponse(command,fn,document,compileCache);this.initExcept
20、ion=()=astToException(command,fn,document,compileCache);this.args=fn.args.map(arg)=arg.name.value);this.returns=0,.(fn.throws|).map(err)=err.name.value);private get request()if(!this._request)this._request=this.initRequest();return this._request;private get response()if(!this._response)this._respons
21、e=this.initResponse();return this._response;private get exception()if(!this._exception)this._exception=this.initException();return this._exception;內存占降低了 18%;有提升,但是有限;可能會影響次請求 rt;對 IDL 利率的應提升;RPC 性能優化過程-預編譯思路預編譯主要做的就是進序列化的代碼的預成,如前舉的例中是在運時實時根據 AST 結構進序列化操作,預編譯則是將 AST 轉換成邏輯更簡單的代碼:/encodebuf.writeInt8(
22、12);/Struct 標識buf.writeInt16BE(0);/idstructs.fields.forEach(field=const value=datafield.name;if(field.type=string)buf.writeInt8(11);/String 標識 buf.writeInt16BE(field.id);/id buf.writeInt32BE(Buffer.byteLength(value)buf.write(value);else if(field.type=i32)buf.writeInt8(8);/I32 標識 buf.writeInt16BE(fie
23、ld.id);/id buf.writeInt32BE(value);else if(field.type=i64)buf.writeInt8(10);/I64 標識 buf.writeInt16BE(field.id);/id buf.writeInt32BE(value);else /.等其他類型 );buf.writeInt8(0);/struct 結束標識/encode 開始buf.writeInt8(12);/Struct 標識buf.writeInt16BE(0);/idlet value=dataping;buf.writeInt8(11);/String 標識buf.write
24、Int16BE(field.id);/idbuf.writeInt32BE(Buffer.byteLength(value)buf.write(value);value=datai32;buf.writeInt8(8);/I32 標識buf.writeInt16BE(field.id);/idbuf.writeInt32BE(value);value=datai64;buf.writeInt8(10);/I64 標識buf.writeInt16BE(field.id);/idbuf.writeInt32BE(value);/struct 結束標識buf.writeInt8(0);RPC 性能優
25、化過程-預編譯思路成的代碼序列化代碼反序列化代碼RPC 性能優化過程-預編譯思路 測試環境:1C2G;RPC method 數量:3000+;測試式:部署后使調試模式,在 chrome devtools 抓取快照對預編譯帶來的內存收益RPC 性能優化過程-預編譯思路預編譯帶來的調試便利RPC 性能優化過程-同步序列化思路歷史的異步化式RPC 性能優化過程-同步序列化思路使 TTHeader/FramedHeader 同步式RPC 性能優化過程-同步序列化思路使 TTHeader/FramedHeader 同步式RPC 性能優化過程-同步序列化思路預編譯+同步序列化的收益 復雜結構為實際業務中的
26、個 idl(100+structs),序列化數據 500KB。簡單結構為覆蓋所有數據類型的 PingPongServiceRPC 性能優化過程-可能得其他思路?性能肯定有提升(語優勢);遠來看維護成本較,容易給后留坑;編譯后成盒,問題排查難度提升,不容易 debug;銹化?使 RPC 可能遇到的問題內存泄漏等資源占的問題服務發現異常問題數據類型轉換問題了解更多技術實踐案例思博(msup)有限公司是家向技術型企業的培訓咨詢機構,攜2000余位中外客座導師,服務于技術團隊的能提升、軟件程效能和產品創新迭代,超過3000余家企業續約學習,是科技領域占有率第1的客座導師品牌,msup以整合全球領先經驗實踐為任,為中國產業快速發展提供智庫??杉軜嬛饕P注互聯架構及可、可擴展及性能領域的知識傳播。訂閱戶覆蓋主流互聯及軟件領域系統架構技術從業員??杉軜嬒盗猩缛菏莻€社區組織,其精神是“分享+交流”,提倡社區的參與,同時從社區獲得質量的內容。