搜公众号
推荐 原创 视频 Java开发 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库
Lambda在线 > DotNet程序员 > 聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信

DotNet程序员 2019-02-10
举报

来自:笑笑

链接:http://www.cnblogs.com/xxred/p/9859893.html


介绍


首先看下面这张很具有代表性的图,2018年5月份做的测试。当时单服务器得到 2256tps(Transactions Per Second,每秒事务数) 的吞吐率。


这次测试只是说明一个问题,.Net可以做超高吞吐率的应用。



聊聊如何设计千万级吞吐量的.NET Core网络通信



记录:https://www.cnblogs.com/nnhy/p/newlife_net_benchmark.html


1.1 开始网络编程


简单的网络程序示例


相关使用介绍《NewLife.Net—开始网络编程》https://www.cnblogs.com/nnhy/p/newlife_net_echo.html

克隆上面的代码,运行EchoTest项目,打开编译的exe,打开两次,一个选1作为服务器,一个选2作为客户端


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


在客户端连接服务器和给服务端发送数据的时候,分别触发Start和OnReceive方法,连接之后服务端发送了Welcome 的消息,客户端发送5次“你好”。


服务端回传收到的数据,打了一个日志,把收到的信息转成字符串输出到控制台。


NetServer是应用级网络服务器,支持tcp/udp/ipv4/ipv6。上面可以看到,同时监听了四个端口。


