本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
问题排查常见问题
在应用程序中使用AWS SDK for Java 2.x 时,您可能会遇到本主题中列出的运行时错误。使用此处的建议来帮助找出根本原因并解决错误。
如何修复“java.net.SocketException: Connection reset”或“server failed to complete the response”错误?
连接重置错误表示您的主机、AWS 服务或任何中间方(例如,NAT 网关、代理、负载均衡器)在请求完成之前关闭了连接。由于原因有很多,因此要找到解决方案,就必须知道连接关闭的原因。以下各项通常会导致连接关闭。
-
连接处于非活动状态。这种情况在流式操作中很常见:由于一段时间内没有任何数据通过网络进行读写,中间方检测到连接已经失效并将其关闭。为防止出现这种情况,请确保您的应用程序会主动下载或上传数据。
-
您关闭了 HTTP 或 SDK 客户端。确保不要在资源仍在使用时将其关闭。
-
代理配置错误。尝试绕过您配置的任何代理,以查看这样是否可以解决问题。如果这样可以解决问题,则表明代理出于某种原因正在关闭您的连接。请研究您的特定代理以确定代理关闭连接的原因。
如果您无法识别问题,请尝试在网络的客户端边缘(例如,在您控制的任何代理之后)为受影响的连接运行 TCP 转储。
如果您看到 AWS 端点正在发送 TCP RST(重置),请联系受影响服务的支持人员
如何修复“连接超时”?
连接超时错误表示您的主机、AWS 服务或任何中间方(例如,NAT 网关、代理、负载均衡器)未能在配置的连接超时时间内与服务器建立新连接。以下项目列出了导致此问题的常见原因。
-
配置的连接超时太低。默认情况下,AWS SDK for Java 2.x 中的连接超时为 2 秒。如果将连接超时设置得太低,则可能会出现此错误。如果您只进行区域内调用,则建议的连接超时为 1 秒;如果您发出跨区域请求,则建议的连接超时为 3 秒。
-
代理配置错误。尝试绕过您配置的任何代理,以查看这样是否可以解决问题。如果这样可以解决问题,则表明连接超时的根源在于代理。研究您的特定代理以确定发生这种情况的原因
如果您无法识别问题,请尝试在网络的客户端边缘(例如,在您控制的任何代理之后)为受影响的连接运行 TCP 转储,以便调查是否存在任何网络问题。
如何修复“java.net.SocketTimeoutException: Read timed out”?
读取超时错误表示 JVM 尝试从底层操作系统读取数据,但未在通过 SDK 配置的时间内返回数据。如果操作系统、AWS 服务或任何中间方(例如,NAT 网关、代理、负载均衡器)未能在 JVM 预期的时间内发送数据,则会发生此错误。由于原因有很多,因此要找到解决方案,就必须知道为什么没有返回数据。
尝试在网络的客户端边缘(例如,在您控制的任何代理之后)为受影响的连接运行 TCP 转储。
如果您看到 AWS 端点正在发送 TCP RST(重置),请联系受影响服务的支持人员
如何修复“Unable to execute HTTP request: Timeout waiting for connection from pool”错误?
此错误表示请求无法在指定的最长时间内从池中获得连接。要解决此问题,我们建议您启用 SDK 客户端指标,以便将指标发布到 Amazon CloudWatch。HTTP 指标有助于缩小根本原因范围。以下项目列出了导致此错误的常见原因。
-
连接泄露。您可以通过检查
LeasedConcurrency、AvailableConcurrency和MaxConcurrency指标来调查此问题。如果LeasedConcurrency持续增长,直至达到MaxConcurrency但从未下降,则可能存在连接泄露。发生泄露的常见原因是流式操作(例如 S3getObject方法)未关闭。我们建议您的应用程序尽快读取输入流中的所有数据,之后关闭输入流。下图显示了连接泄露的 SDK 指标看起来可能是什么样子。
-
连接池匮乏。如果您的请求速率过高,并且已配置的连接池大小无法满足请求需求,则可能会发生这种情况。默认连接池大小为 50,当池中的连接达到最大值时,HTTP 客户端会将传入的请求排入队列,直到连接可用为止。下图显示了连接池匮乏的 SDK 指标看起来可能是什么样子。
要缓解此问题,请考虑采取以下任何措施。
-
增加连接池的大小。
-
增加获取超时。
-
降低请求速率。
通过增加最大连接数,可以增加客户端吞吐量(除非网络接口已充分利用)。但是,您最终可能会达到操作系统对进程所使用的文件描述符数量施加的限制。如果您已经充分使用网络接口或无法进一步增加连接数,请尝试增大获取超时时间。随着时间的增加,在超时之前,您可以获得更多时间来请求获取连接。如果连接没有释放,则后续请求仍将超时。
如果您无法通过前两种机制解决问题,请尝试通过以下选项来降低请求速率。
-
将您的请求流量平滑化,避免突发的大流量冲击导致客户端过载。
-
更高效地调用 AWS 服务。
-
增加发送请求的主机数量。
-
-
I/O 线程太忙。这仅适用于使用基于
NettyNioAsyncHttpClient的异步 SDK 客户端的情况。如果 AvailableConcurrency指标不低(表示池中存在连接),但ConcurrencyAcquireDuration很高,则可能是因为 I/O 线程无法处理请求。确保您未将Runnable:run作为未来完成执行器来传递,并且未在响应未来完成链中执行耗时的任务,因为这会阻塞 I/O 线程。如果情况并非如此,请考虑使用 eventLoopGroupBuilder方法增加 I/O 线程的数量。作为参考,NettyNioAsyncHttpClient实例的默认 I/O 线程数是主机 CPU 核心数的两倍。 -
TLS 握手延迟高。如果您的
AvailableConcurrency指标接近 0 且LeasedConcurrency小于MaxConcurrency,则可能是因为 TLS 握手延迟很高。下图显示了 TLS 握手延迟高的 SDK 指标可能是什么样子。
对于 Java SDK 提供的未基于 CRT 的 HTTP 客户端,请尝试启用 TLS 日志来解决 TLS 问题。对于基于 AWS CRT 的 HTTP 客户端,请尝试启用 AWS CRT 日志。如果您发现 AWS 端点似乎需要很长时间才能执行 TLS 握手,则应联系受影响服务的支持人员
。
我该如何修复 NoClassDefFoundError、NoSuchMethodError 或 NoSuchFieldError?
NoClassDefFoundError 表示在运行时无法加载某个类。出现此错误的两个最常见原因是:
-
因为 JAR 丢失或类路径上的 JAR 版本不正确,该类在类路径中不存在。
-
因为该类的静态初始化器引发了异常,所以该类无法加载。
同样,NoSuchMethodError 和 NoSuchFieldError 通常是由于 JAR 版本不匹配所致。我们建议您执行以下步骤。
-
检查您的依赖项,确保使用的所有 SDK JAR 包版本一致。找不到类、方法或字段的最常见原因是,您升级到新的客户端版本,但继续使用旧的“共享”SDK 依赖项版本。新的客户端版本可能会尝试使用仅存在于较新的“共享”SDK 依赖项中的类。尝试运行
mvn dependency:tree或gradle dependencies(对于 Gradle)来验证 SDK 库版本是否全部匹配。为了完全避免将来出现此问题,我们建议使用 BOM(物料清单)来管理 SDK 模块版本。下面的示例演示混合使用 SDK 版本的示例。
[INFO] +- software.amazon.awssdk:dynamodb:jar:2.20.00:compile [INFO] | +- software.amazon.awssdk:aws-core:jar:2.13.19:compile [INFO] +- software.amazon.awssdk:netty-nio-client:jar:2.20.00:compiledynamodb的版本是 2.20.00,aws-core的版本是 2.13.19。aws-core构件版本也应为 2.20.00。 -
在日志的早期部分检查相关语句,以查看某个类是否因静态初始化失败而无法加载。当类第一次加载失败时,可能会抛出一个不同的、更有用的异常来指定无法加载该类的原因。这个可能有用的异常只发生一次,因此后续日志语句只会报告未找到该类。
-
检查您的部署过程,确保所需的 JAR 文件已随应用程序一同部署。您可能确实使用了正确的版本进行构建,但为应用程序创建类路径的过程却排除了一个必需的依赖项。
如何修复“SignatureDoesNotMatch”错误或“The request signature we calculated does not match the signature you provided”错误?
SignatureDoesNotMatch 错误表示AWS SDK for Java 生成的签名和AWS 服务 生成的签名不匹配。以下项目说明了潜在原因。
-
代理或中间方修改请求。例如,代理或负载均衡器可能会修改由 SDK 签名的标头、路径或查询字符串。
-
服务和 SDK 在各自生成待签名字符串时,对请求进行编码的方式不一致。
要调试此问题,我们建议您为 SDK 启用调试日志记录。尝试重现错误并找到 SDK 生成的规范请求。在日志中,规范请求用 AWS4 Canonical
Request: ... 标记,待签名字符串用 AWS4 String to sign: ... 标记。
如果您无法启用调试(例如,因为错误只能在生产环境中重现),请向应用程序添加逻辑,以便在错误发生时记录有关请求的信息。然后,您可以使用该信息,尝试在生产环境之外(例如,在启用了调试日志记录的集成测试中)复现该错误。
收集规范请求和待签名字符串后,将其与 AWS 签名版本 4 规范进行比较,以确定 SDK 生成待签名字符串的方式是否存在问题。如果出现问题,可以创建 GitHub 错误报告
如果没有出现任何问题,您可以将 SDK 的待签名字符串与某些 AWS 服务(例如 Amazon S3)在失败响应中返回的待签名字符串进行比较。如果未提供此信息,则应联系受影响服务的支持人员
有关签署请求的更多背景信息,请参阅《AWS Identity and Access Management 用户指南》中的签署 AWS API 请求。
例规范请求的
PUT /Example-Bucket/Example-Object partNumber=19&uploadId=string amz-sdk-invocation-id:f8c2799d-367c-f024-e8fa-6ad6d0a1afb9 amz-sdk-request:attempt=1; max=4 content-encoding:aws-chunked content-length:51 content-type:application/octet-stream host:xxxxx x-amz-content-sha256:STREAMING-UNSIGNED-PAYLOAD-TRAILER x-amz-date:20240308T034733Z x-amz-decoded-content-length:10 x-amz-sdk-checksum-algorithm:CRC32 x-amz-trailer:x-amz-checksum-crc32
例待签名字符串的
AWS4-HMAC-SHA256 20240308T034435Z 20240308/us-east-1/s3/aws4_request 5f20a7604b1ef65dd89c333fd66736fdef9578d11a4f5d22d289597c387dc713
如何修复“java.lang.IllegalStateException: Connection pool shut down”错误?
此错误表示底层 Apache HTTP 连接池已关闭。以下项目说明了潜在原因。
-
SDK 客户端过早关闭。只有在关闭了关联客户端时,SDK 才会关闭连接池。确保不要在资源仍在使用时将其关闭。
-
出现
java.lang.Error错误。OutOfMemoryError等错误会导致 Apache HTTP 连接池关闭。检查您的日志,找出错误的堆栈跟踪信息。还要检查您的代码中是否存在捕获了 Throwable或Error但却“吞掉”输出,从而导致错误无法暴露出来的地方。如果您的代码未报告错误,请重写代码,以便可以记录信息。记录的信息有助于确定错误的根本原因。 -
在
DefaultCredentialsProvider#create()返回的凭证提供程序关闭后您仍然尝试使用它。DefaultCredentialsProvider#create返回一个单例实例,因此,如果它已关闭且您的代码调用了 resolveCredentials方法,则在缓存的凭证(或令牌)过期后会引发异常。检查您的代码中是否存在关闭了
DefaultCredentialsProvider的地方,如下面的示例中所示。-
通过调用
DefaultCredentialsProvider#close().来关闭单例实例。DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create(); // Singleton instance returned. AwsCredentials credentials = defaultCredentialsProvider.resolveCredentials(); // Make calls to AWS 服务. defaultCredentialsProvider.close(); // Explicit close. // Make calls to AWS 服务. // After the credentials expire, either of the following calls eventually results in a "Connection pool shut down" exception. credentials = defaultCredentialsProvider.resolveCredentials(); // Or credentials = DefaultCredentialsProvider.create().resolveCredentials();
-
在 try-with-resources 块中调用
DefaultCredentialsProvider#create()。try (DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create()) { AwsCredentials credentials = defaultCredentialsProvider.resolveCredentials(); // Make calls to AWS 服务. } // After the try-with-resources block exits, the singleton DefaultCredentialsProvider is closed. // Make calls to AWS 服务. DefaultCredentialsProvider defaultCredentialsProvider = DefaultCredentialsProvider.create(); // The closed singleton instance is returned. // If the credentials (or token) has expired, the following call results in the error. AwsCredentials credentials = defaultCredentialsProvider.resolveCredentials();
如果您的代码已关闭单例实例,并且您需要使用
DefaultCredentialsProvider解析凭证,则通过调用DefaultCredentialsProvider.builder().build()来创建新的非单例实例。 -
如何修复“Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain”?
此错误表示AWS SDK for Java 2.x 无法通过默认凭证提供程序链中的任何凭证提供程序找到有效的 AWS 凭证。SDK 会自动按特定顺序搜索凭证,当链中的所有提供程序都无法提供有效凭证时,就会发生此错误。
完整的错误消息通常如下所示(为了提高可读性,添加了行尾结束符号和缩进):
Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain( credentialsProviders=[ SystemPropertyCredentialsProvider(), EnvironmentVariableCredentialsProvider(), WebIdentityTokenCredentialsProvider(), ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])), ContainerCredentialsProvider(), InstanceProfileCredentialsProvider() ]) : [ SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., EnvironmentVariableCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., WebIdentityTokenCredentialsProvider(): To use web identity tokens, the 'sts' service module must be on the class path., ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])): Profile file contained no credentials for profile 'default': ProfileFile(sections=[]), ContainerCredentialsProvider(): Cannot fetch credentials from container - neither AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables are set., InstanceProfileCredentialsProvider(): Failed to load credentials from IMDS.]
常见原因和解决方案
检查您的凭证配置
当您使用默认凭证提供程序(在没有显式配置凭证的情况下调用 ServiceClient.create())时,SDK 会按特定顺序搜索凭证。查看默认凭证提供程序链的工作原理,以了解 SDK 会检查哪些凭证来源以及按什么顺序检查。
确保在您的环境中正确设置了您打算使用的凭证配置方法:
对于 Amazon EC2 实例
-
检查 IAM 角色:验证 IAM 角色是否已附加到您的实例。
-
IMDS 间歇性故障:如果您遇到间歇性故障(通常持续几百毫秒),这通常表示实例元数据服务(IMDS)出现暂时性网络问题。
解决方案:
-
启用调试日志记录以分析发生故障的时间和频率
-
考虑在应用程序中实施重试逻辑以应对与凭证相关的故障
-
检查您的实例和 IMDS 端点之间的网络连接问题
-
对于容器环境
确认已配置任务角色(Amazon ECS)或服务账户(Amazon EKS),并已设置所需的环境变量。
对于本地开发
确认已设置好环境变量、凭证文件或 IAM Identity Center 配置。
对于 Web 身份联合验证
-
验证配置:确认存在 Web 身份令牌文件且已配置所需的环境变量。
-
缺少 STS 模块依赖项:如果您看到错误
To use web identity tokens, the 'sts' service module must be on the class path,则需要将 STS 模块添加为依赖项。在使用 Amazon EKS 容器组身份或其他 Web 身份令牌身份验证时,这种情况很常见。解决方案:将 STS 模块添加到您的项目依赖项中:
-
<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sts</artifactId> </dependency>对于某些服务,您可能还需要
aws-query-protocol依赖项:<dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>aws-query-protocol</artifactId> </dependency>
-
网络或代理连接问题
如果您在凭证提供程序链中看到 Connection refused 错误,这通常表示 SDK 尝试访问 AWS 端点时存在网络连接问题。
解决方案:
-
如果正在使用代理服务器,请验证代理配置
-
确认您的网络是否允许向 AWS 端点发起出站 HTTPS 连接
-
启用调试日志记录以查看详细的连接尝试
-
使用
curl等工具测试连接,以便验证 AWS 端点的网络访问