了解rxjs中的反压 - 只缓存等待上传的5张图像

时间:2016-11-28 11:16:30

标签: javascript node.js rxjs reactive-programming rxjs5

我正在开发一个节点项目,需要提交数千张图片进行处理。在将这些图像上传到处理服务器之前,需要调整它们的大小,以便我有类似的内容:

imageList
    .map(image => loadAndResizeImage)
    .merge(3)
    .map(image => uploadImage)
    .merge(3)
    .subscribe();

图像大小调整通常需要十分之几秒,上传和处理大约需要4秒钟。

当我等待上传队列清除时,如何防止在内存中累积数千个已调整大小的图像?我可能希望调整5张图像大小并等待,这样一旦图像上传完成,下一个已调整大小的图像就会从队列中拉出并上传,并且会调整新图像的大小并将其添加到'缓冲区中。

可以在此处找到问题的说明:

https://jsbin.com/webaleduka/4/edit?js,console

这里有一个加载步骤(耗时200ms)和一个处理步骤(耗时4秒)。每个进程限制为2的并发性。 我们可以看到,在25个初始项目中,我们在内存中获得了20个图像。

我确实看过缓冲区选项,但似乎都没有做我想做的事。

目前我刚刚将负载,调整大小和上传到一个延迟的observable中,我将其与最大并发性合并。我想让图像等待上传,我相信它一定是可能的。

我正在使用RXjs 4,但我认为5的主体是相同的。

非常感谢。

2 个答案:

答案 0 :(得分:4)

在RxJS 5中我会这样做:

Observable.range(1, 25)
    .bufferCount(5)
    .concatMap(batch => { // process images
        console.log('process', batch);
        return Observable.from(batch)
            .mergeMap(val => Observable.of('p' + val).delay(300))
            .toArray();
    })
    .concatMap(batch => { // send images
        console.log('send batch', batch);
        return Observable.from(batch)
            .mergeMap(val => Observable.of('s' + val).delay(500))
            .toArray();
    })
    .subscribe(val => {
        // console.log('response');
        console.log('response', val);

    });

使用bufferCount运算符,我将输入数组拆分为5个项目的批处理。然后每个批处理首先使用第一个concatMap()进行处理(我故意使用concat,因为我想等到嵌套的Observable完成)。然后,处理后的数据将发送到另一个concatMap(),并将其发送到您的服务器。

我正在使用两个delay()运算符来模拟不同的任务需要不同的时间。在我们的情况下,处理图像非常快,因此第一个concatMap将比第二个concatMap更快地发出项目concatMap能够将它们发送到服务器,这是正常的。处理后的图像将堆叠在process [ 1, 2, 3, 4, 5 ] send batch [ 'p1', 'p2', 'p3', 'p4', 'p5' ] process [ 6, 7, 8, 9, 10 ] process [ 11, 12, 13, 14, 15 ] response [ 'sp1', 'sp2', 'sp3', 'sp4', 'sp5' ] send batch [ 'p6', 'p7', 'p8', 'p9', 'p10' ] process [ 16, 17, 18, 19, 20 ] process [ 21, 22, 23, 24, 25 ] response [ 'sp6', 'sp7', 'sp8', 'sp9', 'sp10' ] send batch [ 'p11', 'p12', 'p13', 'p14', 'p15' ] response [ 'sp11', 'sp12', 'sp13', 'sp14', 'sp15' ] send batch [ 'p16', 'p17', 'p18', 'p19', 'p20' ] response [ 'sp16', 'sp17', 'sp18', 'sp19', 'sp20' ] send batch [ 'p21', 'p22', 'p23', 'p24', 'p25' ] response [ 'sp21', 'sp22', 'sp23', 'sp24', 'sp25' ] 内,并将一个接一个地批量发送。

此演示的输出如下所示:

concatMap

查看现场演示:https://jsbin.com/mileqa/edit?js,console

但是,如果您希望始终首先处理批处理而不是发送批处理,而不是继续处理另一批处理,则必须在{{1}的末尾从toArray()移动第二个内部Observable。在第一个concatMap()电话中。

.concatMap(batch => { // process images
    console.log('process', batch);
    return Observable.from(batch)
        .mergeMap(val => Observable.of('p' + val).delay(100))
        .toArray()
        .concatMap(batch => { // send images
            console.log('send batch', batch);
            return Observable.from(batch)
                .mergeMap(val => Observable.of('s' + val).delay(500))
                .toArray();
        });
})

查看现场演示:https://jsbin.com/sabena/2/edit?js,console

这将产生如下输出:

process [ 1, 2, 3, 4, 5 ]
send batch [ 'p1', 'p2', 'p3', 'p4', 'p5' ]
response [ 'sp1', 'sp2', 'sp3', 'sp4', 'sp5' ]
process [ 6, 7, 8, 9, 10 ]
send batch [ 'p6', 'p7', 'p8', 'p9', 'p10' ]
response [ 'sp6', 'sp7', 'sp8', 'sp9', 'sp10' ]
process [ 11, 12, 13, 14, 15 ]
send batch [ 'p11', 'p12', 'p13', 'p14', 'p15' ]
response [ 'sp11', 'sp12', 'sp13', 'sp14', 'sp15' ]
process [ 16, 17, 18, 19, 20 ]
send batch [ 'p16', 'p17', 'p18', 'p19', 'p20' ]
response [ 'sp16', 'sp17', 'sp18', 'sp19', 'sp20' ]
process [ 21, 22, 23, 24, 25 ]
send batch [ 'p21', 'p22', 'p23', 'p24', 'p25' ]
response [ 'sp21', 'sp22', 'sp23', 'sp24', 'sp25' ]

您可以看到“进程”,“发送批处理”和“响应”日志都是有序的。

RxJS 4中的实现应该几乎相同(只是运营商名称可能略有不同)。

在RxJS 4中,还有controlled() operator在RxJS 5中不存在(但是?)。我的确可能与你需要的东西非常相似。

答案 1 :(得分:0)

我认为我已经设法使用$ awk -v values="hello adieu" 'FS values FS ~ FS $1 FS' file hello 23 adieu 99 rxjs运算符解决了这个问题:

controlled()

最初请求4张图片。这些是从光盘加载然后处理。在加载每个图像然后处理时,使用var queuedImages = 0; var imageSource = Rx.Observable.range(1, 25) .map(index => "image_" + index) .controlled(); imageSource .map(image => loadImage(image)) .merge(2) .do((image) => { queuedImages++; console.log(`Images waiting for processing: ${queuedImages}`); }) .map(image => processImage(image)) .merge(2) .do( () => { queuedImages--; console.log(`Images waiting for processing: ${queuedImages}`); if(queuedImages < 4){ console.log(`requesting more image loads`); imageSource.request(4-queuedImages); } }) .subscribe( (item) => {}, null, () => console.log(`All Complete`) ); imageSource.request(4); 变量跟踪存储器中的图像数量。当此数字低于4时,请求更多图像。

这里可以看到一个jsbin:

https://jsbin.com/webaleduka/11/edit?js,console

此方法意味着缓存中的图像数量不会超过6个,并确保缓存中始终有足够的图像等待上传。