java - 如何让 OkHttpClient 遵守 REST API 速率限制?

标签 java android kotlin okhttp

我正在编写一个向 REST API 服务发出频繁请求的 Android 应用程序。此服务的硬请求限制为每秒 2 个请求,之后它将返回 HTTP 503,没有其他信息。我想成为一名优秀的开发人员并限制我的应用程序以符合服务的要求(即在我的请求成功之前不要重试垃圾邮件服务),但事实证明这很难做到。
我正在尝试限制 OkHttpClient具体来说,因为我可以干净地将客户端实例插入 CoilRetrofit这样我的所有网络请求都会受到限制,而无需我在调用站点为它们中的任何一个做任何额外的工作:我可以调用 enqueue()不假思索。然后重要的是我可以调用cancel()dispose()enqueue()例如,当用户更改页面时,我可以避免执行不必要的网络请求。
我首先关注 this question 的答案使用 Guava RateLimiter OkHttp 内部 Interceptor ,而且效果很好!直到我意识到我需要能够取消挂起的请求,而 Guava 的 RateLimiter 无法做到这一点。 , 因为它在 acquire() 时阻塞了当前线程s,然后防止请求被立即取消。
然后我尝试关注 this suggestion ,您调用Thread.interrupt()让被阻止的拦截器恢复,但它不起作用,因为 Guava RateLimiter s block uninterruptibly由于某些原因。 (注意:做 tryAcquire() 而不是 acquire() 然后中断 Thread.sleep() 不是一个很好的解决方案,因为你不知道要睡多久。)
因此,我开始考虑放弃 Guava 解决方案并实现一个自定义 ExecutorService ,它将请求保存在一个队列中,该队列将由计时器定期分派(dispatch),但对于一些可能工作或可能不工作的东西来说,这似乎是很多复杂的工作我现在已经远离杂草了。有没有更好或更简单的方法来做我想做的事?

最佳答案

最终我决定不配置 OkHttpClient完全被限速。对于我的具体用例,我 99% 的请求是通过 Coil 完成的,剩下的少数请求不常见并且通过 Retrofit 完成,所以我决定:

  • 不使用 Interceptor根本,而是允许通过客户端的任何请求照常进行。假定改造请求很少发生,以至于我不在乎限制它们。
  • 创建一个包含 Queue 的类和 Timer定期弹出并运行任务。它并不聪明,但它的效果出奇的好。我的线圈图像请求被放入队列中,以便它们调用 imageLoader.enqueue()当他们到达前面时,但如果我需要取消请求,也可以从队列中清除它们。
  • 毕竟,如果我以某种方式错误地超出了速率限制(技术上可能,但不太可能),我可以接受 OkHttp 偶尔不得不重试请求,而不是担心永远不会达到限制。

  • 这是我想出的(非常简单的)队列:
    import java.util.*
    
    class RateLimitedQueue(private val millisecondsPerTask: Long) {
        private val timer = Timer()
        private val queue = ArrayDeque<Task>()
    
        init {
            timer.scheduleAtFixedRate(RunTaskTimerTask(this), 0, millisecondsPerTask)
        }
    
        private class RunTaskTimerTask(val parent: RateLimitedQueue) : TimerTask() {
            override fun run() {
                synchronized(parent.queue) {
                    if (!parent.queue.isEmpty())
                        parent.queue.pop().run()
                }
            }
        }
    
        fun interface Task {
            fun run()
        }
    
        fun add(t: Task) {
            synchronized(queue) {
                queue.add(t)
            }
        }
    
        fun remove(t: Task) {
            synchronized(queue) {
                queue.remove(t)
            }
        }
    
        fun removeAll(filter: (Task) -> Boolean) {
            synchronized(queue) {
                queue.removeAll(filter)
            }
        }
    }
    
    我对这个解决方案很满意,但有一些值得注意的警告:
  • Timer + Queue不是很聪明:它实际上只是每隔 X 检查一个任务毫秒,所以如果一个任务在很长的延迟之间到达一个空队列,它可能会不必要地等待。你可以想出一些更优雅的东西,但我发现它可以很好地处理我 500 毫秒的延迟。
  • 它不像 Interceptor 那样干净,因为它不在 Retrofit 和 Coil 之间共享。
  • 技术上可以超过速率限制,但就我而言,这是可以接受的。
  • 任务到达队列的最前面后,您不能取消它们;您只能在执行之前将它们从队列中删除。
  • 关于java - 如何让 OkHttpClient 遵守 REST API 速率限制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66684303/

    相关文章:

    java - 用Java将图像数据保存在 "PNG"文件中

    机器人 :-Alarm manager not working

    Kotlin - `if` 和 `when` 表达式的类型

    java - super 关键字是否应该访问私有(private)字段和方法。我能够访问私有(private)方法和字段

    java - Java中的“特殊属性/属性”而不是getter/setter,以避免样板代码

    android - CookieManager.removeExpiredCookie 在 Android 4.1.2 上崩溃

    java - 我应该使用哪些 Android API?简单的 map 应用

    android - 更新到 Kotlin 1.3.30 会破坏 Dagger 2.21 的构建

    android - Koin vs Kodein - 依赖注入(inject)你更喜欢什么? Kotlin

    java - Hibernate、websphere 和 DB2 数据库的集成问题