2015-03-24 4 views
2

Я предполагаю, что это на краю того, что считается хорошим вопросом для этого форума, но я думаю, что это довольно интересно и, надо надеяться, даст некоторую острую проницательность. По сути, у меня есть перманентная проблема. У меня есть фрагмент кода здесь.Медленный запрос HttpClient на Youtube

http://pastebin.com/3WHB7V9G

Я пытался документировать это хорошо. По сути, все это делает запрос HEAD на URL-адрес - он в конечном итоге перенаправляется на youtube (и в этом случае он запускается как перенаправитель на самом youtube.com). Как только он получит окончательный URL-адрес, он получает идентификатор видео и использует API-интерфейс youtube для получения некоторых данных. Он даже не анализирует эти данные на данный момент - это просто запросы HttpClient.

Для меня это просто - это должно занять < 1 секунда. И последующие запросы часто бывают. Однако, если вы делаете примерно одну минуту, вы обнаружите, что все это занимает 3 + секунды очень часто, а иногда и не получается. Хуже того, простой запрос HEAD на главный сайт YouTube часто занимает 2-3 секунды. Я тестировал это на 2 несвязанных сетях и нашел то же самое - не стесняйтесь проверять меня на этом.

Что мне здесь не хватает? Являются ли мои ожидания производительности необоснованными? Является ли HTTPClient ужасным при инициировании соединений? Я что-то неправильно настраиваю? Любое понимание очень важно.

+0

Пожалуйста, дайте мне знать, если я могу предоставить больше данных или контрольных показателей. – Ben

+0

Мое время лучше: GetYoutubeVideoIDFromUriAsync TTR: 00: 00: 00.7850011 GetYoutubeDataFromIDAsync TTR: 00: 00: 00.393002 Выполнено с GetYoutubeData. Общее время RTT: 00: 00: 01.1830035. Я запускал его несколько раз и никогда не видел 3 или 5 секунд. – Den

+0

Если вы верите в проблемы с 'HttpClient', попробуйте открыть TCP-соединение для себя и вручную записать данные. –

ответ

0

верьте, что мой или нет, добавил некоторые заголовки в заголовке запроса, может помочь.

Например, пользовательский агент, Connection: keep-alive или Access-Control-Allow-Headers: X-Requested-With (ajax отправит это сообщение).

Вы можете попробовать другие комбинации, чтобы узнать, есть ли разница.

0

Не устанавливая пользовательские агенты или конкретные заголовки принятия, вы оставите некоторые серверы в дилемме, сервер не будет знать, на каком языке вы хотите ответить, какой формат вы предпочитаете или какой пользовательский агент использует некоторые веб-сайты для рендеринга мобильного страницы.

Чем больше информации вы предоставляете для него, тем меньше будет работать сервер на своей тарелке, другая проблема (та, которой мы не контролируем) - это то место, куда направляется ваш запрос, если он перенаправлен на данные центр, который находится далеко от вас, тогда, естественно, запрос займет больше времени.

Мы можем преодолеть это до такой степени, установив некоторые тайм-аут & повторов логики в коде ниже я настроил запросов к тайм-аут после 450ms и повторить до 3 раз.

Я также избавился от HTTPClient ... Да, это обеспечивает простой интерфейс, чтобы сделать запросы, но минусы перевешивают выгоды, как только оптимизация под вопросом.

Для дальнейшей оптимизации наших запросов, мы должны масштаба, мы должны использовать нитей, в 1 минуту 22 секунд я побежал через 2000 запросов только с 7 таймаутов!

void Main() 
{ 
    const string Youtube  = "youtube.com"; 
    const string UriRegexPattern = @"v=([^\&]*)"; 
    const string UriPath   = "/watch"; 
    const string UriQuery  = "v="; 
    const string TestUri   = "https://www.youtube.com/watch/HYE9H_ZUuOI"; 
    const string ApiUri   = "https://www.googleapis.com/youtube/v3/videos?id={0}&key=AIzaSyDWaA2OoArAjQTHqmN6r9XrpHYNkpKGyGw&part=snippet,contentDetails,statistics,status"; 
    const int TotalThreads = 10; 
    ConcurrentQueue<string> UriQueue = new ConcurrentQueue<string>(); 

    for (int i = 0; i < 1000; i++) 
    { 
     UriQueue.Enqueue(TestUri); 
    } 

    Thread[] threads = new Thread[TotalThreads]; 
    for (int i = 0; i < TotalThreads; i++) 
    { 
     int iCopy = i; 
     threads[iCopy] = new Thread(()=> 
     { 
      Stopwatch sw = new Stopwatch(); 
      sw.Start(); 

      string uri; 

      while (UriQueue.TryDequeue(out uri)) 
      { 
       // Locate the final redirect Uri 
       Uri finalUri = Http.GetRedirectDestination(new Uri(uri)); 
       Console.WriteLine ("THREAD[{0}] >>> Time taken locating redirect: {1}", iCopy, sw.Elapsed); 
       sw.Reset(); 

       // Ensure that the host is youtube, and the page contains a video 
       if (!finalUri.ContainsHost(Youtube) || !finalUri.ContainsPath(UriPath) || !finalUri.ContainsQuery(UriQuery)) return; 

       // Extract the youtubeId using a regular expression. 
       string youtubeId = finalUri.ExtractQuery(UriRegexPattern); 

       // The uri of api to query including the youtubeId extracted 
       string apiUri = string.Format(ApiUri, youtubeId); 

       // Reset the stopwatch and query the api 
       sw.Start(); 
       string json = Http.Get(new Uri(apiUri)); 
       Console.WriteLine ("THREAD[{0}] >>> Time taken querying api:  {1}", iCopy, sw.Elapsed); 
       sw.Stop(); 

       // Also lets try not to get blacklisted by youtube 
       Thread.Sleep(500); 
      } 
     }); 

     threads[iCopy].Start(); 
    } 

    foreach(var thread in threads) 
    { 
     thread.Join(); 
    } 
} 

