Является ли Invoke-WebRequest PowerShell масштабируемым?

У меня есть требование проверить доступность 1000 различных URL-адресов из заданного текстового файла через одну виртуальную машину Windows Server 2016 с установленной PowerShell v5.1.

Требуемая проверка должна быть с интервалом каждые 5 минут.

Моим первым предположением было использование командлетов PowerShell: Get-Content с Invoke-WebRequest в цикле For-Each:

$urlList = Get-Content -Path "c:\URLsList.txt"

foreach ($url in $urlList) {

    $result = Invoke-WebRequest $url
    $result.StatusCode
}

Но, учитывая количество URL-адресов (1000), я не уверен, что PowerShell Invoke-WebRequest достаточно масштабируем для этой задачи.

Я не видел в официальной документации упоминаний о передовом опыте или каких-либо ограничений: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view= пауэршелл-5.1

Но во время поиска я узнал о рабочих местах PowerShell.

Но в связи с тем, Что 1000 URL должны проверяться каждые 5 минут.

Я не уверен, что это будет актуально.

Итак... вы написали какой-нибудь код, чтобы проверить, выполнимо это или нет?

Mathias R. Jessen 15.05.2023 12:56

@MathiasR.Jessen, я нахожусь на этапе разработки алгоритма, который будет использоваться в конечном решении.

edwio 15.05.2023 13:06

@MathiasR.Jessen, добавил образец фрагмента для общей идеи. Но все же остается вопрос, можно ли масштабировать PowerShell для обработки 1000 веб-запросов каждые 5 минут. Или это ограничение виртуального сервера, на котором работает этот реальный скрипт.

edwio 15.05.2023 13:21

используйте ForEach-Parallel learn.microsoft.com/en-us/powershell/module/psworkflow/about‌​/…

rinat gadeev 15.05.2023 13:26

@rinatgadeev, спасибо, но я написал, что использую PowerShell v5.1.

edwio 15.05.2023 13:31

@edwio Хорошо, хорошее начало! Код завершает работу через 5 минут или нет?

Mathias R. Jessen 15.05.2023 13:43

@MathiasR.Jessen, совсем нехорошо. При использовании команды Measure-Command общее время выполнения сценария составляет 6 минут. Я не уверен, связана ли основная причина с текущим ресурсом виртуального сервера (8-ядерный ЦП, 16 ГБ оперативной памяти) или, возможно, с тем фактом, что некоторые URL-адреса недоступны, и из-за этого Invoke-WebRequest не имеет параметра тайм-аута. может быть установлено для каждого HTTP-запроса Get, который он выполняет.

edwio 15.05.2023 13:52
Invoke-WebRequest в PowerShell 5.1 обязательно есть параметр тайм-аута: Invoke-WebRequest ... -TimeoutSec 2. При этом вы, вероятно, захотите группировать URL-адреса по 10 за раз (.NET FX имеет внутренний пул потоков для HTTP-запросов клиента, ограничивающий вас до 10 одновременных запросов) в качестве фоновых заданий.
Mathias R. Jessen 15.05.2023 14:04

@MathiasR.Jessen, тайм-аут находится на уровне запроса? И что касается пакетной обработки, следует ли использовать задания PowerShell? Если да, то как он будет вычислять список URL-адресов, чтобы в сумме все URL-адреса запускались каждые 5 минут?

edwio 15.05.2023 14:11

Вы получите улучшения, если будете запускать задачу параллельно. Я бы ограничил количество параллельных задач количеством ядер вашего микропроцессора. Я бы взял список машин и разделил его на равные части, а затем использовал бы foreach с параметром parallel для запуска кода:

jdweng 15.05.2023 17:19

@jdweng, ограничение параллелизма количеством ядер ЦП для сетевых задач нецелесообразно. То, на что вы ссылаетесь, относится к устаревшей технологии рабочего процесса Windows PowerShell, которая отличается от обычного кода PowerShell и в настоящее время ее лучше избегать; он принципиально больше не доступен в PowerShell (Core) 7+, который теперь предлагает параметр -Parallel как часть обычного командлета ForEach-Object.

mklement0 15.05.2023 19:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
11
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Неотъемлемым ограничением Invoke-WebRequest (и Invoke-RestMethod) является возможность одновременного действия только с одним URL.

Параллельное нацеливание на несколько URL-адресов требует командно-внешнего параллелизма, например, через (медленные и ресурсоемкие) задания PowerShell или (легкие и, следовательно, предпочтительнее) задания потоков — через модуль ThreadJob, который можно установить по запросу в Windows PowerShell поставляется с PowerShell (Core) 7+ и, что наиболее эффективно, с такой же поточной функцией ForEach-Object -Parallel в PowerShell 7+.

Однако командно-внешний параллелизм, даже в форме, основанной на потоках, неизменно влечет за собой нетривиальные накладные расходы.


Поэтому рассмотрите возможность использования curl.exe, который поставляется с последними версиями Windows и имеет встроенную поддержку таргетинга на несколько URL-адресов, а также параллельную работу.


