Akka流 - 限制流量而不引入延迟

时间:2017-04-20 08:17:16

标签: java stream akka

我正在使用Akka(版本2.4.17)来构建Java中的观察流程(让我们说类型<T>的元素保持通用)。

我的要求是,此Flow应该是可自定义的,以便在单个时间到达时提供最大数量的观察。例如,它应该能够每分钟传送最多2个观察结果(第一个到达,其余部分可以被丢弃)。

我非常仔细地看了Akka文档,特别是this page详细介绍了内置阶段及其语义。

到目前为止,我尝试了以下方法。

  • 使用throttleshaping()模式(在超出限制时不关闭流):

      Flow.of(T.class)
           .throttle(2, 
                     new FiniteDuration(1, TimeUnit.MINUTES), 
                     0, 
                     ThrottleMode.shaping())
    
  • 使用groupedWith和中间自定义方法:

    final int nbObsMax = 2;
    
    Flow.of(T.class)
        .groupedWithin(Integer.MAX_VALUE, new FiniteDuration(1, TimeUnit.MINUTES))
        .map(list -> {
             List<T> listToTransfer = new ArrayList<>();
             for (int i = list.size()-nbObsMax ; i>0 && i<list.size() ; i++) {
                 listToTransfer.add(new T(list.get(i)));
             }
             return listToTransfer;
        })
        .mapConcat(elem -> elem)  // Splitting List<T> in a Flow of T objects
    

以前的方法可以为每单位时间提供正确的观察次数,但这些观察结果会被保留,并且仅在时间窗结束时传递(因此会有额外的延迟)。

举一个更具体的例子,如果以下观察到达我的流程:

  

[Obs1 t = 0s] [Obs2 t = 45s] [Obs3 t = 47s] [Obs4 t = 121s] [Obs5 t = 122s]

它应该只在它们到达时输出以下值(这里可以忽略处理时间):

  

窗口1:[Obs1 t~0s] [Obs2 t~45s]   窗口2:[Obs4 t~121s] [Obs5 t~122s]

感谢您阅读我的第一篇StackOverflow帖子,感谢任何帮助;)

3 个答案:

答案 0 :(得分:3)

我无法想到一个开箱即用的解决方案。由于铲斗模型的实施方式,节流阀将以稳定的流量排放,而不是在每个时间段的开始都有允许的租赁。

要获得您之后必须创建自己的自定义速率限制阶段(可能不那么难)的确切行为。您可以在此处找到有关如何创建自定义阶段的文档:http://doc.akka.io/docs/akka/2.5.0/java/stream/stream-customize.html#custom-linear-processing-stages-using-graphstage

一个可行的设计是有一个容差计数器,说明每个间隔重置多少个可以发出的元素,每个传入的元素从计数器中减去一个并发出一个,当容差用完时你继续拉上游但是丢弃元素而不是发射它们。使用TimerGraphStageLogic GraphStageLogic可以设置可以重置限额的定时回调。

答案 1 :(得分:2)

答案 2 :(得分:0)

感谢@johanandren的回答,我成功实现了一个符合我要求的基于时间的自定义GraphStage。

如果有兴趣的话,我发布下面的代码:

import akka.stream.Attributes;
import akka.stream.FlowShape;
import akka.stream.Inlet;
import akka.stream.Outlet;
import akka.stream.stage.*;
import scala.concurrent.duration.FiniteDuration;

public class CustomThrottleGraphStage<A> extends GraphStage<FlowShape<A, A>> {

    private final FiniteDuration silencePeriod;
    private int nbElemsMax;

    public CustomThrottleGraphStage(int nbElemsMax, FiniteDuration silencePeriod) {
        this.silencePeriod = silencePeriod;
        this.nbElemsMax = nbElemsMax;
    }

    public final Inlet<A> in = Inlet.create("TimedGate.in");
    public final Outlet<A> out = Outlet.create("TimedGate.out");

    private final FlowShape<A, A> shape = FlowShape.of(in, out);
    @Override
    public FlowShape<A, A> shape() {
        return shape;
    }

    @Override
    public GraphStageLogic createLogic(Attributes inheritedAttributes) {
        return new TimerGraphStageLogic(shape) {

            private boolean open = false;
            private int countElements = 0;

            {
                setHandler(in, new AbstractInHandler() {
                    @Override
                    public void onPush() throws Exception {
                        A elem = grab(in);
                        if (open || countElements >= nbElemsMax) {
                            pull(in);  // we drop all incoming observations since the rate limit has been reached
                        }
                        else {
                            if (countElements == 0) { // we schedule the next instant to reset the observation counter
                                scheduleOnce("resetCounter", silencePeriod);
                            }
                            push(out, elem); // we forward the incoming observation
                            countElements += 1; // we increment the counter
                        }
                    }
                });
                setHandler(out, new AbstractOutHandler() {
                    @Override
                    public void onPull() throws Exception {
                        pull(in);
                    }
                });
            }

            @Override
            public void onTimer(Object key) {
                if (key.equals("resetCounter")) {
                    open = false;
                    countElements = 0;
                }
            }
        };
    }
}