Google Drive API请求配额如何计算?低于已知配额时会触发“ userRateLimitExceeded”

时间:2019-04-30 09:42:57

标签: google-api google-drive-api google-api-java-client quota

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

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

已尝试为每个请求设置随机的 quotaUser 参数,如error resolution page中所述,但这产生了相同的结果。

documentation中描述的指数退避策略在1秒钟开始等待然后增加,实际上并没有多大帮助,随着配额不断被触发,请求在等待20、30秒后基本上会逐渐通过。 / p>

要对此进行诊断,请针对不同的运行场景创建一个小型单元测试,在该场景中,我们运行1000个可调用对象,它们使用Drive的实例简单地列出了该域中知名Google云端硬盘区域中的前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个请求均通过
      • 最短时间:6毫秒49秒(平均每秒2.44个请求->每100秒244个请求)
      • 最长时间:7m52s(平均每秒2.12个请求->每100秒212个请求)
  • 2个线程
    • 所有1000个请求均通过
      • 最短时间:3毫秒(平均每秒4.63个请求->每100秒463个请求)
      • 最长时间:3m46s(平均每秒4.42个请求->每100秒442个请求)
  • 3个线程
    • 所有1000个请求均通过
      • 最短时间:2m30s(平均每秒6.67个请求->每100秒667个请求)
      • 最长时间:2m31s(平均每秒6.62个请求->每100秒662个请求)
  • 4个线程
    • 最短时间:11s(平均每秒8.27个请求->每100秒827个请求),在大约91个请求后触发 userRateLimitExceeded
    • 最长时间:40秒(平均每秒8.75个请求->每100秒875个请求),在大约350个请求后触发 userRateLimitExceeded
  • 5个线程
    • 最短时间:4s(平均每秒8.75个请求->每100秒875个请求),在大约35个请求后触发 userRateLimitExceeded
    • 最长时间:7s(平均每秒9.57个请求->每100秒957个请求),在大约67个请求后触发 userRateLimitExceeded

已经确认没有其他人正在使用该域,因此没有任何东西可以干扰这些测试。

如果我们没有达到100秒的时间点,为什么后两种情况会失败,并触发用户配额,即使将速率推算到100秒并且确实接近,它们仍然不足1000个请求每100秒配额中有多少用户?

1 个答案:

答案 0 :(得分:0)

与Google支持的通信着重指出,除了已知的配额外,后端服务还具有突发保护功能。

同样地,如果请求率是恒定的,则配额将适用,但是,如果请求率遭受突发并且所述突发,如果推断为正常流量,则会导致配额被超支,则API将使用usageLimit错误进行回复