繁体   English   中英

有条件活动的rxjs可观察速率限制器

[英]conditionally active rxjs observable rate limiter

我想为有条件的可观察对象创建一个速率限制器,以有条件地更改其限制值的方式。 该用例适用于不断接收新网址进行下载的下载程序。 我希望能够排队输入URL,并在两种排队方法之间进行切换。 两种方法分别是通过限制速率(例如,每2秒不超过10个请求)和并发请求的数量(例如,一次不能触发10个以上的请求)。

可以像下面这样实现一个简单的速率限制器(从此处借用):

const rateLimit = (limit, rate, scheduler = asyncScheduler) => {
  let tokens = limit
  const tokenChanged = new BehaviorSubject(tokens)
  const consumeToken = () => tokenChanged.next(--tokens)
  const renewToken = () => tokenChanged.next(++tokens)
  const availableTokens = tokenChanged.pipe(filter(() => tokens > 0))
  return source =>
    source.pipe(
      mergeMap(val =>
        availableTokens.pipe(
          take(1),
          map(() => {
            consumeToken()
            timer(rate, scheduler).subscribe(renewToken)
            return val
          })
        )
      )
    )
}
const o = urlSource.pipe(
  rateLimit(10, 2000),
  mergeMap(downloadUrl)
)
o.toPromise()

这是一个简单的并发限制器:

const o = urlSource.pipe(
  mergeMap(downloadUrl, maxConcurrent)
)
o.toPromise()

最后,我可以创建此组合切换器以选择要使用的限制器类型:

const toggleableLimiter = (func, limit, rate, concurrent, toggleObservable) => {
  let useRateLimiter = true
  toggleObservable.subscribe(() => (useRateLimiter = !useRateLimiter))
  const rateLimiter = rateLimit(limit, rate)
  return source => {
    const operators = useRateLimiter
      ? [rateLimiter, mergeMap(func)]
      : [mergeMap(func, concurrent)]
    return source.pipe(...operators)
  }
}

const e = new EventEmitter()
const toggler = fromEvent(e, 'toggle')
const o = urlSource.pipe(
    toggleableLimiter(downloadUrl, 2, 1000, 2, toggler)
)
o.toPromise()
// using rate limiter
e.emit('toggle')
// incoming values now use concurrent limiter

所有这些都可以很好地解决我的问题。 我可以使用事件发射器在两种方法之间切换。 但是,问题在于, 发出事件之前传递给toggleableLimiter任何内容都必须遵守该限制器运算符。 我想知道的是,是否可以有条件地将值保留在队列中,并选择如何随心所欲地限制排队的值。

好! 我有一个解决方案,不幸的是它涉及到我自己处理队列。 由于可观察性和背压的性质,我认为这是必要的,我在本期中对此主题进行了很多讨论。 在过去,可以使用controlled运算符来更简单地处理此问题,但已弃用 取而代之的是,我自己只是使用了一个可观察的timer并自己包装了mergeMap的并发控件(尽管mergeMap仍然在内部管理并发作为一种安全措施。

const rateLimitToggle = (func, limit, rate, maxConcurrent, toggler) => {
  const rateTimer = Rx.timer(0, rate).pipe(ops.mapTo(true))
  return source =>
    new Rx.Observable(subscriber => {
      const concurrentLimiter = new Rx.Subject()
      // stateful vars
      const queue = []
      let inProgress = 0
      let closed = false

      const enqueue = val => {
        queue.push(val)
        concurrentLimiter.next()
      }
      const dequeue = useRateLimit => {
        const availableSlots = useRateLimit ? limit : maxConcurrent - inProgress
        const numberToDequeue = Math.min(availableSlots, queue.length)
        const nextVals = queue.splice(0, numberToDequeue)
        inProgress += availableSlots
        return nextVals
      }

      Rx.merge(Rx.of(true), toggler)
        .pipe(
          ops.switchMap(useRateLimiter => (useRateLimiter ? rateTimer : concurrentLimiter)),
          ops.takeWhile(() => !closed || queue.length),
          ops.mergeMap(dequeue),
          ops.mergeMap(val => func(val), maxConcurrent)
        )
        .subscribe(val => {
          inProgress--
          concurrentLimiter.next()
          subscriber.next(val)
        })

      source.subscribe({
        next(val) {
          enqueue(val)
        },
        complete() {
          closed = true
        }
      })
    })
}

用法示例:

const timeout = n => val => {
  console.log('started', val)
  return new Promise(resolve => setTimeout(() => resolve(val), n))
}

const emitter = new EventEmitter()
const toggler = Rx.fromEvent(emitter, 'useRateLimiter')
const downloader = Rx.range(0, 10).pipe(
  rateLimitToggle(timeout(1000), 2, 1000, 10, toggler)
)

downloader.subscribe(val => console.log('finished', val))
setTimeout(() => {
  console.log('now use concurrentLimiter')
  emitter.emit('useRateLimiter', false)
}, 2000)
/* outputs:

  started 0
  started 1
  started 2
  started 3 // 0 - 3 all executed under rateLimiter
  finished 0
  finished 1
  now use concurrentLimiter
  started 4 // 4 - 9 executed under concurrentLimiter
  started 5
  started 6
  started 7
  started 8
  started 9
  finished 2
  finished 3
  finished 4
  finished 5
  finished 6
  finished 7
  finished 8
  finished 9
*/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM