合并多个流,保持排序并避免重复

时间:2015-10-22 14:42:57

标签: system.reactive rx-java

我有一个问题,我不知道如何处理与RX精美。 我有多个流,所有这些都应该包含相同的元素 但是,与其他人相比,每个流可能会丢失消息(涉及UDP)或迟到/早。这些消息中的每一条都有一个序列号

现在我想要实现的是从所有这些流中获取单个流,没有重复并保持消息顺序。换句话说,相同的序列号不应出现两次,它们的值只需要增加,不要减少。 当消息在所有流上丢失时,我可以丢失它(因为涉及的另一个TCP机制允许我明确询问丢失的消息)。

我希望在RxJava中这样做,但我想我的问题不是特定于Java。

这是一张大理石图,可帮助我看到我想要实现的目标: marble diagram

您可以在该图表中看到我们正在等待第一个流上的 2 从第二个流输出 3 。 同样,只有在我们从第二个流收到 6 时才输出 6 ,因为只有在那时我们才能确定 5 永远不会被任何流收到。

2 个答案:

答案 0 :(得分:2)

这是浏览器代码,但我认为它应该让你很好地了解如何解决这个问题。

public static IObservable<T> Sequenced<T>(
    this IObservable<T> source,
    Func<T, int> getSequenceNumber,
    int sequenceBegin,
    int sequenceRedundancy)
{
    return Observable.Create(observer =>
    {
        // The next sequence number in order.
        var sequenceNext = sequenceBegin;

        // The key is the sequence number.
        // The value is (T, Count).
        var counts = new SortedDictionary<int, Tuple<T, int>>();
        return source.Subscribe(
            value =>
            {
                var sequenceNumber = getSequenceNumber(value);

                // If the sequence number for the current value is
                // earlier in the sequence, just throw away this value.
                if (sequenceNumber < sequenceNext)
                {
                    return;
                }

                // Update counts based on the current value.
                Tuple<T, int> count;
                if (!counts.TryGetValue(sequenceNumber, out count))
                {
                    count = Tuple.Create(value, 0);
                }
                count = Tuple.Create(count.Item1, count.Item2 + 1);
                counts[sequenceNumber] = count;

                // If the current count has reached sequenceRedundancy,
                // that means any seqeunce values S such that
                // sequenceNext < S < sequenceNumber and S has not been
                // seen yet will never be seen. So we emit everything
                // we have seen up to this point, in order.
                if (count.Item2 >= sequenceRedundancy)
                {
                    var removal = counts.Keys
                        .TakeWhile(seq => seq <= sequenceNumber)
                        .ToList();
                    foreach (var seq in removal)
                    {
                        count = counts[seq];
                        observer.OnNext(count.Item1);
                        counts.Remove(seq);
                    }
                    sequenceNext++;
                }

                // Emit stored values as long as we keep having the
                // next sequence value.
                while (counts.TryGetValue(sequenceNext, out count))
                {
                    observer.OnNext(count.Item1);
                    counts.Remove(sequenceNext);
                    sequenceNext++;
                }
            },
            observer.OnError,
            () =>
            {
                // Emit in order any remaining values.
                foreach (var count in counts.Values)
                {
                    observer.OnNext(count.Item1);
                }
                observer.OnCompleted();
            });
    });
}

如果您有两个流IObservable<Message> AIObservable<Message> B,则可以通过执行Observable.Merge(A, B).Sequenced(msg => msg.SequenceNumber, 1, 2)来使用此流。

对于示例大理石图,这将如下所示,其中source列显示Observable.Merge(A, B)发出的值,counts列显示{{1}的内容在算法的每个步骤之后。我假设原始源序列的“消息”(没有任何丢失的值)是(A,1),(B,2),(C,3),(D,4),(E,5), (F,6)其中每条消息的第二个组成部分是其序列号。

SortedDictionary

答案 1 :(得分:0)

前一段时间出现了类似的问题I have a custom merge operator,当给定有序流时,它会按顺序合并它们,但不会进行重复数据删除。

修改

如果你“负担得起”它,你可以使用这个自定义合并,然后使用distinctUntilChanged(Func1)来过滤掉具有相同序列号的后续消息。

Observable<Message> messages = SortedMerge.create(
    Arrays.asList(src1, src2, src3), (a, b) -> Long.compare(a.id, b.id))
.distinctUntilChanged(v -> v.id);