如何在RxJava 2中组合/加入以不同速度到达的可观察对象?

时间:2018-06-15 14:40:20

标签: rx-java rx-java2

我正在尝试组合以不同速度生成的Observable。

考虑从外部服务推送的价格报价流,它们可以(有时)以高速率到达。收到每个报价后,其他服务会根据此价格计算指标并发出这些指标。

我现在想要将这些价格和指标的观察结合起来,并将它们提供给另一项服务。每个价格都有一个时间戳,每个指标都有用于计算的价格的时间戳。价格和指标应按此时间戳分组。

在下面的代码中,我尝试说明这一点。我正在使用Observable.combineLatest(),因为这似乎合适,但正如您在combineLatestToFast()中看到的那样,如果在计算所有指标之前新价格到货,这种方法将无效。但是,如果在每个指标计算完毕后到达新价格,它就会有效,正如您在combineLatestToFast()中看到的那样。

好像我错过了什么。我尝试过使用窗口,缓冲和Flowables的不同方法,但似乎我没有理解这一点。

非常感谢任何帮助。谢谢!

package rx;

import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.observables.ConnectableObservable;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


public class RxCombineObservablesReproducer {

    public static void main(String[] args) {
        RxCombineObservablesReproducer reproducer = new RxCombineObservablesReproducer();
        //this will work as indented
        reproducer.combineLatestExample();
        //this not
        reproducer.combineLatestToFast();
    }

    private void combineLatestExample() {

        ConnectableObservable<Data> priceObs = Observable.interval(100, TimeUnit.MILLISECONDS).map(i -> new Data("price", i * 1.00, Instant.now())).publish();
        Observable<Data> indicator1Obs = priceObs.map(i -> new Data("indicator1", i.getValue() * 2.00, i.getTs())).delay(10, TimeUnit.MILLISECONDS);
        Observable<Data> indicator2Obs = priceObs.map(i -> new Data("indicator2", i.getValue() / 2.0, i.getTs())).delay(50, TimeUnit.MILLISECONDS);

        Disposable dis = Observable.combineLatest(priceObs, indicator1Obs, indicator2Obs, (Data price, Data indicator1, Data indicator2) -> {
                    if (checkSameTs(price, indicator1, indicator2)) {
                        Map result = new HashMap<String, Data>();
                        result.put("price", price);
                        result.put("indicator1", indicator1);
                        result.put("indicator2", indicator2);
                        return result;
                    }
                    return new HashMap<String, Data>();
                }

        )
                .filter(m -> !m.isEmpty())
                .subscribe(System.out::println);
        priceObs.connect();
        sleep(1000);
        dis.dispose();
    }

    private void combineLatestToFast() {

        ConnectableObservable<Data> priceObsToFast = Observable.interval(10, TimeUnit.MILLISECONDS).map(i -> new Data("price", i * 1.00, Instant.now())).publish();
        Observable<Data> indicator1ObsToFast = priceObsToFast.map(i -> new Data("indicator1ToFast", i.getValue() * 2.00, i.getTs())).delay(9, TimeUnit.MILLISECONDS);
        Observable<Data> indicator2ObsToFast = priceObsToFast.map(i -> new Data("indicator2ToFast", i.getValue() / 2.00, i.getTs())).delay(15, TimeUnit.MILLISECONDS);

        System.out.println("\n------------------------------toFast------------------------------\n");
        Disposable dis = Observable.combineLatest(priceObsToFast, indicator1ObsToFast, indicator2ObsToFast, (Data priceToFast, Data indicator1ToFast, Data indicator2ToFast) -> {
                    //System.out.println("combineToFast");
                    if (checkSameTs(priceToFast, indicator1ToFast, indicator2ToFast)) {
                        Map result = new HashMap<String, Data>();
                        result.put("priceToFast", priceToFast);
                        result.put("indicator1ToFast", indicator1ToFast);
                        result.put("indicator2ToFast", indicator2ToFast);
                        return result;
                    }
                    return new HashMap<String, Data>();
                }

        )
                .filter(m -> !m.isEmpty())
                .subscribe(System.out::println);
        priceObsToFast.connect();
        sleep(500);
        dis.dispose();
        System.out.println("The End");
    }

    private boolean checkSameTs(Data... dataObjects) {
        Instant ts = dataObjects[0].getTs();
        boolean same = true;
        for (Data data : dataObjects) {
            if (!data.getTs().equals(ts)) {
                same = false;
                break;
            }
        }
        return same;
    }

