微服务和消息队列的基础都是RPC框架,比较有名的有WCF、gRPC、Dubbo等,我们的NewLife.ApiServer建立在网络库NewLife.Net之上,支持.Net Core,追求轻量级和高性能,只有最简单的远程调用功能。

现在是网络系列文章的第五篇,前面四篇快速过了一遍网络库基本用法,也做了压力测试并给出数字 2266万tps

本章正式进入应用层面,并且采用.Net Core作为例程,说明我们一开始就支持.Net Core,也算是回答了很多支持者的疑问。

老规矩,先上代码:https://github.com/NewLifeX/NewLife.Net (例程RpcTest)

ApiServer源码:https://github.com/NewLifeX/X/tree/master/NewLife.Core/Remoting

ApiServer实在太小了,就让它和Net一起分别作为X组件核心库的一个目录。

一、 背景

ApiServer开始于2014年,我们为了建立物联网云平台,解决云端、硬件设备端、手机端、网页端相互通信,而建立的一套完整的通信体系。

公司业务需要,在ApiServer上建立了包括服务治理、注册发现、负载均衡、设备鉴权、通信加密、压缩、P2SP网络、WebSocket等等一系列模块。

这一套物联网云平台已经用在很多家公司上,根据NewLife两年解封惯例,大概在2019年开源放出大部分源码。

本文所指的ApiServer,仅指开源的RPC部分。

2017年4月1日晚,我们想知道ApiServer的表现,做了一次最大并发数测试,目标是单节点支持100万设备接入。

租用60台阿里云ECS,实际测试单节点最大支持84.5万模拟设备接入,设备端的心跳包(5~60s) 拖垮了32核服务端。

二、功能特点

先看看例程最终效果:

ApiServer主要特点如下:

  1. 支持.Net Core/Net40/Net45,这个最近太热门了,其实X组件绝大部分功能都支持.Net Core
  2. 多年积累。从2014年起,遇到并解决了很多问题,也去掉了很多可选功能,只保留必要功能
  3. 性能尚可。网络库2266tps,ApiServer在40核服务器上单客户端带业务测试得到16万tps
  4. 简单易用。高仿MVC的Controller风格,支持上下文和执行前后过滤器,客户端直接Invoke,无需生成Stub代码,参数无需完全一致,便于多版本兼容
  5. 容易调试。默认通信参数和返回采用Json封送,打开编码器日志后,远程调用的收发一目了然。(网络库的高性能就是用来给Json浪费的……)
  6. 大包请求。支持收发大数据包(如1M~1000M),特殊服务接口避开Json序列化,直接走二进制。
  7. 支持异常。服务接口抛出的异常,能够封装传递到客户端

三、服务端例程

新建.Net Core 2.0项目RpcTest,我们把服务端客户端代码写到一起。

服务暴露高仿MVC,一个控制器内可以暴露多个服务方法

/// <summary>自定义控制器。包含多个服务</summary>
class MyController
{
/// <summary>添加,标准业务服务,走Json序列化</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public Int32 Add(Int32 x, Int32 y) => x + y; /// <summary>RC4加解密,高速业务服务,二进制收发不经序列化</summary>
/// <param name="pk"></param>
/// <returns></returns>
public Packet RC4(Packet pk)
{
var data = pk.ToArray();
var pass = "NewLife".GetBytes(); return data.RC4(pass);
}
}

这里暴露了两个服务,分别是 加法My/Add 和 加密My/RC4 ,控制器名称加上方法名,作为寻址路径。

不使用Api特性时,控制器类的所有共有方法都将暴露成为服务。

返回值比较简单支持,该什么类型就什么类型。理论上来说,支持Json序列化的类型,都可以作为参数和返回类型。

服务方法也可以指定名称,支持方法过滤接口

