web编程(3) reactor

1. 概述

响应式编程基于观察者模型,以异步非阻塞方式处理问题。在2009 年,由微软提出的一个实现异步编程的方式,随后java语言也很快跟进,例如RxJava、Akka Stream 等技术。

reactor是一个反应式编程库,它是Spring 5反应式编程的基础。该类库遵循Reactive Streams规范。

2. 类库

2.1 reactive-streams

反应式规范,主要有4个接口 Publisher,Subscriber,Subscription,Processor。

Publisher负责生成数据,并将数据发送给Subscriber。

Subscriber订阅Publisher,订阅成功后,会得到一个Subscription对象,Subscriber可以通过Subscription提供的request、cancle方法来管理其订阅。

Processor是一个处理器,负责处理数据,从接口上看,它是一个发布者和订阅者的组合。

2.2 reactor

Reactor有两种核心类型:一个是Mono, 另一个是Flux,二者都继承了Publisher接口,Mono 负责0到1个元素的处理,Flux负责0到N个元素的处理。

3. 操作

Reactor 中所提供的操作符非常丰富,本文只介绍常见的操作,以下按创建、组合、转换、过滤、事件的大致分类来介绍。

3.1 创建

通过简单数据类型,创建数据。

		Mono.just(1)
		.subscribe(System.out::println);
		
		Flux.just(1, 2, 3)
		.subscribe(System.out::println);
		
		Flux.range(1, 5)
		.subscribe(System.out::println);
		
		List<Integer> list1 = new ArrayList<>();
		list1.add(1);
		list1.add(2);		
		Flux.fromIterable(list1)
		.subscribe(System.out::println);

create方法

		Mono.create(x -> {	
			x.success("aa");
		})
		.subscribe(System.out::println);
		
		
		Flux.create(sink -> {
		    for (int i = 0; i < 5; i++) {
		        sink.next("aa" + i);
		    }
		    sink.complete();
		})
		.subscribe(System.out::println);

generate方法

		Flux.generate(
				// 生成一个对象,用作状态
				AtomicInteger::new,

				(state, sink) -> {
					int i = state.getAndIncrement();
					sink.next("aa" + i);

					if (i == 5) {
						sink.complete();
					}
					return state;
				},
				// 结束时,输出状态
				(state) -> System.out.println("state:" + state)

		).subscribe(System.out::println);

3.2 组合

merge、mergeSequential方法

	    Flux.merge(
	            Flux.interval(Duration.ofSeconds(1)).take(3).map(x -> "a-" + x),
	            Flux.interval(Duration.ofSeconds(2)).take(3).map(x -> "b-" + x))
	    .subscribe(System.out::println);

输出结果:
a-0
a-1
b-0
a-2
b-1
b-2
	    Flux.mergeSequential(
	            Flux.interval(Duration.ofSeconds(1)).take(3).map(x -> "a-" + x),
	            Flux.interval(Duration.ofSeconds(2)).take(3).map(x -> "b-" + x))
	    .subscribe(System.out::println);

输出:
a-0
a-1
a-2
b-0
b-1
b-2

通过输出可发现,merge是按流的自然序列合并输出,而mergeSequential则以流为单位,一个流一个流输出。

concatMap、concatWith方法

		Flux.just(10, 20)
		.concatMap( x -> Flux.range(x, 2))
	    .subscribe(System.out::println);

输出:
10
11
20
21
		Flux.just(10, 20)
		.concatWith(Flux.just(30, 40))
	    .subscribe(System.out::println);

输出:
10
20
30
40

zipWith方法

		Flux.just("a", "b")
		.zipWith(Flux.just("c", "d"), (s1, s2) -> String.format("%s-%s", s1, s2))
		.subscribe(System.out::println);

输出:
a-c
b-d

3.3 转换

map可以将原数据,映射为另一种数据类型,map对数据的变换是1对1

		Mono.just(1)
		.map(x -> new Apple(x, "aa"))
		.subscribe(System.out::println);
		
		Flux.range(10, 2)
		.map(x -> new Apple(x, "aa"))
		.subscribe(System.out::println);

flatMap可对每个元素进行一对多的转换,跟concatMap很相似,区别是flatMap不保证转换后的元素的顺序,而concatMap保证转换后的顺序,与原数据顺序一致。

		Flux.just(10, 20)				
		.flatMap( x -> Flux.range(x, 2))
	    .subscribe(System.out::println);

3.4 过滤

filter对流中数据过滤,只允许满足条件的数据通过。

		Flux.range(1, 10)
		.filter(i -> i % 2 == 0)
		.subscribe(System.out::println);

3.5 事件

