【问题标题】:How are Google Drive API request quotas calculated? 'userRateLimitExceeded' being triggered when below known quotasGoogle Drive API 请求配额是如何计算的?低于已知配额时触发“userRateLimitExceeded”
【发布时间】: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个请求)
  • 2 个线程
    • 所有 1000 个请求都通过
      • 分钟时间:3m36s(平均每秒4.63个请求-->每100秒463个请求)
      • 最大时间:3m46s(平均每秒4.42个请求-->每100秒442个请求)
  • 3 线程
    • 所有 1000 个请求都通过
      • 分钟时间:2m30s(平均每秒6.67个请求-->每100秒667个请求)
      • 最大时间:2m31s(平均每秒6.62个请求-->每100秒662个请求)
  • 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


    【解决方案1】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 2019-08-21
      • 2019-02-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多