TL; DR - 我正在寻找一种方法来控制在使用RxJS时HTTP请求并发连接到REST API的数量。
我的Node.js应用程序将向第三方提供商发出几千个REST API调用。但是,我知道如果我立即发出所有这些请求,由于DDoS攻击,服务可能会关闭或拒绝我的请求。所以,我想在任何给定的时间设置最大并发连接数。我曾经通过利用Throat Package来实现Promise的并发控制,但是我没有找到类似的方法来实现它。
我尝试使用merge
和1作为本帖How to limit the concurrency of flatMap?中建议的并发,但所有请求都是立即发送的。
这是我的代码:
var Rx = require('rx'),
rp = require('request-promise');
var array = ['https://httpbin.org/ip', 'https://httpbin.org/user-agent',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3'
];
var source = Rx.Observable.fromArray(array).map(httpGet).merge(1);
function httpGet(url) {
return rp.get(url);
}
var results = [];
var subscription = source.subscribe(
function (x) {
console.log('=====', x, '======');
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
答案 0 :(得分:4)
您可以使用mergeMap
运算符执行HTTP请求,并将响应展平为组合的observable。 let source = Rx.Observable
.fromArray(array)
.mergeMap(httpGet, 1);
采用可选的mergeMap
参数,您可以使用该参数指定并发订阅的可观察量的最大数量(即HTTP请求):
concurrent
请注意,1
concatMap
指定为httpGet
等同于map
。
您的问题中的代码一次性发送所有请求的原因取决于httpGet
运算符中httpGet
函数的调用。 httpGet
返回一个Promise并且promises不是懒惰的 - 只要调用mergeMap
,就会发送请求。
使用上面的代码,toArray
只会在let source = Rx.Observable
.fromArray(array)
.mergeMap(httpGet, 1)
.toArray();
实现中被调用,如果少于指定的并发请求数。
上面的代码将从组合的observable中单独发出每个响应。如果您希望将响应组合成一个在所有请求完成后发出的数组,则可以使用{{1}}运算符:
{{1}}
您还应该查看Martin在评论中引用的食谱。
答案 1 :(得分:4)
Rx.Observable.fromPromise
可能会有用。扩展cartant的答案,试试这个,其中concurrent
被指定为1
:
Rx.Observable.from(array)
.mergeMap(url => Rx.Observable.fromPromise(rp.get(url)), 1)
.subscribe(x => console.log(x))
对于基于时间的控制,这是我能想到的:
Rx.Observable.from(array)
.bufferCount(2)
.zip(Rx.Observable.timer(0, 1000), x => x)
.mergeMap(x => Rx.Observable.from(x)
.mergeMap(url => Rx.Observable.fromPromise(rp.get(url)))
.subscribe(x => console.log(x))
答案 2 :(得分:1)
感谢上面的回复。我的问题与使用rx而不是rxjs NPM模块有关。在我卸载rx并安装了rxjs后,所有示例都开始按预期使用并发。因此,使用Promises,Callbacks和Native Observables的http并发调用工作正常。
如果有人遇到类似问题并且可以进行问题排查,我会将它们发布在此处。
HTTP请求基于回调的示例:
var Rx = require('rxjs'),
request = require('request'),
request_rx = Rx.Observable.bindCallback(request.get);
var array = [
'https://httpbin.org/ip',
'https://httpbin.org/user-agent',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3'
];
var source = Rx.Observable.from(array).mergeMap(httpGet, 1);
function httpGet(url) {
return request_rx(url);
}
var subscription = source.subscribe(
function (x, body) {
console.log('=====', x[1].body, '======');
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
基于承诺的示例:
var Rx = require('rxjs'),
rp = require('request-promise');
var array = ['https://httpbin.org/ip', 'https://httpbin.org/user-agent',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3',
'https://httpbin.org/delay/3'
];
var source = Rx.Observable.from(array).mergeMap(httpGet, 1);
function httpGet(url) {
return rp.get(url);
}
var results = [];
var subscription = source.subscribe(
function (x) {
console.log('=====', x, '======');
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
原生RxJS示例:
var Rx = require('rxjs'),
superagent = require('superagent'),
Observable = require('rxjs').Observable;
var array = [
'https://httpbin.org/ip',
'https://httpbin.org/user-agent',
'https://httpbin.org/delay/10',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/1',
];
let start = (new Date()).getTime();
var source = Rx.Observable.from(array)
.mergeMap(httpGet, null, 1)
.timestamp()
.map(stamp => [stamp.timestamp - start, stamp.value]);
function httpGet(apiUrl) {
return Observable.create((observer) => {
superagent
.get(apiUrl)
.end((err, res) => {
if (err) {
return observer.onError(err);
}
let data,
inspiration;
data = JSON.parse(res.text);
inspiration = data;
observer.next(inspiration);
observer.complete();
});
});
}
var subscription = source.subscribe(
function (x) {
console.log('=====', x, '======');
});