【发布时间】:2019-09-18 22:48:32
【问题描述】:
当前使用Drive API Client Library for Java 将项目与 Google Drive 集成,当使用服务帐户模拟用户检索其 Drive 内容时,当报告的请求数远低于时触发 userRateLimitExceeded可以在控制台中看到的最低定义配额。
用于测试 Google 云端硬盘集成的域当前将每用户每 100 秒的请求配额设置为 1000。 在程序运行期间,服务帐户用于模拟用户并检索其文件,Java 客户端由于 usageLimits 而抛出 GoogleJsonResponseException,即 userRateLimitExceeded。但是,控制台报告的最大峰值为 198 个请求/分钟,远低于上述限制。
已尝试为每个请求设置一个随机 quotaUser 参数,如 error resolution page 中所述,但结果相同。
documentation 中描述的指数退避策略在开始等待 1 秒然后递增确实没有多大帮助,随着配额不断触发,请求基本上会在等待 20 秒、30 秒后逐渐通过。
为了诊断这一点,我们为不同的运行场景创建了一个小型单元测试,其中我们运行了 1000 个可调用程序,它们使用Drive 对象的实例简单地列出了所述域中众所周知的 Google Drive 区域中的前 100 个文件。
public class GoogleDriveRequestTest {
private Drive googleDrive;
//other class attributes
@Before
public void setup() throws Exception {
//sensitive data
final Credential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JacksonFactory.getDefaultInstance())
.setServiceAccountId(serviceAccountId)
.setServiceAccountPrivateKey(gdrivePrivateKey)
.setServiceAccountScopes(ImmutableSet.of(DriveScopes.DRIVE,
DirectoryScopes.ADMIN_DIRECTORY_USER,
DirectoryScopes.ADMIN_DIRECTORY_GROUP))
.setServiceAccountUser(serviceAccountUser)
.build();
this.googleDrive = new Drive.Builder(httpTransport, JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("Google Drive API Load Test")
.build();
//other initialization code
}
@Test
public void shouldRequestListOfFilesOverAndOverAgain() {
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);
AtomicInteger requestCounter = new AtomicInteger(1);
infiniteStream
.limit(1000)
.map(i -> new GoogleDriveCallable(requestCounter))
.parallel()
.map(executorService::submit)
.map(execution -> {
try {
return execution.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.forEach(triple -> System.out.println("Completed request n " + triple.getMiddle() + " in " + triple.getRight() + " millis on thread " + triple.getLeft()));
}
private class GoogleDriveCallable implements Callable<Triple<String, Integer, Long>> {
private final AtomicInteger requestNumber;
public GoogleDriveCallable(AtomicInteger requestNumber) {
this.requestNumber = requestNumber;
}
@Override
public Triple<String, Integer, Long> call() throws Exception {
try {
try {
StopWatch timeIt = StopWatch.createStarted();
googleDrive
.files()
.list()
.setSpaces("drive")
.setQuotaUser(UUID.randomUUID().toString())
.setFields("nextPageToken, files(id, name, mimeType)")
.setPageSize(100)
.execute();
timeIt.stop();
return new ImmutableTriple<>(Thread.currentThread().getName(), requestNumber.getAndIncrement(), timeIt.getTime());
} catch (GoogleJsonResponseException gjre) {
GoogleJsonError.ErrorInfo firstReportedError = gjre.getDetails().getErrors().get(0);
if (USER_LIMIT_QUOTA_EXCEEDED_ERROR_REASON.equals(firstReportedError.getReason())) {
fail("Google user rate limit triggered during request n " + requestNumber);
} else {
throw gjre;
}
}
} catch (Exception e) {
throw new RuntimeException("BOOM during request n " + requestNumber, e);
}
return null;
}
}
}
使用不同数量的线程运行此单元测试(运行之间至少有 5 分钟的差异以保证没有干扰)具有以下优势:
- 1 线程
- 所有 1000 个请求都通过
- 分钟时间:6m49s(平均每秒2.44个请求-->每100秒244个请求)
- 最大时间:7m52s(平均每秒2.12个请求-->每100秒212个请求)
- 所有 1000 个请求都通过
- 2 个线程
- 所有 1000 个请求都通过
- 分钟时间:3m36s(平均每秒4.63个请求-->每100秒463个请求)
- 最大时间:3m46s(平均每秒4.42个请求-->每100秒442个请求)
- 所有 1000 个请求都通过
- 3 线程
- 所有 1000 个请求都通过
- 分钟时间:2m30s(平均每秒6.67个请求-->每100秒667个请求)
- 最大时间:2m31s(平均每秒6.62个请求-->每100秒662个请求)
- 所有 1000 个请求都通过
- 4 个线程
- 分钟时间:11 秒(平均每秒 8.27 个请求 --> 每 100 秒 827 个请求),userRateLimitExceeded 在大约 91 个请求后触发
- 最长时间:40 秒(平均每秒 8.75 个请求 --> 每 100 秒 875 个请求),userRateLimitExceeded 在大约 350 个请求后触发
- 5 个线程
- 分钟时间:4 秒(平均每秒 8.75 个请求 --> 每 100 秒 875 个请求),userRateLimitExceeded 在大约 35 个请求后触发
- 最大时间:7 秒(平均每秒 9.57 个请求 --> 每 100 秒 957 个请求),userRateLimitExceeded 在大约 67 个请求后触发
已确认没有其他人在使用该域,因此不应干扰这些测试。
如果我们没有达到 100 秒的时间点,为什么最后两种情况会失败并触发用户配额,即使将速率外推到 100 秒并且它们确实接近了,它们仍然低于每次 1000 个请求每 100 秒配额的用户数?
【问题讨论】:
标签: google-api google-drive-api google-api-java-client quota