Java Cosmos DB SDK 4.52.0读取数据时频繁出现状态码0的CosmosException
Java Cosmos DB SDK 频繁CosmosException排查与解决
问题背景
使用Java版Cosmos DB SDK 4.52.0同步读取容器数据时,频繁触发CosmosException,异常细节不足以定位根源。Cosmos DB主账号部署在美东(读写),日东部署只读副本;应用通过Spring Boot activeProfile在日东环境将日东设为首选区域。
配置信息
cosmosdb.endpoint = https://redacted.documents.azure.com:443/ cosmosdb.database = database1 cosmosdb.container = container1 cosmosdb.max-retry-wait-time = PT1S cosmosdb.max-retry-attempts-on-throttled-requests = 0 cosmosdb.content-response-on-write-enabled = false cosmosdb.cosmos-item-request-options.cosmos-end-to-end-operation-latency-policy-config-duration = PT0.020S cosmosdb.connection-timeout = PT5S cosmosdb.idle-connection-timeout = PT5M cosmosdb.max-connections-per-endpoint = 100 cosmosdb.max-requests-per-connection = 30
核心代码片段
配置类代码
import com.azure.cosmos.CosmosEndToEndOperationLatencyPolicyConfig; import com.azure.cosmos.CosmosEndToEndOperationLatencyPolicyConfigBuilder; import com.azure.cosmos.GatewayConnectionConfig; import com.azure.cosmos.ThrottlingRetryOptions; import com.azure.cosmos.ConsistencyLevel; import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosClientBuilder; import com.azure.cosmos.CosmosContainer; import com.azure.cosmos.models.CosmosItemRequestOptions; import com.azure.cosmos.models.CosmosPatchItemRequestOptions; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; import java.util.List; @Slf4j @Configuration public class CosmosClientConfigurer { @Bean GatewayConnectionConfig getGatewayConnectionConfig(@Value("${cosmosdb.idle-connection-timeout}") final Duration idleConnectionTimeout, @Value("${cosmosdb.max-connections-per-endpoint}") final int maxConnectionsPerEndpoint) { final var gatewayConnectionConfig = new GatewayConnectionConfig() .setIdleConnectionTimeout(idleConnectionTimeout) .setMaxConnectionPoolSize(maxConnectionsPerEndpoint); log.info("GatewayConnectionConfig: {}", gatewayConnectionConfig); return gatewayConnectionConfig; } @Bean ThrottlingRetryOptions getThrottlingRetryOptions( @Value("${cosmosdb.max-retry-wait-time}") final Duration maxRetryWaitTime, @Value("${cosmosdb.max-retry-attempts-on-throttled-requests}") final int maxRetryAttemptsOnThrottledRequests) { final var throttlingRetryOptions = new ThrottlingRetryOptions() .setMaxRetryWaitTime(maxRetryWaitTime) .setMaxRetryAttemptsOnThrottledRequests(maxRetryAttemptsOnThrottledRequests); log.info("ThrottlingRetryOptions: {}", throttlingRetryOptions); return throttlingRetryOptions; } @Bean CosmosItemRequestOptions getCosmosItemRequestOptions( @Value("${cosmosdb.cosmos-item-request-options.cosmos-end-to-end-operation-latency-policy-config-duration}") final Duration duration) { final CosmosEndToEndOperationLatencyPolicyConfig latencyPolicyConfig = new CosmosEndToEndOperationLatencyPolicyConfigBuilder(duration).build(); final CosmosItemRequestOptions cosmosItemRequestOptions = new CosmosPatchItemRequestOptions(); cosmosItemRequestOptions.setCosmosEndToEndOperationLatencyPolicyConfig(latencyPolicyConfig); return cosmosItemRequestOptions; } CosmosContainer getUserProfileContainer(final CosmosClient cosmosClient, @Value("${cosmosdb.database}") final String database, @Value("${cosmosdb.container}") final String container) { return getCosmosContainer(cosmosClient, database, container); } private CosmosContainer getCosmosContainer(final CosmosClient cosmosClient, final String database, final String container) { return cosmosClient .getDatabase(database) .getContainer(container); } @Bean CosmosClient getCosmosClient(@Value("${cosmosdb.endpoint}") final String endpoint, @Value("${cosmosdb.content-response-on-write-enabled}") final boolean contentResponseOnWriteEnabled, @Value("${cosmosdb.key}") final String key, @Value("${spring.profiles.active}") final String activeProfile, final GatewayConnectionConfig gatewayConnectionConfig, final ThrottlingRetryOptions throttlingRetryOptions) { final List<String> japanEastRegion = List.of("Japan East"); final CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder() .endpoint(endpoint) .key(key) .throttlingRetryOptions(throttlingRetryOptions) .gatewayMode(gatewayConnectionConfig) .contentResponseOnWriteEnabled(contentResponseOnWriteEnabled) .consistencyLevel(ConsistencyLevel.EVENTUAL); if (activeProfile.equals("jpe")) cosmosClientBuilder.preferredRegions(japanEastRegion); return cosmosClientBuilder.buildClient(); } }
调用代码
public Optional<CosmosData> read(final String id) { try { final var startTime = Instant.now(); final CosmosItemResponse<CosmosData> cosmosItemResponse = readFromCosmos(id); cosmosMetrics.recordClientSideReadLatency(Duration.between(startTime, Instant.now())); cosmosMetrics.recordServerSideReadLatency(cosmosItemResponse.getDuration()); cosmosMetrics.recordReadRus(cosmosItemResponse.getRequestCharge()); cosmosMetrics.countContainerStatusCodes(cosmosItemResponse.getStatusCode()); return Optional.of(cosmosItemResponse.getItem()); } catch (CosmosException e) { cosmosMetrics.countContainerStatusCodes(e.getStatusCode()); } catch (Exception e) { cosmosMetrics.incrementErrorsForUserProfile(e); } return Optional.empty(); } public CosmosItemResponse<CosmosData> readFromCosmos(final String id) throws CosmosException { final PartitionKey partitionKey = new PartitionKey(id); try { return cosmosContainer.readItem( id, partitionKey, cosmosItemRequestOptions, CosmosData.class ); } catch (CosmosException e) { if (e.getStatusCode() == 404 || e.getStatusCode() == 408 || e.getStatusCode() == 409 || e.getStatusCode() == 410 || e.getStatusCode() == 429 || e.getStatusCode() == 503) { log.info("Exception while reading for id: {}: {}", id, e.getMessage()); } else { log.error("CosmosException for id {}: {}", id, e.getMessage()); } throw e; } }
捕获到的CosmosException片段
{ "innerErrorMessage": null, "cosmosDiagnostics": { "userAgent": "azsdk-java-cosmos/4.52.0 Linux/5.15.0-1051-azure JRE/21.0.1", "activityId": "4f02cde3-c9c9-4c09-af6b-258f4d82db5d", "requestLatencyInMs": 0, "requestStartTimeUTC": "2023-12-15T18:23:24.381516148Z", "requestEndTimeUTC": "2023-12-15T18:23:24.381762054Z", "responseStatisticsList": [], "supplementalResponseStatisticsList": [], "addressResolutionStatistics": {}, "regionsContacted": [ "japan east" ], "retryContext": { "statusAndSubStatusCodes": null, "retryLatency": 0, "retryCount": 0 }, "metadataDiagnosticsContext": { "metadataDiagnosticList": null }, "serializationDiagnosticsContext": { "serializationDiagnosticsList": null }, "gatewayStatisticsList": [ { "sessionToken": null, "operationType": "Read", "resourceType": "Document", "statusCode": 0, "subStatusCode": 0, "requestCharge": 0, "requestTimeline": [ { "eventName": "connectionAcquired", "startTimeUTC": "2023-12-15T18:23:24.381635051Z", "durationInMilliSecs": 0.122203 }, { "eventName": "connectionConfigured", "startTimeUTC": null, "durationInMilliSecs": 0 }, { "eventName": "requestSent", "startTimeUTC": null, "durationInMilliSecs": 0 }, { "eventName": "transitTime", "startTimeUTC": null, "durationInMilliSecs": 0 }, { "eventName": "received", "startTimeUTC": null, "durationInMilliSecs": 0 } ], "partitionKeyRangeId": null, "responsePayloadSizeInBytes": 0, "exceptionResponseHeaders": "{}" } ], "samplingRateSnapshot": 1, "systemInformation": { "usedMemory": "20807154 KB", "availableMemory": "10650126 KB", "systemCpuLoad": "(2023-12-15T18:22:55.851238339Z 40.2%), (2023-12-15T18:23:00.851247379Z 37.0%), (2023-12-15T18:23:05.851238334Z 35.5%), (2023-12-15T18:23:10.851252759Z 38.7%), (2023-12-15T18:23:15.851245468Z 42.3%), (2023-12-15T18:23:20.851240443Z 45.6%)", "availableProcessors": 31 }, "clientCfgs": { "id": 2, "machineId": "vmId_17b8a761-9046-429e-8c32-42505a66b26c", "connectionMode": "GATEWAY", "numberOfClients": 2, "excrgns": "[]", "clientEndpoints": { "https://redacted.documents.azure.com:443/": 2 }, "connCfg": { "rntbd": null, "gw": "(cps:10, nrto:PT1M, icto:PT5M, p:false)", "other": "(ed: true, cs: false, rv: true)" }, "consistencyCfg": "(consistency: Eventual, mm: true, prgns: [japaneast])", "proactiveInit": "", "e2ePolicyCfg": "" } } }
异常观察点
- 请求时间线仅
connectionAcquired事件有记录,requestSent等后续事件均为null,请求未真正发起 requestStartTimeUTC与requestEndTimeUTC间隔极短,请求未发送至Cosmos DB
排查结论与解决办法
核心原因
- End-to-End延迟策略阈值过短:配置的20毫秒阈值远低于跨区域请求的正常延迟,SDK提前终止请求
- 请求选项类型不匹配:读操作使用了用于更新的
CosmosPatchItemRequestOptions,引发内部逻辑异常 - 网关连接缺失请求超时配置:未设置请求超时,连接获取后无后续处理触发异常
- 重试策略关闭:限流重试次数设为0,无容错能力应对跨区域网络波动
解决步骤
- 调整延迟策略阈值:将
cosmos-end-to-end-operation-latency-policy-config-duration改为PT0.5S(500毫秒),匹配跨区域请求正常延迟 - 替换正确的请求选项类型:修改配置类中的请求选项Bean:
@Bean CosmosItemRequestOptions getCosmosItemRequestOptions( @Value("${cosmosdb.cosmos-item-request-options.cosmos-end-to-end-operation-latency-policy-config-duration}") final Duration duration) { final CosmosEndToEndOperationLatencyPolicyConfig latencyPolicyConfig = new CosmosEndToEndOperationLatencyPolicyConfigBuilder(duration).build(); final CosmosItemRequestOptions cosmosItemRequestOptions = new CosmosItemRequestOptions(); cosmosItemRequestOptions.setCosmosEndToEndOperationLatencyPolicyConfig(latencyPolicyConfig); return cosmosItemRequestOptions; }
- 补充网关请求超时配置:在GatewayConnectionConfig中添加请求超时设置:
@Bean GatewayConnectionConfig getGatewayConnectionConfig(@Value("${cosmosdb.idle-connection-timeout}") final Duration idleConnectionTimeout, @Value("${cosmosdb.max-connections-per-endpoint}") final int maxConnectionsPerEndpoint, @Value("${cosmosdb.connection-timeout}") final Duration connectionTimeout) { final var gatewayConnectionConfig = new GatewayConnectionConfig() .setIdleConnectionTimeout(idleConnectionTimeout) .setMaxConnectionPoolSize(maxConnectionsPerEndpoint) .setRequestTimeout(connectionTimeout); log.info("GatewayConnectionConfig: {}", gatewayConnectionConfig); return gatewayConnectionConfig; }
- 开启限流重试:将
max-retry-attempts-on-throttled-requests改为3-5,提升容错性:
cosmosdb.max-retry-attempts-on-throttled-requests = 3
- 优化异常日志:捕获CosmosException时打印完整诊断信息,便于排查:
log.error("CosmosException for id {}: {}", id, e.getDiagnostics());
内容的提问来源于stack exchange,提问作者Shubham
相关产品推荐
相关产品推荐

