首页 > 解决方案 > .net C# API GW 触发 AWS Lambda 在发送到 AWS SQS 时返回“连接被拒绝”

问题描述

我的琐碎 .net C# AWS Lambda 函数,由 HTTP GET 触发到 API GW 运行,但在发送到 AWS SQS 时返回“连接被拒绝”

responseSendMsg = await sqsClient.SendMessageAsync(sendMessageRequest);

似乎对 SQS 的调用是问题所在。

这是完整的代码,它是由 Visual Studio 2019、AWS Toolkit for Visual Studio、新的 AWS 项目模板“AWS Serverless Application (.NET Core - C#)”生成的,我只有:

  1. 将 lambda 函数处理程序方法签名更新为与任务异步,我遵循https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html#csharp-handler-async中记录的模式
public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context)
  1. 在代码中添加以写入 SQS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.SQS;
using TEST.SQS;
using Amazon.SQS.Model;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace TestAsyncFromLambda
{
    public class Functions
    {
        public Functions()
        {
        }

        public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context)
        {
            Console.WriteLine("Get Request\n");

            // Call a local async method to prove that works, leaving SQS out of the situation for the moment
            await Method1();


            // Create the Amazon SQS client
            var clientConfig = new AmazonSQSConfig
            {
                ServiceURL = SQSConstants.AWS_SERVICE_URL,
            };
            var sqsClient = new AmazonSQSClient(clientConfig);

            // Create and initialize a SendMessageRequest instance
            SendMessageRequest sendMessageRequest = new SendMessageRequest();
            sendMessageRequest.QueueUrl = SQSConstants.MyQueueUrl;
            sendMessageRequest.MessageBody = "this is a test message";

            Console.WriteLine($"About to send message to queue: {sendMessageRequest.QueueUrl}");

            // Send the SQS message using "await"
            SendMessageResponse responseSendMsg = null;
            try
            {
                responseSendMsg =
                await sqsClient.SendMessageAsync(sendMessageRequest);
                //responseSendMsg =
                //sqsClient.SendMessageAsync(sendMessageRequest).GetAwaiter().GetResult(); ;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw e;
            }

            Console.WriteLine("SendMessage() complete");

            var response = new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = "Hello AWS Serverless",
                Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
            };

            return response;
        }

        public static async Task Method1()
        {
            await Task.Run(() =>
            {
                int iterations = 3;
                for (int i = 1; i <= iterations; i++)
                {
                    Console.WriteLine(" Method 1, i=" + i + " of " + iterations);
                    // Do something
                    Task.Delay(100).Wait();
                }
            });
        }
    }
}

显然这是一个简化的示例,我计划在函数中进行大量 SQS 处理,并期望使用 async 可能是有益的。

云监视日志显示该函数已被触发,到达发送到 SQS 调用,然后抛出异常:

