- 工信部備案號 滇ICP備05000110號-1
- 滇公安備案 滇53010302000111
- 增值電信業務經營許可證 B1.B2-20181647、滇B1.B2-20190004
- 云南互聯網協會理事單位
- 安全聯盟認證網站身份V標記
- 域名注冊服務機構許可:滇D3-20230001
- 代理域名注冊服務機構:新網數碼
隨著業務發展,很多系統需要經歷服務拆分的過程。微服務化過程踩坑也是很正常的事。如果在服務拆分之前做好充分準備,能幫我們少走很多彎路。本文主要從服務依賴,接口版本,隔離,數據一致等方面說說微服務化過程應該注意的點。
一、循環依賴問題
微服務化之后服務之間會存在各種依賴關系,不過依賴需要遵循一定的規則,不能太隨意。否則,就會出現循環依賴的問題,而且會讓調用關系變得錯綜復雜難于維護。下面是服務依賴的幾條規則:
1,上層服務可以調用下層服務。
2,同級服務之間不能產生依賴關系,及不能產生調用關系。
3,下層服務不能調用上層服務。
4,服務之間的調用關系只能是單向的。
例如,在電商系統里包括支付服務(Pay),庫存服務(Inventory),訂單服務(Order)。支付服務和庫存服務屬于基礎服務,訂單服務屬于上層服務。支付服務和庫存服務是同級的服務,他們之間不能存在調用關系。訂單服務屬于上層服務,訂單服務可以調用支付服務和庫存服務,但是支付服務和庫存服務不能調用上層的訂單服務。
假設我們不管這些規則,讓Order和Pay可以互相調用。這樣就會產生循環依賴,Order調用Pay,Pay也調用Order,這樣彼此都會依賴對方。
循環依賴導致哪些問題?
1,無限遞歸調用
假如,Order調用Pay的A方法,Pay調用Order的B方法。然后,A方法里又調用了Order的B方法,B方法里又調用了Pay的A方法。這樣就會產生無限的遞歸調用,后果自然不言而喻了。
2,部署依賴問題
假設Order,Pay,Inventory彼此之間都可以通過API互相調用。當API接口發生變更時,為了讓其他服務能夠正常調用,API需要重新編譯。如果Order和Pay的API都有變化,上線發布時就需要特別小心。為了保證發布成功,就需要根據服務間API的依賴關系,詳細考慮先打包部署哪個服務,后打包部署哪個服務,才不至于發布失敗。如果有更多的服務呢?比如10幾個,梳理依賴關系都會把人搞瘋的。
3,另外,循環依賴會讓服務間的調用關系變得錯綜復雜,系統難于維護。
二、接口版本兼容
一些初中級程序員往往會忽略接口變更的問題,經常會因為接口變更導致線上問題。比如某個小型電商平臺的訂單服務調用支付服務的某個接口,產品突然提了一個需求,這個需求需要在這個支付接口上加一個參數。開發這個需求的是個新手,他直接在原來的接口方法上實現了需求并加上了參數,聯調測試通過后就發布上線了。結果剛上線訂單服務就開始報錯,因為方法變了,加了參數,訂單服務找不到老的方法了。所以就會一直報錯,直到訂單服務上線為止。
所以我們一定要注意接口版本問題。我們可以新加一個方法去重載老的方法,在新方法里實現新的功能,新方法的定義除了多一個參數外,其他的和老方法一樣。也就是給老方法加了一個新版本。
這樣在支付服務上線后,訂單服務上線之前就不會報錯了,因為老方法仍然可用。訂單服務上線后就直接切到了新版本的方法。
如果我們服務框架選用的是Dubbo,當一個接口的實現,出現不兼容升級時,可以用Dubbo的版本號過渡,版本號不同的服務相互間不引用。
可以按照以下的步驟進行版本遷移:
1. 在低壓力時間段,先升級一半提供者為新版本
2. 再將所有消費者升級為新版本
3. 然后將剩下的一半提供者升級為新版本
老版本服務提供者配置:
新版本服務提供者配置:
老版本服務消費者配置:
新版本服務消費者配置:
三、關于隔離的考慮
1.數據隔離:
實際上,服務化的其中一個基本原則就是數據隔離,不同服務應該有自己的專屬數據庫,而不應該共用相同的數據庫,數據訪問可以通過服務接口或者消息隊列的方式。
很多公司微服務化后,只做了代碼工程的拆分,不同服務對應的數據仍然存放在同一個數據庫中。這樣做至少存在四個問題:
(1)數據安全問題。別人的服務不但可以訪問你的數據,而且還能修改和刪除你的數據。
(2)導致數據庫連接耗盡。一旦某個服務的開發者寫了一個慢SQL,并且這個服務也沒有合理限制連接數。可能會消耗掉所有的數據庫連接,進而造成訪問相同數據庫的其他服務拿不到數據庫連接,無法訪問數據庫。
(3)表關聯查詢。無法避免其他服務的開發者,為了快速上線某些需求。直接查詢其他服務的表,或者跨服務做表關聯查詢。這樣會造成服務間的耦合越來越嚴重。
(4)表結構變化的影響。如果某個服務直接依賴于其他服務的數據,一旦表結構發生任何變化,比如修改表名或者字段。很可能會產生災難性后果。
2.部署隔離:
我們經常會遇到秒殺業務和日常業務依賴同一個服務,以及C端服務和內部運營系統依賴同一個服務的情況,比如說都依賴支付服務。而秒殺系統的瞬間訪問量很高,可能會對服務帶來巨大的壓力,甚至壓垮服務。內部運營系統也經常有批量數據導出的操作,同樣會給服務帶來一定的壓力。這些都是不穩定因素。所以我們可以將這些共同依賴的服務分組部署,不同的分組服務于不同的業務,避免相互干擾。
3.業務隔離:
以秒殺為例。從業務上把秒殺和日常的售賣區分開來,把秒殺做為營銷活動,要參與秒殺的商品需要提前報名參加活動,這樣我們就能提前知道哪些商家哪些商品要參與秒殺,可以根據提報的商品提前生成商品詳情靜態頁面并上傳到CDN預熱,提報的商品庫存也需要提前預熱,可以將商品庫存在活動開始前預熱到Redis,避免秒殺開始后大量訪問穿透到數據庫。
四、數據一致性問題
做了微服務拆分后,還可能會出現數據不一致的問題。比如支付服務中,支付狀態發生變更后要通知訂單服務修改對應訂單的狀態。如果支付服務沒有正常通知到訂單服務,或者訂單服務接到通知后沒能正常處理通知,就會導致支付服務的支付狀態和訂單服務的支付狀態不一致,也就是數據會不一致。
1.那么如何避免數據不一致的問題產生呢?
我們通常所說的服務間數據一致性,主要包括數據強一致性和最終一致性。對于強一致性,使用的業務場景很少,而且會有明顯的性能問題。所以這里我們主要討論最終一致性。
一般我們可以采用如下幾種方式來保證服務間數據的最終一致:
2.定時任務重試,同步調用接口
這種方式,采用定時任務去掃表,每次定時任務掃描所有未成功的記錄,并發起重試。注意,要保證重試操作的冪等性。
這種方式的優點是:實現簡單。缺點是:需要啟動專門的定時任務,定時任務存在一定的時間間隔,實時性會比較差。而且同步接口調用的方式,耦合較重,有時無法避免循環依賴的問題。
比如,Order服務可以調用Pay,Pay做為基礎服務不應該調用Order。當Pay的某筆交易狀態發生變更后,需要通知Order。如果采用定時任務的方式就需要Order提供一個接口,定時任務掃描過程中同步調用這個接口去更新Order的訂單狀態。這樣又違反了單向依賴的原則,形成了循環依賴。
異步消息隊列,發送事務型消息
如上圖,以電商下單流程為例。下單流程最后一步,通知WMS撿貨出庫,是異步消息走消息隊列。
按上面代碼,大家不難發現問題!如果發送撿貨出庫消息失敗,數據就會不一致!有人說我可以在代碼上加上重試邏輯和回退邏輯,發消息失敗就重發,多次重試失敗所有操作都回退。這樣一來邏輯就會特別復雜,回退失敗要考慮,而且還有可能消息已經發送成功了,但是由于網絡等問題發送方沒得到MQ的響應。還有可能出現發送方宕機的情況。這些問題都要考慮進來!
幸好,有些消息隊列幫我們解決了這些問題。比如阿里開源的RocketMQ(目前已經是Apache開源項目),4.3.0版本開始支持事務型消息(實際上早在貢獻給Apache之前曾經支持過事務消息,后來被閹割了,4.3.0版本重新開始支持事務型消息)。
先看看RocketMQ發送事務型消息的流程:
(1)發送半消息(所有事務型消息都要經歷確認過程,從而確定最終提交或回滾(拋棄消息),未被確認的消息稱為“半消息”或者“預備消息”,“待確認消息”)
(2)半消息發送成功并響應給發送方
(3)執行本地事務,根據本地事務執行結果,發送提交或回滾的確認消息
(4)如果確認消息丟失(網絡問題或者生產者故障等問題),MQ向發送方回查執行結果
(5)根據上一步驟回查結果,確定提交或者回滾(拋棄消息)
看完事務型消息發送流程,有些讀者可能沒有完全理解,不要緊,我們來分析一下!
問題1:假如發送方發送半消息失敗怎么辦?
半消息(待確認消息)是消息發送方發送的,如果失敗,發送方自己是知道的并可以做相應處理。
問題2:假如發送方執行完本地事務后,發送確認消息通知MQ提交或回滾消息時失敗了(網絡問題,發送方重啟等情況),怎么辦?
沒關系,當MQ發現一個消息長時間處于半消息(待確認消息)的狀態,MQ會以定時任務的方式主動回查發送方并獲取發送方執行結果。這樣即便出現網絡問題或者發送方本身的問題(重啟,宕機等),MQ通過定時任務主動回查發送方基本都能確認消息最終要提交還是回滾(拋棄)。當然出于性能和半消息堆積方面的考慮,MQ本身也會有回查次數的限制。
問題3:如何保證消費一定成功呢?
RocketMQ本身有ack機制,來保證消息能夠被正常消費。如果消費失敗(消息訂閱方出錯,宕機等原因),RocketMQ會把消息重發回Broker,在某個延遲時間點后(默認10秒后)重新投遞消息。
結合上面幾個同步調用hmily完整代碼如下:
如果執行到TransactionListenerImpl.executeLocalTransaction方法,說明半消息已經發送成功了,也說明OrderService.makePayment方法的四個步驟都執行成功了,此時tcc也到了confirm階段,所以在TransactionListenerImpl.executeLocalTransaction方法里可以直接返回LocalTransactionState.COMMIT_MESSAGE 讓 MQ提交這條消息,同時將該訂單信息和對應的消息狀態保存在共享map里,以備確認消息發送失敗時MQ回查消息狀態使用。
3.采用TCC,SAGA,Seata等框架
以上就是微服務化后需要注意的幾點。
、
提交成功!非常感謝您的反饋,我們會繼續努力做到更好!
這條文檔是否有幫助解決問題?
售前咨詢
售后咨詢
備案咨詢
二維碼
TOP