Reactor学习笔记

Reactor学习笔记

简介

Reactor(反应式/响应式)

Reactor是一个用于JVM的完全非阻塞的响应式编程框架,具备高效的需求管理(即对背压的控制)能力

Reactor响应式编程范式的实现,总结起来有如下几点:

  1. 响应式编程是一种关注于数据流变化传递的异步编程方式
  2. 响应式编程的实现方式是基于观察者模式的扩展

背压:由消费者控制生产者的生产速度,以解决生产者生产的速度远大于消费者消费的速度时所造成的消息的积压

Flux

Flux 是一个以数据流的形式发出 0 到 N 个元素的发布者(Publisher)Flux发布的数据数据流可以被一个错误(error)完成(completion)信号终止,也可以被取消信号取消

Mono

Mono 是一个特殊的Flux, 它最多发出一个元素,然后终止于一个 onComplete 信号或一个 onError 信号

Flux

创建

just

直接使用元素创建Flux, just直接在创建Flux时就拿到数据,之后有谁订阅它,就重新发送数据给订阅者

如果给just传入的数据是一个 HTTP 调用的结果,那么在初始化just的时候会进行唯一的一次网络调用

        Flux<String> stringFlux = Flux.just("a");
        
        Flux<Integer> integerFlux = Flux.just(1, 2);
fromfromArrayfromIterablefromStream

从其他数据源创建

        // 从其他Flux创建
        Flux<String> from = Flux.from(stringFlux);
        // 从数组创建
        Flux<String> fromArray = Flux.fromArray(new String[]{"1", "2", "3"});

        List<String> stringList = List.of("a", "b", "c");
        // 从迭代对象创建
        Flux<String> fromIterable = Flux.fromIterable(stringList);
        // 从Stream创建
        Flux.fromStream(stringList.stream());
create

使用FluxSink对象创建,支持同步和异步的消息产生,并且可以在一次调用中产生多个元素,可以用于 pushpull 模式

        Flux.create((sink) -> {
            sink.next("1");
            // 这里发出了complete信号,所有2不会发给订阅者
            sink.complete();
            sink.next("2");
        }).subscribe(System.out::println);
generate

同步的逐个地 产生值的方法

      Flux.generate(synchronousSink -> {
            // 只能调用一次next 
            synchronousSink.next("a");
            synchronousSink.complete();
        });


        // 基于状态值的generate,
        // 返回值作为一个新的状态值用于下一次调用
        // 直到接收到complete信号才会停止generate
        Flux.generate(
                () -> 0,
                (state, synchronousSink) -> {
                    synchronousSink.next(state);
                    if (state == 20) {
                        // 发送complete信息,停止generate
                        synchronousSink.complete();
                    }
                    return state + 1;
                });
range

产生从起始值递增的那个整数

// 第一个参数是 range 的开始,第二个参数是要生成的元素个数
Flux<Integer> range = Flux.range(2, 10);
interval

周期性生成从0开始的的Long。周期从delay之后启动,每隔period时间返回一个加1后的Long

        // 延迟30秒之后,每隔1秒产生从0开始递增加1的数据流
        Flux.interval(Duration.ofSeconds(30), Duration.ofSeconds(1));

