NewLife.Net压力测试,峰值4.2Gbps,50万pps,消息大小24字节,消息处理速度2266万tps!
共集合20台高配ECS参与测试,主服务器带宽6Gbps、100万pps,16核心64G内存。另外19台共模拟400个用户连接,13*16+6*32=400,每用户发送2000万个消息,服务端收到后原样返回。
tps意义非常重大,就是告诉所有人,.Net下普普通通的Socket封装,甚至没有使用MSDN的Pool,就能得到非常不错的性能 !
*感谢楼下提醒,错误计算了速度 ,算法如下:
每秒流量 = 4.2G / 8 = 537.6M (进出都是4.2Gbps)
包头消耗 = 50万 * 40 = 19M (50万包,ip+tcp头40字节)
处理速度 = (537.6M - 19M) / 24 = 22,657,979 = 2266万
另外每个nc客户端均有速度计算,102万~200万tps之间,共19客户端,与上吻合
由于我的疏忽,只是简单拿4.2G除以24得到1.88亿,也没有汇总各客户端数据,给出了错误数据,非常抱歉!
(有考虑tcp包头,因报文很短必然粘包,按MTU=1500算误差2.7%,所以直接没有算进去)
有些同学比较着急,觉得前面两篇有点小儿科,群友就说,上数字吧!
我们在2017.4.1做了一个极限并发测试,奔着单机100万并发,实际上只得到了84.5万连接,这次补一个吞吐量的压力测试好了。
老规矩,先上代码: https://github.com/NewLifeX/NewLife.Net

