使用最新版本代码编辑:
我有一个 PowerShell 脚本正在执行以下步骤:
当步骤在串行设置中完成时,此脚本运行良好。带有 while 循环的原始脚本将输入项一一对应,连接到 API,获取答案,将 JSON 答案保存到文件中。
问题:但是这种方式非常慢。每个周期大约需要 1 分钟,我有大约 11,000 个条目需要处理。大约需要7天才能完成。
解决方案:基于本文:文字。我决定使用多线程作业解决方案,我可以并行运行多个作业。
问题:在这个多线程解决方案中,似乎每个独立作业都过早完成。在几毫秒内,整批 11K 文件被保存,并带有每个变量的正确概念。但所有文件都是空的。 我仍然期望(即使是并行运行)每个作业平均持续 1 分钟。我确信,该脚本不会等到它从 API 获得答案并立即跳转到下一步保存一个空文件。
这是 PowerShell 脚本:
#Connect to SQL server
$SQLServer = "XXXXXXXXXXXXX" #use Server\Instance for named SQL instances
$SQLDBName = "XXX"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security=True;"
$SqlConnection.Open()
$execute_query = New-Object System.Data.SqlClient.SqlCommand
$execute_query.connection = $SqlConnection
$DataSet = Invoke-Sqlcmd -Query "SELECT Input_1 ,Input_2 ,Input_3 FROM XXX.dbo.API_Input"
#Close SQL Connection
$SqlConnection.Close()
$Commnad_Block = {
Param( [string]$Variable_Input_1, [string]$Variable_Input_2, [string]$Variable_Input_3)
# Compile URL link based on input
$URL = 'https://example.com/Stag?Seg='+ $Variable_Input_2 +'&Cust='+$Variable_Input_1+'&Prod='+$Variable_Input_3
# Call REST API GET method, with 3 variables
$response = Invoke-RestMethod -Uri $URL -Method GET -ContentType "application/json"
#Create File name to be exported
$FileNamePath = 'D:\File_Export\'+$Variable_Input_3+'.json'
# Store JSON output from API into JSON raw file,
$response | ConvertTo-Json -depth 100 | Out-File $FileNamePath
}
#Remove all jobs
Get-Job | Remove-Job
$MaxThreads = 4
#Start the jobs. Max 4 jobs running simultaneously.
foreach($element in $DataSet){
While ($(Get-Job -state running).count -ge $MaxThreads){
Start-Sleep -Milliseconds 1
}
Start-Job -ScriptblVariable_Input_3k $Commnad_Block -ArgumentList $element.Input_1, $element.Input_2, $element.Input_3
}
#Wait for all jobs to finish.
While ($(Get-Job -State Running).count -gt 0){
start-sleep 1
}
#Get information from each job.
foreach($job in Get-Job){
$info= Receive-Job -Id ($job.Id)
}
#Remove all jobs created.
Get-Job | Remove-Job
尝试解决问题:我尝试使用各种等待语句,让每个作业从API获取答案,但似乎没有任何帮助。即便如此,这也不是理想的解决方案。我想强制脚本等待 API 部分完成。我一无所知。我要强调的是,如果脚本被序列化,它本身运行得很好。
Invoke-RestMethod
的 -OutFile
参数时,它仅将响应写入磁盘并且不再返回结果:
-OutFile
将响应正文保存在指定的输出文件中。输入路径和文件名。如果省略路径,则默认为当前位置。该名称被视为文字路径。包含方括号 ([]) 的名称必须用单引号 (') 括起来。
所以你的问题是
$response
是 $null
在这行之后:
$response = Invoke-RestMethod `
-Uri "$URL" `
-Method "GET" `
-ContentType "application/json" `
-OutFile $FileNamePath
当代码稍后涉及到这一行时:
$response | ConvertTo-Json -depth 100 | Out-File $FileNamePath
你正在清空
Invoke-RestMethod
写的内容
要按要求回答您的问题,如果您想返回
Invoke-RestMethod
的结果并使用-OutFile
,您可以添加-PassThru
开关:
-PassThru
命令中同时使用OutFile参数时,该参数有效。目的是将结果写入文件和管道。
并且
$response
将包含 Invoke-RestMethod
的结果。
但是,由于您只是在代码中覆盖相同的文件,因此您可能只需要选择一种方式 -
Invoke-RestMehod ... -OutFile $FileNamePath
或 $response | ... | Out-File $FileNamePath
并且只需要这样做...
我已经能够解决这个问题了。通过实际将错误消息拉入文件中。当我在网络上发现错误后,问题并不是像我想象的那样提前结束作业,而是由于服务器上使用的 TLS 版本导致错误。我只是将此行放在代码中的正确位置,现在它可以按预期工作:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
正如我提到的,如果代码以序列化方式执行,则它可以正常工作。这是因为我在代码中设置了 Tls1.2,但位于代码的顶部。看来多线程作业执行需要将此行插入每个并行循环内部,而不是外部。
这是完整的工作代码:
#Connect to SQL server
$SQLServer = "XXXXXXXXXXXXXXXXX" #use Server\Instance for named SQL instances
$SQLDBName = "XXX"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security=True;"
$SqlConnection.Open()
$execute_query = New-Object System.Data.SqlClient.SqlCommand
$execute_query.connection = $SqlConnection
$DataSet = Invoke-Sqlcmd -Query "SELECT Var_1 ,Var_2 ,Var_3 FROM dbo.Table WHERE Cntry = 'US'"
#Close SQL Connection
$SqlConnection.Close()
$Command_Block = {
Param([string]$Var_1, [string]$Var_2, [string]$Var_3)
# Compile URL link based on input
$URL = "https://example.com/Prod?cntry=US&Seg=$Var_2&Drth=$Var_1&Prd=$Var_3"
try {
#On the server only TLS1.2 or higher is allowed, by defualt, PS is try to use TLS1.1, which is disabled on the server
##### below line was the solution ######
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Call REST API GET method, with 3 variables
$response = Invoke-RestMethod -Uri $URL -Method GET -ContentType "application/json"
# Create File name to be exported
$FileNamePath = "D:\File_Export\$Var_3.json"
# Store JSON output from API into JSON raw file
$response | ConvertTo-Json -Depth 100 | Out-File $FileNamePath -Force
}
catch {
$ErrorMessage = "Error occurred while processing URL: $URL `n$($_.Exception.Message)"
$ErrorFileName = "D:\File_Export\Error_$Var_3.txt"
$ErrorMessage | Out-File $ErrorFileName -Force
}
}
# Start the jobs. Max 4 jobs running simultaneously.
$MaxThreads = 4
$Jobs = @()
foreach ($element in $DataSet) {
while ($(Get-Job -State Running).Count -ge $MaxThreads) {
Start-Sleep -Milliseconds 50
}
$Jobs += Start-Job -ScriptBlock $Command_Block -ArgumentList $element.Var_1, $element.Var_2, $element.Var_3
}
# Wait for all jobs to finish.
$Jobs | Wait-Job
# Get information from each job.
foreach ($job in $Jobs) {
$info = Receive-Job -Id $job.Id
}
# Remove all jobs created.
$Jobs | Remove-Job