Java Spring Boot GCP数据扳手延迟问题
在Google Cloud Env中使用带扳手的Spring Boot。我们现在正努力解决性能问题。 为了演示,我建立了一个小的演示案例,将如何从扳手检索数据的不同方法作为基线 第一种方法 使用Google的“本机”驱动程序实例化dbClient并检索数据,如下所示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
@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那里得到。您可以使用不同的后端,或者 上面的示例也可以导出,您可以查看延迟和跟踪
关于设置它的教程:如果两个方法之间的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()
,直到它重新启动