有没有办法用RxJS管理并发?

时间:2017-03-26 09:41:30

标签: node.js rxjs

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');
  });

3 个答案:

答案 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, '======');
  });