聊聊如何设计千万级吞吐量的.Net Core网络通讯!

原文: 聊聊如何设计千万级吞吐量的.Net Core网络通讯!

聊聊如何设计千万级吞吐量的.Net Core网络通讯!

  • 做者:大石头
  • 时间:2018-10-26 晚上 20:00
  • 地点:QQ群-1600800
  • 内容:网络通讯,
    1. 网络库使用方式
    2. 网络库设计理念,高性能要点

介绍

1.1 开始网络编程

简单的网络程序示例

  • 相关使用介绍:https://www.cnblogs.com/nnhy/p/newlife_net_echo.html
  • 克隆上面的代码,运行EchoTest项目,打开编译的exe,打开两次,一个选1做为服务器,一个选2做为客户端

  • 在客户端链接服务器和给服务端发送数据的时候,分别触发StartOnReceive方法,链接以后服务端发送了Welcome 的消息,客户端发送5次“你好”。服务端回传收到的数据,打了一个日志,把收到的信息转成字符串输出到控制台。
  • NetServer是应用级网络服务器,支持tcp/udp/ipv4/ipv6。上面能够看到,同时监听了四个端口。
  • 码神工具也能够链接上来

解释

  • 对于网络会话来讲,最关键的就是客户端连上来,以及收到数据包,这两部分,对应上面StartOnReceive两个方法

服务端

  • 上面是最小的网络库例程,简单演示了服务端和客户端,链接和收发信息。网络应用分为NetServer/NetSession,服务端、会话,N个客户端链接服务器,就会有N个会话。来一个客户端链接,服务端就new一个新的NetSession,并执行Start,收到一个数据包,就执行OnReceive,链接断开,就执行OnDispose,这即是服务端的所有。
  • 客户端链接刚上来的时候,没有数据包等其它信息,因此这个时候没有参数。客户端发数据包过来,OnReceive函数在处理。
  • 服务端的建立,能够是很简单,看如下截图。这里为了测试方便,开了不少Log,实际使用的时候,根据须要注释。
  • 长链接、心跳第二节设计理念再讲。

客户端

  • 跟不少网络库不一样,NewLife.Net除了服务端,还封装了客户端。客户端的核心,也就是Send函数和Received事件,同步发送,异步接收。
  • 由于是长链接,因此服务端随时能够向客户端发送数据包,客户端也能够收到。tcp在不作设置的时候,默认长链接2小时。
  • NetServer默认20分钟,在没有心跳的时候,20分钟没有数据包往来,服务端会干掉这个会话。
  • 虽然上面讲的NetServer和Client,都是tcp,可是换成其它协议也是能够的。这里的NetServer和NetUri.CreateRemote,同时支持Tcp/Udp/IPv4/IPv6等,CreateRemote内部,就是根据地址的不一样,去new不一样的客户端。因此咱们写的代码,根本不在乎用的是tcp仍是udp,或者IPv6。有兴趣的能够看看源码

1.2 构建可靠网络服务

  • 相关博客
  • 要真正造成一个网络服务,那得稳定可靠。上面例程EchoTest只是简单演示,接下来看下一个例程EchoAgent。


安装运行

  • 这是一个标准的Windows服务,有了这个东西,咱们就能够妥妥的注册到Windows里面去。这也是目前咱们大量数据分析程序的必备。
  • 首先运行EchoAgent,按2,安装注册服务,用管理员身份运行。安装成功而后能够在服务里面找到刚刚安装的服务。



  • 安装完成能够在服务上找到,再次按2就是卸载,这个是XAgent提供的功能

  • 这时候按3,启动服务

代码解释

  • 接下来看代码,服务启动的时候,执行StartWork。在这个时候实例化并启动NetServer,获得的效果就跟例程EchoTest同样,区别是一个是控制台一个是服务。中止服务时执行StopWork,咱们能够在这里关闭NetServer。详细请看源码
  • 必须有这个东西,你的网络服务程序,才有可能达到产品级。linux上直接控制台,上nohup,固然还有不少其它办法。之后但愿这个XAgent可以支持linux吧,这样就一劳永逸了

