处理大量 PUT 请求休息 api

Handle large number of PUT requests to a rest api

我一直在努力寻找一种方法来提高这项任务的效率。我正在使用基于 REST 的 Web 服务,需要为超过 2500 个客户端更新信息。

我正在使用 fiddler 来监视请求,并且我也在更新 table 完成后的更新时间。我每秒收到大约 1 个响应。我的期望值高吗?我什至不确定在这种情况下我会定义什么 'fast'。

我正在我的控制器中处理所有事情,并且已经尝试 运行 基于周围示例的并行网络请求,但它似乎没有什么不同。老实说,我对它的理解还不够好,只是想尝试构建它。我怀疑它仍在等待每个请求完成后再触发。

我还根据另一项建议增加了我的网络配置文件中的连接,但没有成功:

 <system.net>
    <connectionManagement>
      <add address="*" maxconnection="20" />
    </connectionManagement>
  </system.net>

我的控制器操作方法如下所示:

public async Task<ActionResult> UpdateMattersAsync()
        {
           //Only get  matters we haven't synced yet
            List<MatterClientRepair> repairList = Data.Get.AllUnsyncedMatterClientRepairs(true);
        //Take the next 500
        List<MatterClientRepair> subRepairList = repairList.Take(500).ToList();          

        FinalisedMatterViewModel vm = new FinalisedMatterViewModel();


        using (ApplicationDbContext db = new ApplicationDbContext())
        {
            int jobCount = 0;
            foreach (var job in subRepairList)
            {
                // If not yet synced - it shouldn't ever be!!
                if (!job.Synced)
                {
                    jobCount++;

                   // set up some Authentication fields
                    var oauth = new OAuth.Manager();
                    oauth["access_token"] = Session["AccessToken"].ToString();
                    string uri = "https://app.com/api/v2/matters/" + job.Matter;

                    // prepare the json object for the body
                    MatterClientJob jsonBody = new MatterClientJob();
                    jsonBody.matter = new MatterForUpload();
                    jsonBody.matter.client_id = job.NewClient;
                    string jsonString = jsonBody.ToJSON();                        

                   // Send it off. It returns the whole object we updated - we don't actually do anything with it
                    Matter result = await oauth.Update<Matter>(uri, oauth["access_token"], "PUT", jsonString);

                    // update our entities                       
                    var updateJob = db.MatterClientRepairs.Find(job.ID);
                    updateJob.Synced = true;
                    updateJob.Update_Time = DateTime.Now;
                    db.Entry(updateJob).State = System.Data.Entity.EntityState.Modified;
                    if (jobCount % 50 == 0)
                    {
                        // save every 50 changes
                        db.SaveChanges();
                    }
                }
            }

            // if there are remaining files to save
            if (jobCount % 50 != 0)
            {
                db.SaveChanges();
            }

            return View("FinalisedMatters", Data.Get.AllMatterClientRepairs());
        }
    }

当然还有处理 Web 请求的 Update 方法本身:

  public async Task<T> Update<T>(string uri, string token, string method, string json)
                {  
                var authzHeader = GenerateAuthzHeader(uri, method);
                // prepare the token request

                var request = (HttpWebRequest)WebRequest.Create(uri);
                request.Headers.Add("Authorization", authzHeader);
                request.Method = method;
            request.ContentType = "application/json";
            request.Accept = "application/json, text/javascript";
            byte[] bytes = System.Text.Encoding.ASCII.GetBytes(json);
            request.ContentLength = bytes.Length;
            System.IO.Stream os = request.GetRequestStream();
            os.Write(bytes, 0, bytes.Length);
            os.Close();
            WebResponse response = await request.GetResponseAsync();

            using (var reader = new System.IO.StreamReader(response.GetResponseStream()))
            {
                return JsonConvert.DeserializeObject<T>(reader.ReadToEnd());

            }

        }

如果每秒无法执行超过 1 个请求,那么我有兴趣查看 Ajax 解决方案,以便在处理过程中向用户提供一些反馈。在我当前的解决方案中,我无法在操作方法尚未达到 'return' 时向用户提供反馈,我可以吗?

好吧,我花了几天时间(以及大量的反复试验),但我已经解决了这个问题。希望它可以帮助别人。我终于找到了我的银弹。这可能是我应该开始的地方: MSDN: Consuming the Task-based Asynchronous Pattern

最后,下面这行代码让一切都曝光了。

string [] pages = await Task.WhenAll(from url in urls select  DownloadStringAsync(url));

我替换了一些东西以使其适用于 Put 请求,如下所示:

HttpResponseMessage[] results = await Task.WhenAll(from p in toUpload select client.PutAsync(p.uri, p.jsonContent));

'toUpload' 是 MyClass 的列表:

  public class MyClass
    {
        // the URI should be relative to the base pase
        // (ie: /api/v2/matters/101)
        public string uri { get; set; }

        // a string in JSON format, being the body of the PUT request
        public StringContent jsonContent { get; set; }
    }

关键是停止尝试将我的 PutAsync 方法放入循环中。在所有响应都返回之前,我的新代码行仍然处于阻塞状态,但这正是我想要的。此外,了解到我可以使用这种 LINQ 风格的表达式来动态创建任务列表,这对我的帮助是无法估量的。我不会 post 所有的代码(除非有人想要)因为它没有像原来的那样很好地重构而且我仍然需要检查每个项目的响应是否为 200 OK 在我将其记录为成功保存之前我的数据库。那么它快了多少?

结果

我测试了来自本地计算机的 50 个网络服务调用示例。 (最后将一些记录保存到 Azure 中的 SQL 数据库)。

原同步码:70.73秒

异步代码:8.89秒

从每秒 1.4146 个请求下降到每秒 0.1778 个请求! (如果你平均)

结论

我的旅程还没有结束。我刚刚触及了异步编程的皮毛并且很喜欢它。我现在需要弄清楚如何只保存返回 200 OK 的结果。我可以反序列化 returns 一个 JSON 对象(它有一个我可以查找的唯一 ID 等)的 HttpResponse 或者我可以使用 Task.WhenAny 方法,并试验交错。