知乎服務化的實踐與思考
服務化是知乎幾年來技術演進故事里的一個主角,公司規模從幾十人到幾百人,在監控、tracing、框架、容器等基礎設施從無到有的同時,也擴展出多個后端技術團隊。在服務化演進的過程里,我們也進行了一些新的思考。
服務化的愿景
「微服務」 是業內最近兩三年業內很火的 buzzword,遷移到微服務架構,大多強調這些好處:
松耦合
獨立發布
快速迭代
故障隔離
增加重用
經過服務的拆分,將復雜到難以移動的單體應用,拆分為多個可以獨立部署的服務,單個服務的復雜性遠遠小于整體,這樣不同服務的開發者可以并行開發,從而提高開發效率;因為服務的細粒度,可以 assign 給一個具體的人讓他負責,隨著業務的增長對服務做定向擴容;同時因為服務的隔離性,可以隔離故障,提高整體的穩定性。
不過在推動服務化的過程中,也感受到一些意外的痛點。
我們首先感受到的是服務數量的不可控。新 feature 經常會以新服務的形式開始開發,而非擴展現有的服務。隨著產品的需求迭代,服務越來越碎片化,擴展現有服務變得越來越困難,因為很難選擇加入到哪個服務更好。而且擴展一個其他人負責的服務,溝通成本不能忽視,這時出于產品進度的壓力,另起爐灶開始一個新服務實在是最快的選擇。
其次,部分業務基于 RPC 做水平分拆,原則上 RPC 的演進要保持向前兼容,而項目前期需求不穩定階段,難免引進相當多的 break change,發包、上線、聯調成本居高不下,也引進了額外的發布風險:上層服務和下層服務兩者務必同時發布,但不能回滾其中之一。這在重要的功能開發中,成為了我們快速迭代的一個絆腳石。
拆分為 RPC 之后,通信介質發生了不同,而訪問模式的適應不一定準備好。對有緩存覆蓋的數據訪問對象而言,N+1 式的訪問模式不算問題,但是 N+1 query 對 RPC 卻不能接受。服務的調用壓力經常出乎維護者的意料,RPC 放大使得下層服務的波動放大,風險增加之余也增加了對資源的需求,而資源占用更多的同時,性能仍遠低于過去對緩存的訪問。
基于 SSO 的分拆
RPC (遠程過程調用)是服務化體系中基礎的基礎,但是慢慢的我們發現 RPC 并非分拆的唯一選擇。基于 RPC 的水平分拆會引入中間層次,增加聯調的環節,對于快速開發的新業務而言,無法忽視額外的聯調成本。聯調中發現問題時不得不在各個層次定位,難以自動化測試。經過今年幾個快速迭代的新項目從主站的分拆,我們發現基于 SSO (單點登錄)機制對 HTTP 接口做垂直拆分是一條可行的路,可灰度控制風險、對客戶端透明。新項目對主站的耦合往往較低,只需要 get_member 等少量幾個 RPC 接口,對外暴露的接口也非常少。新項目的迭代速度快,經過拆分,能夠使服務與后端團隊的完整業務相匹配,降低了主站聯動的發布頻率,某種程度上也減少了出于部署積壓而堆積的發布風險。
這里我們得到的啟發是,服務的分拆并非 RPC 不可。相反,我們希望看到更少的 RPC,更多的內聚。更少的 RPC 接口意味著更小的服務邊界,更穩定的接口,更少的 break change。內聚意味著允許功能需求的獨立演進,對其他業務的影響降到最低,也意味著內聚的業務模塊內部,可以充分利用緩存來優化性能。
桌面端怎么拆?
新項目之所以拆分順利,也得益于它們當前只面向客戶端,HTTP API 的拆分仍較為簡單。然而桌面端的情形有所不同,一個頁面往往不能單純對應到一個服務上,比如問答頁,除了問答服務,還需要評論、、內容推薦等信息需要展現,這些頁面組件背后的服務會趨于不同的工程師或團隊維護。
這里可以注意到一件有意思的事情,在后端社區 buzzword 「服務化」 的同時,前端社區更加如火如荼地 buzzword 著 「前后端分離」 和 「組件化」。出于內容展現的復雜性,頁面并不能成為分拆的好單位,但頁面內的組件無疑更容易與后端的功能模塊相匹配。「服務化」 和 「組件化」 是一對孿生子,分別允許后端團隊和前端團隊去隔離業務模塊、獨立演進。
知乎也正在進行著桌面端的前后端分離改造。隨后桌面端不再特殊,允許后端面向 iOS / Android / Web 三端按同一套接口進行對接,也就可以像移動端 API 同樣的方式,基于單點登錄進行拆分了。
垂直拆分的風險
當前感受到垂直拆分的主要風險在于保證不同垂直業務對同類型實體展現邏輯的一致性。其中 feed 對這點要求最高,因為它對所有實體類型都有引用。為此在拆分 feed 流之前,先行補充了所有實體的 swagger 文檔,通過中心的 swagger 文檔約束住同一實體在不同服務中的字段展現。
如何劃分服務邊界
理想的世界里,服務邊界恰好匹配于業務邊界。然而工程師首先要承擔業務需求的壓力,只能抽時間重構拆分,業務邊界也并不總是如新項目那樣明晰。
這意味著要考慮優先級,也需要在拆分之前認真地思考業務的邊界。排定優先級,考量拆分的收益與風險即可。劃分業務的邊界,則需要更多的思考拆分后的未來將如何溝通協作,然后再考慮技術因素。目前我們主要有這幾個考量:
是否擁有獨立團隊來維護,或者是否擁有發展為一項獨立業務的潛力;
圍繞領域而非 feature,有明確的維護團隊,避免過于細粒度;
拆分之后,能否改善現有的合作流程;
能否幫助區分核心、非核心業務,改善穩定性;
以 feed 為例,它首先擁有獨立團隊維護,通過拆分,技術層面上允許 feed 團隊重構掉下層服務與上層展現之間的冗余 RPC 調用,且調用模式較 uniform,在產品層面接受數據最終一致性的前提下可以通過 TTL 緩存提升性能,乃至按自己的業務場景做更細致的優化(優化結束后我們的某些接口 P95 性能加快了一倍);更重要的是對協作方式的影響,未來專欄、問答等生產信息的垂直業務,只提供一個 RPC 接口對接 feed 流即可,而不必集成到主站,這一來 「接入 feed」 流程的參與者,從 feed 組、垂直業務、主站三方,簡化為 feed 組和垂直業務雙方;此外 feed 通過 TTL 緩存,實質上冗余了一份垂直業務的數據,配合斷路器的使用,依賴的垂直業務的抖動甚至崩潰在 feed 這邊都可以優雅降級且保持正常展現了。將 feed 與主站的變更相隔離,也有助于改進作為一項核心業務的 feed 的穩定性。
服務分層:業務服務和公共服務
在垂直業務之外,也存在多數業務都會重用的公共服務,如用戶、話題、網頁抓取、多媒體、推送等。業務服務和公共服務在關注點上有所不同:
我們希望業務服務快速迭代,更快、更好地響應多變的業務需求,更多地面向前端工程師;
我們希望公共服務穩定可靠,較少發生改動,但 SLA 要好,更多地為業務重用;
這里會形成一個自然的分層:上層業務求快、下層公共服務求穩。
經驗與教訓
按領域思考合作的方式,而非技術角色分工: 首先思考團隊的合作方式,面向領域去劃分工作,減少跨服務的溝通環節,減少聯調環節;
有目標地、適度地服務化: 服務化有成本和風險,在執行大規模的技術改進期間,要目標導向,小成本達成目標為上,不必一條路線走到底;
面向未來
回到文章開頭提到的這幾點特質:松耦合、獨立發布、快速迭代、故障隔離、增加重用,為了這些目標,我們還有很多工作要繼續。沒有完美的架構,真實世界也充滿 trade off,但是圍繞我們期望的目標,能一點點做工程改進,收集反饋不停地修正工作方法。這是一段漫漫長路,途中請切記最初的目標!
References
[1] Microservices: a definition of this new architectural term:https://link.zhihu.com/?target=http%3A//www.martinfowler.com/articles/microservices.html
[2] Seven Microservices Anti-patterns:https://link.zhihu.com/?target=https%3A//www.infoq.com/articles/seven-uservices-antipatterns
[3] Service Level Agreement:https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Service-level_agreement
[4] http://swagger.io/:https://link.zhihu.com/?target=http%3A//swagger.io/
[5] 最終一致性:https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Eventual_consistency
出處:https://zhuanlan.zhihu.com/p/24044342
架構文摘
ID:ArchDigest
互聯網應用架構丨架構技術丨大型網站丨大數據丨機器學習
https://mp.weixin.qq.com/s?__biz=MzAxNjk4ODE4OQ==&mid=2247484333&idx=1&sn=5c7c6c8c1bda925f8eb360f1b07958f2&chksm=9bed22dfac9aabc98a1598082b6abb380b68eddaa0ce8fa52482b38262b270836b9758e75600&scene=21#wechat_redirect
RPC 緩存
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。