Rx:通过匹配ID

时间:2017-05-26 08:02:12

标签: rx-java rx.net

我们假设有两个可观察量o1, o2。第一个接收来自内部进程的事件(在很长的计算完成之后),第二个通过REST端点接收外部事件(发信号通知另一个外部组件也完成)。事件数据只是一个ID。

现在我想设计一个工作流程,这样只有当两个observable中都存在一个ID时,才会发出一个新事件(即内部和外部计算完成时)。

在某个时间点o1包含ID {1,2,3},然后我想区分这些情况:

  1. 正常情况:例如ID 2到达o2。两个ID现在都存在于两个observable中,输出“Success:2”
  2. 过期案例:内部计算完成后的一段时间,外部事件尚未到达。例如。 ID 2出现在o1o2中,即使在一小时后也是如此,输出:“已过期:2”
  3. 未知案例:ID,例如4,通过o2中不存在的REST端点到达o1,可能是因为ID已经过期或仅仅因为外部组件有问题,输出:“Unknown:3”< / em>的
  4. 我发现groupJoin运算符可能会执行我想要的操作,这里甚至是属性匹配的示例:GroupJoin - Joins two streams matching by one of their attributes

    然而,似乎这个例子每次新事件到来时都对所有元素执行耗尽(线性时间)扫描。我认为可以滚动我自己的版本来检查地图,但是:我想知道是否有规范的方式甚至是开箱即用的功能(因为我猜测这是一个很常见的用例。)

    (因为我是Rx的新手,实现此类连接操作的到期情况的最佳方法是什么)

3 个答案:

答案 0 :(得分:2)

我是通过在外部对象中使用中间状态来实现的:

public class ItemJoinCache<T> {
   private Map<Integer, T> items;
   public Observable<T> ingestInternal(T item) {
      // an internal item arrived, do the necessary work
   }
   public Observable<T> ingestExternal(T item) {
      // an external item arrived, do the necessary work
   }
}

externalRestCallThatReturnsObservable()
.flatMap(myItemJoinCache::ingestExternal)
...

internalProcessThatTakesALongTime()
.flatMap(myItemJoinCache::ingestInternal)
...

通过这种方式,您可以进行任何可能需要的处理。

答案 1 :(得分:1)

你也标记了问题rx.net,所以我会假设在C#中给出答案的奢侈。我不确定这会转化为Java,如果这就是你正在寻找的东西。

Rx的JoinGroupJoin并不是真正的意思:它们是基于时间窗口加入的。你想通过身份证加入。

对Rx友好的解决方案将起作用。因为你需要一些状态,所以我们可以使用烘焙成Scan函数的不可变状态。在C#中,来自Nuget包ImmutableDictionary<TKey, TItem>的{​​{1}}。我不确定Java中有相同的东西。

鉴于这些课程:

System.Collections.Immutable

你可以得到这样的解决方案:

public class CustomEvent
{
    public int Id { get; set; }
}

public class Result
{
    public ResultType Type { get; set; }
    public int Id { get; set; }
}

public enum ResultType
{
    Success,
    Unknown,
    Expired
}

不可变字典是我们的核心状态,并保存来自IObservable<CustomEvent> o1; IObservable<int> o2; TimeSpan expirationTimeDelay = TimeSpan.FromHours(1); IObservable<Result> results = Observable.Merge( o1.SelectMany(ce => Observable.Merge( Observable.Return(new Func<ImmutableDictionary<int, CustomEvent>, Tuple<ImmutableDictionary<int, CustomEvent>, Result, bool>>(h => Tuple.Create(h.Add(ce.Id, ce), default(Result), false) )), Observable.Return(new Func<ImmutableDictionary<int, CustomEvent>, Tuple<ImmutableDictionary<int, CustomEvent>, Result, bool>>(h => h.ContainsKey(ce.Id) ? Tuple.Create(h.Remove(ce.Id), new Result { Type = ResultType.Expired, Id = ce.Id}, true) : Tuple.Create(h, default(Result), false) )) .Delay(expirationTimeDelay) )), o2.Select(id => new Func<ImmutableDictionary<int, CustomEvent>, Tuple<ImmutableDictionary<int, CustomEvent>, Result, bool>>(h => h.ContainsKey(id) ? Tuple.Create(h.Remove(id), new Result { Type = ResultType.Success, Id = id }, true) : Tuple.Create(h, new Result { Type = ResultType.Unknown, Id = id }, true) )) ) .Scan(Tuple.Create(ImmutableDictionary<int, CustomEvent>.Empty, default(Result), false), (t, f) => f(t.Item1)) .Where(t => t.Item3) .Select(t => t.Item2); 的“实时”事件。 accumulator函数返回一个具有三个属性的元组:表示核心状态的不可变字典,结果对象和布尔值。 Boolean对象是一个过滤器,显示是否应传播结果对象。

使用o1的一个有趣技巧是反转正常用法:将项目流转换为无关状态的函数。在我们的例子中,函数的类型是Func,Tuple,Results,Boolean&gt;&gt; (一个接受字典的函数,并返回一个带有三个值的元组)。

这就是我们在这里所做的:每个Scan项都会弹出两个函数:一个将项添加到不可变字典中(并且没有推送结果)。一小时后出现另一个功能,看看事件是否尚未加入。如果加入,则没有任何反应。如果未加入,则会弹出Expired结果。每个o1项都会弹出一个函数:检查项目是否在地图中。如果存在,则弹出正常结果。如果不存在,则为未知。

如果您使用的是Java,并且没有容易获得的等效于o2,那么您可以替换常规ImmutableDictionary,但是您必须防止来自多个订阅者的恶劣状态问题通过HashMap电话。

答案 2 :(得分:0)

您始终可以使用scan将o1缩减为Set。当o2发出一个值时,你从{1}获取o1的最新集并检查包含。 withLatestFrom可以解决到期部分。 RxJs 5中的示例:

timeout