.NET Core微服務之服務間的呼叫方式(REST and RPC)
一、REST or RPC ?
1.1 REST & RPC
微服務之間的介面呼叫通常包含兩個部分,序列化和通訊協議。常見的序列化協議包括json、xml、hession、protobuf、thrift、text、bytes等;通訊比較流行的是http、soap、websockect,RPC通常基於TCP實現,常用框架例如dubbo,netty、mina、thrift。
REST:嚴格意義上說介面很規範,操作物件即為資源,對資源的四種操作(post、get、put、delete),並且引數都放在URL上,但是不嚴格的說Http+json、Http+xml,常見的http api都可以稱為Rest介面。
RPC:即我們常說的遠端過程呼叫,就是像呼叫本地方法一樣呼叫遠端方法,通訊協議大多采用二進位制方式。
1.2 HTTP vs 高效能二進位制協議
HTTP相對更規範,更標準,更通用,無論哪種語言都支援HTTP協議。如果你是對外開放API,例如開放平臺,外部的程式語言多種多樣,你無法拒絕對每種語言的支援,相應的,如果採用HTTP,無疑在你實現SDK之前,支援了所有語言,所以,現在開源中介軟體,基本最先支援的幾個協議都包含RESTful。
RPC協議效能要高的多,例如Protobuf、Thrift、Kyro等,(如果算上序列化)吞吐量大概能達到http的二倍。響應時間也更為出色。千萬不要小看這點效能損耗,公認的,微服務做的比較好的,例如,netflix、阿里,曾經都傳出過為了提升效能而合併服務。如果是交付型的專案,效能更為重要,因為你賣給客戶往往靠的就是效能上微弱的優勢。
所以,最佳實踐一般是對外REST,對內RPC,但是追求極致的效能會消耗很多額外的成本,所以一般情況下對內一般也REST,但對於個別性能要求較高的介面使用RPC。
二、案例結構
這裡假設有兩個服務,一個ClinetService和一個PaymentService,其中PaymentService有兩部分,一部分是基於REST風格的WebApi部分,它主要是負責一些對效能沒有要求的查詢服務,另一部分是基於TCP的RPC Server,它主要是負責一些對效能要求高的服務,比如支付和支出等涉及到錢的介面。假設User在消費ClientService時需要呼叫PaymentService根據客戶賬戶獲取Payment History(走REST)以及進行交易事務操作(走RPC)。
三、REST呼叫
3.1 一個好用的REST Client : WebApiClient
使用過Java Feign Client的人都知道,一個好的宣告式REST客戶端可以幫我們省不少力。在.NET下,園子裡的大大老九就寫了一款類似於Feign Client的REST Client:WebApiClient。WebApiClient是開源在github上的一個httpClient客戶端庫,內部基於HttpClient開發,是一個只需要定義C#介面(interface),並打上相關特性,即可非同步呼叫http-api的框架 ,支援.net framework4.5+、netcoreapp2.0和netstandard2.0。它的GitHub地址是:https://github.com/dotnetcore/WebApiClient
如何安裝?
NuGet>Install-Package WebApiClient-JIT
3.2 使用例項:走API Gateway
Step1.定義HTTP介面
[HttpHost("http://yourgateway:5000")] public interface IPaymentWebApi: IHttpApi { // GET api/paymentservice/history/edisonzhou // Return 原始string內容 [HttpGet("/api/paymentservice/history/{account}")] ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account); }
這裡需要注意的是,由於我們要走API閘道器,所以這裡定義的HttpHost地址是一個假的,後面具體呼叫時會覆蓋掉,當然你也可以直接把地址寫在這裡,不過我更傾向於寫到配置檔案中,然後把這裡的HttpHost設定註釋掉。
Step2.在Controller中即可非同步呼叫:
[Route("api/[controller]")] public class PaymentController : Controller { private readonly string gatewayUrl;public PaymentController(IConfiguration _configuration) { gatewayUrl = _configuration["Gateway:Uri"]; } [HttpGet("{account}")] public async Task<IList<string>> Get(string account) { using (var client = HttpApiClient.Create<IPaymentWebApi>(gatewayUrl)) { var historyList = await client.GetPaymentHistoryByAccountAsync(account); // other business logic code here // ...... return historyList; } }
}
當然你也可以在Service啟動時注入一個單例的IPaymentServiceWebApi例項,然後直接在各個Controller中直接使用,這樣更加類似於Feign Client的用法:
(1)StartUp類注入
public void ConfigureServices(IServiceCollection services) { // IoC - WebApiClient services.AddSingleton(HttpApiClient.Create<IPaymentServiceWebApi>(Configuration["PaymentService:Url"])); }
(2)Controller中直接使用
[HttpPost] public async Task<string> Post([FromBody]ModelType model, [FromServices]IPaymentServiceWebApi restClient) { ...... var result = await restClient.Save(model); ...... }
這裡PaymentService的實現很簡單,就是返回了一個String集合:
// GET api/history/{account} [HttpGet("{account}")] public IList<string> Get(string account) { // some database logic // ...... IList<string> historyList = new List<string> { "2018-06-10,10000RMB,Chengdu", "2018-06-11,11000RMB,Chengdu", "2018-06-12,12000RMB,Beijing", "2018-06-13,10030RMB,Chengdu", "2018-06-20,10400RMB,HongKong" }; return historyList; }
最終呼叫結果如下:
3.3 使用例項:直接訪問具體服務
在服務眾多,且單個服務就部署了多個例項的情況下,我們可以通過API閘道器進行中轉,但是當部分場景我們不需要通過API閘道器進行中轉的時候,比如:效能要求較高,負載壓力較小單個例項足夠等,我們可以直接與要通訊的服務進行聯接,也就不用從API閘道器繞一圈。
Step1.改一下HTTP介面:
[HttpHost("http://paymentservice:8880")] public interface IPaymentDirectWebApi: IHttpApi { // GET api/paymentservice/history/edisonzhou // Return 原始string內容 [HttpGet("/api/history/{account}")] ITask<IList<string>> GetPaymentHistoryByAccountAsync(string account); }
同理,這裡的HttpHost也是後面需要被覆蓋的,原因是我們將其配置到了配置檔案中。
Step2.改一下呼叫程式碼:
[Route("api/[controller]")] public class PaymentController : Controller { private readonly string gatewayUrl; private readonly string paymentServiceUrl; public PaymentController(IConfiguration _configuration) { gatewayUrl = _configuration["Gateway:Uri"]; paymentServiceUrl = _configuration["PaymentService:Uri"]; } [HttpGet("{account}")] public async Task<IList<string>> Get(string account) { #region v2 directly call PaymentService using (var client = HttpApiClient.Create<IPaymentDirectWebApi>(paymentServiceUrl)) { var historyList = await client.GetPaymentHistoryByAccountAsync(account); // other business logic code here // ...... return historyList; } #endregion }
最終呼叫結果如下:
四、RPC呼叫
4.1 Thrift簡介
Thrift是一個軟體框架,用來進行可擴充套件且跨語言的服務的開發。它結合了功能強大的軟體堆疊和程式碼生成引擎,以構建在 C++, Java, Go,Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些程式語言間無縫結合的、高效的服務。
當然,還有gRPC也可以選擇,不過從網上的效能測試來看,Thrift效能應該優於gRPC 2倍以上,但是gRPC的文件方面要比Thrift友好很多。
4.2 Thrift的使用
(1)下載Thrift (這裡選擇Windows版)
下載完成後解壓,這裡我將其改名為thrift.exe(去掉了版本號),一會在命令列敲起來更方便一點。
(2)編寫一個PaymentService.thrift,這是一個IDL中間語言
namespace csharp Manulife.DNC.MSAD.Contracts service PaymentService { TrxnResult Save(1:TrxnRecord trxn) } enum TrxnResult { SUCCESS = 0, FAILED = 1, } struct TrxnRecord { 1: required i64 TrxnId; 2: required string TrxnName; 3: required i32 TrxnAmount; 4: required string TrxnType; 5: optional string Remark; }
(3)根據thrift語法規則生成C#程式碼
cmd>thrift.exe -gen csharp PaymentService.thrift
(4)建立一個Contracts類庫專案,將生成的C#程式碼放進去
4.3 增加RPC Server
(1)新增一個控制檯專案,作為我們的Payment Service RPC Server,並引用Contracts類庫專案
(2)引入thrift-netcore包:
NuGet>Install-Package apache-thrift-netcore
(3)加入一個新增的PaymentService實現類
public class PaymentServiceImpl : Manulife.DNC.MSAD.Contracts.PaymentService.Iface { public TrxnResult Save(TrxnRecord trxn) { // some business logic here //Thread.Sleep(1000 * 1); Console.WriteLine("Log : TrxnName:{0}, TrxnAmount:{1}, Remark:{2}", trxn.TrxnName, trxn.TrxnAmount, trxn.Remark); return TrxnResult.SUCCESS; } }
這裡輸出日誌僅僅是為了測試。
(4)編寫啟動RPC Server的主程式
public class Program { private const int port = 8885; public static void Main(string[] args) { Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched..."); TServerTransport transport = new TServerSocket(port); var processor = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl()); TServer server = new TThreadedServer(processor, transport); // lanuch server.Serve(); } }
(5)如果是多個服務實現的話,也可以如下這樣啟動:
public static void Main(string[] args) { Console.WriteLine("[Welcome] PaymentService RPC Server is lanuched..."); TServerTransport transport = new TServerSocket(port); var processor1 = new Manulife.DNC.MSAD.Contracts.PaymentService.Processor(new PaymentServiceImpl()); var processor2 = new Manulife.DNC.MSAD.Contracts.PayoutService.Processor(new PayoutServiceImpl()); var processorMulti = new Thrift.Protocol.TMultiplexedProcessor(); processorMulti.RegisterProcessor("Service1", processor1); processorMulti.RegisterProcessor("Service2", processor2); TServer server = new TThreadedServer(processorMulti, transport); // lanuch server.Serve(); }
4.4 呼叫RPC
在ClientService中也引入apache-thrift-netcore包,然後在呼叫的地方修改如下:
[HttpPost] public string Post([FromBody]TrxnRecordDTO trxnRecordDto) { // RPC - use Thrift using (TTransport transport = new TSocket( configuration["PaymentService:RpcIP"], Convert.ToInt32(configuration["PaymentService:RpcPort"]))) { using (TProtocol protocol = new TBinaryProtocol(transport)) { using (var serviceClient = new PaymentService.Client(protocol)) { transport.Open(); TrxnRecord record = new TrxnRecord { TrxnId = GenerateTrxnId(), TrxnName = trxnRecordDto.TrxnName, TrxnAmount = trxnRecordDto.TrxnAmount, TrxnType = trxnRecordDto.TrxnType, Remark = trxnRecordDto.Remark }; var result = serviceClient.Save(record); return Convert.ToInt32(result) == 0 ? "Trxn Success" : "Trxn Failed"; } } } } private long GenerateTrxnId() { return 10000001; }
最終測試結果如下:
五、小結
本篇簡單的介紹了下微服務架構下服務之間呼叫的兩種常用方式:REST與RPC,另外前面介紹的基於訊息佇列的釋出/訂閱模式也是服務通訊的方式之一。本篇基於WebApiClient這個開源庫介紹瞭如何進行宣告式的REST呼叫,以及Thrift這個RPC框架介紹瞭如何進行RPC的通訊,最後通過一個小例子來結尾。最後,服務呼叫的最佳實踐一般是對外REST,對內RPC,但是追求極致的效能會消耗很多額外的成本,所以一般情況下對內一般也REST,但對於個別性能要求較高的介面使用RPC。
參考資料
作者:周旭龍
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。
相關推薦
.NET Core微服務之服務間的呼叫方式(REST and RPC)
一、REST or RPC ? 1.1 REST & RPC 微服務之間的介面呼叫通常包含兩個部分,序列化和通訊協議。常見的序列化協議包括json、xml、hession、protobuf、thrift、text、bytes等;通訊比較流行的是http、soap、websockect,RP
.NET Core 3.0之深入原始碼理解ObjectPool(一)
寫在前面 物件池是一種比較常用的提高系統性能的軟體設計模式,它維護了一系列相關物件列表的容器物件,這些物件可以隨時重複使用,物件池節省了頻繁建立物件的開銷。 它使用取用/歸還-重複取用的操作模式,如下圖所示: 本文將主要介紹物件池的基本概念、物件池的優勢及其工作機制,下一篇文件將從原始碼角度介紹.NET
.NET Core 3.0之深入原始碼理解ObjectPool(二)
寫在前面 前文主要介紹了ObjectPool的一些理論基礎,本文主要從原始碼角度理解Microsoft.Extensions.ObjectPool是如何實現的。下圖為其三大核心元件圖: 核心元件 ObjectPool ObjectPool是一個泛型抽象類,裡面只有兩個抽象方法,Get和Return。它從底
.NET Core 3.0之深入原始碼理解HealthCheck(一)
寫在前面 我們的系統可能因為正在部署、服務異常終止或者其他問題導致系統處於非健康狀態,這個時候我們需要知道系統的健康狀況,而健康檢查可以幫助我們快速確定系統是否處於正常狀態。一般情況下,我們會提供公開的HTTP介面,用於專門化健康檢查。 NET Core提供的健康檢查庫包括Microsoft.Extensio
.NET Core 3.1之深入原始碼理解HealthCheck(二)
寫在前面前文討論了HealthCheck的理論部分,本文將討論有關HealthCheck的應用內容。可以監視記憶體、磁碟和其他物理伺服器資源的使用情況來了解是否處於正常狀態。執行狀況檢查可以測試應用的依賴項(如資料庫和外部服務終結點)以確認是否可用和正常工作。執行狀況探測可以由容器業務流程協調程式和負載均衡器
ASP.NET Core 簡單實現七牛圖片上傳(FormData 和 Base64)
private stream public 圖片 ASP.NET Core 簡單實現七牛圖片上傳(FormData 和 Base64)七牛圖片上傳 SDK(.NET 版本):https://developer.qiniu.com/kodo/sdk/1237/csharpUpoladServic
同“窗”的較量:部署在 Windows 上的 .NET Core 版部落格站點發布上線(已暫時下線)
為了驗證 docker swarm 在高併發下的效能問題,週一我們釋出了使用 docker-compose 部署的 .net core 版部落格站點(博文連結),但由於有1行程式碼請求後端 web api 時沒有使用快取,結果造成大量 web api 請求發向跑後端服務的叢集,悲劇的是這個叢集是用 docke
STL之劃分與排序演算法(Partions and Sorting)
目錄 STL之劃分與排序演算法(Partions and Sorting) 劃分演算法 一、is_partitoned 二、partition 三、partition_copy(beg,
C#LeetCode刷題之#771-寶石與石頭(Jewels and Stones)
問題 給定字串J 代表石頭中寶石的型別,和字串 S代表你擁有的石頭。 S 中每個字元代表了一種你擁有的石頭的型別,你想知道你擁有的石頭中有多少是寶石。 J 中的字母不重複,J 和 S中的所有字元都是字母。字母區分大小寫,因此"a"和"A"是不同型別的石頭。 輸入: J
.NET Core微服務開發服務間呼叫篇-GRPC
在單體應用中,相互呼叫都是在一個程序內部呼叫,也就是說呼叫發生在本機內部,因此也被叫做本地方法呼叫;在微服務中,服務之間呼叫就變得比較複雜,需要跨網路呼叫,他們之間的呼叫相對於與本地方法呼叫,可稱為遠端過程呼叫,簡稱RPC(Remote procedure call)。 看過上篇API閘道器篇,知道案例中包含
.NET Core微服務之基於Consul實現服務治理
請求轉發 1.0 asp.net AC port prefix 我們 tle nan 一、Consul基礎介紹 Consul是HashiCorp公司推出的開源工具,用於實現分布式系統的服務發現與配置。與其他分布式服務註冊與發現的方案,比如 Airbnb的Smart
.NET Core微服務之基於Consul實現服務治理(續)
shell pla code tst 分層 編輯 set req \n 上一篇發布之後,這一篇把上一篇沒有弄到的東西補一下,也算是給各位前來詢問的朋友的一些回復吧。一、Consul服務註冊之配置文件方式1.1 重溫Consul實驗集群 這裏我們有三個Consul Serv
.net core 微服務之Api閘道器(Api Gateway)
微服務閘道器目錄 1、 微服務引子 2、使用Nginx作為api閘道器 3、自創api閘道器(重複輪子) 3.1、構建初始化 3.2、構建中介軟體 4、結語
基於Apollo實現.NET Core微服務統一配置(測試環境-單機) .NET Core微服務之基於Apollo實現統一配置中心
一、前言 注:此篇只是為測試環境下的快速入門。後續會給大家帶來生產環境下得實戰開發。 具體的大家可以去看官方推薦。非常的簡單明瞭。以下介紹引用官方內容: Apollo(阿波羅)是攜程框架部門研發的分散式配置中心,能夠集中化管理應用不同環境、不同叢集的配置,配置修改後能夠實時推送到應用端,並且具
.NET Core微服務之基於Steeltoe使用Eureka實現服務註冊與發現
一、關於Steeltoe與Spring Cloud Steeltoe is an open source project that enables .NET developers to implement industry standard best practices when b
.NET Core微服務之基於Steeltoe整合Zuul實現統一API閘道器
一、關於Spring Cloud Zuul API Gateway(API GW / API 閘道器),顧名思義,是出現在系統邊界上的一個面向API的、序列集中式的強管控服務,這裡的邊界是企業IT系統的邊界。 Zuul 是Netflix 提供的一個開源元件,致力於在雲平臺上提供動態路由,監
.NET Core微服務之基於Steeltoe使用Hystrix熔斷保護與監控
一、關於Spring Cloud Hystrix 在微服務架構中,我們將系統拆分為很多個服務,各個服務之間通過註冊與訂閱的方式相互依賴,由於各個服務都是在各自的程序中執行,就有可能由於網路原因或者服務自身的問題導致呼叫故障或延遲,隨著服務的積壓,可能會導致服務崩潰。為了解決這一系列的問題
.NET Core微服務之基於Steeltoe使用Spring Cloud Config統一管理配置
一、關於Spring Cloud Config 在分散式系統中,每一個功能模組都能拆分成一個獨立的服務,一次請求的完成,可能會呼叫很多個服務協調來完成,為了方便服務配置檔案統一管理,更易於部署、維護,所以就需要分散式配置中心元件了,在Spring Cloud中,就有這麼一個分散式配置中心元件 —
NET Core微服務之路:自己動手實現Rpc服務框架,基於DotEasy.Rpc服務框架的介紹和整合
本篇內容屬於非實用性(拿來即用)介紹,如對框架設計沒興趣的朋友,請略過。 快一個月沒有寫博文了,最近忙著兩件事; 一:閱讀劉墉先生的《說話的魅力》,以一種微妙的,你我大家都會經常遇見的事物,來建議說話的“藝術和魅力”,對於我們從事軟體開發、不太善於溝通
.NET Core微服務之路:利用DotNetty實現一個簡單的通訊過程
上一篇我們已經全面的介紹過《基於gRPC服務發現與服務治理的方案》,我們先複習一下RPC的呼叫過程(筆者會在這一節的幾篇文章中反覆的強調這個過程呼叫方案),看下圖