一、交互方式
1.1 第一個維度關注的是一對一和一對多:
- 一對一:每個客戶端請求由一個服務實例來處理。
- 一對多:每個客戶端請求由多個服務實例來處理。
1.2 第二個維度關注的是同步和異步:
- 同步模式:客戶端請求需要服務端實時響應,客戶端等待響應時可能導致堵塞。
- 異步模式:客戶端請求不會阻塞進程,服務端的響應可以是非實時的。
1.3 一對一的交互方式有以下幾種類型:
- 請求/響應:一個客戶端向服務端發起請求,等待響應;客戶端期望服務端很快就會發送響應。在一個基于線程的應用中,等待過程可能造成線程阻塞。這樣的方式會導致服務的緊耦合。
- 異步請求/響應:客戶端發送請求到服務端,服務端異步響應請求??蛻舳嗽诘却憫獣r不會阻塞線程,因為服務端的響應不會馬上就返回。
- 單向通知:客戶端的請求發送到服務端,但是并不期望服務端做出任何響應。
1.4 一對多的交互方式有以下幾種類型:
- 發布/訂閱方式:客戶端發布通知消息,被零個或者多個感興趣的服務訂閱。
- 發布/異步響應方式:客戶端發布請求消息,然后等待從感興趣的服務發回的響應。
二、同步模式
2.1 使用REST
2.1.1 定義REST API
按照研發前后端約定的API規范來制定API文檔,Swagger用于開發和記錄REST API的工具。
2.1.2 在一個請求中獲取多個資源
這種方法在許多場景中都很有效,但對于更復雜的場景來說,它通常是不夠的。實現它也可能很耗時??墒褂肎raphQL來支持高效的數據獲取。
2.2 使用RPC(Dubbo)
按照dubbo接口設計規范來制定接口文檔。
2.3 使用服務發現(Nacos)
2.3.1 應用層服務發現模式
這種服務發現方法是兩種模式的組合。第一種模式是自注冊模式。服務實例調用服務注冊表的注冊API來注冊其網絡位置。它還可以提供運行狀況檢查URL,運行狀況檢查URL是一個API端點,服務注冊表會定期調用該端點來驗證服務實例是否正常且可用于處理請求。服務注冊表還可能要求服務實例定期調用“心跳” API以防止其注冊過期。
2.3.2 客戶端發現模式
當客戶端想要調用服務時,它會查詢服務注冊表以獲取服務實例的列表。為了提高性能,客戶端可能會緩存服務實例。然后,服客戶端使用負載平衡算法(例如循環或隨機)來選擇服務實例。然后它向選擇的服務實例發出請求。
2.4 使用斷路器(Hystrix)
分布式系統中,當服務試圖向另一個服務發送同步請求時,永遠都面臨著局部故障的風險。因為客戶端和服務端是獨立的進程,服務端很有可能無法在有限的時間內對客戶端的請求做出響應。服務端可能因為故障或維護的原因而暫停。或者,服務端也可能因為過載而對請求的響應變得極其緩慢。
客戶端等待響應被阻塞,這可能帶來的麻煩就是在其他客戶端甚至使用服務的第三方應用之間傳導,并導致服務中斷。
2.4.1 開發可靠的調用代理
- 網絡超時:在等待針對請求的響應時,一定不要做成無限阻塞,而是要設定一個超時。使用超時可以保證不會一直在無響應的請求上浪費資源。
- 限制客戶端向服務器發出請求的數量:把客戶端能夠向特定服務發起的請求設置一個上限,如果請求達到了這樣的上限,很有可能發起更多的請求也無濟于事,這時就應該讓請求立刻失敗。
- 斷路器模式:監控客戶端發出請求的成功和失敗數量,如果失敗的比例超過一定的閾值,就啟動斷路器,讓后續的調用立刻失效。如果大量的請求都以失敗而告終,這說明被調服務不可用,這樣即使發起更多的調用也是無濟于事。在經過一定的時間后,客戶端應該繼續嘗試,如果調用成功,則解除斷路器。
2.4.2 從服務失效故障中恢復
使用Hystrix只是解決方案的一部分。還必須根據具體情況決定如何從無響應的遠程服務中恢復服務。一種選擇是服務只是向其客戶端返回錯誤。返回備用值(fallback value,例如默認值或緩存響應)可能會有意義。
三、異步模式
3.1 基于消息機制的API
3.1.1 記錄異步操作
- 請求/異步響應式API:包括服務的命令消息通道、服務接受的命令式消息的具體類型和格式,以及服務發送的回復消息的類型和格式。
- 單向通知式API:包括服務的命令消息通道,以及服務接受的命令式消息的具體類型和格式。
3.1.2 記錄事件發布
服務還可以使用發布/訂閱的方式對外發布事件。此API風格的規范包括事件通道以及服務發布到通道的事件式消息的類型和格式。
消息和消息通道模型是一種很好的抽象,也是設計服務異步API的好方法。但是,為了實現服務,需要選擇具體的消息傳遞技術并確定如何使用它們的能力來實現設計。
3.2 使用代理消息
消息代理是所有消息的中介節點。發送方將消息寫入消息代理,消息代理將消息發送給接收方。使用消息代理的一個重要好處是發送方不需要知道接收方的網絡位置。另一個好處是消息代理緩沖消息,直到接收方能夠處理它們。 有許多消息代理可供選擇。流行的開源消息代理包括:
- Apache RocketMQ
- RabbitMQ
- Apache Kafka
3.3 并發處理和消息順序
如何在保留消息順序的同時,橫向擴展多個接收方的實例。為了同時處理消息,擁有多個實例是一個常見的要求。而且,即使單個服務實例也可能使用線程來同時處理多個消息。使用多個線程和服務實例來并發處理消息可以提高應用程序的吞吐量。但同時處理消息的挑戰是確保每個消息只被處理一次,并且是按照它們發送的順序來處理的。
Kafka使用consumer group,特定的每個事件都發布到同一個分片,而且該分片中的消息始終由同一個接收方實例讀取。這樣做就能夠保證按順序處理這些消息。
3.4 重復消息處理
理想情況下,消息代理應該只傳遞一次消息,但保證有且僅有一次的消息傳遞通常成本很高。相反,大多數消息代理承諾至少成功傳遞一次消息。當系統正常工作時,保證傳遞的消息代理只會傳遞一次消息。但是客戶端、網絡或消息代理的故障可能導致消息被多次傳遞。假設客戶端在處理消息后、發送確認消息之前,它的數據庫崩潰了,這時消息代理將再次發送未確認的消息,在數據庫重新啟動時向該客戶端或客戶端的另一個副本發送。
3.4.1 編寫冪等消息處理器
如果應用程序處理消息的邏輯是滿足冪等的,那么重復的消息就是無害的。通常情況下應用程序邏輯通常不是冪等的?;蛘呖赡苷谑褂孟⒋?,該消息代理在重新傳遞消息時不會保留排序。重復或無序消息可能會導致錯誤。在這種情況下,就必須編寫跟蹤消息并丟棄重復消息的消息處理程序。
3.4.2 跟蹤消息并丟棄重復消息
消息接收方使用message id跟蹤它已處理的消息并丟棄任何重復項。