Сравнение производительности на основе примера кода, который выполняет запросы GET с 10 URL-адресами и сообщает коды состояния HTTP ответов, используя различные последовательные и параллельные подходы.

  • Абсолютное время будет варьироваться даже между прогонами, но соотношение должно давать представление о том, что работает лучше всего.

  • Рейтинг может отличаться на Unix-подобных платформах. Любопытно, что на Mac M1 я вижу, что операции в целом медленнее, и curl даже медленнее, чем сопоставимые Invoke-WebRequest подходы.

  • Исходный код ниже; вы можете легко настроить его, чтобы предоставить больше URL-адресов и поэкспериментировать со степенью параллелизма.

Примеры результатов из Windows PowerShell (значения в секундах, сначала самые быстрые):

Method                                       Duration
------                                       --------
curl, parallel                              0.2868489
Invoke-WebRequest, Start-ThreadJob          0.5779788
curl, sequential                            1.9407611
Invoke-WebRequest, sequential               2.3540807
Invoke-WebRequest, ForEach-Object -Parallel       N/A

Примеры результатов PowerShell (Core) 7.3.4 (в Windows):

Method                                      Duration
------                                      --------
curl, parallel                                  0.27
Invoke-WebRequest, ForEach-Object -Parallel     0.42
Invoke-WebRequest, Start-ThreadJob              0.52
curl, sequential                                1.89
Invoke-WebRequest, sequential                   2.05

Исходный код:

# Sample URLs
$urls = @(
  'http://www.example.org'
  'http://www.example.com'
  'https://en.wikipedia.org'
  'https://de.wikipedia.org'
  'https://fr.wikipedia.org'
  'https://it.wikipedia.org'
  'https://es.wikipedia.org'
  'https://ru.wikipedia.org'
  'https://ru.wikipedia.org'
  'https://als.wikipedia.org'
)

# Code that implements various approaches.
$scriptBlock = {
  param(
    [switch] $UseCurl,
    [switch] $Parallel,
    [switch] $UseThreadJobs
  )
    
  if ($useCurl) {
    # use curl.exe
    $curlExe = if ($IsCoreCLR) { 'curl' } else { 'curl.exe' }
    $urlArgs = foreach ($url in $urls) { $url, '-o', '/dev/null' }
    $parallelArgs = @()
    if ($Parallel) { $parallelArgs = '--parallel', '--parallel-max', $numParallelTransfers }
    & $curlExe -s -w '%{url} = %{http_code}\n' $parallelArgs -L $urlArgs
  }
  else {
    # use Invoke-WebRequest
    $ProgressPreference = 'SilentlyContinue'
    if ($Parallel) {
      if ($UseThreadJobs) {
        $urls | ForEach-Object {
          Start-ThreadJob -ThrottleLimit $numThreads { "$using:_ = " + (Invoke-WebRequest $using:_).StatusCode } 
        } | Receive-Job -Wait -AutoRemoveJob
      }
      else { # ForEach-Object -Parallel
        $urls | ForEach-Object -ThrottleLimit $numThreads -Parallel { "$_ = " + (Invoke-WebRequest $_).StatusCode }
      }
    }
    else { # sequential
      $urls | ForEach-Object {  "$_ = " + (Invoke-WebRequest $_).StatusCode }
    }
  }
} 

# Set the desired number of parallel threads / transfers:
$numThreads = 10 # for ForEach-Object -Parallel, whose default is 5
$numParallelTransfers = 50 # For curl.exe: 50 is the default, and lowering it hurts performance

# Run benchmarks
@(
  [pscustomobject] @{
    Method   = 'Invoke-WebRequest, sequential'
    Duration = (Measure-Command { Write-Verbose -Verbose 'Invoke-WebRequest sequential solution:'; & $scriptBlock | Out-Host }).TotalSeconds
  }

  [pscustomobject] @{
    Method   = 'Invoke-WebRequest, ForEach-Object -Parallel'
    Duration =
      if ($PSVersionTable.PSVersion.Major -lt 7) { 'N/A' }
      else { (Measure-Command { Write-Verbose -Verbose 'Invoke-WebRequest with ForEach-Object -Parallel:'; & $scriptBlock -Parallel | Out-Host }).TotalSeconds }
  }

  [pscustomobject] @{
    Method   = 'Invoke-WebRequest, Start-ThreadJob'
    Duration =
      if (-not (Get-Command -ErrorAction Ignore Start-ThreadJob)) { 'N/A' }
      else { (Measure-Command { Write-Verbose -Verbose 'Invoke-WebRequest with Start-ThreadJob:'; & $scriptBlock -Parallel -UseThreadJobs | Out-Host }).TotalSeconds }
  }

  [pscustomobject] @{
    Method   = 'curl, sequential'
    Duration = (Measure-Command { Write-Verbose -Verbose 'curl.exe sequential solution:'; & $scriptBlock -UseCurl | Out-Host }).TotalSeconds
  }

  [pscustomobject] @{
    Method   = 'curl, parallel'
    Duration = (Measure-Command { Write-Verbose -Verbose 'curl.exe parallel solution:'; & $scriptBlock -UseCurl -Parallel | Out-Host }).TotalSeconds
  } 

) | 
  ForEach-Object -Begin {
    Write-Verbose -Verbose "Timing in seconds for $($urls.Count) URLs, based on $numThreads simultaneous threads running Invoke-WebRequest / up to $numParallelTransfers parallel curl.exe transfers:"
  } -Process {
    $_
  } |
  Sort-Object Duration

Другие вопросы по теме