响应式编程入门系列(二)

问题

相应式编程的概念推出后,特别是RxJava1.x后,出现了越来越多的响应式编程库。若一个代码中存在两个依赖于同一个异步非阻塞通信概念但具有不同API的库,会导致我们需要提供额外的工具类,以便将一个回调转换为另一个回调。

另外,大多数库的设计思想都是把数据从源头推送到订阅者。由于不知道未来请求的数量,这意味着发布者不能提前生成数据,并因此处于空闲状态,效率会比较低下。

因此,在2013年年末,来自Lightbend、Netflix和Pivotal的一群天才工程师齐聚一堂,共同解决上述问题并为JVM社区提供标准。经过长达一年的努力,响应式流规范的初稿公诸于世。这个提议概念设想就是响应式编程模式的标准化。

响应式流规范

响应式流规范定义了4个主要接口: Publisher 、 Subscriber 、Subscription 和 Processor 。该倡议的发展独立于任何组织,可作为单独的JAR文件获取,而其中所有接口都存在于org.reactivestreams 包中。

总的来说,规范中指定的接口与我们前面介绍的RxJava 1. x类似。这些接口中的前两个类似于 Observable和Observer,与传统的发布-订阅模型比较相似。

Publisher接口定义如下:

package org.reactivestreams;
public interface Publisher<T> {
    void subscribe(Subscriber<? super T> s);
}

可以看到只有一种方法可以注册 Subscriber ,Publisher 代表了发布者和订阅者直接连接的标准化入口点。

Subscriber接口的定义如下,Subscriber与RxJava中的Observer 接口几乎完全相同:

package org.reactivestreams;
public interface Subscriber<T> {
    void onSubscribe(Subscription s);
    void onNext(T t);
    void onError(Throwable t);
    void onComplete();
}

除3个与RxJava  Observer 中名称相同的方法之外,规范还提供了一个新的名为 onSubscribe方法,提供了一种标准化的方式来通知 Subscriber 订阅成功。同时,该方法的传入参数为我们引入一个名为 Subscription (订阅)的新接口。该接口定义如下:

package org.reactivestreams;
public interface Subscription {
    void request(long n);
    void cancel();
}

Subscription 为控制元素的生产提供了基础。与RxJava 1. x的 Subscription的unsubscribe() 类似,这里的 cancel() 方法使我们能取消对流的订阅甚至完全取消发布。响应式流规范引入了 request 方法以扩展 Publisher 和 Subscriber 之间的交互能力。为了通知Publisher 应该推送多少数据, Subscriber 可以通过 request 方法发出关于所需数量的信号,并且确保传入元素的数量不超过限制,也就是背压机制。如下图所示。

Publisher 现在保证只有在 Subscriber 要求时才发送元素中新的部分。 Publisher 的整体实现情况可能多种多样:它既可能采用纯粹的阻塞等待,也可能采用仅在Subscriber 请求下才生成数据的复杂机制。与纯推模型相反,该规范为我们提供了混合推拉模型,可以对背压进行合理控制。在此规范下,也可以实现纯推模型,也就是请求 2^{63}-1( java.lang.Long.MAX_VALUE )个元素的需求。

下面使用一个新闻订阅的示例演示这些接口的使用。

NewsServicePublisher newsService = new NewsServicePublisher();
NewsServiceSubscriber subscriber = new NewsServiceSubscriber;
newsService.subscribe(subscriber);
...
subscriber.eventuallyReadDigest();

NewsServicePublisher和NewsServiceSubscriber分别是新闻发布者和订阅者,subscriber.eventuallyReadDigest模拟了用户阅读新闻的过程,新闻发布者只有在新闻被阅读了之后才会继续推送新的新闻列表给订阅者。新闻订阅者的实现如下:

class NewsServiceSubscriber implements Subscriber<NewsLetter> {
	final Queue<NewsLetter> mailbox = new ConcurrentLinkedQueue<>();
	final int take; // 每次请求新新闻的数量
	final AtomicInteger remaining = new AtomicInteger();
	Subscription subscription;
	
	// 构造函数
	public NewsServiceSubscriber(int take) { ... }
	
	public void onSubscribe(Subscription s) {
		// 保存接收到的Subscription并将用户的读取数量发送到服务器
		subscription = s;
		subscription.request(take);
		...
	}
	
	public void onNext(NewsLetter newsLetter) {
		// 接收到新的新闻,保存留待阅读
		mailbox.offer(newsLetter);
	}

