高德渲染閘道器Go語言重構實踐
1.導讀
高德啟動Go業務建設已經有段時間了,主要包含 Go應用落地, Go中介軟體建設, 雲原生三個部分。經過持續的發力,在這些方面取得了不錯的進展。高德Go業務落地過程是如何實現的,遇到過哪些問題,如何解決?本文將為大家介紹相關經驗,希望對感興趣的同學有所幫助。
2. 高德為什麼要落地Go應用
現在高德內主流的語言還是Java,Java應用最多,機器數十分驚人。而且高德整體業務也在快速向前奔跑,成本增加的速度非常快。 在減少機器負載方面,Go語言在語言級別對Java語言有相當優勢。減少機器成本是我們落地Go應用的第一個考慮因素。
其次,Go語言近幾年發展勢頭迅猛,不論是阿里集團內部,還是在高德內部,對使用Go語言的呼聲越來愈高。落地Go應用可以很好的驗證Go中介軟體的穩定性。當然我們可以透過混沌工程等手段去驗證,但經過生產環境考驗才最具有說服力。 驗證沉澱Go語言中介軟體穩定性是我們落地Go應用的第二個考慮因素。
最後,Go語言作為雲原生基礎框架使用較多的語言,提前落地Go應用,對後續落地雲原生可以減少不少阻力。高德目前落地的Serverless/Faas規模相當大。 落地Go應用的第三個考慮因素是為後續雲原生落地鋪路。
3. 大流量場景Go應用落地
3.1 渲染閘道器介紹
本文所述中提到的高德渲染閘道器,是我們落地的Go應用中業務流量、改造難度、風險,收益均處前列的應用。渲染閘道器在接入層,佔高德總流量的一半,重要性可想而知。
接下來簡要介紹下渲染閘道器承接的業務,方便大家有一些更立體的認識。
渲染閘道器承接高德手機App、車機、開放平臺等來源所有的圖面渲染。大家在使用高德時,看到的建築物、地形圖、名稱、路線、地鐵站、公交站、紅綠燈等等所有圖面,都是由渲染引擎透過渲染閘道器透出到端。下面放幾張圖,方便大家有一些更感性的認識。
上面圖一為行前,圖二為行中,圖三為叫車頁面,圖四為景區手繪圖。渲染閘道器涉及業務眾多,以上僅為舉例,其他業務就不在這裡貼圖了。
3.2 重構難點
做過重構專案的同學相信都深有體會, 重構專案中最大難點有二,一是要保證業務正確性,二是要保證服務穩定性。
對於保證業務正確性,一般來說,重構的服務大多數為老服務,老服務面臨的最大問題是歷史邏輯複雜,人員更迭,文件缺失,這些因素都是重構過程中的“攔路虎”。
渲染閘道器重構同樣如此,它涉及高德手機端、車機端、開放平臺、叫車等各個業務線,所有的歷史版本,再加上上述因素,所以保證業務正確性是一件非常困難的工作。
對於保證服務穩定性,做過閘道器的同學應該都知道,閘道器本身的屬性就決定了它並不會有頻繁的業務迭代,穩定性是閘道器的第一訴求。我們要保證,無論外部環境/依賴是否正常,閘道器始終能保持高可用。由於 Go版本中介軟體缺乏在大流量場景的充分驗證,這一難點需要仔細評測,用合適的方法和手段,儘可能的在模擬環境裡驗證各種邊界情況,從而保證在生產環境不出問題。
3.3 技術方案
在重構高德渲染閘道器時,我們整體技術方案分三大步走:
3.3.1 線上流量對比
如何驗證新服務的業務正確性呢?我們採用了線上流量對比的方式。
我們前期做了大量調研,希望找到一個 滿足(近)實時,二進位制級對比的工具,但可惜並沒有找到一個滿足要求的工具。由於渲染業務的特殊屬性,渲染閘道器絕大多數介面返回的是二進位制向量資料,所以理想的工具不僅要能支援常規資料對比,也要能支援二進位制級對比。
二進位制級對比的另一個好處是,可以排除字符集差異,不同語言庫函式差異。更能保證對比的準確性。有些同學可能會想到打日誌,然後離線讀取比較的方式來做對比,這種方式有很多弊端。
首先,流量無法重放至指定機器。其次,這種使用方式一般為固定語料,語料完整度不夠,不能完全模擬線上環境。此外,打日誌對比帶來的字符集和語言庫函式差異,會對比較準確性有較大影響,特別是對於特殊字元(當7層協議為二進位制協議時更加明顯)。沒有現成的稱手工具,怎麼辦?"逢山開路,遇水搭橋"。
我們 自主研發了一款(近)實時流量對比工具,它保障了此次重構的業務正確性,並且還能服務於高德其他業務的重構。其技術細節對TCP/IP涉及較多,非常有意思,感興趣的同學可以直接跳至《流量對比工具(ln)技術細節》一節。
3.3.2 模擬環境壓測
做服務的同學相信都深有體會,想讓服務保障做到5個9的可用性並不是一件容易的事。真實生產環境中可能會出現各種情況,我們要想辦法驗證各種邊界情況下服務的穩定性,才能保障服務高可用。對於重構完成的新服務,更需要一個模擬環境,進行各種情況驗證。
構建模擬環境,我們需要保持 機器基線、外部依賴、外部流量均一致(比如從線上引流)。模擬環境不僅要提供正常態環境的能力,更要能提供異常態環境的能力。
異常態包括斷網,網路丟包等等。有句話說的好:20%的程式碼完成功能,80%的程式碼來處理各種異常情況。我們 在實踐中構建異常態的主要手段為混沌工程,透過混沌工程模擬下至作業系統級的異常(如斷網,丟包等),上至應用層的異常(如訊息中介軟體積壓,JVM方法前後Hook模擬業務異常等等)。
在模擬環境裡,同時進行長時間極限壓測,語料從線上導流,壓測在正常態,異常態均進行,觀察服務在一段較長時間內的表現,從而得出服務的穩定性,可用性結論。
觀測指標包括 基礎指標,例如CPU、磁碟利用率、記憶體利用率、連線數,以及業務指標,例如業務介面成功率、成功量、總量、TP99。透過這種方式,基本上完全覆蓋了可能出現各種情況,充分保證了服務穩定性和高可用。
3.3.3 平滑灰度切流
前邊講了如何保證業務正確性和服務穩定性。接下來說說如何保證平滑灰度切流。牢牢遵守 阿里釋出三原則是平滑灰度切流的“法寶”: 可灰度, 可監控, 可回滾。
在具體實踐中,我們按照如下步驟 灰度切流:
a. 原Java叢集不動,新申請一套Go叢集。修改路由規則,部分白名單使用者使用Go叢集服務。
b. 逐個介面修改路由規則至Go叢集,慢慢灰度,期間密切觀察機器姿態,業務日誌,監控指標。如有異常一鍵切回至Java叢集。
c. 介面全量切至Go叢集后,Java叢集/Go叢集同時共存一段時間。
d. 逐漸下掉Java叢集機器。
3.4 主要收益
第一個重要收益: 降本提效。高德渲染閘道器由Java換成Go語言之後,機器數減少近一半。用原來一半的資源完成了相同的工作,大大降低了成本,提高了資源利用率,更好支援了業務發展,大大降低了業務流量快速增長帶來的接入層機器增長速度。
第二個重要的收益是: 驗證了高德與集團合作共建的Go版本中介軟體的穩定性,一定程度上完善繁榮了集團Go生態。在大流量場景考驗過後,高德與集團合作共建的Go版本中介軟體穩定性得到了相當充分的驗證。
第三個重要的收益是: 為閘道器雲原生化鋪路。閘道器Go化只是第一步,Go是雲原生基礎設施實現使用較多的語言,第一步抹平語言差異,對於閘道器後續雲原生化,好處多多,可降低改造風險和成本。
當然,高德渲染閘道器重構過程中還有許多非常有用的工具沉澱。可為後續業務重構提供關鍵性保障,比如自研的流量對比工具ln。
4. 技術乾貨
4.1 流量對比工具(ln)技術細節
先提一個問題,做一款(近)實時流量對比工具需要完成哪些功能?沒錯,就是流量複製,流量解析,流量重放,流量比對。其實不止這些,在實踐中更多是一個流量回歸閉環,如下圖:
4.1.1 流量複製
為了支援所有的7層協議,流量獲取必須從3層或4層開始。有同學會立馬想到tcpdump。沒錯,就是tcpdump。tcpdump出的檔案就是實實在在的流量。複製流量這一步已經有著落了,至於實時,可以兩到三個程式錯開時間,時間段首尾互相重疊即可完成實時。
另外,設計此工具的另一個考量點是,對線上機器不能有太重的負載,避免對線上機器產生穩定性影響。此種流量複製方式非常輕量,對線上機器增加的負載非常小,可以忽略不計。
4.1.2 流量上傳&流量拉取
流量上傳和流量拉取均使用內部檔案服務。
4.1.3 流量對比
流量對比為了保證對比的嚴謹性,排除可能的字符集干擾/不同庫函式實現干擾,我們原生支援了二進位制流對比。
4.1.4 問題流量本地重放Debug
迴歸流量時,可能會發現部分流量比對不一致,這時我們希望只重放特定流量到指定機器,以便於Debug或其他操作,ln原生支援了此功能。
4.1.5 流量解析
流量解析非常有意思,這種單純的快樂來自於對網路協議的"把玩"。
實際做法就是如何解析tcpdump檔案,拿到tcp payload,還原出http請求。
這裡有兩個關鍵點,一是我們如何從tcpdump檔案中拿到tcp payload,二是我們如何把四層的tcp payload重新聚合成七層的http請求。
4.1.5.1 tcpdump檔案格式
先說如何從tcpdump檔案拿到tcp payload,如果能知道tcpdump檔案的格式,不就可以知道tcp payload在哪個位置,長度如何了麼?這一趴我們就來看看tcpdump檔案格式。
先看tcpdump檔案總覽
檔案頭的格式和長度都是固定的,如下:
我們可以在讀取tcpdump檔案後,往後移動23位元組,然後開始處理每個資料包。每個資料包的格式如下:
我們處理每個資料包,將前邊的包頭,資料鏈路頭,ip層頭,tcp協議頭依次跳過,最終偏移到tcp payload第一個位元組位置。其中的更多實現細節(不同層的頭欄位值的判斷,不同長度的判斷,大小端的判斷,請求資料包與響應資料包如何對應等等)在此不再展開。這裡只介紹大體思路,感興趣的同學可以深挖網路協議。
4.1.5.2 tcp payload還原http請求
這一部分介紹如何將tcp payload還原成http請求(此處http指http1.0/1.1,不含http2),ln工具中的完整實現是由tcp payload還原出請求及對應的響應,此處為了便於理解,僅講解如何解析http請求。解析出http請求實際上已可以重新分別請求新老服務,對比響應二進位制流。
一條tcp連線,多個payload傳送(這裡僅做示意,判斷丟包重發等諸多情況屬於程式碼細節,在此不再展開)。可能多個payload對應一個http請求;也可能一個payload的前一部分對應一個http請求,後一部分對應另一個http請求。我們要做的就是把多個payload形成的位元組流讀入,按http幀的格式,聚合http請求即可。另外,http2的請求不能按這種方式聚合。
4.2 一些go語言最佳實踐
4.2.1 sync.pool 實踐
由於Go語言和Java語言的記憶體管理機制不相同,在記憶體的申請,釋放開銷也有差別。
對於Go語言來說,sync.pool是複用記憶體的一把利器。sync.pool優點有許多,比如減少記憶體的申請,減少了系統呼叫,減少了gc的壓力。但事物都有兩面性,sync.pool同樣如此,我們在使用sync.pool的時候需要注意,存放在sync.pool裡的物件會在不通知的情況下被回收掉,所以類似資料庫連線等資源不適合使用sync.pool。
總之,sync.pool可以複用記憶體,減少機器負載,非常適合臨時物件。
4.2.2 Golang Byte
Go語言Byte型別為無符號,Java語言Byte型別為有符號,在Java服務遷移Go服務過程中,Java程式碼中Byte型別正、負、零的比較要注意。
4.2.3 Golang位元組切片與字串高效轉換
位元組切片轉字串
字串轉位元組切片
使用此種方式轉換,效能很高。原因在於底層無新的記憶體申請與複製。但是不論是位元組切片轉字串,還是字串轉位元組切片,位元組切片中的值更改都會影響字串的值,使用者要根據業務邏輯判斷能否接受,要更精確的把控生命週期。
4.2.4 Golang庫函式重寫
對於閘道器來說,耗CPU比較多的一部分是Hash函式/編解碼函式/加解密函式/序列化反序列化函式等。在實踐中我們重寫了相關的庫函式,在CPU負載上做了大量最佳化。
想要降低CPU負載,我們得先知道CPU是如何工作的,才能知道如何寫程式碼會更好的降低CPU負載。這裡會介紹粗略的CPU工作原理。
放張CPU 流水線工作步驟圖
- 指令讀取(instruction fetch,IF)
- 指令解碼(instruction decode,ID)
- 執行(execute,EXE)
- 記憶體訪問(memory access,MEM)
- 暫存器回寫(register write-back,WB)
主要最佳化MEM步驟,利用CPU快取儘可能減少MEM步驟所佔時鐘週期,從而降低CPU負載。
類似NUMA架構,affinity等降低CPU負載的方式也是同樣的思想,儘可能減少Load資料所需的時鐘週期。
對於最佳化Golang庫函式來說,可以提升的點有兩個:最佳化演算法本身;最佳化CPU快取親和度。
我們專注於第二種,拿base64編解碼函式舉例,傳入的Byte切片與返回Byte切片,底層並非為同一陣列,同一記憶體。這中間就涉及兩塊可以額外消耗CPU時鐘週期的點,一是記憶體的申請與釋放,二是兩塊記憶體分別訪問帶來的CPU快取爭用問題(與偽共享不完全一樣)。
如果我們複用傳入的記憶體呢?即邊解碼邊覆寫同一塊記憶體。美妙的事情發生了,上邊所說的問題不存在了。用更少的時鐘週期完成了一樣的工作。需要注意的是,由於函式的輸入和輸出使用同一塊記憶體,對程式開發者來說有更高的編碼要求,即對資料在程式中流轉的生命週期有更精準的把控力,程式碼要打磨的很細緻。
5.未來展望
閘道器的下一步是 雲原生化,採用 Service Mesh方式實現。這可以解決目前中心化閘道器的弊端,去中心化可以提升接入層穩定性,減少爆炸半徑,增強隔離能力,實現更精細粒度的管控。
其次, 降低機器成本,按照目前內部壓測及業界已有的實踐壓測結論,Mesh化後成本會進一步減少,考慮到現有RPC框架本身的消耗,成本會進一步縮減。且資料面代理也在不斷最佳化中,後續效能表現會更優異,額外兩跳對機器的負載將進一步下降。
再有,**網路層能力集大大增強。**閘道器Mesh化,可以帶動上游業務Mesh化,最後在整個網路層做一個能力超集。
現有的Service Mesh框架提供的能力可以概括為Connect,Secure,Control,Observe四大部分,其能力是現有閘道器能力的超集,可以做到之前做不到的事情,最明顯的是Observe能力帶來的好處,可大大加強全鏈路服務可觀測性,這於對後續開展服務穩定性,全鏈路故障快速定位等工作有極大幫助。
以上要做的事情任重而道遠,另外我們在會做更多雲原生的試點和落地,技術同學都清楚,從技術選型到技術原型,再到實際業務落地,中間有很長的路要走。但路選對了,就不怕遠。
誠招同路人
筆者所在團隊求賢若渴,盼有熱情的技術小夥伴一起做些有趣的事,各技術棧均可,有意願的小夥伴請盡情砸簡歷到郵箱gdtech@alibaba-inc.com,郵件主題為:姓名-技術方向-來自高德技術。
Happy Hacking!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69941357/viewspace-2789618/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 個推微服務閘道器架構實踐微服務架構
- 前端閘道器踩坑實踐前端
- 開放API閘道器實踐(一) ——設計一個API閘道器API
- EDAS 流量入口閘道器最佳實踐
- 開放API閘道器實踐(三) —— 限流API
- 微服務閘道器Gateway實踐總結微服務Gateway
- 微服務實踐分享(2)api閘道器微服務API
- 得物自研API閘道器實踐之路API
- eBay 基於 Apache Kyuubi 構建統一 Serverless Spark 閘道器的實踐ApacheServerSpark
- 重新整理 .net core 實踐篇————閘道器[三十六]
- 高效能API閘道器(1)、微服務API閘道器架構設計API微服務架構
- AI閘道器對企業的意義及如何構建 AI 閘道器AI
- Janusec應用安全閘道器(WAF閘道器)
- Ceph物件閘道器,多區域閘道器物件
- 雲原生閘道器的可觀測性體系實踐
- 億級流量架構之閘道器設計思路、常見閘道器對比架構
- 構建SpringCloud閘道器服務SpringGCCloud
- Go 語言目錄結構與實踐Go
- 閘道器GatewayGateway
- gateway 閘道器Gateway
- 什麼是閘道器?閘道器的作用是什麼,閘道器的作用詳解
- 長連線閘道器技術專題(九):去哪兒網酒店高效能業務閘道器技術實踐
- 【杭州活動】API 閘道器與高效能服務最佳實踐API
- 開放API閘道器實踐(二) —— 重放攻擊及防禦API
- 基於雲原生閘道器的可觀測性最佳實踐
- VPN閘道器最佳實踐系列(一)如何讓VPC之間互通
- 大眾點評支付渠道閘道器係統的實踐之路
- [譯] 使用 Go 編寫微服務及其 GraphQL 閘道器Go微服務
- Envoy實現.NET架構的閘道器(三)代理GRPC架構RPC
- 微服務架構閘道器介面設計微服務架構
- 探索使用Nginx +Lua 構建 API 閘道器NginxAPI
- PHP和JAVA雙語言重構專案PHPJava
- Go語言重新開始,Go Modules 的前世今生與基本使用Go
- Envoy實現.NET架構的閘道器(五)整合Redis實現限流架構Redis
- API閘道器,企業級閘道器可擴充套件API套件
- 宜人貸蜂巢API閘道器技術解密之Netty使用實踐API解密Netty
- vivo統一接入閘道器VUA轉發效能最佳化實踐
- Ocelot API閘道器的實現剖析API