系统重构数据迁移_重构为响应式-JDBC迁移的剖析

系统重构数据迁移

重要要点

  • 响应式编程带有学习曲线,需要经验,实践和开放思维才能完全适应。
  • 它可以在任何应用程序逐步推行React式编程。
  • 响应式编程和Netty等无阻塞库提高了可伸缩性和弹性,并降低了运营和开发成本。 (有关更多上下文,请参见React式宣言 。)
  • 将未来价值流转换为其他未来价值流是一种强大的编程范例,通过实践可以提供巨大的回报。
  • 可组合性是函数式编程的标志。 在本文中,我们将使用Observable的flatMap运算符探索单子组合。

响应式编程是一个新兴的领域,它为编程中一些最困难的概念(包括并发管理和流控制)提供内置解决方案。 但是,如果您在应用程序开发团队中工作,则很有可能您没有使用React式,因此您可能会遇到问题-我如何到达那里,如何对其进行测试,可以分阶段进行介绍?

在本文中,我们将一个真实的(有意简单的)遗留应用程序(具有由Web服务器和数据库后端组成的经典设置)转换为一个响应模型,力争实现三重好处:

  1. 以功能性样式工作,允许我们的代码进行组合并重新使用,这归功于更高的抽象性,不变性和流利的样式,使其更加清晰易读。 例如, Function<T,Observable<U>>类型是可组合的,因为我们可以使用ObservableflatMap运算符将多个这样的函数链接在一起。
  2. 构建一个更具弹性的应用程序,使我们的服务器可以容纳比实际可用线程数更多的并发用户,同时仍允许代码被并发执行,而不会进入某些线程池。

    我们将通过让应用程序在从数据库接收到每个信息块时做出React来实现此目的,而不是让线程等待该信息。 这是从拉取机制(执行阻止I / O)到推入机制的转换,当数据可用或发生某些事件(例如超时或异常)时,该推入机制将继续执行。
  3. 构建一个响应速度更快的应用程序,即使第一个字节开始从数据库返回时,也允许更新我们的浏览器视图。 这可以通过Servlet 3.1流API来实现,并将随Spring Reactor和Spring堆栈上的相关Web扩展一起提供。

可以从此链接下载本文中的示例。

我们希望使用增量重构在较小的步骤中为我们的程序赋予新的React式化妆。

在开始之前,让我们丰富用于旧版应用程序的测试套件,因为在程序向其新的功能和React形式转变的过程中,我们依赖于此类测试来确定程序重构版本的正确性。

测试完成后,我们可以从第一步重构开始:用RxJava的所有部分ObservableSingleCompletable替换方法返回类型。 具体来说,对于每个返回类型T值的方法,我们可以返回Single<T> ,具有要发射的一个值或一个错误的Observable<T> 。 对于List<T> ,我们将返回类型更改为Observable <T>,而对于每个void方法,我们将返回Completable,可以将其转换为Single<Void>

与其同时替换所有层中的所有方法返回类型,不如选择一个层并从那里开始。 将返回T的方法转换为返回Single <T>的方法; 或一个返回List<T>Observable<T>;Completablevoid不需要更改该方法(如其客户端代码所知):相反,您可以保留该方法的原始签名,但可以将其实现为新方法的委派方法,该方法包含返回一个Observable<T> 。 委派方法(具有原始方法签名)在返回任何值之前,在可观察toBlocking上调用toBlocking以强制执行原始同步协定。 尽可能小地开始是一个不错的迁移策略,可帮助您克服学习曲线并实现稳定的增量进步。 您可以使用RxJava应用增量重构。

这是一个具体的例子。 您可以在此处查看经典的Spring应用程序tkssharma / Spring-JDBC并以两种方式将其转换为RxJava:使用RxJDBC库(在rx-jdbc分支中)和使用pgasync库(在此处 ,您可以看到所有代码及其历史)。在pgasync分支中)。
让我们看一下Spring-JDBC项目中的以下方法:

List<Student> getStudents();
Student getStudent(int studentId);

按照上述迁移策略,我们将保留这些方法签名,但对它们的实现进行一些小的更改:

public List<Student> getStudents() {
  return getStudentStream().toList().toBlocking().single();
}

