一. Go微服務--隔離設計

failymao發表於2021-08-30

1. 前言

隔離設計源於船舶行業,一般而言無論大船還是小船,都會有一些隔板,將船分為不同的空間,這樣如果有船艙漏水一般只會影響這一小塊空間,不至於把整個船都給搞沉了。

同樣我們的軟體服務也是一個道理,我們要儘量避免出現一個問題就把這個業務給搞掛的情況出現

那什麼是「服務隔離」呢?

顧名思義,它是指將系統按照一定的原則劃分為若干個服務模組,各個模組之間相對獨立,無強依賴。當有故障發生時,能將問題和影響隔離在某個模組內部,而不擴散風險,不波及其它模組,不影響整體的系統服務。

2. 服務的演進

2.1 最簡單的架構

如下圖,今天天氣不錯,我們開始創業(天氣和創業有啥關係???),搞了一個電商網站,由於前期人手不足技術也不夠,就一個服務和一個資料庫就開始對外提供服務了

隨著貨物的不斷上架,我們發現產品相關介紹的圖片、視訊等資訊佔用了我們服務的大部分頻寬,並且也不太好管理,使用者訪問呢也比較慢,影響了剁手的體驗。

2.2 動靜隔離

這時候我們做了一波優化,把 靜態資源的資料使用雲服務商提供的物件儲存給儲存了起來,然後在前面接入了一個 CDN 給使用者提供更好的體驗。

使用物件儲存和 CDN 將靜態資源和動態 API 進行了隔離

然後突然有一天我們發現,使用者大量投訴說什麼垃圾網站,突然訪問這麼慢,進過緊鑼密鼓的排查發現,原來是我們的運營同學在後臺進行資料統計準備出報告的時候影響了生產的資料庫,導致影響了我們的使用者

2.3 資料庫主從隔離,使用者隔離

所以我們有進行了一波優化,我們對資料庫進行了主從分離然後將運營後臺和我們的商城主服務做了拆分,後續所有的統計查詢請求我們都從從庫查詢,其他請求才會去修改主庫。

是滴,我們又做了一次隔離,一個是將資料庫做了主從隔離,另外一個按照不同的使用者屬性,做了使用者隔離。當然這是比較巨集觀的在這過程中我們肯定也會對資料中的表進行一些拆分設計,例如將經常變化的資料和不太經常變化的資料分配到兩張表等。

2.3.1 使用者隔離

怎麼給使用者分類?
可以用按照使用者是否VIP使用者等級使用者IP等等,方法很多,要結合自己實際業務的特性來做。

其實這也是一種「多租戶架構」,在SaaS服務中用得比較多。
多租戶模式有三種形式:

  1. 完全的隔離,即服務和資料都是完全獨立的。
  2. 公共服務、獨立資料來源,即多個租戶是用的同一臺服務程式,但是底層的資料來源是獨立的。
  3. 公用服務、公用資料來源,即多個租戶的服務程式與資料庫源都是共享的,不同資料可能會做分割槽分表來獨立。

上述三種方式,從下到上,獨立性和安全性越來越高,資源利用率越來越低,根據業務特性去選擇,一般選擇折中方案。

2.4 微服務架構

不知道是隨著一些爆款活動的推出,以及不知名大 V 的推廣使得我們的業務欣欣向上,還是我們新來的技術總監為了 KPI 我們進行了轟轟烈烈的微服務改造的活動,最後經過長達一年多的改造,我們的服務架構改造成了下面這個模樣。

請求在訪問之前都會先經過 WAF 防火牆,然後再到對應的 CDN 節點然後經過我們的 API 閘道器到 BFF 層。然後 BFF 層再去呼叫各種服務聚合成業務資料並且返回。

這裡 其實又做了一層按照服務的隔離,我們將一個單體服務拆分成了一個個的小服務,就不會出現評論掛掉了導致整個服務掛掉無法下單的情況。

2.5 服務優先順序隔離

微服務改造完成之後我們發現,的確整體的服務質量都好了很多,但是突如其來的一個 bug 導致我們的監控大量告警,這是為什麼呢,原來是我們的推薦服務出現了一個記憶體洩漏的問題,然後我們的服務限制做的不夠好根本沒有設定任何限制,這就導致它佔用了資源池中的大量資源,讓我們的其他服務資源緊缺

然後我們就又做了一個改造,我們把支付和商城這種最重要的業務單獨放在了一個池子裡面,對於像評論推薦這種沒有那麼重要的業務放在共享的資源池當中

所以這一次我們按照服務的優先順序進行了隔離

2.6 熱點快取

突然有一天,我們的一個商品成為了爆款,大量使用者湧入訪問,成功將我們的 cache 打掛,後續我們做了什麼改動呢,我們將 remote cache 提升為了 local cache,在 sdk 當中自動識別出熱點流量,然後將其快取,大大減少了 redis 的壓力

這一次我們就將熱點資料進行了隔離

3. 隔離設計要點

隔離設計分為以下幾種

  • 服務隔離
    • 動靜隔離:上面講到的CDN
    • 讀寫隔離:如上面講到的主從, 除此之外還有常見的CQRS模式,分庫分表
  • 輕重隔離
    • 核心隔離:例如上面講到的將核心業務獨立部署,非核心業務共享資源
    • 熱點隔離:例如上面講到的 remote cache 到local cache
    • 使用者隔離:不同的使用者可能有不同的級別,例如上面講到的外部使用者和管理員
  • 物理隔離
    • 執行緒:常見的例子就是執行緒池,這個在Golang中一般不用考慮過多, runtime已經幫我們管理好了
    • 程式: 現在一般使用容器化部署,跑在k8s上就是一種程式級別的隔離
    • 機房:我們目前在k8s基礎上做一些開發, 常見的一種做法就是將服務的不同副本儘量的分配在不同的可用區,實際上就是雲廠商的不同機房,避免機房停電或者著火之類的影響
    • 叢集:非常重要的服務我們可以部署多套,在物理上進行隔離,常見的有異地部署,也可能就部署在同一個區域

3.1 服務隔離的注意事項

在做服務隔離的時候,還是有一些原則和事項需要注意的:

  1. 不可越界:能在隔離模組內完成的邏輯,就儘量不要跨模組呼叫,減少依賴。
  2. 不可共享:資料和資源能獨享的就儘量不要共享,不然很容易造成隔離失效。
  3. 考慮效率:設計隔離模組的時候,要根據業務情況而定,充分的考慮到未來的拓補結構,減少呼叫效率的損失。
  4. 考慮顆粒度:隔離模組設計的大小問題,過大和過小都不合適,需充分考慮。
  5. 服務的全面監控:既然服務或使用者進行隔離了,那麼系統的複雜度肯定是比之前要高了,那麼針對多服務的全鏈路監控是必不可少的。

4. 服務隔離弊端

  1. 當我們某個功能操作需要關聯多個服務模組或者同時查詢所個模組資料的時候,程式碼寫起來就會相對麻煩一些了,其中涉及到多模組呼叫的效能問題、資料一致性問題、事物問題等。
  2. 不同服務模組之間的互動也會比較複雜一些,因為要做服務隔離,避免服務強依賴,所以模組之間的互動呼叫最好是走非同步模式,需要通過非同步執行緒或訊息中介軟體來傳遞實現。
  3. 在進行運營大資料分析的時候,由於資料是散落在不同服務模組的,因此需要做額外的匯聚操作,還得有唯一欄位保證資料在不同模組產生的先後順序。

5. 參考

  1. https://lailin.xyz/post/go-training-week6-usability-1-bulkhe.html
  2. https://zhuanlan.zhihu.com/p/40475855

相關文章