我有很多事件要发送到服务。但请求是速率限制的,每个请求都有一个计数限制:
bufferTime(1000)
bufferCount(100)
问题是,我不确定如何以有意义的方式组合它们。
进一步复杂化,如果我们没有达到任何限制,我需要确保事件瞬间完成。
例如,如果在非繁忙时间只有一个事件发生,我不希望它实际等待100个事件项目。
我还发现RxJS v4中存在bufferWithTimeOrCount
,但我不确定即使我拥有它也会如何使用它。
这是我为您测试解决方案的JSBin:
http://jsbin.com/fozexehiba/1/edit?js,console,output
非常感谢任何帮助。
答案 0 :(得分:2)
bufferTime()
运算符采用三个参数,这些参数结合了bufferTime
和bufferCount
的功能。请参阅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)
<强>文档强>
.windowCount(number)
:[ Rx Doc ] .bufferTime(number)
:[ Rx Doc ] <强>演示强>
// 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;
经过更多测试。我发现上面的答案在极端条件下有一些问题。我认为它们是由.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的答案,以支持您在待处理请求中添加有限数量的值(即事件)的用例。
其中的评论应该解释它是如何运作的。
因为您需要记录在费率限制期内提出的请求,我不相信可以使用bufferTime
和bufferCount
运算符来执行您的操作想要 - 需要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>