Mutiny-初识

Mutiny、Project-Reactor好像都挺冷门的,人气不高,相关资料也不多。不过其实整个Java Reactive都不是很热门,国内用的人不是很多。我个人觉得这些Reactive库都非常优秀,值得学习。

这篇博客主要是Mutiny的Hello World,重点是初步认识Mutiny和如何使用Mutiny API。文章的例子都基于官网,在此基础上写了一些自己的理解和总结。

对于Vertx core我还是比较熟悉的,但是对Mutiny和Reactor这些基于Reactive-Stream的还不是很了解。Quarkus底层基于Vertx,但不推荐我们直接使用Vertx,而是推荐使用Mutiny的API进行响应式编程。相比较而言Mutiny和Reactor提供了更强大、更炫酷的API,Vertx提供的异步API相较而言就显得有些简陋了。不少人被reactive stream的api劝退,但是入门之后还是很有趣的。

官方描述如下:Intuitive Event-Driven Reactive Programming Library for Java.

SmallRye的Mutiny库是一个响应式的、事件驱动的Java库。

Uni 和 Multi是什么
Uni 和 Multi都可以产生元素,Unit产生1个或0个元素,Multi产生多个元素。

Mutiny是惰性的,如果没有被subscribe就不会被执行。初学者经常忘记这一点,然后"为什么我写的代码没有执行?"

而且,reactive stream的代码debug是有一定难度的。

Uni和Mutiny产生event,我们写的代码就是对这些事件进行处理。我们可以用 onItem(), onFailure(), onCompletion() 来处理这些event。event的传播是具有方向性的,可以向上游传播也可以向下游传播,

和其他响应式流框架类似,流上面的元素(事件)会流经处理流水线(processing pipeline),每个operation会处理事件或者转换为别的类型的事件。

onItem().transform(Function)
transform()对元素进行转换操作。自然的,该操作是同步的。

下面做一些demo。

    private void printWithThread(Object o) {
        System.out.println(Thread.currentThread() + ": " + o);
    }
    private static Executor executor = new ForkJoinPool();
        Multi<Integer> multi = Multi.createFrom().items(1, 2, 3, 4, 5)
                .emitOn(executor)
                .onItem()
                .transform(i -> {
                    System.out.println(Thread.currentThread() + "is working on item: "+i);
                    return i * 2;
                });
        multi.subscribe().with(this::printWithThread);

输出:
Mutiny-初识_第1张图片
worker线程对stream的每个元素进行处理后,执行subscribe,继续处理下一个元素。


tricks(),用于构建一条周期性产生元素的stream。

Multi<Long> ticks = Multi.createFrom()
						.ticks()
						.every(Duration.ofMillis(100));
ticks.subscribe().with(this::printWithThread);
Thread.sleep(500);

Mutiny-初识_第2张图片

invoke()和call()
这也是对event进行订阅的两个方法,其实是对item注册一些回调。两者都是执行回调之后才会将原始的event传递到下游。invoke和call的区别就是注册的回调是同步还是异步的区别,invoke注册的是一个runnable,call注册的回调是一个Supplier> action,action是一个以item为参数返回Uni的一个函数。它们不会对event的继续传播产生任何影响,不像subscribe()、transform()等方法。使用它们的场景是当一些事件发生的时候做出相应的处理而且让这些事件继续传播。其实invoke、call的名字以及足够描述这两个方法了。

官网描述如下:
Mutiny does not dispatch the original event downstream until the Uni returned by the callback emits an item.

invoke()和call()的区别在于invoke()是同步的,call()是异步的
invoke:
Mutiny-初识_第3张图片

call:
Mutiny-初识_第4张图片

下面看一个例子。

有一个远程的服务:

    Uni<String> invokeRemoteGreetingService(String name) {
        return Uni.createFrom().item(name)
                .emitOn(executor)
                .onItem().delayIt().by(Duration.ofSeconds(Math.abs(random.nextInt(10))))
                .onItem().transform(s -> "hello " + s);
    }

delay主要是为了模拟耗时。

有一个不断产生String的source,而且要调用上面的服务,如何保证顺序性?
先不考虑顺序性,写出来的代码如下:

Multi.createFrom().items(IntStream.range(0, 10).boxed())
        .onItem().transform(i -> "delicious-" + i)
        .onItem().transform(this::invokeRemoteGreetingService)
        .subscribe().with(uni -> uni.subscribe().with(System.out::println));

或者:

Multi.createFrom().items(IntStream.range(0, 10).boxed())
        .onItem().transform(i -> "delicious-" + i)
        .onItem().transformToUniAndMerge(this::invokeRemoteGreetingService)
        .subscribe().with(System.out::println);

transformToUni()将Item转为Uni,stream中的每个元素变异成一种异步状态,然后用merge将这些Uni中的async result组装回来,但不保证顺序。

结果自然是乱序的。(两种写法都是)
Mutiny-初识_第5张图片
如何实现消费的顺序和生产的顺序一致呢?

 Multi.createFrom().items(IntStream.range(0, 10).boxed())
         .onItem().transform(i -> "delicious-" + i)
         .onItem().transformToUniAndConcatenate(this::invokeRemoteGreetingService)
         .subscribe().with(System.out::println);

结果是有序的。
Mutiny-初识_第6张图片

再明确一下,调用onItem()就使得元素从stream的上游流向下游了。

transformToUni()之后使用concatenate()保证元素到达下游的顺序性。准确的说,元素i到达下游之前,元素i+1一直在上游呆着。

异常处理
如果传播的过程中出现了failure,则这个错误一直向下游传播直到一个遇到一个能处理它的地方,直到最后抛出异常。遇到一个failure之后,下游不会收到更多的元素。

比如失败时做出一些动作:
uni.onFailure().invoke()
multi.onFailure().invoke()
注意invoke之后这个failure仍然会向下游传播。

Multi.createFrom().items("a","b","c")
        .onItem().failWith((Supplier<Throwable>) Exception::new)
        .onFailure().invoke(()->{})
        .onFailure().recoverWithItem("feedbackItem")
        .subscribe().with(System.out::println);

这里下游的subscribe仅能收到一个feedbackItem。

出现failure之后,可以进行retry,retry还可以配置最多重试次数、延时重试、重试间隔等等。

你可能感兴趣的:(quarkus,java,stream)