排查将 Azure Cosmos DB Java SDK v4 与 SQL API 帐户配合使用时出现的问题

适用于: SQL API

重要

本文仅介绍 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 与 Azure Cosmos DB SQL API 帐户配合使用时的常见问题、解决方法、诊断步骤和工具。 Azure Cosmos DB Java SDK v4 提供客户端逻辑表示用于访问 Azure Cosmos DB SQL API。 本文介绍了在遇到任何问题时可以提供帮助的工具和方法。

从本列表开始:

  • 请查看本文中的常见问题和解决方法部分。
  • 查看 Azure Cosmos DB 中心存储库中的 Java SDK,它以 GitHub 上的开放源代码的形式提供。 该 SDK 拥有受到主动监视的问题部分。 检查是否已提交包含解决方法的任何类似问题。 一个有用的提示是通过 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());
});

Cosmos 异常

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);
}

重试设计

有关如何设计可复原的应用程序的指导并了解哪些是 SDK 的重试语义,请参阅使用 Azure Cosmos SDK 设计可复原的应用程序指南。

常见问题和解决方法

网络问题、Netty 读取超时故障、低吞吐量、高延迟

常规建议

为获得最佳性能:

  • 确保应用与 Azure Cosmos DB 帐户在同一区域运行。
  • 检查运行应用的主机 CPU 使用情况。 如果 CPU 使用率为 50% 或更高,请在具有更高配置的主机上运行应用。 或者,可将负载分发到更多计算机。

连接限制

连接限制可能会因主机上的连接限制Azure SNAT (PAT) 端口耗尽而出现。

主机上的连接限制

某些 Linux 系统(例如 CentOS)对打开的文件总数存在上限。 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 虚拟机虚拟网络的子网添加 Azure Cosmos DB 服务终结点。 有关详细信息,请参阅 Azure 虚拟网络服务终结点

    启用服务终结点后,不再从公共 IP 向 Azure Cosmos DB 发送请求, 而是发送虚拟网络和子网标识。 如果仅允许公共 IP,则此更改可能会导致防火墙丢失。 如果使用防火墙,则在启用服务终结点后,请使用虚拟网络 ACL 将子网添加到防火墙。

  • 将公共 IP 分配给 Azure VM。

不能访问服务 - 防火墙

ConnectTimeoutException 指示 SDK 不能访问服务。 使用直接模式时,可能会出现如下所示的故障:

GoneException{error=null, resourceAddress='https://cdb-ms-prod-chinanorth-fd4.documents.azure.cn: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-chinanorth-fd4.documents.azure.cn/101.13.12.5:14940]

如果应用计算机上有防火墙运行,请打开 10,000 到 20,000 这一端口范围,该范围由直接模式使用。 另请按主机上的连接限制中的说明操作。

UnknownHostException

UnknownHostException 表示 Java 框架无法解析受影响计算机中 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 反应式链中的错误处理

当涉及到客户端的应用程序逻辑时,Cosmos DB Java SDK 中的错误处理很重要。 reactor-core 框架提供了不同的错误处理机制,可用于不同的方案。 建议客户详细了解这些错误处理操作符,并使用最适合其重试逻辑方案的操作符。

重要

不建议使用 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 文件中该 lib 上的依赖项并排除可传递依赖项,如下所示(假定 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 classpath 中添加以下库。

<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 命令,掌握处于 ESTABLISHEDCLOSE_WAIT 等状态的连接数。

在 Linux 上可以运行以下命令。

netstat -nap

在 Windows 上,可以使用不同的参数标志运行同一命令:

netstat -abn

筛选结果,以便仅显示到 Azure Cosmos DB 终结点的连接。

处于 ESTABLISHED 状态的 Azure Cosmos DB 终结点连接数不应大于配置的连接池大小。

到 Azure Cosmos DB 终结点的许多连接可能会处于 CLOSE_WAIT 状态。 可能会有超过 1,000 个连接。 较大的数字表明建立和销毁连接的速度很快。 这种情况可能会导致问题。 有关详细信息,请参阅常见问题和解决方法部分。