如何异步调用列表中的多个 URL
How can I call many URLs from a list asynchronously
我有几十万个 URL 需要调用。这些是对应用程序服务器的调用,该应用程序服务器将处理它们并将状态代码写入 table。我不需要等待响应 (success/fail),只需要服务器收到请求即可。我还希望能够指定一次 运行 可以处理多少并发作业,因为我还没有计算出 tomcat 可以处理多少并发请求。
这是我到目前为止所得到的,基本上是从其他人尝试做类似的事情中获取的,只是没有使用 url 调用。文本文件包含每个 url 各占一行。 url 看起来像这样:
http://webserver:8080/app/mwo/services/create?server=ServerName&e1user=admin&newMWONum=123456&sourceMWONum=0&tagNum=33-A-1B
和代码:
$maxConcurrentJobs = 10
$content = Get-Content -Path "C:\Temp\urls.txt"
foreach ($url in $content) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $maxConcurrentJobs) {
Start-Job {
Invoke-WebRequest -UseBasicParsing -Uri $using:url
}
} else {
$running | Wait-Job -Any
}
Get-Job | Receive-Job
}
我遇到的问题是每个 "job" 给出 2 个错误,我不确定为什么。当我转储 url 数组 $content 时它看起来很好,当我 运行 我的 Invoke-WebRequest 一个一个地工作时它们没有错误。
126 Job126 BackgroundJob Running True localhost ...
Invalid URI: The hostname could not be parsed.
+ CategoryInfo : NotSpecified: (:) [Invoke-RestMethod], UriFormatException
+ FullyQualifiedErrorId : System.UriFormatException,Microsoft.PowerShell.Commands.InvokeRestMethodComman
d
+ PSComputerName : localhost
Invalid URI: The hostname could not be parsed.
+ CategoryInfo : NotSpecified: (:) [Invoke-RestMethod], UriFormatException
+ FullyQualifiedErrorId : System.UriFormatException,Microsoft.PowerShell.Commands.InvokeRestMethodComman
d
+ PSComputerName : localhost
任何帮助或替代实施将不胜感激。我愿意不使用 powershell,但我仅限于 Windows 7 台台式机或 Windows 2008 R2 服务器,我可能会 运行 在服务器上安装最终脚本本身在 url 中使用本地主机来减少网络延迟。
使用作业会产生大量开销,因为每个新作业都会产生一个新进程。
改用Runspaces!
$maxConcurrentJobs = 10
$content = Get-Content -Path "C:\Temp\urls.txt"
# Create a runspace pool where $maxConcurrentJobs is the
# maximum number of runspaces allowed to run concurrently
$Runspace = [runspacefactory]::CreateRunspacePool(1,$maxConcurrentJobs)
# Open the runspace pool (very important)
$Runspace.Open()
foreach ($url in $content) {
# Create a new PowerShell instance and tell it to execute in our runspace pool
$ps = [powershell]::Create()
$ps.RunspacePool = $Runspace
# Attach some code to it
[void]$ps.AddCommand("Invoke-WebRequest").AddParameter("UseBasicParsing",$true).AddParameter("Uri",$url)
# Begin execution asynchronously (returns immediately)
[void]$ps.BeginInvoke()
# Give feedback on how far we are
Write-Host ("Initiated request for {0}" -f $url)
}
如链接的 ServerFault post 中所述,您还可以使用更通用的解决方案,例如 Invoke-Parallel
,它基本上可以执行上述操作
您还可以使用.net webclients 的异步方法。假设您只需要向您的网址发送一个获取请求,Net.WebClient 就可以了。以下是 example.com:
的虚拟示例
$urllist = 1..97
$batchSize = 20
$results = [System.Collections.ArrayList]::new()
$i = 1
foreach($url in $urllist) {
$w = [System.Net.Webclient]::new().DownloadStringTaskAsync("http://www.example.com?q=$i")
$results.Add($w) | Out-Null
if($i % $batchSize -eq 0 -or $i -eq $urllist.Count) {
While($false -in $results.IsCompleted) {sleep -Milliseconds 300} # waiting for batch to complete
Write-Host " ........ Batch completed ......... $i" -ForegroundColor Green
foreach($r in $results) {
New-Object PSObject -Property @{url = $r.AsyncState.AbsoluteURI; jobstatus =$r.Status; success = !$r.IsFaulted}
# if you need response text use $r.Result
}
$results.Clear()
}
$i+=1
}
我同意顶部 post 使用 Runspaces。但是,提供的代码并未显示如何从请求中取回数据。这是最近发布到我的 GitHub 页面的 PowerShell 模块:
它将通过 SSL/TLS(TCP 端口 443)向单个域提交异步 HTTP 请求。这是 README.md
中的示例
Import-Module AsyncHttps
Invoke-AsyncHttps -DnsName www.contoso.com -UriPaths $('dir1','dir2','dir3')
它returns一个System.Object[]包含每个请求的结果。结果属性如下:
Uri - Request Uri
Status - Http Status Code or Exception Message
BeginTime - Job Start Time
EndTime - Job End Time
查看您的示例后,您可能需要进行以下修改:
- 允许使用备用端口 (
webserver:8080
)。最简单的方法是更新脚本块中的 URI。或者为端口添加另一个参数到模块和脚本块。
- 测试在 HTTP 请求中使用时,查询参数的格式是否正确且未被百分比编码破坏。考虑在脚本块中跳过
UriBuilder
的使用,只要您的 Uri 路径列表已知是正确的。
我有几十万个 URL 需要调用。这些是对应用程序服务器的调用,该应用程序服务器将处理它们并将状态代码写入 table。我不需要等待响应 (success/fail),只需要服务器收到请求即可。我还希望能够指定一次 运行 可以处理多少并发作业,因为我还没有计算出 tomcat 可以处理多少并发请求。
这是我到目前为止所得到的,基本上是从其他人尝试做类似的事情中获取的,只是没有使用 url 调用。文本文件包含每个 url 各占一行。 url 看起来像这样:
http://webserver:8080/app/mwo/services/create?server=ServerName&e1user=admin&newMWONum=123456&sourceMWONum=0&tagNum=33-A-1B
和代码:
$maxConcurrentJobs = 10
$content = Get-Content -Path "C:\Temp\urls.txt"
foreach ($url in $content) {
$running = @(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le $maxConcurrentJobs) {
Start-Job {
Invoke-WebRequest -UseBasicParsing -Uri $using:url
}
} else {
$running | Wait-Job -Any
}
Get-Job | Receive-Job
}
我遇到的问题是每个 "job" 给出 2 个错误,我不确定为什么。当我转储 url 数组 $content 时它看起来很好,当我 运行 我的 Invoke-WebRequest 一个一个地工作时它们没有错误。
126 Job126 BackgroundJob Running True localhost ...
Invalid URI: The hostname could not be parsed.
+ CategoryInfo : NotSpecified: (:) [Invoke-RestMethod], UriFormatException
+ FullyQualifiedErrorId : System.UriFormatException,Microsoft.PowerShell.Commands.InvokeRestMethodComman
d
+ PSComputerName : localhost
Invalid URI: The hostname could not be parsed.
+ CategoryInfo : NotSpecified: (:) [Invoke-RestMethod], UriFormatException
+ FullyQualifiedErrorId : System.UriFormatException,Microsoft.PowerShell.Commands.InvokeRestMethodComman
d
+ PSComputerName : localhost
任何帮助或替代实施将不胜感激。我愿意不使用 powershell,但我仅限于 Windows 7 台台式机或 Windows 2008 R2 服务器,我可能会 运行 在服务器上安装最终脚本本身在 url 中使用本地主机来减少网络延迟。
使用作业会产生大量开销,因为每个新作业都会产生一个新进程。
改用Runspaces!
$maxConcurrentJobs = 10
$content = Get-Content -Path "C:\Temp\urls.txt"
# Create a runspace pool where $maxConcurrentJobs is the
# maximum number of runspaces allowed to run concurrently
$Runspace = [runspacefactory]::CreateRunspacePool(1,$maxConcurrentJobs)
# Open the runspace pool (very important)
$Runspace.Open()
foreach ($url in $content) {
# Create a new PowerShell instance and tell it to execute in our runspace pool
$ps = [powershell]::Create()
$ps.RunspacePool = $Runspace
# Attach some code to it
[void]$ps.AddCommand("Invoke-WebRequest").AddParameter("UseBasicParsing",$true).AddParameter("Uri",$url)
# Begin execution asynchronously (returns immediately)
[void]$ps.BeginInvoke()
# Give feedback on how far we are
Write-Host ("Initiated request for {0}" -f $url)
}
如链接的 ServerFault post 中所述,您还可以使用更通用的解决方案,例如 Invoke-Parallel
,它基本上可以执行上述操作
您还可以使用.net webclients 的异步方法。假设您只需要向您的网址发送一个获取请求,Net.WebClient 就可以了。以下是 example.com:
的虚拟示例$urllist = 1..97
$batchSize = 20
$results = [System.Collections.ArrayList]::new()
$i = 1
foreach($url in $urllist) {
$w = [System.Net.Webclient]::new().DownloadStringTaskAsync("http://www.example.com?q=$i")
$results.Add($w) | Out-Null
if($i % $batchSize -eq 0 -or $i -eq $urllist.Count) {
While($false -in $results.IsCompleted) {sleep -Milliseconds 300} # waiting for batch to complete
Write-Host " ........ Batch completed ......... $i" -ForegroundColor Green
foreach($r in $results) {
New-Object PSObject -Property @{url = $r.AsyncState.AbsoluteURI; jobstatus =$r.Status; success = !$r.IsFaulted}
# if you need response text use $r.Result
}
$results.Clear()
}
$i+=1
}
我同意顶部 post 使用 Runspaces。但是,提供的代码并未显示如何从请求中取回数据。这是最近发布到我的 GitHub 页面的 PowerShell 模块:
它将通过 SSL/TLS(TCP 端口 443)向单个域提交异步 HTTP 请求。这是 README.md
中的示例Import-Module AsyncHttps
Invoke-AsyncHttps -DnsName www.contoso.com -UriPaths $('dir1','dir2','dir3')
它returns一个System.Object[]包含每个请求的结果。结果属性如下:
Uri - Request Uri
Status - Http Status Code or Exception Message
BeginTime - Job Start Time
EndTime - Job End Time
查看您的示例后,您可能需要进行以下修改:
- 允许使用备用端口 (
webserver:8080
)。最简单的方法是更新脚本块中的 URI。或者为端口添加另一个参数到模块和脚本块。 - 测试在 HTTP 请求中使用时,查询参数的格式是否正确且未被百分比编码破坏。考虑在脚本块中跳过
UriBuilder
的使用,只要您的 Uri 路径列表已知是正确的。