在基于Reactor的应用程序上执行阻塞性调用

306 阅读2分钟

在基于Reactor的应用程序上执行阻塞性调用

Project Reactor是一个完全非阻塞的基础,包括背压支持。虽然外面的大多数库都支持异步方法,从而协助其使用,但在某些情况下,一个库包含复杂的阻塞方法而没有异步实现。在反应器流中调用这些方法会产生不好的结果。相反,我们需要把这些方法变成异步的,或者找到一个变通的办法。

如果你的时间很短,不可能给所使用的工具打补丁,或者你不能确定如何逆向设计阻塞式调用并实现非阻塞式版本,那么利用一些线程是有意义的。

首先,让我们为我们的项目导入依赖项

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.projectreactor</groupId>
                <artifactId>reactor-bom</artifactId>
                <version>2020.0.11</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

让我们从阻塞式服务开始

public String get(String url) throws IOException {
        HttpURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
        connection.setRequestMethod("GET");
        connection.setDoOutput(true);
        try(InputStream inputStream = connection.getInputStream()) {
            return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
        }
    }

我们使用了HttpsURLConnection,因为我们知道它是一个阻塞式调用。为此我们需要一个Scheduler。对于阻塞式调用,我们将使用boundedElastic调度器。一个调度器也可以由现有的执行器服务来创建。

所以让我们把这个方法转化为非阻塞的方法:

package com.gkatzioura.blocking;

import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

public class BlockingAsyncService {

    private final BlockingService blockingService;

    public BlockingAsyncService(BlockingService blockingService) {
        this.blockingService = blockingService;
    }

    private Mono<String> get(String url) {
        return Mono.fromCallable(() -> blockingService.get(url))
                .subscribeOn(Schedulers.boundedElastic());
    }

}

我们可以看到的是一个由可调用方法创建的Mono。一个调度器订阅了这个单体,因此将收到发出的事件,这些事件应被安排执行。

让我们进行一次测试

package com.gkatzioura.blocking;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

class BlockingAsyncServiceTest {

    private BlockingAsyncService blockingAsyncService;

    @BeforeEach
    void setUp() {
        blockingAsyncService = new BlockingAsyncService(new BlockingService());
    }

    @Test
    void name() {
        StepVerifier.create(
                            Mono.just("https://www.google.com/")
                                .map(s -> blockingAsyncService.get(s))
                                .flatMap(s -> s)
                    )
                .consumeNextWith(s -> s.startsWith("<!doctype"))
                .verifyComplete();
    }
}

就这样吧!显然,最好的办法是找到一种方法,将这个阻塞调用变成异步调用,并尝试使用外面的异步库找到一个变通的方法。当它不可行的时候,我们可以使用Threads。