我们引入了一个额外的层:

Observable<Student> getStudentStream();
Single<Student> getStudent(int studentId);

通过以下实现:

public Observable<Student> getStudentStream() {
  List<Student> students = getStudentsViaJDBC();
  return Observable.from(students);
}

其中getStudentsViaJDBC是初始实现。

我们有效完成的工作是创建一个新的React层,同时保留我们原来的非React性签名,然后用对我们的新React性化身的调用代替了原始实现。 我们将在此数据访问层上进行一些进一步的迭代,然后使应用程序朝着控制器层向上React,最终目标是使它端到端React。
Observable.toBlocking方法充当经典世界与React世界之间的桥梁。 您需要使用它来将响应式代码(即使仅在API中)插入到大规模的经典代码中:例如一端是servlet,而另一端是JDBC。 在使这些目标也变得被动之前,我们需要此方法。

当然,在重构结束时,对toBlocking的同步调用是对异步React式范式的厌恶,因此最终我们将希望从生产代码中删除这些。

假设您有一个方法List<Student> getStudents() 。 您将创建一个新方法Observable<Student> getStudentStream()并将实现移至该方法,然后使用Observable.from(Iterable)将结果列表包装到Observable中。 原始方法将调用新方法,然后再调用studentStream.toList().toBlocking().single()将其转换回List 。 这本质上是阻塞的,但是可以,因为此时getStudentStream的现有实现已经阻塞了。

被动式学习曲线中的最大障碍是学习根据可观察性进行思考。 您可能会发现将List变成流是直观的(毕竟,这正是Observable的含义),但是将这种概念应用于单个值则不太直观。 为了对此进行概念化,请考虑传统的Java未来概念:尽管它包含单个值,但可以将其视为将来值流的特殊情况,恰好只包含单个值,如果没有值则出错实际上可以成功发射。

我们的第一步,将返回类型包装在Observable中,不会改变执行的性质。 它仍然是同步的,就像JDBC一样阻塞I / O。 。

我们已经完成了重构的第一步:将签名从List<Student> getStudents()更改为Observable<Student> getStudents() 。 有趣的是,即使只返回单个学生的Student getStudent()方法也被重构为Observable<Student> getStudent()或潜在地重构为Single<Student> getStudent() 。 此外,即使void方法也被重构为返回Completable
通过将大型或小型部件包装到React性信封(API)中,然后在需要异步性或非阻塞I / O的情况下进一步分解每个部分,可以自上而下进一步应用React性范式。

为了实现新的签名,我们返回Observable.just(studentList)而不是返回studentList

至此,我们在几个地方介绍了Observable ,但是除了简单地包装和展开列表外,基本上没有任何变化。 但这是重要的一步,因为它使我们的代码可组合,并且我们现在准备采取下一步行动,并开始使用Observable背后的某些功能,即惰性评估,Java流中也提供了惰性评估。 让我们返回而不是返回Observable.just(studentList)

Observable.defer(
   ()->Observable.just(actuallyProduceStudentsViaTripToDatabase()
)).

请注意, actuallyProduceStudentsViaTripToDatabase ProduceStudentsViaTripToDatabase actuallyProduceStudentsViaTripToDatabase是我们从其开始的旧方法,该方法返回List<Student> 。 通过使用Observable.deferObservable.fromCallable包装它,我们获得了一个懒惰的Observable,它仅在订阅者订阅该数据时才向数据库发起查询。

此时,仅数据访问层API已被修改为返回Observable; 控制器方法尚未更改,因此它们必须消耗(订阅)可观察对象并在同一线程中等待其结果; 又名阻塞I / O。 但是,本文的目标是创建一个端到端的异步处理,这意味着我们希望以异步方式结束,而不是使用控制器方法返回已填充的Result (数据已经可用于模板渲染)。 Spring MVC,由DeferredResult类提供,Spring MVC提供了异步对象。 (Spring计划在即将到来的Spring Reactive Web(由Spring Reactor生态系统提供支持)中支持流式传输。)使用这种方法,控制器方法不会返回完整的Result ,而是会保证在结果可用时将其设置在先前返回的DeferredResult 。 如果仅修改返回Result的控制器方法以返回DeferredResult ,那么它本身就足以提供一定程度的异步性。

