REST RPC HTTP vs 高性能二进制协议 序列化和通信协议
edisonchou https://mp.weixin.qq.com/s/-XZXqXawR-NxJMPCeiNsmg
.NET Core微服务之服务间的调用方式(REST and RPC)
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接口
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[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);
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
这里需要注意的是,由于我们要走API网关,所以这里定义的HttpHost地址是一个假的,后面具体调用时会覆盖掉,当然你也可以直接把地址写在这里,不过我更倾向于写到配置文件中,然后把这里的HttpHost设置注释掉。
Step2.在Controller中即可异步调用:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[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;
}
}
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
当然你也可以在Service启动时注入一个单例的IPaymentServiceWebApi实例,然后直接在各个Controller中直接使用,这样更加类似于Feign Client的用法:
(1)StartUp类注入
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
public void ConfigureServices(IServiceCollection services)
{ // IoC - WebApiClient
services.AddSingleton(HttpApiClient.Create<IPaymentServiceWebApi>(Configuration["PaymentService:Url"])); }
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
(2)Controller中直接使用
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[HttpPost] public async Task<string> Post([FromBody]ModelType model, [FromServices]IPaymentServiceWebApi restClient)
{
...... var result = await restClient.Save(model);
......
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
这里PaymentService的实现很简单,就是返回了一个String集合:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
// 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;
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
最终调用结果如下:
3.3 使用实例:直接访问具体服务
在服务众多,且单个服务就部署了多个实例的情况下,我们可以通过API网关进行中转,但是当部分场景我们不需要通过API网关进行中转的时候,比如:性能要求较高,负载压力较小单个实例足够等,我们可以直接与要通信的服务进行联接,也就不用从API网关绕一圈。
Step1.改一下HTTP接口:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[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);
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
同理,这里的HttpHost也是后面需要被覆盖的,原因是我们将其配置到了配置文件中。
Step2.改一下调用代码:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[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
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
最终调用结果如下:
四、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中间语言
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
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;
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
(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实现类
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
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;
}
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
这里输出日志仅仅是为了测试。
(4)编写启动RPC Server的主程序
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
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();
}
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
(5)如果是多个服务实现的话,也可以如下这样启动:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
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();
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
4.4 调用RPC
在ClientService中也引入apache-thrift-netcore包,然后在调用的地方修改如下:
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
[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;
}
aaarticlea/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="" data-ratio="1" data-src="https://mmbiz.qpic.cn/mmbiz_png/fCpd1cf8iacYj7ick27pRuJPKmHDpDV57ugruP6xUicj1ZwIc3bbDj0TppXpjV3HGSSL4tibmXxZ2L9uib0jFl1TNxw/640?wx_fmt=gif" data-type="gif" data-w="20" />
最终测试结果如下:
五、小结
本篇简单的介绍了下微服务架构下服务之间调用的两种常用方式: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性能测试与分析》
【序列化和通信协议】
微服务之间的接口调用通常包含两个部分,序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等;通信比较流行的是http、soap、websockect,RPC通常基于TCP实现,常用框架例如dubbo,netty、mina、thrift。
REST RPC HTTP vs 高性能二进制协议 序列化和通信协议的更多相关文章
- Netty实现高性能RPC服务器优化篇之消息序列化
在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...
- 开源!一款功能强大的高性能二进制序列化器Bssom.Net
好久没更新博客了,我开源了一款高性能的二进制序列化器Bssom.Net和新颖的二进制协议Bssom,欢迎大家Star,欢迎参与项目贡献! Net开源技术交流群 976304396,禁止水,只能讨论技术 ...
- 轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)
在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Head ...
- [.net 面向对象程序设计进阶] (9) 序列化(Serialization) (一) 二进制流序列化
[.net 面向对象程序设计进阶] (9) 序列化(Serialization) (一) 二进制流序列化 本节导读: 在.NET编程中,经常面向对象处理完以后要转换成另一种格式传输或存储,这种将对 ...
- HTTP与私有二进制协议之间的区别
简单的文本协议.二进制协议 写网络程序躲不过协议,协议其实就是定义了消息的格式,以及消息是如何交换的.协议可简单可复杂,复杂精密如TCP协议,简单奔放如HTTP的协议.这里将我所接触到的协议稍微总结一 ...
- C#轻量级通通讯组件StriveEngine —— C/S通信开源demo(2) —— 使用二进制协议 (附源码)
前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好友关 ...
- 使用Netty实现通用二进制协议的高效数据传输
Netty是一个高性能的NIO通信框架,提供异步的.事件驱动的网络编程模型.使用Netty可以方便用户开发各种常用协议的网络程序.例如:TCP.UDP.HTTP等等. Netty的最新版本是3.2.7 ...
- 二进制协议gob及msgpack介绍
本文主要介绍二进制协议gob及msgpack的基本使用. 最近在写一个gin框架的session服务时遇到了一个问题,Go语言中的json包在序列化空接口存放的数字类型(整型.浮点型等)都序列化成fl ...
- 二进制协议gob和msgpack介绍
二进制协议gob和msgpack介绍 本文主要介绍二进制协议gob及msgpack的基本使用. 最近在写一个gin框架的session服务时遇到了一个问题,Go语言中的json包在序列化空接口存放的数 ...
随机推荐
- 九度oj 题目1014:排名
题目描述: 今天的上机考试虽然有实时的Ranklist,但上面的排名只是根据完成的题数排序,没有考虑每题的分值,所以并不是最后的排名.给定录取分数线,请你写程序找出最后通过分数线的考生,并将他 ...
- vue v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', { inserted:function(el) { const dragDom = el.querySelector('.jsPropupLay ...
- vue 自定义日历组件
<template> <div class=""> <div class="calendarTraffic" name=" ...
- Unity3D for iOS初级教程:Part 3/3
转自Unity 3D for iOS 这篇文章还可以在这里找到 英语 Learn how to use Unity to make a simple 3D iOS game! 这份教程是由教程团队成员 ...
- spring之lazy-init
lazy-init:延迟实例化 ApplicationContext实现的默认行为就是在启动服务器时将所有singleton bean提前进行实例化.提前实例化意味着作为初始化过程的一部分,appli ...
- 维修队列(bzoj 1500)
Description Input 输入的第1 行包含两个数N 和M(M ≤20 000),N 表示初始时数列中数的个数,M表示要进行的操作数目.第2行包含N个数字,描述初始时的数列.以下M行,每行一 ...
- Radio Transmission(bzoj 1355)
Description 给你一个字符串,它是由某个字符串不断自我连接形成的. 但是这个字符串是不确定的,现在只想知道它的最短长度是多少. Input 第一行给出字符串的长度,1 < L ≤ 1, ...
- ObjectDataSource配合存储过程(采用数据集)的使用(删除可以解决,但是编辑出错好像它的方法也无法解决
原文发布时间为:2008-08-01 -- 来源于本人的百度文章 [由搬家工具导入] ObjectDataSource是比较有意思的一个东西 通过在网络上遍访各位高手,终于自己有了一些心得体会。现总结 ...
- angular中的this指向问题
this是指向当前$scope的. 例如在ng-click的使用中,this是指向当前的$scope而并不是dom元素的. 我们可以使用this的一些方法和属性 我们打印一下this就会发现,this ...
- CatchTheCaw ----广搜入门
抓住那头牛(POJ3278)农夫知道一头牛的位置,想要抓住它.农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000).农夫有 ...