`interval方法返回的Flux运行在另外的线程中,main线程需要休眠或者阻塞之后才能看到周期性的输出

defer

延迟提供发布者,只有在被订阅的时候才去构造Flux,每次被订阅的时候都会重新构造Flux

   public void test(){
        Flux<String> defer = Flux.defer(() -> Flux.fromIterable(list()));
        System.out.println("defer创建Flux");
        defer.subscribe();

        System.out.println("****************");
        Flux<String> iterable = Flux.fromIterable(list());
        System.out.println("fromIterable创建Flux");
        iterable.subscribe();
    }

    public List<String> list(){
        System.out.println("创建list");
        return List.of("1", "2");
    }

/**
    defer创建Flux
    创建list
     ****************
     创建list
     fromIterable创建Flux
**/
push

类似于create

        Flux<String> push = Flux.push(emitter -> {
            emitter.next("a");
            emitter.next("b");
            emitter.complete();
            emitter.next("c");
        });
empty

创建一个空的Flux

Flux<Object> empty = Flux.empty();

订阅

Flux被订阅后会将数据逐个发给订阅者,订阅者通过subscribe方法订阅消费Flux发来的数据

// 订阅并触发
subscribe(); 

// 对每一个生成的元素进行消费
subscribe(Consumer<? super T> consumer); 

// 对正常元素进行消费,对产生的错误进行响应
subscribe(Consumer<? super T> consumer, Consumer<? super Throwable> errorConsumer); 

// 对正常元素和错误均有响应,还定义了消费完成后的回调
subscribe(Consumer<? super T> consumer, Consumer<? super Throwable> errorConsumer,
          Runnable completeConsumer); 

合并Flux

concat

按照首位相连的方式合并两个Flux,并返回一个新的Flux

        Flux<String> stringFlux = Flux.just("a", "b");
        Flux<String> stringFlux1 = Flux.just("1", "2", "3");
        Flux<String> concat = Flux.concat(stringFlux, stringFlux1);
merge

按照Flux中数据出现的顺序合并两个Flux,并返回一个新的FLux

       Flux<String> interval = Flux.interval(Duration.ofSeconds(1), Duration.ofSeconds(3))
                .map(l -> l + "a");
       Flux<Long> interval1 = Flux.interval(Duration.ofSeconds(2));

       Flux<Object> merge = Flux.merge(interval, interval1);
combineLatest

把所有源中最新产生的元素合并成一个新元素下发。只要其中任何一个源中产生了新元素,合并操作就会执行一次,然后下发新产生的元素

        Flux<String> interval = Flux.interval(Duration.ofSeconds(1), Duration.ofSeconds(3))
                .map(l -> l + "a");
        Flux<Long> interval1 = Flux.interval(Duration.ofSeconds(2));

        Flux<List<Object>> combineLatest = Flux.combineLatest(str -> List.of(str), interval, interval1);
zip

按照所有源中元素出现的顺序,将所有源的元素一比一组合成元组

	Flux<Tuple2<String, String>> zip = Flux.zip(stringFlux, stringFlux1);
startWith

Flux的开头添加元素,返回一个新的Flux

        Flux<String> stringFlux = Flux.just("a", "b");
        Flux<String> newStringFlux = stringFlux.startWith("1", "2");
concatWithValues

Flux的末尾添加元素,返回一个新的Flux

        Flux<String> stringFlux = Flux.just("a", "b");
        Flux<String> concatWithValues = stringFlux.concatWithValues("1", "2");

转换

map

Flux中的元素按照map中定义的规则进行转换

        Flux<String> stringFlux = Flux.just("a", "b");
        Flux<char[]> map = stringFlux.map(str -> str.toCharArray());
flatMap

Flux中的元素按照flatMap中定义的规则进行转换, flatMap中函数的返回值是一个PublisherflatMap会将多个Publisher合并展开其中的元素

 	Flux<String> stringFlux = Flux.just("hello", "world");
       Flux<String> flatMap = stringFlux.flatMap(str -> Flux.fromArray(str.split("")));
handle

Flux中的元素按照handle中定义的规则进行转换, handle的参数是一个BiConsumer

	Flux<String[]> handle = stringFlux.handle((str, sink) -> sink.next(str.split("")));
toIterable

Flux转成迭代器

         Flux<String> stringFlux = Flux.just("a", "b", "c", "d", "e");
         Iterable<String> strings = stringFlux.toIterable();
toStream

Flux转成Stream

	Stream<String> stringStream = stringFlux.toStream();
collectList

Flux中的元素放入list中,并用Mono包裹返回

	Mono<List<String>> listMono = stringFlux.collectList();
	// 获取Mono中的元素
	List<String> block = listMono.block();
collectMap

Flux中的元素放入map中,并用Mono包裹返回

        Mono<Map<Integer, Integer>> mapMono = Flux.range(1, 10).collectMap(i -> i % 3);
        Map<Integer, Integer> map = mapMono.block();
collect

Flux中的元素放入输入的集合类型(collect中的第一个参数)中,并用Mono包裹返回

	Mono<ArrayList<String>> collect = stringFlux.collect(ArrayList::new, (a, b) -> a.add(b));

分组处理

buffer

Flux中的元素按个数进行分组放入一个集合中

	Flux<String> stringFlux = Flux.just("a", "b", "c", "d", "e");
        Flux<List<String>> buffer = stringFlux.buffer(2);
window

根据个数、时间等条件,或能够定义边界的发布者, 把Flux中的元素分组放入一个Flux

	Flux<Flux<String>> window = stringFlux.window(2);
groupBy

Flux中的元素按自定义的规则进行分组放入一个GroupedFlux

        Flux<GroupedFlux<String, Integer>> groupedFluxFlux = Flux.range(1, 10)
                .groupBy(i -> i % 2 == 0 ? "even" : "odd");

过滤

filter

过滤Flux中的元素,保留filter断言为真的元素

	Flux<Integer> filter = Flux.range(1, 10).filter(i -> i % 3 == 0);
filterWhen

根据自定义判断函数,异步地进行判断,

	Flux<Integer> integerFlux = Flux.range(1, 10).filterWhen(i -> Flux.just(i % 2 == 0));
ofType

根据元素类型过滤

	Flux<String> stringFlux = Flux.just("a", "b", 1, 2).ofType(String.class);
distinct

去除重复元素

	Flux<String> distinct = Flux.just("a", "b", "a", "c").distinct();
distinctUntilChanged

去除连续重复的元素

	Flux<String> distinctUntilChanged = Flux.just("a", "b", "a", "c", "c", "1", "a").distinctUntilChanged();
take

按个数、时间等条件保留前几个元素

	Flux<String> take = Flux.just("a", "b", "a", "c").take(2);
takeLast

保留指定个数的后几个元素

	Flux<String> takeLast = Flux.just("a", "b", "a", "c").takeLast(3);
takeUntil

保留直到断言为真的前几个元素

	Flux<String> takeUntil = Flux.just("a", "b", "a", "c", "c", "1", "a").takeUntil(str -> str.equals("1"));
takeWhile

takeUntil相反

	Flux<String> takeWhile = Flux.just("a", "b", "a", "c", "c", "1", "a").takeWhile(str -> !str.equals("1"));
skip

按个数、时间等条件跳过前几个元素

	Flux<String> skip = Flux.just("a", "b", "a", "c", "c", "1", "a").skip(3);
skipLast
skipUntil
skipWhile

错误处理

error

创建一个errorFlux

	Flux<String> error = Flux.error(new RuntimeException("哈哈哈哈哈哈"));
onErrorReturn

捕获异常然后返回缺省值

	Flux<String> just = Flux.just("a", "b", "c");
	Flux<String> error = Flux.error(new RuntimeException("哈哈哈哈哈哈"));

	just.concatWith(error).onErrorReturn("error");
onErrorMap

捕获异常,处理后重新抛出一个异常

	just.concatWith(error).onErrorMap(err -> new RuntimeException("出错了"))
onErrorResume

捕获异常,将异常转换成正常的元素

	just.concatWith(error).concatWithValues("1", "2").onErrorResume(str -> Flux.just(str.getMessage()));
onErrorContinue

出现错误跳过错误,使用原数据继续执行

	Flux.range(1, 20)
        	.map(i -> {
            	if ( i%3 == 0 ){
                	throw new RuntimeException("出错数字: " + i);
          	  	}
            	return i;
        	})
                 // 捕获异常并打印异常信息,也可以不打印
       		 .onErrorContinue((err, obj) -> System.out.println(err.getMessage()))
        	  .subscribe(System.out::println);

得到通知(完成、出现错误、取消订阅等)信号后执行一些操作

doOnNext

获取下一个元素的时候触发,不改变源Flux

	Flux<String> stringFlux = Flux.just("a", "b", "a", "c");
	stringFlux.doOnNext(str -> System.out.println("str: " + str))
        	.subscribe(System.out::println);
doOnComplete

订阅者消费完成后触发

	stringFlux.doOnComplete(() -> System.out.println("doOnComplete"))
        	.subscribe(System.out::println);
doOnCancel

取消订阅时触发

stringFlux.doOnCancel(() -> System.out.println("doOnCancel"))
       // 订阅时触发
        .doOnSubscribe(subscription -> {
            System.out.println("doOnSubscribe");
            // 取消订阅
            subscription.cancel();
        }).subscribe(System.out::println);
doOnSubscribe

订阅时触发

doOnError

出现错误时触发

	stringFlux.concatWith(Flux.error(new RuntimeException("出错了")))
        	.doOnError(err -> System.out.println(err.getMessage()))
       		.subscribe(System.out::println);
doOnRequest

请求数据时触发

        stringFlux.doOnCancel(() -> System.out.println("doOnCancel"))
                // 请求数据时触发,l为请求数
                .doOnRequest(l -> System.out.println(l))
                .doOnSubscribe(subscription -> {
                    System.out.println("doOnSubscribe");
                    // 设置请求数
                    subscription.request(4);
                }).subscribe(System.out::println);
doOnEach

所有类型的信号都会触发

doFinally

所有结束的情况(完成complete、错误error、取消cancel)都会触发

Context

Context 是一个类似于 Map(这种数据结构)的接口:它存储键值(key-value)对,它作用于一个 Flux 或一个 Mono 上,而不是应用于一个线程(Thread

  • key 和 value 都是 Object 类型,所以 Context 可以包含任意数量的任意对象
  • Context不可变的(immutable)
  • put(Object key, Object value) 方法来存储一个键值对,返回一个新的 Context 对象
  • putAll(Context)方法将两个 context 合并为一个新的 context
  • hasKey(Object key) 方法检查一个 key 是否已经存在
  • getOrDefault(Object key, T defaultValue) 方法取回 key 对应的值(类型转换为 T), 或在找不到这个 key 的情况下返回一个默认值
  • getOrEmpty(Object key) 来得到一个 Optional<T>(context 会尝试将值转换为 T
  • delete(Object key) 来删除 key 关联的值,并返回一个新的 Context

创建一个 Context 时,你可以用静态方法 Context.of 预先存储最多 5 个键值对。 它接受 2, 4, 6, 8 或 10 个 Object 对象,两两一对作为键值对添加到 Context。 你也可以用 Context.empty() 方法来创建一个空的 Context

Flux<String> stringFlux = Flux.just("a", "b", "c")
        // 获取Context
        .transformDeferredContextual((flux, ctx) -> {
            // 从context中取数据
            String ctxOrDefault = ctx.getOrDefault("key", "default");
            return flux.flatMap(str -> Flux.just(str + " " + ctxOrDefault));
        });

// 向Context存入数据
stringFlux.contextWrite(Context.of("key", "World")).subscribe(System.out::println);
System.out.println("******************************************************");

// 多次绑定相同的key, 离序列近的会覆盖离序列远的,因为context时从下游向上游传递
stringFlux.contextWrite(Context.of("key", "1234")).contextWrite(Context.of("key", "AAAA"))
                .subscribe(System.out::println);

写入读取 Context相对位置很重要:因为 Context 是不可变的,它的内容只能被上游的操作符看到

如果多次对 Context 中的同一个 key 赋值的话,读取 Context 的操作符只能拿到下游最近的一次写入的值

调度器 Scheduler

Reactor执行模式以及执行过程取决于所使用的SchedulerScheduler是一个拥有广泛实现类的抽象接口Schedulers类提供的静态方法用于达成如下的执行环境:

  • 当前线程(Schedulers.immediate()
  • 可重用的单线程(Schedulers.single())。注意,这个方法对所有调用者都提供同一个线程来使用, 直到该调度器(Scheduler)被废弃。如果你想使用同一个线程,就对每一个调用使用 Schedulers.newSingle()
  • 弹性线程池Schedulers.elastic()。它根据需要创建一个线程池,重用空闲线程。线程池如果空闲时间过长 (默认为 60s)就会被废弃。Schedulers.elastic() 能够方便地给一个阻塞的任务分配它自己的线程,从而不会妨碍其他任务和资源
  • 固定大小线程池(Schedulers.parallel())。所创建线程池的大小与 CPU 个数等同。

此外,你还可以使用 Schedulers.fromExecutorService(ExecutorService) 基于现有的 ExecutorService 创建 Scheduler。(虽然不太建议,不过你也可以使用 Executor 来创建)。你也可以使用 newXXX 方法来创建不同的调度器。比如 Schedulers.newElastic(yourScheduleName) 创建一个新的名为 yourScheduleName 的弹性调度器。

Flux<Integer> integerFlux = Flux.range(1, 10000).publishOn(Schedulers.parallel());

integerFlux.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));
System.out.println("***********1************");
integerFlux.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));
System.out.println("***********2************");
integerFlux.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));

// 等待程序结束,以便观测效果
CountDownLatch countDownLatch = new CountDownLatch(10);
countDownLatch.await();
调整调度器 Scheduler

Reactor 提供publishOnsubscribeOn方法在响应式链中调整调度器Scheduler。它们都接受一个Scheduler作为参数,来替换调度器

  • publishOn 的用法和处于订阅链(subscriber chain)中的其他操作符一样。它将上游信号传给下游,同时执行指定的调度器 Scheduler 的某个工作线程上的回调。 它会改变后续的操作符的执行所在线程(直到下一个 publishOn 出现在这个链上)。
  • subscribeOn 用于订阅(subscription)过程,作用于那个向上的订阅链(发布者在被订阅时才激活,订阅的传递方向是向上游的)。所以,无论你把 subscribeOn 至于操作链的什么位置, 它都会影响到源头的线程执行环境(context)。 但是,它不会影响到后续的 publishOn,后者仍能够切换其后操作符的线程执行环境
        Flux<Integer> integerFlux1 = Flux.range(1, 10000).publishOn(Schedulers.single());

        integerFlux1.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));
        System.out.println("***********1************");
        integerFlux1.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));
        System.out.println("***********2************");
        integerFlux1.subscribe(i -> System.out.println(Thread.currentThread().getName() + " " + i));

	// 等待程序结束,以便观测效果
        CountDownLatch countDownLatch = new CountDownLatch(10);
        countDownLatch.await();
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值