@RequestMapping(value = "/student.html", method = RequestMethod.GET)
public DeferredResult<ModelAndView> showStudents(Model model) {
   Observable<ModelAndView> observable = studentDAO.getAllStudents()
           .toList()
           .map(students -> {
               ModelAndView modelAndView = new ModelAndView("home");
               modelAndView.addObject("students", students);
               return modelAndView;
           });
   DeferredResult<ModelAndView> deferredResult = new DeferredResult<>();
   observable.subscribe(result -> deferredResult.setResult(result), 
                             e -> deferredResult.setErrorResult(e));
   return deferredResult;
}

我们朝着异步性迈出了重要的一步,但是令人惊讶的是,这种方法仍在等待来自数据库的结果,即它仍在阻塞。 这是为什么? 您可能还记得,直到现在,访问层返回的Observable都从调用线程执行其预订,因此,尽管使用DeferredResult方法,该方法仍将阻塞,直到Observable传递数据,从而消耗线程资源。

下一步将是更改Observable,以便它不会阻止订阅调用上的当前线程。 这可以通过两种方式完成:一种方式是使用本机React库,第二种方式是使用Observable.subscribeOn(scheduler)observeOn(scheduler) ,在不同的调度程序上执行subscribe方法和observe方法(考虑调度程序)作为线程池)。

观察方法是mapflatMapfilter (将一个可观察对象转换为另一个可观察对象),以及诸如doOnNext方法,该方法每次在流中发出新元素时都会执行操作。 第二种方法(使用subscribeOn )是朝着完全不阻塞库的目标迈出的一个小中间步骤。 它将subscribeobserve动作简单地移动到不同的线程:这些动作仍将阻塞,直到结果可用(只有它们将阻塞其他线程),之后它们会将结果推回给订户,这又将它们推到DeferredResult 。 有一些在JDBC之上实现RxJava的库,它们使用这种方式来阻塞线程(调用线程或其他已配置线程)。由于JDBC是一种阻塞API,因此JDBC当前需要这种方法。 通常,此方法可以用作实现完全非阻塞库的目标的中间步骤,但是最终本机响应方法才是目标,因为它可以提高可伸缩性,从而允许您支持大量真正的并发用户操作( aka流量)比可用线程数多。

这是使用RxJDBC库的getStudents实现:

public Observable<Student> getStudents() {
   Class<String> stringClass = String.class;
   return database
           .select("select id,name from student")
           .getAs(Integer.class, stringClass)
           .map(row->{
                   Student student = new Student();
                   student.setId(row._1());
                   student.setName(String.valueOf(row._2()));
                   return student;
               });
}

为了获得RxJDBC库,请在Maven项目中添加此依赖项:

<dependency>
   <groupId>com.github.davidmoten</groupId>
   <artifactId>rxjava-jdbc</artifactId>
   <version>0.7.2</version>
</dependency>

第三步是引入一个真正的React库。 甚至对于关系数据库来说,也有一些,但是当您专注于特定数据库(例如Postgres)时,可以找到更多信息。 这是因为数据库访问库特定于每个数据库的每个低级协议。 在这里,我们使用postgres-async-driver项目 ,该项目本身使用RxJava。

再次使用pgasync库,这是getStudents实现:

public Observable<Student> getStudents() {
       return database
       .queryRows("select id,name from student")
       .map(row -> {
           Student student = new Student();
           int idx = 0;
           student.setId(row.getLong(idx++));
           student.setName(row.getString(idx++));
           return student;
       });
   }

要使用pgasync库,请导入以下Maven依赖项:

<dependency>
    <groupId>com.github.alaisi.pgasync</groupId>
    <artifactId>postgres-async-driver</artifactId>
    <version>0.9</version>
</dependency>

目前,我们有一个真正的React式(异步,事件驱动,非阻塞)后端实现。 我们还有一个端到端的异步解决方案,该解决方案使我们可以同时(在I / O流级别)处理比JVM中实际线程更多的用户操作。