/// <summary>用户控制器。会话获取,请求过滤</summary>
[Api("User")]
class UserController : IApi, IActionFilter
{
/// <summary>会话。同一Tcp/Udp会话多次请求共用,执行服务方法前赋值</summary>
public IApiSession Session { get; set; } [Api(nameof(FindByID))]
public User FindByID(Int32 uid, Boolean deleted)
{
// Session 用法同Web
var times = Session["Times"].ToInt();
times++;
Session["Times"] = times; // 故意制造异常
if (times >= )
{
// 取得当前上下文
var ctx = ControllerContext.Current; throw new ApiException(, "[{0}]调用次数过多!Times={1}".F(ctx.ActionName, times));
} var user = new User
{
ID = uid,
Name = Rand.NextString(),
Enable = deleted,
CreateTime = DateTime.Now,
}; return user;
} /// <summary>本控制器执行前</summary>
/// <param name="filterContext"></param>
public void OnActionExecuting(ControllerContext filterContext)
{
// 请求参数
var ps = filterContext.Parameters; // 服务参数
var cs = filterContext.ActionParameters; foreach (var item in ps)
{
if (cs != null && !cs.ContainsKey(item.Key))
XTrace.WriteLine("服务[{0}]未能找到匹配参数 {1}={2}", filterContext.ActionName, item.Key, item.Value);
}
} /// <summary>本控制器执行后,包括异常发生</summary>
/// <param name="filterContext"></param>
public void OnActionExecuted(ControllerContext filterContext)
{
var ex = filterContext.Exception;
if (ex != null && !filterContext.ExceptionHandled)
{
XTrace.WriteLine("控制器拦截到异常:{0}", ex.Message);
}
}
}

这里控制器和方法都加上了Api特性,特别指定了名称,公开服务 User/FindByID。

这里有个硬伤,如果不加Api特性,默认会把 OnActionExecuting/OnActionExecuted两个方法也暴露成为服务。

实现Api接口,是为了得到Session,这个不是必须的,因为控制器上下文ControllerContext.Current也可以得到这个Session。

这个Session代表着网络会话,可以取得各种跟网络相关的东西,甚至包括直接向客户端发送数据。

当然,也可以当做Web的Session来使用,内置有一个字典。

同一客户端的Api多次请求,都共用同一个Session对象,可用于做身份验证,从某种层面上来讲,ApiServer是“有状态”的。

动作过滤接口IActionFilter,让我们能够在本控制器所有服务执行前后进行拦截,包括参数预处理和异常拦截。

服务参数采用Json序列化封送,所以客户端服务端可以不必要求严格一致,跟Http类似,这一点在多版本管理上非常重要,不会说你加了个参数就强制要求所有客户端跟着升级。

服务方法内的各种异常,都将会被拦截并送到客户端,ApiException异常将会得到特殊处理,它包括了一个异常代码,也送到客户端。

没有异常代码的各种异常,都将使用默认错误代码500.

最后实例化ApiServer

static void TestServer()
{
// 实例化RPC服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听
var svr = new ApiServer();
// 注册服务控制器
svr.Register<MyController>();
svr.Register<UserController>(); // 指定编码器
svr.Encoder = new JsonEncoder();
svr.EncoderLog = XTrace.Log; // 打开原始数据日志
var ns = svr.Server as NetServer;
ns.Log = XTrace.Log;
ns.LogSend = true;
ns.LogReceive = true; svr.Log = XTrace.Log;
svr.Start(); _server = svr; // 定时显示性能数据
_timer = new TimerX(ShowStat, ns, , );
}

中间打开的各种日志,纯属为了便于展示通信过程,实际应用中务必去除!

ApiServer采用手工注册控制器的方式,避免了复杂的MVC路由系统。

内置有一个控制器ApiController,它的All服务用于向客户端返回所有可用服务列表。