public static class Http 
{ 
    public static Uri GetRedirectDestination(Uri uri, int retries = 0) 
    { 
     Uri redirectUri = null; 
     HttpWebRequest request    = (HttpWebRequest)WebRequest.Create(uri); 
     request.Method      = "HEAD"; 
     request.Accept      = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; 
     request.Headers["Accept-Language"] = "en-GB,en-US;q=0.8,en;q=0.6"; 
     request.UserAgent     = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36"; 
     request.AutomaticDecompression  = DecompressionMethods.GZip; 
     request.Timeout     = 450; 
     try 
     {   
      using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 
      { 
       if (response.StatusCode == HttpStatusCode.Redirect || 
        response.StatusCode == HttpStatusCode.RedirectKeepVerb || 
        response.StatusCode == HttpStatusCode.RedirectMethod) 
       { 
        redirectUri = GetRedirectDestination(new Uri(response.Headers["Location"])); 
       } 

       return response.ResponseUri; 
      } 

     } 
     catch (WebException exception) 
     { 
      Console.WriteLine ("WebException Uri: {0}", uri); 
      Console.WriteLine (">> Message: {0}", exception.Message); 
      Console.WriteLine (">> Status: {0}", exception.Status); 

      if (retries > 2) 
      { 
       throw; 
      } 

      retries += 1; 

      return GetRedirectDestination(uri, retries); 
     } 
    } 

    public static string Get(Uri uri, int retries = 0) 
    { 
     HttpWebRequest request    = (HttpWebRequest)WebRequest.Create(uri); 
     request.Accept      = "application/json"; 
     request.AutomaticDecompression  = DecompressionMethods.GZip; 

     try 
     {   
      using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) 
      using (Stream stream   = response.GetResponseStream()) 
      using (StreamReader reader  = new StreamReader(stream)) 
      { 
       return reader.ReadToEnd(); 
      } 
     } 
     catch (WebException exception) 
     { 
      // throw; 
      string exceptionResponse = string.Empty; 
      using (Stream stream = exception.Response.GetResponseStream()) 

      if (retries > 2) 
      { 
       throw; 
      } 

      retries += 1; 

      return Get(uri, retries); 
     } 
    } 

} 

public static class Extensions 
{ 
    public static bool IsNullOrWhiteSpace(this string text) 
    { 
     return string.IsNullOrWhiteSpace(text); 
    } 

    public static bool ContainsHost(this Uri uri, string host) 
    { 
     if (uri == null)    throw new ArgumentNullException("uri"); 
     if (host.IsNullOrWhiteSpace()) throw new ArgumentNullException("host"); 

     return uri.Host.Contains(host); 
    } 

    public static bool ContainsPath(this Uri uri, string path) 
    { 
     if (uri == null)       throw new ArgumentNullException("uri"); 
     if (path.IsNullOrWhiteSpace())    throw new ArgumentNullException("path"); 
     if (uri.PathAndQuery.IsNullOrWhiteSpace()) return false; 

     return uri.PathAndQuery.ToLowerInvariant().Contains(path.ToLowerInvariant()); 
    } 

    public static bool ContainsQuery(this Uri uri, string query) 
    { 
     if (uri == null)     throw new ArgumentNullException("uri"); 
     if (query.IsNullOrWhiteSpace())  throw new ArgumentNullException("query"); 
     if (uri.Query.IsNullOrWhiteSpace()) return false; 

     return uri.Query.ToLowerInvariant().Contains(query.ToLowerInvariant()); 
    } 

    public static string ExtractQuery(this Uri uri, string regexPattern) 
    { 
     if (regexPattern.IsNullOrWhiteSpace()) throw new ArgumentNullException("regexPattern"); 
     if (uri.Query.IsNullOrWhiteSpace()) return null; 

     Match match = Regex.Match(uri.Query, regexPattern); 
     return match.Groups[1].Value; 
    } 
} 
+0

Мне нужно еще немного времени, чтобы пройти через все здесь, но FYI Я наградил вас щедростью до того, как он истек, поскольку вы, кажется, приложили сюда много усилий и деталей :) – Ben

+0

Спасибо :) если у вас есть какие-либо последующие вопросы не стесняйтесь снимать их ... –

+0

У меня есть то, что составляет ваш точный код, функции Get/GetRedirected (хотя с асинхронным) и подключен к моей системе. Я все еще получаю 3-6 секунд всего времени. Nuts :( – Ben

Смежные вопросы