Java Spring Boot GCP数据扳手延迟问题

Java Spring Boot GCP数据扳手延迟问题,java,spring-boot,google-cloud-platform,google-cloud-spanner,grpc-java,Java,Spring Boot,Google Cloud Platform,Google Cloud Spanner,Grpc Java,在Google Cloud Env中使用带扳手的Spring Boot。我们现在正努力解决性能问题。 为了演示,我建立了一个小的演示案例,将如何从扳手检索数据的不同方法作为基线 第一种方法 使用Google的“本机”驱动程序实例化dbClient并检索数据,如下所示 @Repository public class SpannerNativeDAO implements CustomerDAO { private final DatabaseClient dbClient; priva

在Google Cloud Env中使用带扳手的Spring Boot。我们现在正努力解决性能问题。 为了演示,我建立了一个小的演示案例,将如何从扳手检索数据的不同方法作为基线

第一种方法 使用Google的“本机”驱动程序实例化dbClient并检索数据,如下所示

@Repository
public class SpannerNativeDAO implements CustomerDAO {

  private final DatabaseClient dbClient;
  private final String SQL = "select * from customer where customer_id = ";

  public SpannerNativeDAO(
      @Value("${spring.cloud.gcp.spanner.instanceId}") String instanceId,
      @Value("${spring.cloud.gcp.spanner.database}") String dbId,
      @Value("${spring.cloud.gcp.spanner.project-id}") String projectId,
      @Value("${google.application.credentials}") String pathToCredentials)
      throws IOException {
    try (FileInputStream google_application_credentials = new FileInputStream(pathToCredentials)) {
      final SpannerOptions spannerOptions =
          SpannerOptions.newBuilder().setProjectId(projectId)
              .setCredentials(ServiceAccountCredentials.fromStream(google_application_credentials)).build();
      final Spanner spanner = spannerOptions.getService();
      final DatabaseId databaseId1 = DatabaseId.of(projectId, instanceId, dbId);
      dbClient = spanner.getDatabaseClient(databaseId1);
      // give it a first shot to speed up consecutive calls
      dbClient.singleUse().executeQuery(Statement.of("select 1 from customer"));
    }
  }

  private Customer readCustomerFromSpanner(Long customerId) {
    try {
      Statement statement = Statement.of(SQL + customerId);
      ResultSet resultSet = dbClient.singleUse().executeQuery(statement);
      while (resultSet.next()) {
        return Customer.builder()
            .customerId(resultSet.getLong("customer_id"))
            .customerStatus(CustomerStatus.valueOf(resultSet.getString("status")))
            .updateTimestamp(Timestamp.from(Instant.now())).build();
      }
    } catch (Exception ex) {
      //log
    }
    return null;
  }


....

}
第二种方法 使用Spring引导数据启动器()

就是这样

@Repository
public interface SpannerCustomerRepository extends SpannerRepository<Customer, Long> {

  @Query("SELECT customer.customer_id, customer.status, customer.status_info, customer.update_timestamp "
      + "FROM customer customer WHERE customer.customer_id = @arg1")
  List<Customer> findByCustomerId(@Param("arg1") Long customerId);
}
@存储库
公共接口SpanRCustomerRepository扩展了SpanRecository{
@查询(“选择customer.customer\u id、customer.status、customer.status\u info、customer.update\u时间戳”
+“来自客户,其中customer.customer_id=@arg1”)
列出findByCustomerId(@Param(“arg1”)长customerId);
}
现在,如果我采用第一种方法,那么建立到Panner的初始gRPC连接需要>5秒,并且所有连续调用大约需要1秒。第二种方法在初始呼叫后每次呼叫仅需约400ms。 为了测试差异,我在一个Spring Boot项目中连接了这两个解决方案,并将其与内存中的解决方案(~100ms)进行了比较。 所有给定的计时都是指在开发人员机器上进行的本地测试,但请返回到调查云环境中的性能问题

我测试了几个不同的span选项(SessionOptions),但没有结果,并在项目上运行了一个分析器。 我觉得96%的响应时间来自于建立一个gRPC到Spaner的通道,而数据库本身在5毫秒内处理和响应

我们真的不理解这种行为。我们只使用很少的测试数据和几个小表

  • DatabaseClient应该管理ConnectionPool,并且它本身连接到一个单一作用域的存储库Bean中。所以会话应该被重用,对吗
  • 为什么第一种方法比第二种方法耗时更长。Spring FW本身只是将DatabaseClient用作span属性/span模板中的成员
  • 我们通常如何减少延迟。每个db调用的普通响应时间超过200毫秒,似乎是我们预期的四倍。(我知道需要谨慎对待本地时间基准)

跟踪使我们能够很好地了解客户机,希望它能帮助您诊断延迟

跑步,我从stackdriver那里得到。您可以使用不同的后端,或者

上面的示例也可以导出,您可以查看延迟和跟踪


关于设置它的教程:

跟踪使我们能够很好地了解客户端,希望它能帮助您诊断延迟

跑步,我从stackdriver那里得到。您可以使用不同的后端,或者

上面的示例也可以导出,您可以查看延迟和跟踪


关于设置它的教程:

如果两个方法之间的SQL字符串相同,您能确认性能不会改变吗?(*vs单独拼写它们)


另外,由于在第一种方法中预期只有一个客户,所以我推断客户ID是一个键列?如果是这样,您可以使用
span-repository
中的按键读取方法,这可能比SQL查询快。

如果两个方法之间的SQL字符串相同,您能否确认性能不会改变?(*vs单独拼写它们)


另外,由于在第一种方法中预期只有一个客户,所以我推断客户ID是一个键列?如果是这样,您可以使用
span-repository
中的按键读取方法,这可能比SQL查询快。

这里的问题与Spring或DAO无关,而是您没有关闭查询返回的
ResultSet
。这使扳手库认为用于执行查询的会话仍在使用中,并使库在每次执行查询时创建一个新会话。这个会话的创建、处理和池都由客户机库负责,但它确实要求您在不再使用资源时关闭资源

我用一个非常简单的例子对此进行了测试,通过不关闭
ResultSet
,我可以重现与您看到的完全相同的行为

考虑以下示例:

/**
*此方法将快速执行查询,如ResultSet所示
*由“尝试使用资源”块自动关闭。
*/
private Long executeQueryFast(){
语句Statement=Statement.of(“从T中选择*,其中ID=1”);
try(ResultSet ResultSet=dbClient.singleUse().executeQuery(语句)){
while(resultSet.next()){
返回resultSet.getLong(“ID”);
}
}捕获(例外情况除外){
//日志
}
返回null;
}
/**
*此方法将缓慢执行查询,因为结果集是
*未关闭,扳手库认为会话已关闭
*仍在使用中。重复执行此方法将导致
*库为每个方法调用创建一个新会话。
*关闭结果集将导致使用的会话失败
*返回到会话池,并且会话将
*重复使用。
*/
private Long executeQuerySlow(){
语句Statement=Statement.of(“从T中选择*,其中ID=1”);
试一试{
ResultSet ResultSet=dbClient.singleUse().executeQuery(语句);
while(resultSet.next()){
返回resultSet.getLong(“ID”);
}
}捕获(例外情况除外){
//日志
}
返回null;
}
只要可能,您应该始终将
ResultSet
s(以及所有其他
AutoCloseable
s)放在try with resources块中

请注意,如果您使用了一个完全由扳手返回的
ResultSet
,即调用
ResultSet#next()
,直到它重新启动