Tip: 此篇已加入.NET Core微服务基础系列文章索引

一、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(:TrxnRecord trxn)
} enum TrxnResult {
SUCCESS = ,
FAILED = ,
} struct TrxnRecord {
: required i64 TrxnId;
: required string TrxnName;
: required i32 TrxnAmount;
: required string TrxnType;
: 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 = ; 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) == ? "Trxn Success" : "Trxn Failed";
}
}
}
} private long GenerateTrxnId()
{
return ;
}

  最终测试结果如下:

  

五、小结

  本篇简单的介绍了下微服务架构下服务之间调用的两种常用方式:REST与RPC,另外前面介绍的基于消息队列的发布/订阅模式也是服务通信的方式之一。本篇基于WebApiClient这个开源库介绍了如何进行声明式的REST调用,以及Thrift这个RPC框架介绍了如何进行RPC的通信,最后通过一个小例子来结尾。最后,服务调用的最佳实践一般是对外REST,对内RPC,但是追求极致的性能会消耗很多额外的成本,所以一般情况下对内一般也REST,但对于个别性能要求较高的接口使用RPC。

参考资料

远方的行者,《微服务 RPC和REST

杨中科,《.NET Core微服务课程:Thrift高效通讯

醉眼识朦胧,《Thrift入门初探--thrift安装及java入门实例

focus-lei,《.net core下使用Thrift

宝哥在路上,《Thrift性能测试与分析

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

.NET Core微服务之服务间的调用方式(REST and RPC)的更多相关文章

  1. Taurus.MVC 微服务框架 入门开发教程:项目集成:6、微服务间的调用方式:Rpc.StartTaskAsync。

    系统目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...

  2. Solidity的三种合约间的调用方式 call、delegatecall 和 callcode

    0x00 前言 Solidity(http://solidity.readthedocs.io/en/v0.4.24/) 是一种用与编写以太坊智能合约的高级语言,语法类似于 JavaScript. S ...

  3. ASP.NET Core 微服务初探[2]:熔断降级之Polly

    当我们从单体架构迁移到微服务模式时,其中一个比较大的变化就是模块(业务,服务等)间的调用方式.在以前,一个业务流程的执行在一个进程中就完成了,但是在微服务模式下可能会分散到2到10个,甚至更多的机器( ...

  4. .NET Core微服务之基于Steeltoe使用Eureka实现服务注册与发现

    Tip: 此篇已加入.NET Core微服务基础系列文章索引 =>  Steeltoe目录快速导航: 1. 基于Steeltoe使用Spring Cloud Eureka 2. 基于Steelt ...

  5. .NET Core 微服务学习与实践系列文章目录索引(2019版)

    参考网址: https://archy.blog.csdn.net/article/details/103659692 2018年,我开始学习和实践.NET Core,并开始了微服务的学习,以及通过各 ...

  6. 小D课堂 - 新版本微服务springcloud+Docker教程_4-01 常用的服务间调用方式讲解

    笔记 第四章 服务消费者ribbon和feign实战和注册中心高可用 1.常用的服务间调用方式讲解     简介:讲解常用的服务间的调用方式 RPC:             远程过程调用,像调用本地 ...

  7. 小D课堂 - 新版本微服务springcloud+Docker教程_4-06 Feign核心源码解读和服务调用方式ribbon和Feign选择

    笔记 6.Feign核心源码解读和服务调用方式ribbon和Feign选择         简介: 讲解Feign核心源码解读和 服务间的调用方式ribbon.feign选择             ...

  8. .NET Core微服务开发服务间调用篇-GRPC

    在单体应用中,相互调用都是在一个进程内部调用,也就是说调用发生在本机内部,因此也被叫做本地方法调用:在微服务中,服务之间调用就变得比较复杂,需要跨网络调用,他们之间的调用相对于与本地方法调用,可称为远 ...

  9. .NET Core微服务系列基础文章索引(目录导航Final版)

    一.为啥要总结和收集这个系列? 今年从原来的Team里面被抽出来加入了新的Team,开始做Java微服务的开发工作,接触了Spring Boot, Spring Cloud等技术栈,对微服务这种架构有 ...

随机推荐

  1. Spider与OpenPyXL的结合

    OpenPyXL的API文档 1.OpenPyXL基础操作 引入Workbook这个类,然后调用 from openpyxl import Workbook wb = Workbook() 通过ope ...

  2. 请注意写代码的习惯与态度(Java)

    注: 以下内容引自http://blog.csdn.net/xtayfjpk/article/details/52136686 请注意写代码的习惯与态度(Java) 原创 2016年08月06日 16 ...

  3. Java 读书笔记 (十二) Java Character 类

    在实际开发过程中, 我们经常会遇到需要使用对象,而不是内置数据类型的情况. 为了解决这个问题, Java语言为内置数据类型char提供了包装类Character类. 可以使用Character的构造方 ...

  4. Mac下安装git

    gti下载地址 https://git-scm.com/downloads 一步一步来就完事了.安装完以后执行 git version 查看是否更新到了该版本

  5. luoguP2526_[SHOI2001]小狗散步_二分图匹配

    luoguP2526_[SHOI2001]小狗散步_二分图匹配 题意: Grant喜欢带着他的小狗Pandog散步.Grant以一定的速度沿着固定路线走,该路线可能自交.Pandog喜欢游览沿途的景点 ...

  6. [Usaco2005 nov]Grazing on the Run 边跑边吃草 BZOJ1742

    分析: 首先,连续选择一段必定最优... 区间DP,f[i][j]表示从i开始,连续j个被吃掉了,并且,牛在i处,g[i][j]则表示在i+j-1处 f[i][j]可以从g[i+1][j]和f[i+1 ...

  7. MYSQL—— 启动MYSQL 57 报错“The service MYSQL57 failed the most recent........等”的问题解决方式!

    每天开机之后,启动MYSQL Notifier就报错,第一次出现重启电脑后解决,后面的几天老是出现,重启电脑好几次都没有解决,感觉很烦人,一定要搞定这个问题找到原因,于是有了下文....... 启动M ...

  8. Nginx 配置 Https 免费证书访问

    配置HTTPS 现在做博客或者做网站没有 https 已经不行了,就记录一下我在腾讯云配置 https 的过程吧,非常简单,1个小时就可以了. 还涉及到 http 访问自动转发到 https 访问路径 ...

  9. python接口自动化(十八)--重定向(Location)(详解)

    简介 在实际工作中,有些接口请求完以后会重定向到别的url,而你却需要重定向前的url.URL主要是针对虚拟空间而言,因为不是自己独立管理的服务器,所以无法正常进行常规的操作.但是自己又不希望通过主域 ...

  10. 基于Unity的AR开发初探:发布AR应用到Android平台

    本文接上一篇,介绍一下如何通过Unity发布第一个AR应用至Android平台,在Android手机上使用我们的第一个AR应用. 一.一些准备工作 1.1 准备Java JDK 这里选择的是JDK 1 ...