控制产生的期货数量以创建背压

时间:2018-01-15 16:54:38

标签: asynchronous rust future amazon-kinesis

我正在使用futures-rs powered version of the Rusoto AWS Kinesis library。我需要生成AWS Kinesis请求的深层管道以实现高吞吐量,因为Kinesis每个HTTP请求的限制为500条记录。结合发送请求的50ms延迟,我需要开始生成许多并发请求。我希望创建大约100个飞行请求的地方。

Rusoto put_records函数签名如下所示:

fn put_records(
    &self,
    input: &PutRecordsInput,
) -> RusotoFuture<PutRecordsOutput, PutRecordsError>

RusotoFuture是这样定义的包装器:

/// Future that is returned from all rusoto service APIs.
pub struct RusotoFuture<T, E> {
    inner: Box<Future<Item = T, Error = E> + 'static>,
}

内部Future已被包裹但RusutoFuture仍在实施Future::poll(),因此我认为它与futures-rs生态系统兼容。 RusotoFuture提供同步调用:

impl<T, E> RusotoFuture<T, E> {
    /// Blocks the current thread until the future has resolved.
    ///
    /// This is meant to provide a simple way for non-async consumers
    /// to work with rusoto.
    pub fn sync(self) -> Result<T, E> {
        self.wait()
    }
}

我可以发出请求并sync(),从AWS获取结果。我想创建许多请求,将它们放入某种队列/列表中,并收集完成的请求。如果请求出错,我需要重新发出请求(这在Kinesis中有点正常,特别是当你的分片吞吐量达到限制时)。如果请求成功完成,我应该发出一个包含新数据的请求。我可以为每个请求生成一个线程并同步它,但是当我运行异步IO线程时这似乎效率低下。

我尝试从我的应用程序线程中使用futures::sync::mpsc::channel(不是从Tokio反应器内部运行),但每当我克隆tx时,它都会生成自己的缓冲区,从而消除{{1}上的任何背压}}:

send

没有克隆,我有错误:

fn kinesis_pipeline(client: DefaultKinesisClient, stream_name: String, num_puts: usize, puts_size: usize) {
    use futures::sync::mpsc::{ channel, spawn };
    use futures::{ Sink, Future, Stream };
    use futures::stream::Sender;
    use rusoto_core::reactor::DEFAULT_REACTOR;

    let client = Arc::new(KinesisClient::simple(Region::UsWest2));
    let data = FauxData::new(); // a data generator for testing

    let (mut tx, mut rx) = channel(1);

    for rec in data {
        tx.clone().send(rec);
    }
}

我还根据建议查看了futures::mpsc::sync::spawn,但它是error[E0382]: use of moved value: `tx` --> src/main.rs:150:9 | 150 | tx.send(rec); | ^^ value moved here in previous iteration of loop | = note: move occurs because `tx` has type `futures::sync::mpsc::Sender<rusoto_kinesis::PutRecordsRequestEntry>`, which does not implement the `Copy` trait (作为rx)的所有者,并没有解决Stream的问题导致无限制行为的Copy

我希望我能使tx / channel用法正常工作,我会有一个系统需要spawn s,等待它们完成,然后提供给我从我的应用程序线程中获取完成结果的简单方法。

1 个答案:

答案 0 :(得分:0)

channel而言,我可以告诉您问题不是Sender的单个克隆将容量增加1,而是为每个项目克隆Sender你试图发送。

您在没有clone的情况下看到的错误来自您对Sink::send界面的错误使用。使用clone,您实际应该看到警告:

warning: unused `futures::sink::Send` which must be used: futures do nothing unless polled

那就是:您当前的代码实际上并没有发送任何内容!

为了应用背压,您需要将send次呼叫链接起来;每个人都应该等到上一个完成(你需要等待最后一个!);成功之后,您将获得Sender。最好的方法是使用iter_ok从迭代器生成Stream并将其传递给send_all

现在你有一个未来SendAll,你需要&#34;驾驶&#34;。如果忽略结果并且出错(.then(|r| { r.unwrap(); Ok::<(), ()>(()) })),您可以将其作为单独的任务生成,但是您可能希望将其集成到主应用程序中(即将其返回到Box)。 / p>

// this returns a `Box<Future<Item = (), Error = ()>>`. you may
// want to use a different error type
Box::new(tx.send_all(iter_ok(data)).map(|_| ()).map_err(|_| ()))

RusotoFuture::syncFuture::wait

不要使用Future::wait:它已经在分支机构中弃用,它通常不会做你真正想要的事情。我怀疑RusotoFuture是否意识到这些问题,所以我建议避免使用RusotoFuture::sync

克隆Sender会增加频道容量

正如您所说,克隆Sender会将容量增加一个。

这似乎是为了提高性能:Sender以未阻止(&#34; unparked&#34;)状态开始;如果Sender未被阻止,则可以不阻止地发送项目。但是,如果Sender发送项目时队列中的项目数达到配置的限制,Sender将被阻止(&#34;停放&#34;)。 (从队列中删除项目将在特定时间取消阻止Sender。)

这意味着在内部队列达到限制后,每个Sender仍然可以发送一个项目,这会导致记录增加容量的效果,但前提是实际上所有Sender都在发送项目 - 未使用的Sender不会增加观察到的容量。

性能的提升来自这样一个事实:只要你没有达到极限,它就不需要停放和通知任务(这是非常沉重的)。

mpsc模块顶部的私人文档描述了更多详细信息。