服务端建立起来后,可以用码神工具的Api工具调试,(https://github.com/NewLifeX/X/tree/master/XCoder

四、客户端例程

为了便于使用,封装一个客户端类

/// <summary>自定义业务客户端</summary>
class MyClient : ApiClient
{
public MyClient(String uri) : base(uri) { } /// <summary>添加,标准业务服务,走Json序列化</summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public async Task<Int32> AddAsync(Int32 x, Int32 y)
{
return await InvokeAsync<Int32>("My/Add", new { x, y });
} /// <summary>RC4加解密,高速业务服务,二进制收发不经序列化</summary>
/// <param name="pk"></param>
/// <returns></returns>
public async Task<Packet> RC4Async(Packet pk)
{
return await InvokeAsync<Packet>("My/RC4", pk);
} public async Task<User> FindUserAsync(Int32 uid, Boolean enable)
{
return await InvokeAsync<User>("User/FindByID", new { uid, enable });
}
}

其实这个类不是必须的,看个人喜好吧。

static async void TestClient()
{
var client = new MyClient("tcp://127.0.0.1:1234"); // 指定编码器
client.Encoder = new JsonEncoder();
client.EncoderLog = XTrace.Log; // 打开原始数据日志
var ns = client.Client;
ns.Log = XTrace.Log;
ns.LogSend = true;
ns.LogReceive = true; client.Log = XTrace.Log;
client.Open(); // 定时显示性能数据
_timer = new TimerX(ShowStat, ns, , ); // 标准服务,Json
var n = await client.AddAsync(, );
XTrace.WriteLine("Add: {0}", n); // 高速服务,二进制
var buf = "Hello".GetBytes();
var pk = await client.RC4Async(buf);
XTrace.WriteLine("RC4: {0}", pk.ToHex()); // 返回对象
var user = await client.FindUserAsync(, true);
XTrace.WriteLine("FindUser: ID={0} Name={1} Enable={2} CreateTime={3}", user.ID, user.Name, user.Enable, user.CreateTime); // 拦截异常
try
{
user = await client.FindUserAsync(, true);
}
catch (ApiException ex)
{
XTrace.WriteLine("FindUser出错,错误码={0},内容={1}", ex.Code, ex.Message);
}
}

这里做了4次不同调用,模拟了常见场景。

五、总结

编译后跑起来就是开头的效果,感兴趣的同学还可以到Linux上试试,也可以新建Net40/Net45项目,同样可用。

并且,Net40项目还可以在树莓派上跑,基于Mono,码神工具(WinForm)也支持。

RpcTest例程概括性讲解了ApiServer的用法,大家可以去尝试、扩展。

实际工作中,我们正准备用于建立一个每天数十亿次调用的微服务系统。

我是大石头,打1999年起,19年老码农。目前在物流行业从事数据分析架构工作,日常工作都是亿万数据的读写使用。欢迎大家一起C#大数据!

NetCore版RPC框架NewLife.ApiServer的更多相关文章

  1. 手写简易版RPC框架基于Socket

    什么是RPC框架? RPC就是远程调用过程,实现各个服务间的通信,像调用本地服务一样. RPC有什么优点? - 提高服务的拓展性,解耦.- 开发人员可以针对模块开发,互不影响.- 提升系统的可维护性及 ...

  2. 手动造轮子——基于.NetCore的RPC框架DotNetCoreRpc

    前言     一直以来对内部服务间使用RPC的方式调用都比较赞同,因为内部间没有这么多限制,最简单明了的方式就是最合适的方式.个人比较喜欢类似Dubbo的那种使用方式,把接口层单独出来,作为服务的契约 ...

  3. NetCore平台下使用RPC框架Hprose

    NetCore下使用RPC框架Hprose https://www.jianshu.com/p/c903fca44d5d Hprose是国内非常优秀的RPC框架,和其它RPC框架比较起来,其它框架一般 ...

  4. SpringBoot2+Netty打造通俗简版RPC通信框架(升级版)

    背景         上篇文章我简单的介绍了自己打造的通俗简版RPC通信框架,这篇是对简版的增强~         如果大家对此项目还感兴趣的话,可到码云上瞄瞄:Netty-RPC         上 ...

  5. SpringBoot2+Netty打造通俗简版RPC通信框架

    2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1.增加服务提供注解(带版本号),然后利用Spring框架的在启动 ...

  6. 微博轻量级RPC框架Motan

    Motan 是微博技术团队研发的基于 Java 的轻量级 RPC 框架,已在微博内部大规模应用多年,每天稳定支撑微博上亿次的内部调用.Motan 基于微博的高并发和高负载场景优化,成为一套简单.易用. ...

  7. 一个轻量级分布式RPC框架--NettyRpc

    1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 RPC 框架>,作者用Zookeeper.Netty和Spring写了一个轻量级的分布式RPC ...

  8. 轻量级分布式RPC框架

    随笔- 139  文章- 0  评论- 387  一个轻量级分布式RPC框架--NettyRpc   1.背景 最近在搜索Netty和Zookeeper方面的文章时,看到了这篇文章<轻量级分布式 ...

  9. 基于HTTP/2和protobuf的RPC框架:GRPC

    谷歌发布的首款基于HTTP/2和protobuf的RPC框架:GRPC Google 刚刚开源了grpc,  一个基于HTTP2 和 Protobuf 的高性能.开源.通用的RPC框架.Protobu ...

随机推荐

  1. LCS问题(最长公共子序列)-动态规划实现

    问题描述: 问题] 求两字符序列的最长公共字符子序列 注意: 并不要求子串(字符串一)的字符必须连续出现在字符串二中. 思路分析: 最优子结构和重叠子问题的性质都具有,所以要采取动态规划的算法 最长公 ...

  2. Mahout 模糊kmeans

    Mahout  模糊KMeans 一.算法流程 模糊 C 均值聚类(FCM),即众所周知的模糊 ISODATA,是用隶属度确定每个数据点属于某个聚类的程度的一种聚类算法.1973 年,Bezdek 提 ...

  3. Linux学习笔记 --服务器优化

    Linux服务器优化 序言: 服务器操作建议 1.严格按照目录规范操作服务器 2.远程服务器不允许关机 3.不要在服务器访问高峰运行高负载命令 4.远程配置防火墙时,不要把自己踢出服务器 一.禁用不必 ...

  4. ubuntu virtualbox xp无声音解决

    太简单了,记录一下解决方法,进入xp,打开设备管理器,对着ac97设备驱动 点右键,点更新驱动,更新一下就ok了. 这时候去控制面板,就可以看到有音频设备了. 具体步骤如下: 第一步,virtualb ...

  5. 【Qt编程】QWT在QtCreator中的安装与使用

    由于导师项目的需要,需要画图,二维+三维.三维图我用的是Qt+opengl,二维图我决定使用qwt工具库来加快我的项目进展,毕竟还有期末考试.关于Qt+opengl的使用有时间的话以后再介绍.     ...

  6. ERP-非财务人员的财务培训教(五)------资本结构筹划

    一.融资渠道   二.融筹资管理                                 第五部分 资本结构筹划 一.融资渠道 l         银行借款 优点:不影响企业的营运资本,不给企 ...

  7. android bitmap的内存分配和优化

    首先Bitmap在Android虚拟机中的内存分配,在Google的网站上给出了下面的一段话 大致的意思也就是说,在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvi ...

  8. Linux文件内容查阅 - cat, tac, nl, more, less, head, tail, od

    cat 由第一行开始显示文件内容 tac 从最后一行开始显示,可以看出 tac 是 cat 的倒著写! nl 显示的时候,顺道输出行号! more 一页一页的显示文件内容 less 与 more 类似 ...

  9. leetcode之旅(6)-Add Digits

    题目: Given a non-negative integer num, repeatedly add all its digits until the result has only one di ...

  10. OS X 平台的 8 个实用终端工具

    本文由 伯乐在线 - shinancao 翻译自 mitchchn.欢迎加入iOS小组.转载请参见文章末尾处的要求. OS X 终端对外开放了许多很强大的UNIX实用工具和脚本.如果你是从Linux转 ...