接下来,让我们开始交易。 我们将采用一个场景,我们要使用DML(数据修改语言)操作INSERT或UPDATE来修改数据。 即使对于由单个DML语句组成的最简单的事务,引入异步性仍然很复杂,因为我们已经习惯于阻塞线程的事务。 而且,在更实际的事务(通常包含多个语句)的情况下尤其如此。

这是交易的外观:

public class T {
 private Observable<Long> dml(String query, Object... params) {
   return database.begin()
           .flatMap(transaction ->
                   executeDmlWithin(transaction, query, params)
                           .doOnError(e -> transaction.rollback()));
}

private Observable<Long> executeDmlWithin(
       Transaction transaction, String query, Object[] params) {
   return transaction.querySet(query, params)
        .flatMap(resultSet -> {
            Long updatedRowsCount = resultSet.iterator().next().getLong(0);
            return commitAndReturnUpdateCount(transaction, updatedRowsCount);
        });
}

private Observable<Long> commitAndReturnUpdateCount(
       Transaction transaction, Long updatedRowsCount) {
   return transaction.commit()
        .map(__ -> updatedRowsCount);
 }
}

这是一个单语句事务,但是它说明了如何在异步React式API中进行事务。 事务开始,提交和回滚都是单子函数:它们返回一个Observable,并且可以与flatMap链接。

让我们从签名开始追溯上面的示例。 dml执行函数采用数据修改语言(DML)语句(如UPDATE或INSERT)以及任何参数,并将其“计划”为执行。 注意, db.begin返回Observable<Transaction> 。 事务不是立即创建的,因为它涉及数据库的I / O。 因此,这是一个异步操作,以便在执行完成时返回一个Transaction对象,随后可以根据需要在该对象上调用SQL查询,然后commitrollback 。 如上面我们所见,该Transaction对象将从Java闭包传递到Java闭包:首先, transaction可用作flatMap operator的参数。 在那里它被用于三个位置:

  • 首先,它在transaction内启动DML语句。 在这里,执行DML的querySet操作的结果也是一个Observable,它保存DML的结果(通常是具有更新的行数的Row ),并进一步用flatMap转换为另一个Observable
  • 然后,第二个flatMap使用我们的交易对象来提交交易。 在那里,交易变量由lambda函数包围,并作为该第二flatMap的参数提供。 这是一种将数据从异步流的一部分发送到另一部分的方法:使用词法作用域中的变量,并在一次创建的lambda表达式中使用它,但稍后再执行,并且有可能在另一个线程中执行。 这就是lambda表达式是Java闭包的意义:它们将表达式中使用的变量括起来。 您可以使用任何Java闭包(不仅是lambda)发送这样的数据。
  • transaction变量的第三种用法是doOnError ,在该处回滚事务。 再次注意transaction变量如何通过通常的Java词法作用域在三个地方传递,即使某些代码段将被同步执行(作为方法执行的一部分,在调用线程中),而其他代码将在以后执行,当某些事件发生时,即当响应来自数据库时,异步地并且在不同的线程上。 但是,在所有这些情况下都可以进行价值transaction 。 理想情况下,共享值应该是不可变的,无状态的或线程安全的。 Java只要求将它们有效地定下来,但这对于非原始值来说还不够。

如果成功,事务提交结果将转换(映射)为更新计数,调用者可以使用该计数。 在这里,为了将更新/插入的行数传输给事务方法的外部调用者,我们无法使用Java闭包捕获结果计数,因为被调用者与调用者不在同一词法范围内。 在这种情况下,我们需要将结果封装在结果可观察的数据类型中。 如果我们需要携带多个结果,则可以诉诸于不变的Java类,数组或不可修改的Java集合。 错误时,将在事务上调用回滚。 错误然后冒泡到可观察链(不是调用堆栈),除非它通过特定的可观察运算符停止运行,该操作员说“当该可观察变量有错误时,请使用另一个可观察变量,或者也许再次尝试相同的操作”。

此事务性更新是flatMap链接的第一个示例:我们以事件驱动的方式将多个步骤彼此管道化:当事务开始时,可​​以发出查询; 当查询结果可用时,可以发出一些结果解析和事务提交; 事务完成后,该结果将用于用结果(此处为更新计数)替换成功提交的结果(不包含任何信息)。 如果最终的observable不是Observable<Void>而是Observable<T >,那么我们可以将T及其结果Long打包到数据传输对象中。