1.3 压测

  • 相关博客
  • 只须要记住一个两个数字,.net应用打出来2266万tps,流量峰值4.5Gbps
  • 两千万吞吐量的数字,固然,只能看不能用。由于服务端只是刚才的Echo而已,并无带什么业务。实际工做中,带着业务和数据库,能跑到10万已经很是很是牛逼了。
  • 咱们工做中的服务能够跑到100万,可是我不敢,怕它不当心就崩了。因此咱们都是按照10万的上限来设计,不够就堆服务器好了,达到5万以上后,稳定性更重要node

    网络编程的坑

  • 主要有粘包
  • 程序员中会网络编程的少,会解决粘包的更少!linux

1.4 网络编程的坎——粘包

  • 广泛状况,上万的程序员,会写网络程序的不到20%,会解决粘包问题的不到1%,若是你们会写网络程序,而且能解决粘包,那么至少已经达到了网络编程的中级水平。

什么是粘包

  • 举个栗子:客户端连续发了5个包,服务端就收到了一个大包。代码就不演示了,把第一个例程的这个睡眠去掉。

  • 客户端连续发了5个包,服务端就收到了一个大包。

缘由

  • 不少人可能都据说Tcp是流式协议,可是不多人去问,什么叫流式吧?流式,就是它把数据像管道同样传输过去。
  • 刚才咱们发了5个 “你好”,它负责把这10个字发到对方,至于发多少次,每次发几个字,不用咱们操心,tcp底层本身处理。tcp负责把数据一个不丢的按顺序的发过去。因此,为了性能,它通常会把相近的数据包凑到一块儿发过去。对方收到一个大包,5个小包都粘在了一块儿,这就是最简单的粘包。
  • 这个特性由NoDelay设置决定。NoDelay默认是false,须要本身设置。若是设置了,就不会等待。可是不要想得那么美好,由于对方可能合包。
  • 局域网MTU(Maximum Transmission Unit,最大传输单元)是1500,处于ip tcp 头部等,大概1472多点的样子。

更复杂的粘包及解决方法

  • A 1000 字节 B 也是 1000字节,对方可能收到两个包,1400 + 600。对方可能收到两个包,1400 + 600。
  • 凡是以特殊符号开头或结尾来处理粘包的办法,都会有这样那样的缺陷,最终是给本身挖坑。因此,tcp粘包,绝大部分解决方案,偏向于指定数据包长度。这其中大部分使用4字节长度,长度+数据。对方收到的时候,根据长度判断后面数据足够了没有。
  • 这是粘包的处理代码:http://git.newlifex.com/NewLife/X/Blob/master/NewLife.Core/Net/Handlers/MessageCodec.cs
  • 每次判断长度,接收一个或多个包,若是接收不完,留下,存起来。等下一个包到来的时候,拼凑完整。
  • 虽然tcp确保数据不丢,可是不免咱们本身失手,弄丢了一点点数据。为了不祸害后面全部包,就须要进行特殊处理了。
  • 每一个数据帧,本身把头部长度和数据体凑一块儿发送啊,tcp确保顺序。这里咱们把超时时间设置为3~5秒,每次凑包,若是发现上次有残留,而且超时了,那么就扔了它,免得祸害后面。
  • 根据以上,粘包的关键解决办法,就是设定数据格式,能够看看咱们的SRMP协议,1字节标识,1字节序号,2字节长度
  • 若是客户端发送太频繁,服务端tcp缓冲区阻塞,发送窗口会逐步缩小到0,再也不接受客户端数据。

1.5 .NetCore版RPC框架

代码分析

  • 咱们看这部分代码,4次调用远程函数,成功获取结果,包括二进制高速调用、返回复杂对象、捕获远程异常,没错,这就是一个RPC。

服务端

  • 有没有发现,这个ApiServer跟前面的NetServer有点像?其实ApiServer内部就有一个NetServer
  • 这么些行代码,就几个地方有价值,一个是注册了两个控制器。你能够直接理解为Mvc的控制器,只不过咱们没有路由管理系统,直接手工注册。
  • 第二个是指定编码器为Json,用Json传输参数和返回值。其实内部默认就是Json,能够不用指定
  • 看看咱们的控制器,特别像Mvc,只不过这里的Controller没有基类,各个Action返回值不是ActionResult,是的,ApiServer就是一个按照Mvc风格设置的RPC框架
  • 返回复杂对象
  • 作请求预处理,甚至拦截异常
  • 像下面这样写RPC服务,而后把它注册到ApiServer上,客户端就能够在1234端口上请求这些接口服务啦

