c# 如何加载测试 web 服务

c# How to load test a webservice

我需要测试我们的应用程序中是否存在任何内存泄漏,并监控在处理请求时内存使用量是否增加过多。 我正在尝试开发一些代码来同时调用我们的 api/webservice 方法。此 api 方法不是异步的,需要一些时间才能完成其操作。

我对任务、线程和并行性做了很多研究,但到目前为止我没有运气。问题是,即使尝试了以下所有解决方案,结果始终相同,似乎当时只处理两个请求。

尝试过:

-> 在一个简单的 for 循环中创建任务,并在使用和不使用 TaskCreationOptions.LongRunning

设置它们的情况下启动它们

-> 在简单的 for 循环中创建线程并以高优先级和不高优先级启动它们

-> 在简单的 for 循环上创建一个动作列表并使用

启动它们
Parallel.Foreach(list, options, item => item.Invoke)

-> 运行 直接在 Parallel.For 循环内(下)

-> 运行 TPL 方法有和没有选项和 TaskScheduler

-> 尝试使用不同的 MaxParallelism 值和最大线程数

-> 也检查了 ,但也没有帮助。 (我会遗漏什么吗?)

-> 在 Whosebug 中检查了其他一些 post,但使用 F# 解决方案时我不知道如何将它们正确转换为 C#。 (我从没用过 F#...)

(任务计划程序 class 取自 msdn

这是我的基本结构:

public class Test
{
    Data _data;
    String _url;

    public Test(Data data, string url)
    {
        _data = data;
        _url = url;
    }

    public ReturnData Execute()
    {
         ReturnData returnData;

         using(var ws = new WebService())
         {
              ws.Url = _url;
              ws.Timeout = 600000;

              var wsReturn = ws.LongRunningMethod(data);

              // Basically convert wsReturn to my method return, with some logic if/else etc
         }
         return returnData;
    }
}

sealed class ThreadTaskScheduler : TaskScheduler, IDisposable
    {
        // The runtime decides how many tasks to create for the given set of iterations, loop options, and scheduler's max concurrency level.
        // Tasks will be queued in this collection
        private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();

        // Maintain an array of threads. (Feel free to bump up _n.)
        private readonly int _n = 100;
        private Thread[] _threads;

        public TwoThreadTaskScheduler()
        {
            _threads = new Thread[_n];

            // Create unstarted threads based on the same inline delegate
            for (int i = 0; i < _n; i++)
            {
                _threads[i] = new Thread(() =>
                {
                    // The following loop blocks until items become available in the blocking collection.
                    // Then one thread is unblocked to consume that item.
                    foreach (var task in _tasks.GetConsumingEnumerable())
                    {
                        TryExecuteTask(task);
                    }
                });

                // Start each thread
                _threads[i].IsBackground = true;
                _threads[i].Start();
            }
        }

        // This method is invoked by the runtime to schedule a task
        protected override void QueueTask(Task task)
        {
            _tasks.Add(task);
        }

        // The runtime will probe if a task can be executed in the current thread.
        // By returning false, we direct all tasks to be queued up.
        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            return false;
        }

        public override int MaximumConcurrencyLevel { get { return _n; } }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            return _tasks.ToArray();
        }

        // Dispose is not thread-safe with other members.
        // It may only be used when no more tasks will be queued
        // to the scheduler.  This implementation will block
        // until all previously queued tasks have completed.
        public void Dispose()
        {
            if (_threads != null)
            {
                _tasks.CompleteAdding();

                for (int i = 0; i < _n; i++)
                {
                    _threads[i].Join();
                    _threads[i] = null;
                }
                _threads = null;
                _tasks.Dispose();
                _tasks = null;
            }
        }
   }

以及测试代码本身:

private void button2_Click(object sender, EventArgs e)
    {
        var maximum = 100;
        var options = new ParallelOptions
        {
             MaxDegreeOfParallelism = 100,
             TaskScheduler = new ThreadTaskScheduler()
        };

        // To prevent UI blocking
        Task.Factory.StartNew(() =>
        {
            Parallel.For(0, maximum, options, i =>
            {
                var data = new Data();
                // Fill data
                var test = new Test(data, _url); //_url is pre-defined
                var ret = test.Execute();

               // Check return and display on screen
               var now = DateTime.Now.ToString("HH:mm:ss");
               var newText = $"{Environment.NewLine}[{now}] - {ret.ReturnId}) {ret.ReturnDescription}";

               AppendTextBox(newText, ref resultTextBox);
           }
     }

    public void AppendTextBox(string value, ref TextBox textBox)
    {
        if (InvokeRequired)
        {
            this.Invoke(new ActionRef<string, TextBox>(AppendTextBox), value, textBox);
            return;
        }
        textBox.Text += value;
    }

我得到的结果基本上是这样的:

[10:08:56] - (0) OK
[10:08:56] - (0) OK
[10:09:23] - (0) OK
[10:09:23] - (0) OK
[10:09:49] - (0) OK
[10:09:50] - (0) OK
[10:10:15] - (0) OK
[10:10:16] - (0) OK
etc

据我所知服务器端没有限制。我对 Parallel/Multitasking 世界还比较陌生。还有其他方法吗?我错过了什么吗?

(为了清晰起见,我简化了所有代码,我相信提供的代码足以描述上述场景。我也没有 post 应用程序代码,但它只是一个简单的 WinForms 屏幕调用并显示结果。如果有任何代码以某种方式相关,请告诉我,我也可以编辑 post。)

提前致谢!

EDIT1:我检查了服务器日志,它正在两个两个地接收请求,所以这确实与发送请求有关,而不是接收请求。 它可能是与框架如何管理 requests/connections 相关的网络 problem/limitation 吗?或者与网络有关的东西(与 .net 无关)?

EDIT2:忘了说,它是一个 SOAP 网络服务。

EDIT3:我发送的其中一个属性(内部数据)需要针对每个请求进行更改。

EDIT4:我注意到每对请求之间总是有大约 25 秒的间隔,如果相关的话。

在不编写自己的项目的情况下创建负载测试的一个很好的解决方案是使用此服务https://loader.io/targets

对于小测试是免费的,你可以 POST 参数,Header,...你有一个很好的报告。

我建议不要重新发明轮子,只使用现有的解决方案之一:

  1. 最明显的选择:如果您的 Visual Studio 许可证允许您使用 MS 负载测试框架,很可能您甚至不必编写一行代码:How to: Create a Web Service Test
  2. SoapUI is a free and open source web services testing tool, it has some limited load testing capabilities
  3. 如果由于某些原因 SoapUI 不合适(即您需要 运行 从多个主机以集群模式加载测试,或者您需要更多增强的报告),您也可以使用 Apache JMeter - free and open source multiprotocol load testing tool which supports web services load testing

"two requests at a time" 是不是 connectionManagement 的默认 maxconnection=2 限制的结果?

<configuration>  
  <system.net>  
    <connectionManagement>  
      <add address = "http://www.contoso.com" maxconnection = "4" />  
      <add address = "*" maxconnection = "2" />  
    </connectionManagement>  
  </system.net>  
</configuration> 

我最喜欢的负载测试库是 NBomber。它具有简单而强大的 API、逼真的用户模拟,并为您提供有关延迟和每秒请求数的漂亮 HTML 报告。 我用它来测试我的 API 并写了一个 article 关于我是如何做到的。