在React式世界中,我们旨在将阻止应用程序变为非阻止状态。 (阻塞应用程序是在执行诸如打开TCP连接之类的I / O操作时阻塞的应用程序。)用于打开套接字,与数据库(JDBC)对话,文件/ inputStream / outputStream的大多数旧版Java API都是阻塞API。 Servlet API和许多其他Java构造的早期实现也是如此。

随着时间的流逝,事物开始采用无障碍的对应对象。 例如,Servlet 3.x集成了一些概念,例如异步和流式传输。 但是在典型的J2EE应用程序中,通常会发现阻塞调用,这并不总是一件坏事。 阻塞语义比显式异步API更容易理解。 诸如C#,Scala和Haskell之类的某些语言具有可从阻塞代码透明生成非阻塞实现的构造,例如C#和Scala中的异步高阶函数。 据我所知,在Java中,执行非阻塞操作的最可靠方法是使用Reactive Streams或RxJava,或使用非阻塞库(例如Netty)。 但是,事情仍然很明确,因此进入壁垒可能很高。 不过,当您需要支持的并发用户数超过线程数时,或者当您的应用程序受I / O限制并且希望将成本降到最低时,非阻塞方式将为您带来可扩展性的额外数量级,弹性和降低成本。

在讨论弹性或健壮性时,考虑所有线程正在等待I / O的时刻会很有帮助。 例如,假设现代JVM可以支持5000个线程。 这意味着当阻塞应用程序中的各种Web服务的5000个调用正在等待它们各自的线程时,在那个阶段根本无法处理更多的用户请求(只能将它们排入队列,以便稍后进行排队的一些专用线程进行处理)。 在公司内部网这样的受控环境中,这可能很好,但是当突然有10倍的用户突然决定检出他们的产品时,初创公司就不需要这么做。

当然,应对流量高峰的一种解决方案是水平可伸缩性。 建立更多服务器,但这还不够灵活,更不用说成本了。 同样,这完全取决于应用程序执行的I / O类型。 但是,即使HTTP是Internet服务暴露给它的唯一潜在的慢I / O,并且所有其他I / O操作都使用具有HA(高可用性)和低延迟的内部数据库和服务,那么至少使用HTTP会与地球另一端的慢客户端一起缓慢移动字节。

确实,这个问题是专业负载平衡器的职责所在,但是您永远不知道什么时候最“高可用性”的内部或外部服务出现故障,什么时候最“低延迟”的服务实际上只是“近实时”和不是硬实时的,那时候由于垃圾回收它只会响应缓慢。 然后,如果您仅在堆栈的一部分中进行阻塞,则会出现阻塞气泡,这意味着线程将在最慢的阻塞I / O上开始阻塞,并且由于单个慢速阻塞访问而导致资源停止流量的5%,并且业务重要性较低。

希望我已经说服了您,使应用程序无阻塞在许多情况下都能增加价值,所以让我们回到我们的旧应用程序。 它阻塞了它的所有层,HTTP和数据库访问,所以让我们从那里开始。 除非垂直的所有层(这里是HTTP和数据库访问)都处于异步状态,否则整个流都不会是异步的。

异步与非阻塞之间还有一个区别,即非阻塞意味着异步(除非我们有语言构造),但对于阻塞调用,总是可以通过简单地将其“移动”到另一个线程来完成异步。 这具有与初始阻止解决方案相同的问题,但是可以通过渐进的方法迈向最终目标。 对于HTTP端,我们已经部分地了解了Servlet规范和Spring MVC的当前状态,这为我们提供了异步行为,但没有流式传输。

异步表示,当数据库完成响应后,处理将开始。处理完成后,Web层将开始呈现。 呈现网页(或JSON有效负载)时,将使用“这是您的完整响应有效负载”来调用HTTP层。