客户端

  • 客户端是ApiClient,这里的MyClient继承自ApiClient

  • 这些就是咱们刚才客户端远程调用的stub代码啦,固然,咱们没有自动生成stub,也没有要求客户端跟服务端共用接口之类。实际上,咱们认为彻底没有必要作接口约束,大部分项目的服务接口不多,而且要求灵活多变
  • stub就是相似于,刚才那个MyController实现IAbc接口,而后客户端根据服务端元数据自动在内存里面生成一个stub类并编译,这个类实现了IAbc接口。
    客户端直接操做接口,还觉得在调用服务端 的函数呢
  • 其实stub代码内部,就是封装了 这里的InvokeAsync这些代码,等同于自动生成这些代码,包括gRPC、Thrift等都是这么干的

框架解析

  • 这个RPC框架,封包协议就是刚才的SRMP,负载数据也就是协议是json
  • 当须要高速传输的时候,参数用Byte[],它就会直接传输,不经json序列化,这是多年经验获得的灵活性与性能的最佳结合点

2.1 人人都有一个本身的高性能网络库

  • 网络库核心代码:http://git.newlifex.com/NewLife/X/Tree/master/NewLife.Core/Net
  • 咱们一开始就是让Tcp/Udp能够混合使用,网络库设计于2005年,应该要比现现在绝大部分网络框架要老。服务端清一色采用 Server+Session 的方式。
  • 网络库的几个精髓文件
  • 其中比较重要的一个,里面实现了 Open/Close/Send/Receive 系列封装,Tcp/Udp略有不一样,重载就行了。打开关闭比较简单,就不讲了
  • 全部对象,无论客户端服务端,都实现ISocket。而后客户端Client,服务端Server+Session。tcp+udp同时支持并不难,由于它们都基于Socket。
  • 目前无状态无会话的通讯架构,作不到高性能。咱们就是依靠长链接以及合并小包,实现超高吞吐量
  • 通常灵活性和高性能都是互相矛盾的

2.2 高性能设计要点

  • 第一要点:同步发送,由于要作发送队列、拆分、合并,等等,异步发送大大增长了复杂度。你们若是未来遇到诡异的40ms延迟,很是可能就是tcp的nodelay做怪,能够设为true解决
  • 第二要点:IOCP,高吞吐率的服务端,必定是异步接收,而不是多线程同步。固然,能够指定若干个线程去select,也就是Linux里面常见的poll,那个不在这里讨论,Windows极少人这么干,大量资料代表,iocp更厉害。

    • SAEA是.net/.netcore当下最流行的网络架构,咱们能够通俗理解为,把这个缓冲区送给操做系统内核,待会有数据到来的时候,直接放在里面,这样子就减小了一次内核态到用户态的拷贝过程。
    • 咱们测试4.5Gbps,除以8,大概是 540M字节,这个拷贝成本极高
  • 第三要点:零拷贝ZeroCopy,这也是netty的核心优点。iocp是为了减小内核态到用户态的拷贝,zerocopy进一步把这个数据交给用户层,不用拷贝了。
    • 数据处理,咱们采用了链式管道,
    • 这些都是管道的编码器
  • 第四要点:合并小包,NoDelay=false,容许tcp合并小包,MTU=1500,除了头部,通常是1472
  • 第五要点:二进制序列化,消息报文尽量短小,每一个包1k,对于100Mbps,也就12M,理论上最多12000包,因此大量Json协议或者字符串协议,吞吐量都在1万上下
    • SRMP头部4字节,ApiServer的消息报文,通常二三十个字节,甚至十几个字节
  • 第五要点:批量操做User FindByID(int id); User[] FindByIDs(int[] ids);

最后

  • 整理不全,你们凑合着看。中途录屏,语音啥的还掉了,准备得不是很好,下周再来一次吧,选Redis
相关文章
相关标签/搜索