如何有效地跨多个协程实施速率限制?

时间:2018-10-25 18:07:12

标签: kotlin kotlinx.coroutines

因此,假设我正在运行一些与某些Web服务交互的Couroutine,由于我不想向其发送垃圾邮件,因此我希望将请求限制为每x秒最大1个请求。为此,我可以使用以下代码:

fun CoroutineScope.rateLimiter(tokens: SendChannel<Unit>, rate: Int) = launch {
    var lastToken = System.currentTimeMillis()
    while (isActive) {
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastToken < rate) {
            delay(currentTime - lastToken)
        }
        tokens.send(Unit)
    }
}

fun CoroutineScope.request(tokens: ReceiveChannel<Unit>) = launch { 
    for (token in tokens) {
        //Do Web request
    }
}

1。)这样有效吗?

2。)这不能扩展为将某些内容限制为x字节/秒或需要从Token Bucket中请求x令牌的内容,这是实现类似内容的最佳方法用协程?

1 个答案:

答案 0 :(得分:0)

如果您不想依赖于可能产生的许可多于消耗的许可的工作和渠道,然后在某个流程开始接受许可后就蜂拥而至,也许这就是适合您的解决方案。

(这里有一些 jvm 风格,但可替换为多平台)


import kotlin.math.max
import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

class SimpleRateLimiter(eventsPerSecond: Double) {

    private val mutex = Mutex()

    @Volatile
    private var next: Long = Long.MIN_VALUE
    private val delayNanos: Long = (1_000_000_000L / eventsPerSecond).toLong()

    /**
     * Suspend the current coroutine until it's calculated time of exit
     * from the rate limiter
     */
    suspend fun acquire() {
        val now: Long = System.nanoTime()
        val until = mutex.withLock {
            max(next, now).also {
                next = it + delayNanos
            }
        }
        if (until != now) {
            delay((until - now) / 1_000_000)
        }
    }
}

它还有其他的权衡。

  • nanoTime 接近 Long 时的行为。MAX_VALUE 肯定已损坏。
  • maxDelay/timeout 没有实现
  • 无法抓取多个白蚁
  • 没有 tryAquire 实现

如果你想要一个 IntervalLimiter 允许 X 请求每 Y 秒,然后抛出异常,有 RateLimiter in Resilience4J 或者,如果您想要功能更全面的东西,我正在研究 PR to create both a RateLimiter and an IntervalLimiter in coroutines core project