下一步将是流式处理:当数据库告诉处理层“这里还有更多数据供您使用”时,处理层会接受它。 这种接受并不一定意味着正在使用专用线程,例如NIO或Linux epoll将是非阻塞的。 这里的想法是,通过一个线程向操作系统查询100K连接,并提出以下问题:“ 100K连接上是否有新内容?” 然后,处理层可以进行转换,以输出更多的语义单元,例如“学生”。 在某些情况下(例如,来自数据库的数据仅代表学生的一部分)可能有用,将部分信息保留在处理层缓冲区中。 当从数据库中获取的批量数据最终获得了该学生的所有数据时,可以将其“关闭”并发送到上层进行渲染。 在这样的管道中,任何组件都可以以任意粒度进行流传输:某些组件将仅从左向右复制字节;其他组件将发送完整的学生实例,甚至批量发送它们,而其他组件(例如MVC Spring的DeferredResult则需要整个结果在开始编写HTTP响应之前。

回到重构的步骤:

  1. 将可观察的签名
  2. 放置observable.subscribeOnobservable.observeOn(scheduler)来将阻塞计算(例如JDBC调用)移动到另一个线程池
  3. 使其异步:使用Spring MVC异步
  4. 使后端成为非阻塞的:对数据库使用专门的库,以实现非阻塞的替代实现
  5. 如果尚未包装RxJava或您首选的React式框架中的非阻塞实现,则将其包装起来,例如本例
  6. 使其流式传输:使用Vert.x
  7. 做写
  8. 做(多语句)交易
  9. 验证您的错误处理
sudo docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword -d postgres

现在,执行student.sql创建表并插入一些示例行。
然后执行mvn安装。 然后将战争文件部署在tomcat或jetty中。
URL在这里 ,单击“学生”。

有关可组合性的更多信息

我们谈论了很多关于可组合性的知识,理解这些语义是值得的。 在React式编程的特定上下文中,我将重点介绍一种组合函数是如何起作用的,我们称其为“排序”。 这意味着我们有一个处理流程,给定一些输入会在一系列步骤中产生一些输出。 每个步骤可以是同步的(例如,应用一些计算来转换值),也可以是异步的(例如,通过Web服务或数据库获取更多信息)。 管道可以由其消费者拉动或由其生产者推动。

让我们考虑另一个例子。 假设我们正在构建一个非阻塞式Web服务器,该服务器需要将数据发送到后端系统进行处理,然后在响应中返回结果。 它还需要对发出请求的用户进行身份验证并应用授权。 因此,在我们已经可以称之为处理流程的管道中已经出现了一些处理步骤,这些处理流程导致后端(和其他系统)的状态更改以及对最终用户的响应。

在组成这样的管道时,如果我们不需要知道任何特定步骤的细节,例如它是同步还是异步,或者执行多少次重试(如果有),那将是理想的选择。 如果是异步的,它将从哪个线程池中使用,或使用哪个非阻塞框架。

相反,当我需要将处理步骤从同步形式更改为异步形式时,只需要修改monadic函数的内部实现,而无需进行外部修改。

为了说明这一点,假设我们要采取步骤从资源服务器(应用服务器)内部验证JWT令牌。 这可以通过检查令牌有效负载上的数据的库来完成。 或者可以通过对身份提供者(IdP)进行网络调用来验证更多内容,例如用户是否仍然有效。

让我们定义这个monadic函数(返回类型是monad,上面带有flatMap的类型):

Observable<Boolean> isValid(String token)

现在,我们可以在内存中实现它,这是一个CPU密集型操作,它使用一些令牌解密库,验证签名和来自签名的一些信息,例如到期日期和一些ID。

或者,如果我们将其用作IdP服务器,则可以添加Google行程。

在这两种情况下,此函数之外的世界(包括管道本身)都不知道如何在Observable<Boolean>实现Observable<Boolean> :是只是在同一线程中调用其订户,例如内存中的版本,然后等效于boolean isValid(token)函数。 可能。 还是与Google进行I / O操作,当答案返回时,解析响应并最终得出布尔结论。 也可以。 该设计与实现无关。

我们还可以将该函数包装到另一个具有相同签名(String->Observable<Boolean)的函数中,该函数在此验证的基础上添加重试机制(如果HTTP请求碰巧是Google旅程的话,则很有意义)流量丢失或延迟较大)。 否则,它会添加一个优雅的降级功能,例如,如果我无法使用网络访问数据中心之外的Google之类的网站,那么我就可以使用我的库验证签名并继续使用它。