除了丰富的操作符,reactor也提供了对数据处理各阶段的事件监听方法,样例如下:

		Flux.range(10, 3)
		.doOnRequest(x -> {
			System.out.println(String.format("请求%d个数据", x));
		})
		.doOnSubscribe(x -> {
			System.out.println("订阅开始");
		})
		.doOnNext(x -> {
			System.out.println("订阅数据:" + x);
		})
		.doOnComplete(() -> {
			System.out.println("订阅完成");
		})
		.doOnError(x -> {
			System.out.println("订阅异常:" + x.getMessage());
		})
		.then()
		.subscribe(System.out::println);

输出:
订阅开始
请求9223372036854775807个数据
订阅数据:10
订阅数据:11
订阅数据:12
订阅完成

通过上面的输出结果,可清晰的了解数据处理的流程。

3.6 其他

除了上面提到的,下面整理了其他几个有趣的操作符。

buffer,它将当前流中的元素收集到集合当中,并将集合对象作为流的新元素。

		Flux.range(1, 10)
		.buffer(3)
		.subscribe(System.out::println);		

输出:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]

windows,它将当前流中的元素收集到固定大小的流当中,并将收集的流对象作为流的新元素。

		Flux.range(1, 10)
		.window(3)
		.subscribe(x -> {
			System.out.println("------------");
			x.subscribe(System.out::println);
		});

输出:
------------
1
2
3
------------
4
5
6
------------
7
8
9
------------
10

4. 线程池

响应式流处理过程中,一些业务逻辑,需要进行线程隔离处理,可通过publishOn,subscribeOn两个方法配置Scheduler任务调度器,样例如下:

		Mono.just(1)		
		.map(x -> {
			System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
			return x + 100;
		})
		.publishOn(Schedulers.newSingle("single-publishOn"))
		.filter(x -> {
			System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
			return true;
		})
		.subscribeOn(Schedulers.newSingle("single-subscribeOn"))
		.doOnNext(x -> {
			System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
		})
		.subscribe(System.out::println);	

输出:
thread: single-subscribeOn-1, x: 1
thread: single-publishOn-2, x: 101
thread: single-publishOn-2, x: 101
101

(1)publishOn、subscribeOn二者区别

虽然publishOn 和 subscribeOn 两个方法都可以配置Scheduler,但二者的作用范围不同,
publishOn 仅对之后的operator 起作用,而subscribeOn 则从源头影响整个执行过程。

(2)Schedulers

通过Schedulers工具类,可创建多种类型的线程池。

// 单个线程
Schedulers.newSingle("xxx");
// 线程数为内核数
Schedulers.newParallel("xxx");
// 可对线程数、任务队列容量, 按需配置
Schedulers.newBoundedElastic(5, 10, "xxx");


(3)自定义

	    Executor executor = new ThreadPoolExecutor(
	            2,  //corePoolSize
	            10,  //maximumPoolSize
	            30L, TimeUnit.SECONDS, //keepAliveTime, unit
	            new LinkedBlockingQueue<>(1000),  //workQueue
	            Executors.defaultThreadFactory()
	    );
        
	    Schedulers.fromExecutor(executor);

5 策略

Publisher生产数据的速度大于Subscriber消费能力时,就会出现溢出问题,Reactor通过OverflowStrategy枚举参数,提供了多种处理策略:

IGNORE:完全忽略下游背压请求。
ERROR:当下游无法跟上时,发出IllegalStateException信号。
DROP:如果下游没有准备好接收传入信号,则丢弃传入。
LATEST:下游将仅获得来自上游的最新信号。
BUFFER:如果下游跟不上,缓冲所有信号。(默认)

例子:

public class Demo1{

	public static void main(String[] args) {

		Flux.create(sink -> {
		    for (int i = 0; i < 100; i++) {
				TestUtils.sleep(100);
				sink.next(i);
		    }
		    sink.complete();
		}, OverflowStrategy.LATEST)		
		.publishOn(Schedulers.newSingle("test-publish"), 5)	
		.subscribe(new MySubscriber());
	}
	
	public static class MySubscriber extends BaseSubscriber {
		
	    @Override
	    protected void hookOnSubscribe(Subscription subscription) {
	    	System.out.println("订阅开始!");
	        request(1);
	    }

	    @Override
	    protected void hookOnNext(Object value) {
			TestUtils.sleep(1000);
			System.out.println("消费数据: " + value);
	        request(1);
	    }
	}		
}

输出:
订阅开始!
消费数据: 0
消费数据: 1
消费数据: 2
消费数据: 3
消费数据: 4
消费数据: 36
消费数据: 37
消费数据: 38
消费数据: 39
消费数据: 71
消费数据: 72
消费数据: 73
消费数据: 74
消费数据: 99

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值