1-为什么选择响应式Spring

1-为什么选择响应式Spring,第1张

1-为什么选择响应式Spring 1-为什么选择响应式Spring

为什么Spring框架的开发团队决定将响应式方法作为Spirng5框架的核心部分?

可以设想一下,当用户访问系统, 系统中存在的服务 A 需要访问服务 B 时,在服务 A 发出请求之后,执行线程会等待服务 B 的返回,这段时间该线程就是阻塞的,整个过程的 CPU 利用效率低下,很多时间线程被浪费在了 I/O 阻塞上,见下图:

我们应该为了针对I/O实现更高的资源利用率, 应该使用异步非阻塞式交互模式。

现实生活中, 这种通信就是(短信或者邮件)消息传递, 我们收到消息, 就会把所有的时间用于阅读与回复, 此外我们通常不会等待对方继续回复, 而会去处理其他事情, 这种工作方式其余时间可以被有效利用。 如下图:

通常在分布式系统中, 为了服务之间的通信实现有效的资源利用, 我们必须采用消息驱动的通信原则。

实现消息驱动通信的方法之一是使用消息代理服务器。(消息中间件)

大型系统由多个小系统组成, 因此也依赖于这些组成部分的响应式特性, 也就是说响应式系统的设计原则适用于各个级别、规模的系统, 有助于他们很好的组合在一起。

在目前传递中我们最常使用也是最流行的传统技术是命令式编程 (如下图: )

阻塞式通信直接违背了消息驱动原则, 后者明确的为我们提供了非阻塞式通信。

另一种非阻塞式的选择是使用JDK8提供的CompletionStage 以及他的直接实现类CompletableFuture

public interface ShoppingCardService {
    CompletionStage calculate(Input value);
}
// -------------------------------------------------
public class CompletionStageShoppingCardService implements ShoppingCardService {

    @Override
    public CompletionStage calculate(Input value) {
		
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return new Output();
        });
    }
}
public class OrdersService {
    private final ShoppingCardService shoppingCardService;

    public OrdersService(ShoppingCardService shoppingCardService) {
        this.shoppingCardService = shoppingCardService;
    }

    void process() {
        Input input = new Input();

        shoppingCardService.calculate(input) // 非阻塞式调用方法, 并在calculate里面中CompletableFuture.supplyAsync执行完毕消费thenAccept
                           .thenAccept(v -> System.out.println(shoppingCardService.getClass().getSimpleName() + " execution completed"));

        System.out.println(shoppingCardService.getClass().getSimpleName() + " calculate called");
    }

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();

        OrdersService ordersService1 = new OrdersService(new CompletionStageShoppingCardService());

        ordersService1.process();
        ordersService1.process();

        System.out.println("Total elapsed time in millis is : " + (System.currentTimeMillis() - start));

        Thread.sleep(1000);
    }
}

在CompletionStage支持下, 我们可以编写函数式和声明式的代码, 并且能够异步的处理结果, 此外我们可以省略阻塞等待结果的过程, 实现执行完毕将结果自动回调处理。

这种模式多线程的成本会很高, 多线程是一门负责的技术, 当使用到多线程的时候, 需要考虑到线程安全, 线程变量共享、线程错误处理等。

并且在Spring 4 MVC 在很长时间内不支持 CompletionStage, 为了补上这一点, 它提供了自己的ListenanblFutre, 这么的做法是为了与旧版本的Java兼容

在Spring5框架与新的响应式WebClient的发布, 事情发生了改变, 在WebClient的支持下, 所有的跨服务通信都不在是阻塞式的,

此外Servlet3.0也引入了异步客户端-服务器通信, 并且很好的集成到SpringMVC中, 但是SpringMVC唯独没有提供一个开箱即用的异步非阻塞客户端。

但是我们可以返回CompletableFuture以构建异步服务。

下面是大概原理图:

  • Spring MVC开始进行异步处理,并把该CompletableFuture对象提交给另一个独立线程的执行器默认的ForkJoinPool (并不推荐使用默认的)处理
  • DispatcherServlet和所有过滤器都退出Servlet容器线程,但此时方法的响应对象仍未返回
  • Callable对象最终产生一个返回结果,此时Spring MVC会重新把请求分派回Servlet容器,恢复处理
  • DispatcherServlet再次被调用,恢复对Callable异步处理所返回结果的处理 上面就是Callable的一个执行流程

返回CompletableFuture的好处在于, 可以极大的提供系统的吞吐量, 但是并不能提升响应速度。

当然异步处理不只只能返回CompletableFuture 其他类型在下面官网查看

以tomcat(支持Servlet 3.0的)为例,你controller方法指定返回DeferredResult或Callable,spring会自动进行异步处理,将方法以及响应的任务交给spring默认或者你定义的taskExecutor线程池bean执行,原本tomcat负责处理请求的线程返回tomcat线程池,不影响servlet接受请求的能力。要阻塞也是taskExecutor线程池里的线程,而不是tomcat线程池的线程。

但是相比使用CompletableFuture更推荐使用webFlux, 因为支持更多功能, 包含背压功能。

与webFlux相比 扩展:

https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-async-deferredresult

https://kevinten10.github.io/2019/09/03/Async/Future/Async-Future-%E5%BC%82%E6%AD%A5%E8%BF%94%E5%9B%9E%E5%80%BC/

https://jishuin.proginn.com/p/763bfbd352e8

1

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5686558.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存