RxJS v5中的速率限制和计数限制事件,但也允许传递

时间:2017-03-30 23:29:25

标签: javascript rxjs rxjs5 reactivex

我有很多事件要发送到服务。但请求是速率限制的,每个请求都有一个计数限制:

  • 每秒1次请求:bufferTime(1000)
  • 每个请求100个事件项:bufferCount(100)

问题是,我不确定如何以有意义的方式组合它们。

允许传递

进一步复杂化,如果我们没有达到任何限制,我需要确保事件瞬间完成。

例如,如果在非繁忙时间只有一个事件发生,我不希望它实际等待100个事件项目。

旧版API

我还发现RxJS v4中存在bufferWithTimeOrCount,但我不确定即使我拥有它也会如何使用它。

测试游乐场

这是我为您测试解决方案的JSBin:

http://jsbin.com/fozexehiba/1/edit?js,console,output

非常感谢任何帮助。

3 个答案:

答案 0 :(得分:2)

bufferTime()运算符采用三个参数,这些参数结合了bufferTimebufferCount的功能。请参阅http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-bufferTime

使用.bufferTime(1000, null, 3),您可以每1000毫秒或当它达到3个项目时创建一个缓冲区。但是,这意味着它不能保证每个缓冲区之间有1000ms的延迟。

所以你可以使用这样的东西很容易使用(缓冲只有3项,最长1000毫秒):

click$
  .scan((a, b) => a + 1, 0)
  .bufferTime(1000, null, 3)
  .filter(buffer => buffer.length > 0)
  .concatMap(buffer => Rx.Observable.of(buffer).delay(1000))
  .timestamp()
  .subscribe(console.log);

查看现场演示:http://jsbin.com/libazer/7/edit?js,console,output

您可能想要的唯一区别是第一次发射可能会延迟超过1000毫秒。这是因为bufferTime()delay(1000)运算符都会延迟,以确保始终存在至少1000毫秒的差距。

答案 1 :(得分:1)

我希望这适合你。

<强>操作

events$
  .windowCount(10)
  .mergeMap(m => m.bufferTime(100))
  .concatMap(val => Rx.Observable.of(val).delay(100))
  .filter(f => f.length > 0)

<强>文档

<强>演示

&#13;
&#13;
// test case
const mock = [8, 0, 2, 3, 30, 5, 6, 2, 2, 0, 0, 0, 1]

const tInterval = 100
const tCount = 10

Rx.Observable.interval(tInterval)
  .take(mock.length)
  .mergeMap(mm => Rx.Observable.range(0, mock[mm]))
  
  // start
  .windowCount(tCount)
  .mergeMap(m => m.bufferTime(tInterval))
  .concatMap(val => Rx.Observable.of(val).delay(tInterval))
  .filter(f => f.length > 0)
  // end

  .subscribe({
    next: (n) => console.log('Next: ', n),
    error: (e) => console.log('Error: ', e),
    complete: (c) => console.log('Completed'),
  })
&#13;
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;

更新

经过更多测试。我发现上面的答案在极端条件下有一些问题。我认为它们是由.window().concat()引起的,然后我在doc#concatMap中找到警告

  

警告 :如果源值无限快地到达其对应的内部Observables可以完成,则会导致内存问题,因为内部Observables在无限制中积聚等待轮到他们订阅的缓冲区。

但是,我认为限制请求率的正确方法是,我们可以限制请求的周期时间。在您的情况下,只限制每10毫秒只有1个请求。它更简单,控制请求可能更有效。

<强>操作

const tInterval = 100
const tCount = 10
const tCircle = tInterval / tCount

const rxTimer = Rx.Observable.timer(tCircle).ignoreElements()

events$
  .concatMap(m => Rx.Observable.of(m).merge(rxTimer)) // more accurate than `.delay()`
  // .concatMap(m => Rx.Observable.of(m).delay(tCircle))

events$
  .zip(Rx.Observable.interval(tCircle), (x,y) => x)

答案 2 :(得分:0)

我已经修改了我给this question的答案,以支持您在待处理请求中添加有限数量的值(即事件)的用例。

其中的评论应该解释它是如何运作的。

因为您需要记录在费率限制期内提出的请求,我不相信可以使用bufferTimebufferCount运算符来执行您的操作想要 - 需要scan,以便您可以在observable中保持该状态。

function rateLimit(source, period, valuesPerRequest, requestsPerPeriod = 1) {

  return source
    .scan((requests, value) => {

      const now = Date.now();
      const since = now - period;

      // Keep a record of all requests made within the last period. If the
      // number of requests made is below the limit, the value can be
      // included in an immediate request. Otherwise, it will need to be
      // included in a delayed request.

      requests = requests.filter((request) => request.until > since);
      if (requests.length >= requestsPerPeriod) {

        const leastRecentRequest = requests[0];
        const mostRecentRequest = requests[requests.length - 1];

        // If there is a request that has not yet been made, append the
        // value to that request if the number of values in that request's
        // is below the limit. Otherwise, another delayed request will be
        // required.

        if (
          (mostRecentRequest.until > now) &&
          (mostRecentRequest.values.length < valuesPerRequest)
        ) {

          mostRecentRequest.values.push(value);

        } else {

          // until is the time until which the value should be delayed.

          const until = leastRecentRequest.until + (
            period * Math.floor(requests.length / requestsPerPeriod)
          );

          // concatMap is used below to guarantee the values are emitted
          // in the same order in which they are received, so the delays
          // are cumulative. That means the actual delay is the difference
          // between the until times.

          requests.push({
            delay: (mostRecentRequest.until < now) ?
              (until - now) :
              (until - mostRecentRequest.until),
            until,
            values: [value]
          });
        }

      } else {

        requests.push({
          delay: 0,
          until: now,
          values: [value]
        });
      }
      return requests;

    }, [])

    // Emit only the most recent request.

    .map((requests) => requests[requests.length - 1])

    // If multiple values are added to the request, it will be emitted
    // mulitple times. Use distinctUntilChanged so that concatMap receives
    // the request only once.

    .distinctUntilChanged()
    .concatMap((request) => {

      const observable = Rx.Observable.of(request.values);
      return request.delay ? observable.delay(request.delay) : observable;
    });
}

const start = Date.now();
rateLimit(
  Rx.Observable.range(1, 250),
  1000,
  100,
  1
).subscribe((values) => console.log(
  `Request with ${values.length} value(s) at T+${Date.now() - start}`
));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@5/bundles/Rx.min.js"></script>