你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
重要
本文仅介绍 Azure Cosmos DB Java SDK v4 的故障排除。 有关详细信息,请参阅 Azure Cosmos DB Java SDK v4 发行说明、 Maven 存储库和性能 提示 。 如果当前使用的是低于 v4 的版本,请参阅 迁移到 Azure Cosmos DB Java SDK v4 指南,获取升级到 v4 的帮助。
本文介绍将 Azure Cosmos DB Java SDK v4 与用于 NoSQL 帐户的 Azure Cosmos DB 配合使用时的常见问题、解决方法、诊断步骤和工具。 Azure Cosmos DB Java SDK v4 提供用于访问 Azure Cosmos DB for NoSQL 的客户端逻辑表示形式。 本文介绍了在遇到任何问题时可以提供帮助的工具和方法。
请从以下列表开始:
- 请查看本文中的常见问题和解决方法部分。
- 查看 Azure Cosmos DB 中央存储库中的 Java SDK,该存储库在 GitHub 上提供开放源代码。 它有一个积极监控的问题部分。 检查是否已提交包含解决方法的任何类似问题。 一个有用的提示是按
*cosmos:v4-item*标记筛选问题。 - 查看 Azure Cosmos DB Java SDK v4 的性能提示 ,并遵循建议的做法。
- 如果找不到解决方案,请阅读本文的其余部分。 然后提交 GitHub 问题。 如果有向 GitHub 问题添加标记的选项,请添加标记
*cosmos:v4-item*。
捕获诊断数据
Java V4 SDK 中的数据库、容器、项和查询响应具有诊断属性。 此属性记录与单一请求相关的所有信息,包括是否发生重试或任何瞬时故障。
诊断以字符串的形式返回。 字符串会随着每个版本而更改,因为它在不断改进,以便更好地排查不同的情景。 对于每个版本的 SDK,字符串的格式可能会发生变化。 为避免破坏性变更,请勿解析字符串。
以下代码示例演示如何使用 Java V4 SDK 读取诊断日志:
重要
建议验证 Java V4 SDK 的最低建议版本,并确保使用此版本或更高版本。 可以 在此处查看建议的版本。
数据库操作
CosmosDatabaseResponse databaseResponse = client.createDatabaseIfNotExists(databaseName);
CosmosDiagnostics diagnostics = databaseResponse.getDiagnostics();
logger.info("Create database diagnostics : {}", diagnostics);
容器操作
CosmosContainerResponse containerResponse = database.createContainerIfNotExists(containerProperties,
throughputProperties);
CosmosDiagnostics diagnostics = containerResponse.getDiagnostics();
logger.info("Create container diagnostics : {}", diagnostics);
项操作
// Write Item
CosmosItemResponse<Family> item = container.createItem(family, new PartitionKey(family.getLastName()),
new CosmosItemRequestOptions());
CosmosDiagnostics diagnostics = item.getDiagnostics();
logger.info("Create item diagnostics : {}", diagnostics);
// Read Item
CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
new PartitionKey(documentLastName), Family.class);
CosmosDiagnostics diagnostics = familyCosmosItemResponse.getDiagnostics();
logger.info("Read item diagnostics : {}", diagnostics);
查询操作
String sql = "SELECT * FROM c WHERE c.lastName = 'Witherspoon'";
CosmosPagedIterable<Family> filteredFamilies = container.queryItems(sql, new CosmosQueryRequestOptions(),
Family.class);
// Add handler to capture diagnostics
filteredFamilies = filteredFamilies.handle(familyFeedResponse -> {
logger.info("Query Item diagnostics through handle : {}",
familyFeedResponse.getCosmosDiagnostics());
});
// Or capture diagnostics through iterableByPage() APIs.
filteredFamilies.iterableByPage().forEach(familyFeedResponse -> {
logger.info("Query item diagnostics through iterableByPage : {}",
familyFeedResponse.getCosmosDiagnostics());
});
Azure Cosmos DB 异常
try {
CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
new PartitionKey(documentLastName), Family.class);
} catch (CosmosException ex) {
CosmosDiagnostics diagnostics = ex.getDiagnostics();
logger.error("Read item failure diagnostics : {}", diagnostics);
}
记录诊断
Java V4 SDK 从 v4.43.0 及更高版本开始支持根据特定条件自动记录所有请求或错误的 Cosmos 诊断日志。 应用程序开发人员可以为延迟、请求费用和有效负载大小设置阈值,这些阈值适用于点操作(创建、读取、替换、更新插入、修补)以及非点操作(查询、更改源、批量和批处理)。 如果请求超过这些定义的阈值,这些请求的 Cosmos 诊断信息将会被自动生成。
默认情况下,Java v4 SDK 以特定格式自动记录这些诊断。 但是,可以通过实现 CosmosDiagnosticsHandler 接口并提供自己的自定义诊断处理程序来更改此设置。
在创建同步或异步客户端时,可以在CosmosClientTelemetryConfig对象中使用CosmosDiagnosticsThresholds和CosmosDiagnosticsHandler,并将该对象传入CosmosClientBuilder。
注意:这些诊断阈值应用于不同类型的诊断,包括日志记录、跟踪和客户端遥测。
以下代码示例演示如何定义诊断阈值、自定义诊断记录器,并通过客户端遥测配置使用这些阈值:
定义自定义诊断阈值
// Create diagnostics threshold
CosmosDiagnosticsThresholds cosmosDiagnosticsThresholds = new CosmosDiagnosticsThresholds();
// These thresholds are for demo purposes
// NOTE: Do not use the same thresholds for production
cosmosDiagnosticsThresholds.setPayloadSizeThreshold(100_00);
cosmosDiagnosticsThresholds.setPointOperationLatencyThreshold(Duration.ofSeconds(1));
cosmosDiagnosticsThresholds.setNonPointOperationLatencyThreshold(Duration.ofSeconds(5));
cosmosDiagnosticsThresholds.setRequestChargeThreshold(100f);
定义自定义诊断处理程序
// By default, DEFAULT_LOGGING_HANDLER can be used
CosmosDiagnosticsHandler cosmosDiagnosticsHandler = CosmosDiagnosticsHandler.DEFAULT_LOGGING_HANDLER;
// App developers can also define their own diagnostics handler
cosmosDiagnosticsHandler = new CosmosDiagnosticsHandler() {
@Override
public void handleDiagnostics(CosmosDiagnosticsContext diagnosticsContext, Context traceContext) {
logger.info("This is custom diagnostics handler: {}", diagnosticsContext.toJson());
}
};
定义 CosmosClientTelemetryConfig
// Create Client Telemetry Config
CosmosClientTelemetryConfig cosmosClientTelemetryConfig =
new CosmosClientTelemetryConfig();
cosmosClientTelemetryConfig.diagnosticsHandler(cosmosDiagnosticsHandler);
cosmosClientTelemetryConfig.diagnosticsThresholds(cosmosDiagnosticsThresholds);
// Create sync client
CosmosClient client = new CosmosClientBuilder()
.endpoint(AccountSettings.HOST)
.key(AccountSettings.MASTER_KEY)
.clientTelemetryConfig(cosmosClientTelemetryConfig)
.buildClient();
重试设计
有关如何设计可复原的应用程序的指导并了解哪些是 SDK 的重试语义,请参阅使用 Azure Cosmos DB SDK 设计可复原的应用程序指南。
常见问题和解决方法
检查门户指标
检查 门户指标 有助于确定它是客户端问题还是服务出现问题。 例如,如果指标中包含较高比率的速率受限请求(HTTP 状态代码 429,表示请求受到限制),请查看请求速率过大部分。
网络问题,Netty 读取超时失败,吞吐量低,延迟高
常规建议
为获得最佳性能:
- 确保应用在 Azure Cosmos DB 帐户所在的同一区域中运行。
- 检查运行应用的主机上的 CPU 使用情况。 如果 CPU 使用率为 50% 或更多,请在配置较高的主机上运行应用。 或者,可以在更多计算机上分配负载。
- 如果在 Azure Kubernetes 服务上运行应用程序,可以使用 Azure Monitor 监视 CPU 利用率。
连接节流
由于主机计算机上的连接限制或Azure SNAT(PAT)端口耗尽,可能会发生连接限制。
主机上的连接限制
某些 Linux 系统(如 Red Hat)对打开的文件总数有上限。 Linux 中的套接字作为文件实现,因此此数字也会限制连接总数。 运行以下命令。
ulimit -a
允许的最大打开文件数(标识为“nofile”)需要至少是连接池大小的两倍。 有关详细信息,请参阅 Azure Cosmos DB Java SDK v4 性能提示。
Azure SNAT (PAT) 端口耗尽
如果应用部署在没有公共 IP 地址的 Azure 虚拟机上,则默认情况下 ,Azure SNAT 端口 与 VM 外部的任何终结点建立连接。 从 VM 到 Azure Cosmos DB 终结点,允许的连接数受 Azure SNAT 配置的限制。
仅当 VM 具有专用 IP 地址并且 VM 中的进程尝试连接到公共 IP 地址时,才使用 Azure SNAT 端口。 有两种解决方法可以避免 Azure SNAT 限制:
将您的 Azure Cosmos DB 服务终结点添加到 Azure 虚拟机虚拟网络的子网中。 有关详细信息,请参阅 Azure 虚拟网络服务终结点。
启用服务终结点后,不再从公共 IP 向 Azure Cosmos DB 发送请求, 而是发送虚拟网络和子网标识。 如果仅允许公共 IP,则此更改可能会导致防火墙丢失。 如果使用防火墙,则在启用服务终结点后,请使用虚拟网络 ACL 将子网添加到防火墙。
将公共 IP 分配给 Azure VM。
无法访问服务 - 防火墙
ConnectTimeoutException 指示 SDK 无法访问服务。
使用直接模式时,可能会遇到类似于以下内容的故障:
GoneException{error=null, resourceAddress='https://cdb-ms-prod-westus-fd4.documents.azure.com:14940/apps/e41242a5-2d71-5acb-2e00-5e5f744b12de/services/d8aa21a5-340b-21d4-b1a2-4a5333e7ed8a/partitions/ed028254-b613-4c2a-bf3c-14bd5eb64500/replicas/131298754052060051p//', statusCode=410, message=Message: The requested resource is no longer available at the server., getCauseInfo=[class: class io.netty.channel.ConnectTimeoutException, message: connection timed out: cdb-ms-prod-westus-fd4.documents.azure.com/101.13.12.5:14940]
如果在应用计算机上运行防火墙,请打开直接模式使用的端口范围 10,000 到 20,000。 另请遵循 主机上的“连接”限制。
UnknownHostException
UnknownHostException 表示 Java 框架无法解析受影响计算机中 Azure Cosmos DB 终结点的 DNS 条目。 应验证计算机是否可解析 DNS 条目,或者,如果你有任何自定义 DNS 解析软件(例如 VPN 或代理,或自定义解决方案),请确保它包含的配置适用于此错误宣称的无法解析的 DNS 终结点。 如果不断出现错误,可以通过 curl 命令验证计算机对错误中所述的终结点的 DNS 解析。
HTTP 代理
如果使用 HTTP 代理,请确保它支持 SDK ConnectionPolicy 中配置的连接数。
否则,将遇到连接问题。
无效的编码模式:阻止 Netty IO 线程
SDK 使用 Netty IO 库与 Azure Cosmos DB 通信。 SDK 具有异步 API,并使用 Netty 的非阻止 IO API。 SDK 的 IO 工作在 IO Netty 线程上执行。 IO Netty 线程数配置为与应用计算机的 CPU 核心数相同。
Netty IO 线程仅用于非阻塞 Netty IO 工作。 SDK 将其中一个 Netty IO 线程上的 API 调用结果返回到应用的代码。 如果应用在 Netty 线程上收到结果后执行持久作,则 SDK 可能没有足够的 IO 线程来执行其内部 IO 工作。 此类应用编码可能会导致低吞吐量、高延迟和 io.netty.handler.timeout.ReadTimeoutException 故障。 解决方法是在你知道操作需要时间时切换线程。
例如,查看以下代码片段,该代码片段将项添加到容器( 在此处 查找有关设置数据库和容器的指导)。可以在 Netty 线程上执行耗时超过几毫秒的长期工作。 如果是这样,最终你可能会进入一种没有 Netty IO 线程来处理 IO 工作的状态。 因此,你会收到 ReadTimeoutException 失败。
Java SDK V4 (Maven com.azure::azure-cosmos) 异步 API
//Bad code with read timeout exception
int requestTimeoutInSeconds = 10;
/* ... */
AtomicInteger failureCount = new AtomicInteger();
// Max number of concurrent item inserts is # CPU cores + 1
Flux<Family> familyPub =
Flux.just(Families.getAndersenFamilyItem(), Families.getAndersenFamilyItem(), Families.getJohnsonFamilyItem());
familyPub.flatMap(family -> {
return container.createItem(family);
}).flatMap(r -> {
try {
// Time-consuming work is, for example,
// writing to a file, computationally heavy work, or just sleep.
// Basically, it's anything that takes more than a few milliseconds.
// Doing such operations on the IO Netty thread
// without a proper scheduler will cause problems.
// The subscriber will get a ReadTimeoutException failure.
TimeUnit.SECONDS.sleep(2 * requestTimeoutInSeconds);
} catch (Exception e) {
}
return Mono.empty();
}).doOnError(Exception.class, exception -> {
failureCount.incrementAndGet();
}).blockLast();
assert(failureCount.get() > 0);
解决方法是更改执行耗时工作的线程。 为应用定义调度器的单例实例。
Java SDK V4 (Maven com.azure::azure-cosmos) 异步 API
// Have a singleton instance of an executor and a scheduler.
ExecutorService ex = Executors.newFixedThreadPool(30);
Scheduler customScheduler = Schedulers.fromExecutor(ex);
可能需要执行需要时间的工作,例如计算密集型工作或阻塞 IO。 在这种情况下,请使用.publishOn(customScheduler) API 将线程切换为由你的customScheduler提供的工作线程。
Java SDK V4 (Maven com.azure::azure-cosmos) 异步 API
container.createItem(family)
.publishOn(customScheduler) // Switches the thread.
.subscribe(
// ...
);
通过使用 publishOn(customScheduler),可以释放 Netty IO 线程并切换到自定义计划程序提供自己的自定义线程。 此修改解决了问题。 你不会再有 io.netty.handler.timeout.ReadTimeoutException 故障了。
请求速率过大
此故障是服务器端故障。 它指示已使用预配的吞吐量。 请稍后重试。 如果经常遇到此故障,请考虑增加集合吞吐量。
在 getRetryAfterInMilliseconds 的间隔时间内实现退避
在性能测试期间,应增加负载,直到小部分请求被限制。 如果受到限制,客户端应用程序应为服务器指定的重试间隔退避。 恪守退避策略可确保在重试之间将等待时间最小化。
来自 Java SDK 反应链的错误处理
在 Azure Cosmos DB Java SDK 中的错误处理对于客户端应用程序的逻辑非常重要。 反应器核心框架提供了不同的错误处理机制,可在不同的方案中使用。 我们建议客户详细了解这些错误处理运算符,并使用最适合其重试逻辑方案的运算符。
重要
不建议使用 onErrorContinue() 运算符,因为所有方案中都不支持它。
请注意,onErrorContinue() 是一个专用操作符,可能会使响应链的行为变得不明确。 它在上游(而非下游)操作符上运行,需要特定的操作符支持才能工作,作用域可以轻松传播到未预料的库代码中,从而导致意外行为。 有关此特殊运算符的更多详细信息,请参阅相关文档onErrorContinue()。
连接到 Azure Cosmos DB 模拟器失败
Azure Cosmos DB 模拟器 HTTPS 证书是自签名的。 若要使 SDK 使用模拟器,请将模拟器证书导入 Java TrustStore。 有关详细信息,请参阅 导出 Azure Cosmos DB 模拟器证书。
依赖项冲突问题
Azure Cosmos DB Java SDK 提取许多依赖项;一般来说,如果项目依赖项树包含 Azure Cosmos DB Java SDK 依赖的项目的较旧版本,这可能会导致运行应用程序时生成意外错误。 如果您正在调试应用程序意外抛出异常的原因,最好仔细检查您的依赖关系树是否无意间拉入了一个或多个 Azure Cosmos DB Java SDK 的旧版本依赖项。
此类问题的解决方法是确定哪些项目依赖项引入旧版本,并排除该旧版本上的可传递依赖项,并允许 Azure Cosmos DB Java SDK 引入较新版本。
若要确定哪些项目依赖项引入 Azure Cosmos DB Java SDK 所依赖的较旧版本,请针对项目 pom.xml 文件运行以下命令:
mvn dependency:tree
有关详细信息,请参阅 maven 依赖项树指南。
知道项目的依赖项依赖于旧版本后,可以修改 pom 文件中该库的依赖项,并排除可传递依赖项,以下示例如下(假定 reactor-core 是过时依赖项):
<dependency>
<groupId>${groupid-of-lib-which-brings-in-reactor}</groupId>
<artifactId>${artifactId-of-lib-which-brings-in-reactor}</artifactId>
<version>${version-of-lib-which-brings-in-reactor}</version>
<exclusions>
<exclusion>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</exclusion>
</exclusions>
</dependency>
有关详细信息,请参阅 排除可传递依赖项指南。
启用客户端 SDK 日志记录
Azure Cosmos DB Java SDK v4 使用 SLF4j 作为日志记录外观,支持登录到常用的日志记录框架,例如 log4j 和 logback。
例如,如果要使用 log4j 作为日志记录框架,请在 Java 类路径中添加以下库。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
此外,添加 log4j 配置。
# this is a sample log4j configuration
# Set root logger level to INFO and its only appender to A1.
log4j.rootLogger=INFO, A1
log4j.category.com.azure.cosmos=INFO
#log4j.category.io.netty=OFF
#log4j.category.io.projectreactor=OFF
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5X{pid} [%t] %-5p %c - %m%n
有关详细信息,请参阅 sfl4j 日志记录手册。
OS 网络统计信息
运行 netstat 命令,了解状态(如 ESTABLISHED 和 CLOSE_WAIT)中的连接数。
在 Linux 上,可以运行以下命令。
netstat -nap
在 Windows 上,可以使用不同的参数标志运行相同的命令:
netstat -abn
将结果筛选为仅包括连接到 Azure Cosmos DB 终结点。
与 Azure Cosmos DB 终结点在 ESTABLISHED 状态的连接数不能大于您配置的连接池大小。
与 Azure Cosmos DB 终结点的许多连接可能处于 CLOSE_WAIT 状态。 可能超过 1,000 个。 如此高的数值表明连接已建立并快速拆除。 这种情况可能会导致问题。 有关详细信息,请参阅 “常见问题和解决方法 ”部分。
常见查询问题
查询指标有助于确定查询大部分时间花费在何处。 在查询指标中,可以查看查询在客户端与后端上花费的时间。 详细了解查询性能指南。
后续步骤
- 了解 Java SDK v4 的性能指南
- 了解 Java SDK v4 的最佳做法