码神工具(https://github.com/NewLifeX/XCoder)也可以连接上来


聊聊如何设计千万级吞吐量的.NET Core网络通信


解释


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


服务端


上面是最小的网络库例程,简单演示了服务端和客户端,连接和收发信息。网络应用分为NetServer/NetSession,服务端、会话,N个客户端连接服务器,就会有N个会话。


来一个客户端连接,服务端就new一个新的NetSession,并执行Start,收到一个数据包,就执行OnReceive,连接断开,就执行OnDispose,这便是服务端的全部。


客户端连接刚上来的时候,没有数据包等其它信息,所以这个时候没有参数。客户端发数据包过来,OnReceive函数在处理。


服务端的创建,可以是很简单,看以下截图。这里为了测试方便,开了很多Log,实际使用的时候,根据需要注释。


聊聊如何设计千万级吞吐量的.NET Core网络通信


长连接、心跳第二节设计理念再讲。


客户端


跟很多网络库不同,NewLife.Net除了服务端,还封装了客户端。客户端的核心,也就是Send函数和Received事件,同步发送,异步接收。


聊聊如何设计千万级吞吐量的.NET Core网络通信


因为是长连接,所以服务端随时可以向客户端发送数据包,客户端也可以收到。tcp在不做设置的时候,默认长连接2小时。


NetServer默认20分钟,在没有心跳的时候,20分钟没有数据包往来,服务端会干掉这个会话。



源码:https://github.com/NewLifeX/X/blob/master/NewLife.Core/Net/NetHelper.cs#L690


1.2 构建可靠网络服务


《NewLife.Net——构建可靠的网络服务》https://www.cnblogs.com/nnhy/p/newlife_net_agent.html


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


安装运行


这是一个标准的Windows服务,有了这个东西,我们就可以妥妥的注册到Windows里面去。这也是目前我们大量数据分析程序的必备。


首先运行EchoAgent,按2,安装注册服务,用管理员身份运行。安装成功然后可以在服务里面找到刚刚安装的服务。


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


这时候按3,启动服务


聊聊如何设计千万级吞吐量的.NET Core网络通信


代码解释


接下来看代码,服务启动的时候,执行StartWork。在这个时候实例化并启动NetServer,得到的效果就跟例程EchoTest一样,区别是一个是控制台一个是服务。


停止服务时执行StopWork,我们可以在这里关闭NetServer。


源码:http://git.newlifex.com/NewLife/NewLife.Net/Blob/master/EchoAgent/Program.cs


聊聊如何设计千万级吞吐量的.NET Core网络通信


必须有这个东西,你的网络服务程序,才有可能达到产品级。linux上直接控制台,上nohup,当然还有很多其它办法。以后希望这个XAgent能够支持linux吧,这样就一劳永逸了


1.3 压测


《NewLife.Net——网络压测单机2266万tps》https://www.cnblogs.com/nnhy/p/newlife_net_benchmark.html


只需要记住一个两个数字,.net应用打出来2266万tps,流量峰值4.5Gbps

两千万吞吐量的数字,当然,只能看不能用。因为服务端只是刚才的Echo而已,并没有带什么业务。实际工作中,带着业务和数据库,能跑到10万已经非常非常牛逼了。


我们工作中的服务可以跑到100万,但是我不敢,怕它不小心就崩了。所以我们都是按照10万的上限来设计,不够就堆服务器好了,达到5万以上后,稳定性更重要


网络编程的坑


主要有粘包


程序员中会网络编程的少,会解决粘包的更少!


1.4 网络编程的坎——粘包


普遍情况,上万的程序员,会写网络程序的不到20%,会解决粘包问题的不到1%,如果大家会写网络程序,并且能解决粘包,那么至少已经达到了网络编程的中级水平。


什么是粘包


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


客户端连续发了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


聊聊如何设计千万级吞吐量的.NET Core网络通信


每次判断长度,接收一个或多个包,如果接收不完,留下,存起来。等下一个包到来的时候,拼凑完整。


虽然tcp确保数据不丢,但是难免我们自己失手,弄丢了一点点数据。为了避免祸害后面所有包,就需要进行特殊处理了。


每个数据帧,自己把头部长度和数据体凑一起发送啊,tcp确保顺序。这里我们把超时时间设置为3~5秒,每次凑包,如果发现上次有残留,并且超时了,那么就扔了它,省得祸害后面。


聊聊如何设计千万级吞吐量的.NET Core网络通信


根据以上,粘包的关键解决办法,就是设定数据格式,可以看看我们的SRMP协议,1字节标识,1字节序号,2字节长度


聊聊如何设计千万级吞吐量的.NET Core网络通信


如果客户端发送太频繁,服务端tcp缓冲区阻塞,发送窗口会逐步缩小到0,不再接受客户端数据。


1.5 .NetCore版RPC框架


NetCore版RPC框架NewLife.ApiServer(https://www.cnblogs.com/nnhy/p/newlife_apiserver.html)。


聊聊如何设计千万级吞吐量的.NET Core网络通信


先看看这个效果


聊聊如何设计千万级吞吐量的.NET Core网络通信


代码分析


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


服务端


有没有发现,这个ApiServer跟前面的NetServer有点像?其实ApiServer内部就有一个NetServer


聊聊如何设计千万级吞吐量的.NET Core网络通信


这么些行代码,就几个地方有价值,一个是注册了两个控制器。你可以直接理解为Mvc的控制器,只不过我们没有路由管理系统,直接手工注册。


第二个是指定编码器为Json,用Json传输参数和返回值。其实内部默认就是Json,可以不用指定

看看我们的控制器,特别像Mvc,只不过这里的Controller没有基类,各个Action返回值不是ActionResult,是的,ApiServer就是一个按照Mvc风格设置的RPC框架


聊聊如何设计千万级吞吐量的.NET Core网络通信


返回复杂对象


聊聊如何设计千万级吞吐量的.NET Core网络通信


做请求预处理,甚至拦截异常


聊聊如何设计千万级吞吐量的.NET Core网络通信


像下面这样写RPC服务,然后把它注册到ApiServer上,客户端就可以在1234端口上请求这些接口服务啦


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


客户端


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


聊聊如何设计千万级吞吐量的.NET Core网络通信

聊聊如何设计千万级吞吐量的.NET Core网络通信


这些就是我们刚才客户端远程调用的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 的方式。


网络库的几个精髓文件


聊聊如何设计千万级吞吐量的.NET Core网络通信


其中比较重要的一个,里面实现了 Open/Close/Send/Receive 系列封装,Tcp/Udp略有不同,重载就好了。打开关闭比较简单,就不讲了


聊聊如何设计千万级吞吐量的.NET Core网络通信


所有对象,不管客户端服务端,都实现ISocket。然后客户端Client,服务端Server+Session。tcp+udp同时支持并不难,因为它们都基于Socket。

目前无状态无会话的通信架构,做不到高性能。我们就是依靠长连接以及合并小包,实现超高吞吐量


一般灵活性和高性能都是互相矛盾的


2.2 高性能设计要点


第一要点:同步发送,因为要做发送队列、拆分、合并,等等,异步发送大大增加了复杂度。大家如果将来遇到诡异的40ms延迟,非常可能就是tcp的nodelay作怪,可以设为true解决


第二要点:IOCP,高吞吐率的服务端,一定是异步接收,而不是多线程同步。当然,可以指定若干个线程去select,也就是Linux里面常见的poll,那个不在这里讨论,Windows极少人这么干,大量资料表明,iocp更厉害。


聊聊如何设计千万级吞吐量的.NET Core网络通信


SAEA是.net/.netcore当下最流行的网络架构,我们可以通俗理解为,把这个缓冲区送给操作系统内核,待会有数据到来的时候,直接放在里面,这样子就减少了一次内核态到用户态的拷贝过程。


我们测试4.5Gbps,除以8,大概是 540M字节,这个拷贝成本极高


第三要点:零拷贝ZeroCopy,这也是netty的核心优势。iocp是为了减少内核态到用户态的拷贝,zerocopy进一步把这个数据交给用户层,不用拷贝了。

数据处理,我们采用了链式管道,


聊聊如何设计千万级吞吐量的.NET Core网络通信


这些都是管道的编码器



第四要点:合并小包,NoDelay=false,允许tcp合并小包,MTU=1500,除了头部,一般是1472


第五要点:二进制序列化,消息报文尽可能短小,每个包1k,对于100Mbps,也就12M,理论上最多12000包,所以大量Json协议或者字符串协议,吞吐量都在1万上下

SRMP头部4字节,ApiServer的消息报文,一般二三十个字节,甚至十几个字节


第五要点:批量操作,User FindByID(int id); User[] FindByIDs(int[] ids);


最后


整理不全,大家凑合着看。中途录屏,语音啥的还掉了,准备得不是很好,下周再来一次吧,选Redis



●编号173,输入编号直达本文

●输入m获取文章目录

推荐↓↓↓

Web开发

更多推荐

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等

版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《聊聊如何设计千万级吞吐量的.NET Core网络通信》的版权归原作者「DotNet程序员」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

举报