    private void sleep(int ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class Data {
        private String name;
        private Double value;
        private Instant ts;

        Data(String name, Double value, Instant ts) {
            this.name = name;
            this.value = value;
            this.ts = ts;
        }

        public String getName() {
            return name;
        }

        Double getValue() {
            return value;
        }

        Instant getTs() {
            return ts;
        }

        @Override
        public String toString() {
            return "Data{" +
                    "ts=" + ts +
                    ",name='" + name + '\'' +
                    ", value=" + value +
                    '}';
        }
    }


}

输出

{price=Data{ts=2018-06-15T13:06:57.061978200Z,name='price', value=0.0}, indicator1=Data{ts=2018-06-15T13:06:57.061978200Z,name='indicator1', value=0.0}, indicator2=Data{ts=2018-06-15T13:06:57.061978200Z,name='indicator2', value=0.0}}
{price=Data{ts=2018-06-15T13:06:57.156025200Z,name='price', value=1.0}, indicator1=Data{ts=2018-06-15T13:06:57.156025200Z,name='indicator1', value=2.0}, indicator2=Data{ts=2018-06-15T13:06:57.156025200Z,name='indicator2', value=0.5}}
{price=Data{ts=2018-06-15T13:06:57.256024800Z,name='price', value=2.0}, indicator1=Data{ts=2018-06-15T13:06:57.256024800Z,name='indicator1', value=4.0}, indicator2=Data{ts=2018-06-15T13:06:57.256024800Z,name='indicator2', value=1.0}}
{price=Data{ts=2018-06-15T13:06:57.356112300Z,name='price', value=3.0}, indicator1=Data{ts=2018-06-15T13:06:57.356112300Z,name='indicator1', value=6.0}, indicator2=Data{ts=2018-06-15T13:06:57.356112300Z,name='indicator2', value=1.5}}
{price=Data{ts=2018-06-15T13:06:57.456075700Z,name='price', value=4.0}, indicator1=Data{ts=2018-06-15T13:06:57.456075700Z,name='indicator1', value=8.0}, indicator2=Data{ts=2018-06-15T13:06:57.456075700Z,name='indicator2', value=2.0}}
{price=Data{ts=2018-06-15T13:06:57.557074500Z,name='price', value=5.0}, indicator1=Data{ts=2018-06-15T13:06:57.557074500Z,name='indicator1', value=10.0}, indicator2=Data{ts=2018-06-15T13:06:57.557074500Z,name='indicator2', value=2.5}}
{price=Data{ts=2018-06-15T13:06:57.656075600Z,name='price', value=6.0}, indicator1=Data{ts=2018-06-15T13:06:57.656075600Z,name='indicator1', value=12.0}, indicator2=Data{ts=2018-06-15T13:06:57.656075600Z,name='indicator2', value=3.0}}
{price=Data{ts=2018-06-15T13:06:57.755255800Z,name='price', value=7.0}, indicator1=Data{ts=2018-06-15T13:06:57.755255800Z,name='indicator1', value=14.0}, indicator2=Data{ts=2018-06-15T13:06:57.755255800Z,name='indicator2', value=3.5}}
{price=Data{ts=2018-06-15T13:06:57.856452700Z,name='price', value=8.0}, indicator1=Data{ts=2018-06-15T13:06:57.856452700Z,name='indicator1', value=16.0}, indicator2=Data{ts=2018-06-15T13:06:57.856452700Z,name='indicator2', value=4.0}}

------------------------------toFast------------------------------

The End

修改

为了回答第一个答案,我添加了一张大理石图。问题不在于根据价格计算指标。如上所述,这已经由外部服务发生,但是根据密钥(在这种情况下是时间戳)将相应的价格和指标分组在一起,无论它们何时以及以何种顺序到达,然后将它们发送到以下服务。 / p>

请点击链接查看图表,我不允许嵌入图片。

Marble diagram

1 个答案:

答案 0 :(得分:0)

基于大理石图,由于序列的顺序不同,combineLatest根本不起作用。您必须为每个流保留哈希映射并向一个公共使用者步骤发出信号,以查看它是否可以从这些哈希映射中建立一行值。

ConcurrentHashMap<Key, A> mapA = new ConcurrentHashMap<>();
ConcurrentHashMap<Key, B> mapB = new ConcurrentHashMap<>();
ConcurrentHashMap<Key, C> mapC = new ConcurrentHashMap<>();

Observable<A> sourceA = ...
Observable<B> sourceB = ...
Observable<C> sourceC = ...

Observable.merge(
    sourceA.map(v -> { mapA.put(v.getKey(), v); return v.getKey(); }),
    sourceB.map(v -> { mapB.put(v.getKey(), v); return v.getKey(); }),
    sourceC.map(v -> { mapC.put(v.getKey(), v); return v.getKey(); }),
)
.flatMap(key -> {
    if (mapA.containsKey(key) && mapB.containsKey(key)
            && mapC.containsKey(key)) {
        return Observable.just(new Result(
            mapA.remove(key), mapB.remove(key), mapC.remove(key)
        ));
    }
    return Observable.empty();
})

.subscribe(result -> { /* do something with a row */ });