特殊時期,釘釘如何透過單元化扛住流量高峰?

陶然陶然發表於2023-01-11

   引言

  釘釘單元化從2018年開始到今年已經是第五個年頭了,五年的時間,釘釘單元化迭代了三個版本,從最初的毛頭小子,到達今年已經小有成就。今天想借這個場來和大家分享我們單元化的心路歷程和一些優秀實踐。本文要分享的內容只涉及部分內容,無法做到面面俱到,主要是想在同路人中形成共鳴,進而能複用一些架構或者系統。在我們單元化建設過程中,除了網上僅有的文章外,其可以直接使用的系統乏善可陳,使我們不得不從最基礎的系統開始,極大的影響建設效率。幸運最近幾年雲原生技術的興起,讓我們能複用很多基礎設施,進而快速的提升我們單元化能力,助力釘釘的發展。

   單元化1.0

  合規驅動下的部署架構

  2018年,部分大客戶出於法律政策、商業機密資料儲存的要求,要求釘釘的資料儲存、訪問接入、服務部署需要在其信任的區域內,既需要滿足其資料儲存私有化要求,同時需要滿足跨地區網路的rt效能要求。結合阿里雲機房部署位置、物理距離、使用者資料安全等方面出發,釘釘在客戶的阿里雲機房內建設了一個單元,將通訊錄、IM資訊等企業資料單獨儲存在客戶機房。  

  我們透過一條專線,將兩個機房邏輯串聯到一起,內部透過DMB/DMR系統,實現了請求互通。釘釘單元化1.0比較簡單,純粹是業務驅動,和支付寶單元化建設的初衷:容災驅動有較大區別。兩個站點透過UID分段,將使用者劃分為中心使用者和專有使用者。上圖只是一個簡化的邏輯結構,內部實現遠比上圖複雜,但是1.0建設主要是從0到1,和大多數異地多活的系統相似,簡單和大家分享。

   單元化2.0

  2.1 被逼的容量架構

  2020年是一個特殊的年份,由於疫情的原因,帶給大家非常多的改變,其中也包括釘釘。由於線上辦公與教育流量的突增,開年第一天上班就給釘釘一個下馬威,平峰的流量已經和除夕跨年的持平,但是和除夕不同的是這個流量是持續的,即使節前準備了三倍容量,也抵擋不住流量對系統的衝擊。只能藉助阿里雲的能力,不斷的擴容。  

  但是每天將近30%的流量增幅,單純的擴容也能難保障服務的連續性,最終也遇到了擴無可擴的場景,張北機房沒有機位了,有機器資源但是沒有機位讓我們有力無處使。我們不得不不斷進行系統最佳化,同時藉助限流、降級、雙推等措施,勉強抗住了流量的最高峰。

  疫情之前,我們一直在做高可用,但是這個高可用主要集中在容災機制上,比如搭建容災單元。如同支付寶一樣,是因為當時光纖被挖斷;又比如銀行的兩地三中心架構,是擔心某一個地域由於天災或者戰爭導致資料丟失。疫情的流量給我們上了一課,僅僅關注容災是不夠的,特別是釘釘的DAU從千萬走向億級別之後,更需要在容量上做出提前規劃。正因如此,我們認為“容量架構不是設計出來而是真真切切被逼出來的”,所以容量架構就成為我們單元化核心要素之一。

  容量架構是將流量劃分到不同單元,每個單元承載各自的流量;容災架構是單元異常時,能保障核心的能力可用,也可以將流量動態排程到別的單元,實現服務的快速恢復。因此釘釘單元化進入了2.0時代,專注於容量和容災的建設。  

  2.2 天生不同:找到屬於自己的業務維度

  要實現流量的劃分,必然要基於一個維度進行劃分,一部分到A單元,一部分到B單元。釘釘單元化架構也是參考了淘系和支付寶的單元化架構,前兩者都是基於UID劃分,釘釘單元化的第一個版本其實也是一樣的,基於UID做拆分。但是當我們設計容量架構時,發現基於UID劃分無法解決我們的容量問題,以IM為例,一條訊息其實屬於聊天雙方的,群聊亦是如此。使用者能和任意一個人聊天,這樣我們根本無法找到一個切入點來劃分流量,強行按照UID拆分,必然導致一個使用者的訊息出現在N個單元,單元的自封閉就無法做了。也有同學會說,為什麼訊息不按照每個人儲存,這不就能按照UID劃分了嗎?結論是不行,首先這個訊息變成了寫擴散,持久化的時候會變成多單元寫,其次是成本翻倍,在DTIM這種過億規模的場景這條路走不通。這裡可以多說一點,因為這個觀點來之不易,大家都知道,人是有慣性的,既然淘寶、支付寶甚至是微信都是UID劃分,為什麼釘釘要特立獨行,當時我們團隊受到了絕大部分釘釘技術團隊的挑戰,持續長達將近一個月的技術選型的“爭吵”,最終還是達成了一致意見。

  DTIM有主要的3個維度,分別是UID、會話(CID)、訊息。其中會話和訊息是繫結的,而系統中最大量的是訊息,按照第一性原則來看,一定要將訊息劃分開來,才能做到將容量劃分開來的效果。我們再來看看音影片,是按照房間維度組織流量和資料的,和IM又完全不同;同樣,文件其實更適合按照企業維度來劃分。不同的業務擁有不同的維度,因此我們認為單元化最重要的找到自身“最大”的業務維度,將這個維護拆分,才能實現單元的橫向擴充套件,我們稱之為“業務路由”。回頭來看,我們之前其實是進入了思考誤區,以為淘系和支付寶都是UID維度,我們也要這個維度,其實UID正是前者的業務維度,比如訂單,也是圍繞使用者,並不會有交集的情況,會話就是IM的劃分維度,因此做單元化之前要先找到屬於自己的業務維度。

  2.3 全域性路由系統

  UID路由有個最大的好處,就是可以按照UID分段,能實現高效的靜態路由,也不用擔心多單元之間的一致性問題。但是這種分段路由侷限性也比較明顯,需要預先分配,單元之間動態排程流量和資料成本極高,而且只能支援這種數值+順序的場景。在釘釘的場景,有會話維度、房間維度、企業維度等等,想簡單採用這種預分段機制難以滿足業務需求。因此我們構建一個業務路由系統(RoutingService),實現流量的精確路由。  

  以IM為例,每次訊息的傳送,在單元化框架層面,會透過訊息的會話(CID),查詢路由資訊,如果是本單元,流量下行並持久化;如果是非本單元,路由到對應的單元中。下圖是三個會話,分別是cid:1001、cid:1002、cid:1003,三個會話隸屬不同單元,不管使用者從哪個單元傳送訊息,都會路由到會話所在的單元。比如使用者在Unit B的cid:1001 中傳送訊息,當訊息進入Receiver之後,會先查詢此cid:1001所在的單元,發現是Unit A,路由框架將請求轉到A單元,訊息在A單元持久化並透過A單元的同步協議,將資料推送到客戶端。

  服務端視角

  客戶端視角

  從上圖可知,每次訊息傳送,都要查詢路由服務,DTIM百萬的峰值,對路由必然會帶來超大的壓力,同時我們能發現,路由資料在多單元實現一致性是一個巨大的挑戰。

  邊緣計算:端到端路由

  在DTIM的場景中,會話的路由資訊幾乎不會變更,只有當我們決定將某些超大的會話或者企業騰挪到新單元時,才會發起路由的變更,因此會話的路由資訊幾乎可以認為是恆定不變的,那麼每次查詢路由服務端,效費比太低,是極大的浪費。既然路由資訊幾乎不可變,是否將路由資訊快取呢?最常見的是使用一個集中式的Cache系統,快取Hot的會話,我們也是這麼做的,但是這麼做還是不夠,一旦Cache系統失效,DTIM還是會出現大面積故障,而且這個百萬級的請求對Cache也是一個極大的壓力。

  考慮到釘釘有強大的客戶端,借用邊緣計算的思路,我們將使用者的會話資料快取到客戶端,對於客戶端來說,也只用快取使用者自身最熱的N會話路由資料,訊息傳送時,透過Header將路由資料攜帶到服務端,服務端路由SDK只要做合法性和續約即可,將路由流量降低了95%以上。當路由服務出現異常時,還可以繼續使用客戶端路由,將路由的可用性提升到一個新的高度。

  SDK本地會依據上行請求的返回中是否有新的路由資訊,進而更新客戶端路由。同時可以藉助釘釘有主動下推的能力,透過同步協議將新的路由資訊主動推送給客戶端,使會話遷移做到更平順。  

  計算下沉:多單元一致性

  對於新會話,比如小明要建立一個群聊,是應該建立在那個單元呢?如果在A單元建立了,當會話訊息來到B單元,系統怎麼能第一時間知道會話已經在被繫結到A單元。這裡一般的方式有兩種,單元之間的儲存系統採用類似DTS的機制進行非同步同步,這種機制有秒級延遲;或者第二種方式是在應用層主動同步,比如接入訊息佇列。這兩種方式由於都是非同步的原因,都會出現不一致的問題,如果會話同時被繫結在兩個單元,邏輯上會導致使用者的歷史訊息丟失,這個是不能接受的。

  多地域(Region)資料同步其實是通用的技術挑戰,我們認為儲存系統提供是最好的方式,正如Google的Spanner一樣,這樣對我們上層才是最友好的方式。因此我們找到了儲存的OTS、Nuwa團隊一起共建了GlobalTable。GlobalTable的核心原理還是藉助Nuwa的一致性組,組分佈在多個地域,採用多數派寫入成功即返回的原理,做到20ms以內的一致性寫。  

  2.4 單元化的另一面:容災能力

  釘釘單元化的容災能力是深度結合釘釘的業務層場景落地的,和淘系支付寶等有明確的區別。以DTIM為例,最大的特點是當服務單元異常時,服務側仍能提供最核心的服務,保障最基本的能力。本質上是由於DTIM是最終一致性系統,可以短暫允許部分環節失敗。可以看一下DTIM傳送訊息的容災場景。當某個單元完全不可用的情況下,使用者訊息傳送鏈路透過降級為local模式,在本地校驗非本單元會話資料透過之後直接做訊息傳送,processor遇到非本單元的會話訊息資料可以做單元間投遞做資料回放,本地是否落庫可選,同步協議推送不必區分是否為本單元會話訊息資料直接透過本單元的topic推送給客戶端,配合使用者無狀態快速遷移能力,單元間可以實現真正的分鐘級別容災切換能力。  

  能力與業務能力突破

  以上是釘釘單元化2.0提供給應用的核心能力,在滿足容災和容量設計需求之後,釘釘單元化給應用帶來了更多的能力和想象空間,比如:快速遷移:當某一地域資源不足時,釘釘單元化可以將業務快速的從A單元遷移到B單元。常態化切流:比如新建的教育會話,可以放到獨立的單元。熱點治理:當前某一個會話過熱,特殊時期可以遷移到獨立叢集。SLA:滿足不同的VIP客戶需求,基於不同的SLA和售賣價格,將VIP客戶放到對應地單元。

  核心還是我們擁有單元化能力之後,實現了多單元流量的快速排程,為業務解決了後顧之憂。

   單元化3.0

  3.1 新時代新挑戰

  魚和熊掌不可兼得

  2022年對釘釘來說是成本之年,成本的壓力不光落到了團隊,還落到了每個人身上。正如儲存的CAP理論是一樣的,我們同時只能滿足兩個維度,對於流量(效能P)、成本(C)、體驗(E)也是一樣,在流量不可預知和干預的情況下,選擇成本必然導致體驗受損,反之選擇體驗,必然導致成本升高。進入下半年,疫情反覆帶來流量的反覆,為了實現可控的教育成本,只能在高峰期降級部分能力,這又導致體驗受損,這段時間的工單量可以窺見一斑。

  流量是使用者側觸發的,我們無法干預,只能在成本和體驗之間尋求平衡。和前面提及的一樣,為了減小成本的消耗這就導致我們在擴容和縮容之間疲於奔命,反應不及時甚至有故障的危險,這種機制不可取也不可持續。到底是要流量與成本,還是要流量與體驗,給我們技術團隊帶來了巨大的挑戰和矛盾。

  商業化路在何方

  當前釘釘為支援大客戶提供了多種解決方案,專業釘釘、專屬儲存與打包、專有釘釘。專屬釘釘透過APP專屬化以及部分專屬功能,比如為一個企業定製一個擁有獨立Logo的APP,能滿足一般的中大型客戶的業務訴求。對於大型以及超大型客戶,我們提供專有釘釘,提供專有化輸出,完全隔離的方案,比如浙政釘。伴隨著釘釘的商業化進入深水區,客戶對釘釘提出了新的訴求,特別是資料安全與歸屬、互聯互通、完整的能力棧等訴求,當前釘釘輸出產品形態都無法同時地滿足以上需求。

  前幾年網際網路上出現的幾起資料安全事件,資料丟失與洩露,未經客戶授權私自訪問客戶資料,讓大多數客戶不信任服務提供商,即使服務商的安全能力已經是業界一線能力。其實這個是可以理解的,資料即客戶的生命線,資料無法在自身可控範圍內,特別是對於很多特殊行業,這是無法接受的,自身性命豈能假手於人。專屬釘釘在面臨這種客戶時,前線售賣同學是無能為力。

  那麼很多同學肯定會提“如果專屬釘釘滿足不了需求,我們專有釘釘不是能解決這些問題嗎?”,其實單單從訴求來看,專有釘釘場景是切合客戶的業務訴求,提供完全獨立執行環境、可控的資料安全。但是專有釘釘由於其獨特的架構帶來高昂的售價以及後期的運維代價,對於超大型的客戶來說也難以承擔如此高的成本。對於釘釘自身來說,從研發到後續運維,維護一套獨立體系也難以在客戶側大面積推廣。  

  3.2 混合雲架構

  釘釘單元化經過四年的發展,在容災和容量上做出一定的積澱,同時完成了一些核心技術的積累。當整體架構成熟之後,我們也在思考,單元化能否從技術架構升級為業務架構,比如搭建獨立的高可用單元,按照售賣的SLA提供給VIP客戶,支援釘釘商業化的發展。同時我們在雲原生逐步發力,將部分核心應用放到雲上,經過這一年多的執行,遇到了新的挑戰,但更獲得雲下無法獲得的計算彈效能力,雲上的彈性對雲下是一個降維打擊,從一個新的方向解決計算問題。

  如上文提到的兩個核心挑戰,釘釘單元化同樣面臨這個問題,在持續的發展中找到了一個合適的架構方向。基本思路是雲下作為基本盤,保障核心流量的問題,畢竟雲下經過集團多年的打磨,不管是穩定性還是流程的合理性都有保障;雲上應對高漲異常的流量,比如和疫情正相關的教育流量,既保證了服務的穩定性,又能充分利用雲上彈效能力,在提供完整能力的前提下做到一個相對較低的成本。其次是升級Geo概念,將Geo作為一個獨立的業務域,實現Geo級別完全獨立部署,分散式雲模式;同時Geo之間按需互通,從研發體系上能做到一套程式碼。  

  因此,釘釘單元化來到了3.0版本,我們稱之為釘釘單元化混合雲架構。混合雲主要是從兩個維度來看:第一是雲上雲下,我們認為雲上雲下並不是取代的關係,而是相互補充的關係,是一個長期的狀態,正如很多大客戶隨著規模的持續擴張,最終依賴的部分核心能力必然走向自研道理一樣,這能做成本的進一步降低,所以架構是一個混合雲架構。第二,業務架構上也是混合雲架構,透過不同的Geo,將不同的業務邏輯上聚合到一起,構建起一張釘釘的大網,不同Geo按需互通,實現了業務架構的混合。3.0從系統架構上相對於2.0,最大的區別就是雲原生技術的運用和互通閘道器的建立。

  雲原生技術 :抵抗系統架構熵增的有效手段

  近幾年,網際網路圈最火的技術莫過於以Docker為代表的雲原生技術最為火熱,各大雲廠商也都在不遺餘力的推廣雲原生技術以及對應的產品;同時釘釘服務過億DAU的客戶,面對各種可靠性、服務連續性、併發、容災等技術挑戰,也都走到了現有技術的邊界。所以我們也在不斷吸收新的技術和架構,希望從體系與架構上降低我們的技術複雜度,以抵抗熵增。我們在2021年底啟動了我們的雲原生升級戰略,我們升級雲原生技術並不是為了技術而升級,而是切實面臨巨大的技術挑戰。

  首先我們面臨多語言的挑戰。我們以IM為例,IM的核心邏輯都是使用C++構建,但是我們常用的中介軟體三大件:儲存、快取、非同步佇列,其中快取和非同步佇列在C++客戶端上長期建設不足,導致IM長期在使用低版本,低版本由於長時間缺乏維護,經常會出現異常,比如佇列假死、消費不均等,導致我們自己不得不親自上陣修改SDK的程式碼;最後難以使用到產品的新能力,阻礙IM服務能力的提升。

  其次是多產品多雲的挑戰。我們以阿里云為例,資料庫類目下的產品,從類別上就有關聯式資料庫、NoSQL資料庫、數倉等等,還有儲存也是一樣。對於我們上層業務,其實絕大部分服務都只依賴了底層的CURD,這麼多產品,每次對接一個產品都要開發一輪。配置系統也是一樣,彈內有Diamond,雲上有Nacos、Mse,K8s有自己的Configmap等,而且這些配置系統不像資料庫有標準,而是百花齊放,但是這樣卻苦了我們使用者。這些內容不是我們的核心路徑,浪費大把時間在各種產品介面的適配上,明顯拖累了釘釘的發展。

  最後就是通用的流量治理挑戰。釘釘很多系統都是最終一致的系統,IM就是典型的最終一致系統,這類系統和強同步系統在架構設計有一個明顯的區別,強一致系統如果遇到失敗,必須要持續重試直到成功,所以一般程式設計上都是重試+退避。但是最終一致系統不是,這類系統允許部分節點失敗,不要阻礙其他流程,失敗的流量透過一個非同步迴旋的佇列,將資料逐步回放回來即可。這種迴旋需要藉助非同步佇列,而且要設計各種消費機制,比如限速、比如丟棄等等,這是一個通用的邏輯,但是每個業務方或多或少都在實現自己的迴旋系統,重複的造輪子。又比如各種故障注入,單元化路由流量等等,要想擁有這個能力,團隊不得不投入人力研發。

  在對付架構複雜度上,我們主要從兩個維度來遮蔽複雜度,首先程式碼層面我們選擇了DDD模式,我們使用DDD分層核心是把對外系統的依賴全部收攏到Infrastructure這一層,全部採用純虛擬函式(Interface)對外提供介面。遮蔽底層中介軟體差異和細節。在架構上採用Sidecar的模式,類似於Dapr的思想,透過標準的GRPC和PB實現應用與中介軟體解耦。Sidecar中整合了各種中介軟體、配置系統、灰度系統等,等價實現了應用和中介軟體的解耦。上文中提到的不管是多語言挑戰、多雲多產品的挑戰、重複造輪子等問題,都能很好的解決。  

  互通閘道器 :混合架構的基石

  雲上雲下互通,或者說多個雲賬戶VPC之間的互通,我們常見的有兩種方案,其一就是VPC直接打通,讓多個VPC之間形成一個大的區域網,RealServer實現點對點互通;其二是中間搭建一個負載均衡器,透過暴露EIP實現互通。兩個方案都有自己的優缺點。比如方案一,打通的VPC涉及到IP規劃,如果前期沒有合理規劃,後續很難打通;還有這種方案有水桶短板安全問題,一旦一個VPC被攻破,這張網也被攻破;但是對於內部的應用來說架構就比較簡單,可以僅僅藉助K8s DNS service就能做到服務發現。對於方案二,最大的缺點就是中間有一個集中式的負載均衡,需要申請獨立的LB才可訪問;但是這種方案隔離性好。

  對於釘釘單元化來說,涉及N個業務方,N * M個應用,對應X個VPC,要想VPC之間打通,幾乎沒有可能性,而且VPC打通,還面臨應用之間的安全性問題。要實現Geo之間互通,環境之間的隔離性是基本要求,與此同時,我們也要考慮到系統的可擴充套件性,所以我們必須要構建一套獨立的流量閘道器,實現流量加密、定址、轉發等通用能力。

  釘釘互通閘道器是構建在Envoy之上的系統,雙向Ingress和Egress,支援GRPC和釘釘自研協議。具備流量管理、傳輸加密、單元定址等能力。釘釘單元化藉助互通閘道器的能力,再配合全域性流控系統,我們可以在多單元之間實現精確的流量控制和排程。

   展望

  伴隨著專屬叢集的持續輸出,客戶對專屬的場景需求會越來越多,需要我們投入更多的人力持續的建設。比如架構側的加強,首先是Sidecar持續強化,支援更多的中介軟體和環境,提供不同維度的安全能力,滿足客戶和應用的安全需求。在運維側,我們需要構建多Geo管理能力,完善Geo和單元之間流量快速排程能力,提供自動化的自檢系統等。在交付側,如果實現快速交付,比如是否能做到新應用一週完成單元化改造,新Geo一天部署完成。這些挑戰都是接下來我們要重點投入的方向。

  對於標準釘來說,這個是我們的基本盤,一個穩定可靠且低成本的釘釘是我們持之以恆的目標,接下來我們會加大雲上流量的佔比,充分的藉助雲上彈效能力,實現可控的成本。

  今天我們只是站在釘釘的角度上拋了一個“磚”,希望在異地多活這個領域激起一層浪花,歡迎大家一起討論。

   核心概念

  Geo:釘釘專有化部署單位,解決資料合規需求,Geo間資料按需互通,並且互通資料在Geo內部做映象複製,解決兩化問題

  Unit: Geo內部資源物理分割槽隔離的最小單位,解決Geo內的容災和容量的問題

  L0:客戶端路由,決定了使用者客戶端接入釘釘伺服器的所屬單元,使用者長連線所在的邏輯單元,起到連線加速作用。使用者接入單元

  L1:接入層路由,以使用者為維度進行排程,即使用者操作發生的單元。使用者歸屬單元

  L2:業務層路由,以業務資源為維度進行排程,大部分的業務資源所在單元應該和使用者排程單元一致,但一些業務無法按照使用者劃分單元,如IM的會話,音影片的會議。 業務歸屬單元

  DMB:負責釘釘應用跨單元RPC呼叫的轉發,可以認為是釘釘單元化RPC路由中介軟體。

  DMR:負責釘釘應用跨單元MQ訊息的轉發,可以認為是釘釘單元化MQ路由中介軟體。

來自 “ 阿里開發者 ”, 原文作者:嘯臺、萬泓;原文連結:http://server.it168.com/a2023/0111/6785/000006785678.shtml,如有侵權,請聯絡管理員刪除。

相關文章