	public void onError(Throwable t) { ... }
	public void onComplete() { ... }
	
	public Optional<NewsLetter> eventuallyReadDigest() {
		// 获取最新的未读新闻摘要
		NewsLetter letter = mailbox.poll();
		if (letter != null) {
			if (remaining.decrementAndGet() == 0) {
				// 读取了所有摘要时,通知Publisher
				subscription.request(take);
				remaining.set(take);
			}
			
			return Optional.of(letter);
		}
		
		// 没有未读,返回空
		return Optional.empty();
	}
}

根据规范,newsService.subscribe(subscriber)调用触发 onSubscribe() 方法,它在本地存储Subscription ,然后通过 request() 方法通知 Publisher 它们是否准备好接收新闻。当新闻发送后,它将存储在队列中以供后续阅读。当订阅者已经从收件箱中读取了所有新闻时,该情况将被告知 Publisher ,然后 Publisher 会准备新的新闻。另外,新闻服务如果改变了订阅策略(在某些情况下意味着当前用户订阅的结束)将通过 onComplete 方法通知订阅者。然后,客户端将被要求接受新策略并重新订阅该服务。eventuallyReadDigest模拟了用户读取新闻后的处理逻辑。

规范中有4个核心接口,最后一个被称为 Processor(处理器),是 Publisher 和 Subscriber 的组合。接口定义如下:

package org.reactivestreams;
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

Processor的目标是在 Publisher 和 Subscriber 之间添加一些处理阶段,Processor 可能代表一些转换逻辑,这使流管道行为和业务逻辑流更容易理解。使用 Processor 的典型示例包括它可以在自定义操作符中描述任何业务逻辑,还可以提供流数据的附加缓存。

为演示Processor的使用,在上面例子的基础上进行升级,用下图作为例子,加入准备新闻和随后向所有订阅者进行多播的步骤。

 在此示例中,NewsServicePublisher 被分成4个附加组件:

  1. DBPublisher阶段,Publisher 负责提供对数据库的访问并返回最新的新闻。
  2. NewsPreparationOperator阶段。这是一个中间转换阶段,该阶段负责聚合所有消息,并在主数据源发出完成信号时将所有新闻组合到摘要中,此操作符总是会生成一个元素。
  3. ScheduledPublisher阶段。此阶段负责定期调度任务。该任务处理结果并将接收的数据合并到下游。ScheduledPublisher 实际上是一个无限流,并且忽略了已合并Publisher 的完成。在缺少来自下游请求的情况下,该 Publisher 通过Subscriber#onError 方法向实际 Subscriber 抛出异常。
  4. SmartMulticastProcessor 阶段。该 Processor 在流程中起着至关重要的作用。首先,它缓存了最新的摘要。同时,该阶段支持多播,这意味着我们无须单独为每个 Subscriber 创建相同的流。此外,如前所述, SmartMulticastProcessor 包括一个智能邮件跟踪机制,只会为那些阅读过前一条摘要的人发送新闻。
  5. 这些是真实的订阅者,它们实际上是 NewsServiceSubscriber 。
Publisher<Subscriber<NewsLetter>> newsLetterSubscribersStream =... 
ScheduledPublisher<NewsLetter> scheduler = new ScheduledPublisher<>(
	() -> new NewsPreparationOperator(new DBPublisher(...),	...), 1, TimeUnit.DAYS);
	
//SmartMulticastProcessor订阅ScheduledPublisher,该操作立即启动调度程序,调度程序又订阅内部 Publisher 。
SmartMulticastProcessor processor = new SmartMulticastProcessor(); //
scheduler.subscribe(processor);

newsLetterSubscribersStream.subscribe(new Subscriber<>() {
	public void onNext(Subscriber<NewsLetter>> s) {
		processor.subscribe(s);
	}
	...
});

小结

有了响应式流规范后,各个响应式开发库都遵循同一套规范,因此相互兼容,不同的开发库之间可以进行交互,我们甚至可以在一个项目中使用多个响应式开发库。同时,该规范为响应式流带来了推-拉通信模型。这种补充解决了背压控制问题,有助于增强通信灵活性。

对Spring框架的用户而言,针对响应式编程发生的最重大变化是引入了名为Project Reactor的新响应式库。Project Reactor扮演着重要的角色,它是新的响应式Spring生态系统的基石。下一节将介绍Project Reactor的概念组成及其应用。

  • 29
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值