我们对前一篇文章的例程稍微调整一下:
class MyNetSession : NetSession<MyNetServer>
{
/// <summary>客户端连接</summary>
public override void Start()
{
base.Start();
// 欢迎语
var str = String.Format("Welcome to visit {1}! [{0}]\r\n", Remote, Environment.MachineName);
Send(str);
}
/// <summary>收到客户端数据</summary>
/// <param name="e"></param>
protected override void OnReceive(ReceivedEventArgs e)
{
//WriteLog("收到:{0}", e.Packet.ToStr());
// 把收到的数据发回去
Send(e.Packet);
}
}
服务主函数的线程数也要从2改为1,关闭第二个向所有客户端定时发送时间的任务。
class MyService : ServiceBase
{
public MyService()
{
ServiceName = "EchoAgent";
DisplayName = "回声服务";
Description = "这是NewLife.Net的一个回声服务示例!";
}
MyNetServer _Server;
/// <summary>开始服务</summary>
/// <param name="reason"></param>
protected override void StartWork(String reason)
{
// 实例化服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听
var svr = new MyNetServer
{
Port = 1234,
Log = XTrace.Log,
Tracer = new DefaultTracer { Period = 15, Log = XTrace.Log },
#if DEBUG
SocketLog = XTrace.Log,
LogSend = true,
LogReceive = true,
#endif
};
svr.Start();
_Server = svr;
_timer1 = new TimerX(s => ShowStat(_Server), null, 1000, 1000) { Async = true };
//_timer2 = new TimerX(s => SendTime(_Server), null, 1000, 1000) { Async = true };
base.StartWork(reason);
}
/// <summary>停止服务</summary>
/// <param name="reason"></param>
protected override void StopWork(String reason)
{
_Server.TryDispose();
_Server = null;
base.StopWork(reason);
}
private TimerX _timer1;
private TimerX _timer2;
private String _last;
/// <summary>显示服务端状态</summary>
/// <param name="ns"></param>
private void ShowStat(NetServer ns)
{
if (ns == null) return;
var msg = ns.GetStat();
if (msg == _last) return;
_last = msg;
WriteLog(msg);
}
/// <summary>向所有客户端发送时间</summary>
/// <param name="ns"></param>
private void SendTime(NetServer ns)
{
if (ns == null) return;
var str = DateTime.Now.ToFullString() + Environment.NewLine;
var buf = str.GetBytes();
ns.SendAllAsync(buf);
}
}
新建控制台项目Benchmark,并从nuget引用NewLife.Core
入口函数需要分析参数:
static void Main(String[] args)
{
XTrace.UseConsole();
try
{
var cfg = new Config();
// 分解参数
if (args != null && args.Length > 0) cfg.Parse(args);
// 显示帮助菜单或执行
if (cfg.Address.IsNullOrEmpty())
ShowHelp();
else
Work(cfg);
}
catch (Exception ex)
{
XTrace.WriteException(ex.GetTrue());
}
//Console.WriteLine("OK!");
//Console.ReadKey();
}
主函数就是开一定数量的LongTask,然后等待
static void Work(Config cfg)
{
var uri = new NetUri(cfg.Address);
var txt = cfg.Content;
if (txt.IsNullOrEmpty()) txt = cfg.Content = "学无先后达者为师";
var buf = txt.StartsWith("0x") ? txt.TrimStart("0x").ToHex() : txt.GetBytes();
var pk = new Packet(buf);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("NewLife.Benchmark v{0}", AssemblyX.Entry.Version);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("目标:{0}", uri);
Console.WriteLine("请求:{0:n0}", cfg.Times);
Console.WriteLine("并发:{0:n0}", cfg.Thread);
Console.WriteLine("内容:[{0:n0}] {1}", pk.Count, txt);
Console.ResetColor();
Console.WriteLine();
var sw = Stopwatch.StartNew();
// 多线程
var ts = new List<Task<Int32>>();
var maxCPU = Environment.ProcessorCount * 2;
for (var i = 0; i < cfg.Thread; i++)
{
if (cfg.Thread <= maxCPU)
{
var tsk = Task.Factory.StartNew(() => WorkOne(uri, cfg, pk), TaskCreationOptions.LongRunning);
ts.Add(tsk);
}
else
{
var tsk = Task.Run(async () => await WorkOneAsync(uri, cfg, pk));
ts.Add(tsk);
}
}
var total = Task.WhenAll(ts.ToArray()).Result.Sum();
sw.Stop();
Console.WriteLine("完成:{0:n0}", total);
var ms = sw.Elapsed.TotalMilliseconds;
Console.WriteLine("速度:{0:n0}tps", total * 1000L / ms);
//Thread.Sleep(5000);
//Console.ReadKey(true);
}
static Int32 WorkOne(NetUri uri, Config cfg, Packet pk)
{
var count = 0;
try
{
var client = uri.CreateRemote();
(client as SessionBase).MaxAsync = 0;
client.Open();
for (var k = 0; k < cfg.Times; k++)
{
client.Send(pk);
if (cfg.Reply)
{
var pk2 = client.Receive();
if (pk2.Count > 0) count++;
}
else
{
count++;
}
}
}
catch { }
return count;
}
static async Task<Int32> WorkOneAsync(NetUri uri, Config cfg, Packet pk)
{
var count = 0;
try
{
var client = uri.CreateRemote();
(client as SessionBase).MaxAsync = 0;
client.Open();
await Task.Yield();
for (var k = 0; k < cfg.Times; k++)
{
client.Send(pk);
if (cfg.Reply)
{
var pk2 = client.Receive();
if (pk2.Count > 0) count++;
}
else
{
count++;
}
await Task.Yield();
}
}
catch { }
return count;
}
从阿里云分批租用最高配置的竞价实例20台。统一选择华东2(上海)的D区,因为代码压测只能使用内网,公网达不到这个速度。
整个华东2D最高配就是大数据网络增强型,仅剩的7台都拿下,其中一台作为服务端跑EchoAgent,另外再补13台8核的机器,共19台跑nc(Benchmark)。

在8核心机器上(13台),测试命令:
nc -n 20000000 -c 16 tcp://172.19.227.198:1234
在16核心机器上(6台),测试命令
nc -n 20000000 -c 32 tcp://172.19.227.198:1234
人有多大胆,地有多大产!
虽然这次的EchoTest只是简单把数据包发回来,没有挂载复杂业务,但是说明了网络库不是瓶颈,只要硬件性能跟得上,它要多强有多强!
e.Packet的设计,实际上实现了ZeroCopy,同时大大减轻了GC负担,后面会有专门文章提到。
网络库NewLife.Net支持.Net Core 2.0,但XAgent不支持,毕竟它是Windows服务。
这次测试在 .Net Framework v4.6.1 上进行。
网络系列文章,是为了一步步介绍X组件网络库 NewLife.Net的设计理念,从2005年开始,活了13年,不管是成功还是失败,都积累了很多的经验。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70012705/viewspace-2851597/ ,如需转载,请注明出处,否则将追究法律责任。