System.Net.Http.HttpRequestException: Connection refused
2021-06-24T16:13:54.751+10:00   START RequestId: MyID-91a3-f1e61c3c3f7a Version: $LATEST
2021-06-24T16:13:55.240+10:00   Get Request
2021-06-24T16:13:55.261+10:00   Method 1, i=1 of 3
2021-06-24T16:13:55.380+10:00   Method 1, i=2 of 3
2021-06-24T16:13:55.481+10:00   Method 1, i=3 of 3
2021-06-24T16:13:56.160+10:00   About to send message to queue: https://sqs.ap-southeast-2.amazonaws.com/MyID/MyQueue.fifo
2021-06-24T16:14:32.503+10:00   System.Net.Http.HttpRequestException: Connection refused
2021-06-24T16:14:32.503+10:00   ---> System.Net.Sockets.SocketException (111): Connection refused
2021-06-24T16:14:32.503+10:00   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   --- End of inner exception stack trace ---
2021-06-24T16:14:32.503+10:00   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.HttpHandler`1.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.SQS.Internal.ValidationResponseHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at TestAsyncFromLambda.Functions.Get(APIGatewayProxyRequest request, ILambdaContext context) in 

我相信我使用的一切都是最新版本: 在此处输入图像描述

我还尝试了 C# async await 语法的其他一些排列,结果相同。

responseSendMsg = sqsClient.SendMessageAsync(sendMessageRequest).GetAwaiter().GetResult(); ;

API GW、Lambda 或 SQS 不涉及 VPC,一切都在同一个 AWS 账户中,只是一个基本的简单设置。

我还做了一个测试,我将 Lambda 发送到的 SQS 中的队列 URL 更改为不存在的无效队列名称,并且我得到了相同的行为,因此这意味着 SQS 发送请求可能甚至永远不会到达 AWS。

对于 SQS 的 AWS .net 开发工具包,在使用 SQS 的同步和异步方法之间没有选择,只有异步:https ://docs.aws.amazon.com/sdk-for-net/latest/developer-指南/SendMessage.html

Lambda 函数附加到具有这些权限的角色,我认为这应该足够了: 在此处输入图像描述 我什至添加了“AdministratorAccess”角色,所以我假设只要 Lambda 和 SQS 在同一个 AWS 账户上,Lambda 就会有访问发送到 SQS。

文件 aws-lambda-tools-defaults.json 具有:

{
    "Information" : [
        "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
        "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
        "dotnet lambda help",
        "All the command line options for the Lambda command can be specified in this file."
    ],
    "profile"     : "myProfile",
    "region"      : "ap-southeast-2",
    "configuration" : "Release",
    "framework"     : "netcoreapp3.1",
    "s3-prefix"     : "TestAsyncFromLambda/",
    "template"      : "serverless.template",
    "template-parameters" : "",
    "s3-bucket"           : "awsserverless2stack-bucket-myID",
    "stack-name"          : "TestAsyncFromLambda"
}

文件 serverless.template 是:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::Serverless-2016-10-31",
  "Description": "An AWS Serverless Application by matt.",
  "Resources": {
    "Get": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Handler": "TestAsyncFromLambda::TestAsyncFromLambda.Functions::Get",
        "Runtime": "dotnetcore3.1",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 187,
        "Role": null,
        "Policies": [
          "AWSLambdaBasicExecutionRole",
          "AmazonSQSFullAccess",
          "AdministratorAccess"
        ],
        "Events": {
          "RootGet": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        }
      }
    }
  },
  "Outputs": {
    "ApiURL": {
      "Description": "API endpoint URL for Prod environment",
      "Value": {
        "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
      }
    }
  }
}

我已经通过 AWS 控制台 Web UI 和 Cloud Formation 脚本尝试了手动创建的队列,示例取自此处

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "MyQueue": {
      "Properties": {
        "QueueName": "MyQueue.fifo",
        "FifoQueue": true,
        "ContentBasedDeduplication": true
      },
      "Type": "AWS::SQS::Queue"
    }
  },
  "Outputs": {
    "QueueName": {
      "Description": "The name of the queue",
      "Value": {
        "Fn::GetAtt": [
          "MyQueue",
          "QueueName"
        ]
      }
    },
    "QueueURL": {
      "Description": "The URL of the queue",
      "Value": {
        "Ref": "MyQueue"
      }
    },
    "QueueARN": {
      "Description": "The ARN of the queue",
      "Value": {
        "Fn::GetAtt": [
          "MyQueue",
          "Arn"
        ]
      }
    }
  }
}

我将 lambda 超时增加到一个长的自定义值:

  1. 将该持续时间内的超时与其他超时区分开来
  2. 给 SQS 发送更多时间重试和/或超时

我通过更改 serverless.template 来增加超时:

"Timeout": 187,

在增加超时之前,当它是默认的 30 秒时,我在云观察日志中没有异常,就像 Lambda 超时一样。

标签: c#.netamazon-web-servicesaws-lambdaamazon-sqs

解决方案


正如我在评论中提到的,用于 AWS 服务 URL 和队列 URL 的 URL 值似乎不正确。这就是发送消息时连接失败的原因。

因此,您需要确保它SQSConstants.AWS_SERVICE_URL具有SQSConstants.MyQueueUrl正确的值。


推荐阅读