可以添加所有这些替代解决方案或装饰器,并且每个解决方案仍然是从String to Observable<Boolean>的函数。

因此,我们的耦合度很低,因为从同步更改为异步再返回不会影响API。

但是,与Java Future不同,Observable类型是可组合的:假设在“令牌有效”的情况下,我们调用返回标准Response的函数,否则返回ErrorResponse

假设我们有一个Observable<String> ,(这并不意味着我们正在等待多个令牌-我们可以只等待一个,这是Future<String>一种形式)。 在此“令牌可观察”上,我们使用isValid函数应用flatMap ,并获得“ boolean observable ”。 在此flatMap ,我们应用带有lambda函数且带有“ if”语句的flatMap :如果事情有效,则返回Observable<Response> ,否则返回另一个Observable<ErrorResponse>

可能看起来像这样:

responseObservable = tokenObservable.flatMap(token -> isValid(token)
	.flatMap(valid -> valid? process(request) : 
                             Observable.just(new ErrorResponse(“invalid”)));

您会注意到,对于每个flatMap,我们都从类型为Observable<T>的值开始,并获得另一个Observable<U> ,其中T和U可以是相同或不同的类型参数。

因此,这种成分是重要的性质。 由某种形状的小零件组成,而相同形状的大零件组成。 但是那是什么形状?

在Monad的情况下,我们可以将其建模为具有类型参数T的“类型”,并具有两个函数: flatMaplift 。 后者很简单:它将类型T的实例转换为monad的实例。 给出两个单子的两个示例是Observable.just(value)Option.ofNullable(value)

flatMap怎么样? 这是一个高阶函数,给定一个名为source的observable<T>实例,以及一个单调函数f(T->Observable<U>) ,则newObservable = sourceObservable.flatMap(t->f(t))的类型为Observable<U>并在Observable的情况下表示,如果类型T的元素在源上可用,则将调用函数f ,从而为每个此类元素产生新的observable,并且当结果元素开始时出现在结果Observables<U> ,它们(也)以出现顺序作为newObservable一部分发出。 为什么使用Observables<U> ? 因为如果sourceObservable发出三个元素,那么应用于每个元素的函数f将产生总共三个Observables。 这些可以合并或连接。 合并意味着将所有三个可观察newObservable所有元素一出现就添加到newObservable “输出”中。 这是flatMap工作,将三个可观察的结果合并。 另一种选择是先等待第一个结果可观察到的所有元素,然后将其与第二个元素连接起来。 这就是concatMap的作用,它将结果的可观察值连接起来。

我可以从一个Observable值生成这种具有Observable类型的属性,它具有增强的功能,更多的处理步骤,更多的装饰器功能(如重试和回退机制),具有增强功能的新Observable值,这是我所说的可组合性的很大一部分。

掩护下不阻塞

曾经提到过,通过使用非阻塞异步I / O库,可能有比可用线程更多的正在进行的流:您可能想知道这怎么可能。 因此,让我们仔细看看Netty之类的库是如何工作的(Netty是Vert.x和PgAsync用作无阻塞I / O主力库的库)。

Java有一个称为NIO的API,该API旨在使用更少的线程来处理多个连接。 它通过在后台进行某些OS syscall来工作。 (对于Linux,它们可以是epoll或poll。)例如,假设我们打开了1000个连接。 线程将调用一个名为selector.select的NIO方法,这是一个阻塞调用,并返回10个连接,这些连接自上次查询以来就已排入队列事件,例如“更多数据可用”,“连接关闭”等。 现在执行查询的线程通常会将10个事件分派给其他线程,以便它可以继续轮询。 因此,第一个线程是一个无限循环,不断查询打开的连接上的事件。 将分派这10个事件以处理到线程池或事件循环。 Netty有一个未绑定的线程池来处理事件。 事件处理是cpu绑定的(计算密集型)。 任何I / O都将委托给NIO。

Tomasz Nurkiewicz和Ben Christensen撰写的经典的Reactive Programming with RxJava创建了异步的,基于事件的应用程序 ,这是深入介绍所有这些技术的重要资源。

翻译自: https://www.infoq.com/articles/Refactoring-Reactive-JDBC/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

系统重构数据迁移

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值