目的:
我正在使用StackExchange Redis客户端。我的目标是从客户端公开的Pub Sub Subscriber创建一个Observable流,然后可以反过来支持Observables的 1-n 订阅,每个订阅都通过LINQ拥有自己的过滤器。 (发布按计划运行,问题纯粹围绕特定频道上的事件流订阅。)
背景
我使用Redis Pub Sub作为事件源CQRS应用程序的一部分。具体用例是将事件发布给多个订阅者,然后更新各种读取模型,发送电子邮件等。
这些订阅者中的每一个都需要过滤他们处理的事件类型,为此我希望使用Rx .Net(Reactive Extensions)和LINQ来实现 在事件流上提供过滤条件,以有效地处理仅对感兴趣的事件做出反应。使用这种方法消除了使用事件总线实现注册处理程序的需要,并允许我通过部署 1-n 微服务来向系统添加新的投影,每个微服务具有 1-n Observables使用自己的特定过滤器订阅了事件流。
我做过什么:
1)我创建了一个继承自ObservableBase的类,重写了SubscribeCore方法,该方法接收来自Observables的订阅请求,将它们存储在ConcurrentDictionary中,并且当每个Redis通知从通道到达时,循环通过已注册的Observable订阅者和调用他们的OnNext方法传递RedisValue。
2)我创建了一个Subject,它也接受来自Observables的订阅,并调用它们的OnNext方法。同样,对象的使用似乎被许多人所厌恶。
问题:
我尝试过的方法功能(至少表面上看)具有不同的性能水平,但是feel like a hack
,并且我没有按照预期的方式使用Rx。
我看到许多评论认为应该尽可能使用内置的Observable方法,例如Observable.FromEvent,但这似乎与StackExchange Redis客户端订阅API无关,至少在我看来。
我也理解接收流并转发到多个观察者的首选方法是使用 ConnectableObservable ,这似乎是针对场景我设计的face(每个微服务将在内部订阅 1-n Observables)。目前,我无法理解如何将 ConnectableObservable 连接到来自StackExchange Redis的通知,或者它是否提供了超过 Observable 的真正好处。
更新:
虽然在我的方案中完成不是问题(Disposal很好),但错误处理 很重要;例如隔离在一个订户中检测到的错误,以防止所有订阅终止。
答案 0 :(得分:13)
以下是一种可用于从IObservable<RedisValue>
和ISubscriber
创建RedisChannel
的扩展方法:
public static IObservable<RedisValue> WhenMessageReceived(this ISubscriber subscriber, RedisChannel channel)
{
return Observable.Create<RedisValue>(async (obs, ct) =>
{
await subscriber.SubscribeAsync(channel, (_, message) =>
{
obs.OnNext(message);
}).ConfigureAwait(false);
return Disposable.Create(() => subscriber.Unsubscribe(channel));
});
}
由于没有完成Redis频道,结果IObservable
永远不会完成,但您可以放弃IDisposable
订阅以取消订阅Redis频道(这将由许多Rx运营商自动完成)
用法可能如此:
var subscriber = connectionMultiplexer.GetSubscriber();
var gotMessage = await subscriber.WhenMessageReceived("my_channel")
.AnyAsync(msg => msg == "expected_message")
.ToTask()
.ConfigureAwait(false);
或者根据你的例子:
var subscriber = connectionMultiplexer.GetSubscriber();
var sendEmailEvents = subscriber.WhenMessageReceived("my_channel")
.Select(msg => ParseEventFromMessage(msg))
.Where(evt => evt.Type == EventType.SendEmails);
await sendEmailEvents.ForEachAsync(evt =>
{
SendEmails(evt);
}).ConfigureAwait(false);
其他微服务可能会有不同的过滤方式。