PowerShell 脚本并行化工作 - 作业不会等到从 REST API 获得答案并提前完成作业

问题描述 投票:0回答:2

使用最新版本代码编辑:

我有一个 PowerShell 脚本正在执行以下步骤

  1. 连接到SQL服务器,下载3个变量的表(三个字段)
  2. 然后连接到 REST API(基于三个变量)并获取 JSON 答案
  3. 然后将 API 的 JSON 输出一一保存为文件,直到脚本到达输入数组池的末尾

当步骤在串行设置中完成时,此脚本运行良好。带有 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 部分完成。我一无所知。我要强调的是,如果脚本被序列化,它本身运行得很好。

multithreading powershell parallel-processing powershell-cmdlet json-api
2个回答
1
投票

当您使用

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
并且只需要这样做...


0
投票

我已经能够解决这个问题了。通过实际将错误消息拉入文件中。当我在网络上发现错误后,问题并不是像我想象的那样提前结束作业,而是由于服务器上使用的 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
© www.soinside.com 2019 - 2024. All rights reserved.