Google Drive API 请求配额是如何计算的? 'userRateLimitExceeded' 低于已知配额时触发

How are Google Drive API request quotas calculated? 'userRateLimitExceeded' being triggered when below known quotas

当前使用 Drive API Client Library for Java 将项目与 Google 驱动器集成,当使用服务帐户模拟用户以检索其驱动器内容时,userRateLimitExceeded当报告的请求数远低于控制台中可以看到的最低定义配额时触发。

用于测试的域 Google 驱动器集成当前将每个用户每 100 秒的请求配额设置为 1000。 在程序的 运行 期间,服务帐户用于模拟用户并检索其文件,Java 客户端投射 GoogleJsonResponseException 由于 usageLimits,即 userRateLimitExceeded。但是,控制台报告的最大峰值为 198 requests/minute,远低于上述限制。

已尝试按照 error resolution page 中的详细说明为每个请求设置随机 quotaUser 参数,但这产生了相同的结果。

documentation 中描述的指数退避策略在 1 秒开始等待然后递增确实没有太大帮助,随着配额不断被触发,请求基本上在等待 20、30 秒后逐渐通过.

为了诊断这个问题,我们为不同的 运行 场景创建了一个小型单元测试,其中我们 运行 1000 个可调用文件简单地列出了众所周知的 Google 中的前 100 个文件使用 Drive 对象的实例在所述域中驱动区域。

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

运行 此单元测试具有不同的线程数(运行s 之间至少相差 5 分钟以保证无干扰)使用以下内容:

已确认没有其他人在使用该域,因此不应干扰这些测试。

如果我们没有达到 100 秒时间点,为什么最后两个场景会失败并触发用户配额,即使将速率外推到 100 秒并且它们确实接近,它们仍然缺少 1000每个用户每 100 秒配额请求?

与 Google 支持人员的沟通强调,除了已知的配额外,后端服务具有突发保护。

因此,如果请求速率恒定,将应用配额,但如果请求速率出现突发,并且所述突发,如果推断为正常流量,会导致配额超限,API 将回复 usageLimit 错误。