使用Vert.x进行响应式开发

最近,似乎我们正在听到有关Java的最新和最好的框架的消息。 忍者SparkJavaPlay等工具; 但是每个人都固执己见,使您感到您需要重新设计整个应用程序以利用它们的出色功能。 这就是为什么当我发现Vert.x时令我感到宽慰的原因。 Vert.x不是一个框架,它是一个工具包,它不受质疑,而且正在解放。 Vert.x不想让您重新设计整个应用程序以使用它,它只是想让您的生活更轻松。 您可以在Vert.x中编写整个应用程序吗? 当然! 您可以在现有的Spring / Guice / CDI应用程序中添加Vert.x功能吗? 是的 您可以在现有JavaEE应用程序中使用Vert.x吗? 绝对! 这就是让它变得惊人的原因。

背景

Vert.x诞生于Tim Fox决定他喜欢NodeJS生态系统中正在开发的许多东西,但他不喜欢在V8中进行权衡取舍:单线程,有限的库支持以及JavaScript本身。 Tim着手编写一个工具包,该工具包在使用方式和位置方面不受质疑,因此他决定在JVM上实现它的最佳位置。 因此,Tim和社区开始着手创建一个事件驱动的,非阻塞的,反应性的工具箱,该工具箱在许多方面都反映了NodeJS可以完成的工作,而且还利用了JVM内部的强大功能。 Node.x诞生了,后来发展成为Vert.x。

总览

Vert.x旨在实现事件总线,该事件总线使应用程序的不同部分可以以非阻塞/线程安全的方式进行通信。 它的一部分是根据Eralng和Akka展示的Actor方法建模的。 它还旨在充分利用当今的多核处理器和高度并发的编程需求。 因此,默认情况下,所有Vert.x VERTICLES默认都实现为单线程。 与NodeJS不同,Vert.x可以在许多线程中运行许多顶点。 此外,您可以指定某些顶点为“工作”顶点,并且可以是多线程的。 为了给蛋糕锦上添花,Vert.x通过使用Hazelcast对事件总线的多节点群集提供了底层支持。 它继续包含许多其他令人惊奇的功能,这些功能在这里没有列出太多,但是您可以在Vert.x官方文档中阅读更多内容。

关于Vert.x,您需要了解的第一件事是,与NodeJS相似,永远不要阻塞当前线程。 默认情况下,Vert.x中的所有内容均已设置为使用回调/未来/承诺。 Vert.x提供了异步方法来执行大多数I / O和处理器密集型操作,这些操作可能会阻塞当前线程,而不是执行同步操作。 现在,使用回调可能很丑陋且很痛苦,因此Vert.x可以选择提供基于RxJavaAPI,API使用Observer模式实现相同的功能。 最后,Vert.x通过在许多异步API上提供executeBlocking(Function f)方法,可以轻松使用现有的类和方法。 这意味着您可以选择喜欢使用Vert.x的方式,而不是由工具包指示必须如何使用它。

了解Vert.x的第二件事是它由顶点,模块和节点组成。 顶点是Vert.x中最小的逻辑单元,通常由单个类表示。 遵循UNIX Philosophy的原则,顶点应该是简单且具有单一用途的。 一组顶点可以放到一个模块中,该模块通常打包为单个JAR文件。 一个模块代表了一组相关的功能,当一起使用时,它们可以代表一个完整的应用程序或一个较大的分布式应用程序的一部分。 最后,节点是运行一个或多个模块/垂直模块的JVM的单个实例。 由于Vert.x具有从头开始内置的群集功能,因此Vert.x应用程序可以跨越一台计算机或跨多个地理位置的多台计算机跨越节点(尽管延迟可能会掩盖性能)。

示例项目

现在,我最近去过一些聚会和会议,在谈论反应式编程时,它们向您展示的第一件事就是构建一个聊天室应用程序。 一切都很好,但这并不能真正帮助您完全理解响应式开发的力量。 聊天室应用程序既简单又简单。 我们可以做得更好。 在本教程中,我们将使用旧版Spring应用程序并将其转换为利用Vert.x的优势。 这具有多个目的:表明该工具包易于与现有Java项目集成,它使我们能够利用可能是生态系统中根深蒂固的现有工具的优势,并且使我们遵循DRY原则 ,因为我们不不必重写大量代码即可获得Vert.x的好处。

我们的旧版Spring应用程序是使用Spring Boot,Spring Data JPA和Spring REST的REST API的简单示例。 源代码可以在“主”分支中找到该处 。 我们还将使用其他分支来演示进行中的进度,因此,只要对gitJava 8有一点经验的人都可以遵循。 让我们从检查常规Spring应用程序的Spring Configuration类开始。

@SpringBootApplication
@EnableJpaRepositories
@EnableTransactionManagement
@Slf4j
public class Application {
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        System.out.println("Let's inspect the beans provided by Spring Boot:");

        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build();
    }

    @Bean
    public EntityManagerFactory entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("com.zanclus.data.entities");
        factory.setDataSource(dataSource());
        factory.afterPropertiesSet();

        return factory.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
        final JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(emf);
        return txManager;
    }
}

正如您在课程顶部看到的那样,我们有一些非常标准的Spring Boot注释。 你还会看到@ SLF4J批注这是一部分Lombok库,旨在帮助降低锅炉板代码。 我们还有@Bean批注的方法,用于提供对JPA EntityManager,TransactionManager和DataSource的访问。 这些项目中的每一个都提供可注入的对象,供其他类使用。 项目中的其余类也非常简单。 有一个客户 POJO,它是服务中使用的实体类型。 通过Spring Data创建了一个CustomerDAO 。 最后,有一个CustomerEndpoints类,它是JAX-RS注释的REST控制器。

如前所述,这是Spring Boot应用程序中的所有标准票价。 该应用程序的问题在于,在大多数情况下,它的可伸缩性有限。 您可以在Servlet容器中运行此应用程序,也可以在Jetty或Undertow之类的嵌入式服务器中运行该应用程序。 无论哪种方式,每个请求都占用一个线程,因此在等待I / O操作时浪费了资源。

切换到Convert-To-Vert.x-Web分支,我们可以看到Application类发生了一些变化。 现在,我们有了一些新的@Bean批注方法来注入Vertx实例本身,以及ObjectMapper实例(Jackson JSON库的一部分)。 我们还用新的CustomerVerticle替换了CustomerEnpoints类。 几乎所有其他内容都是相同的。

CustomerVerticle类带有@Component注释,这意味着Spring将在启动时实例化该类。 它还具有用@PostConstruct注释的start方法,以便在启动时启动Verticle。 查看代码的实际内容,我们看到Vert.x代码的第一部分: Router

Router类是vertx-web库的一部分,它使我们能够使用流畅的API来定义HTTP URL,方法和标头过滤器以进行请求处理。 将BodyHandler实例添加到默认路由可以处理POST / PUT主体并将其转换为JSON对象,然后Vert.x可以将其作为RoutingContext的一部分进行处理。 Vert.x中的路由顺序可能很重要。 如果您定义的路由具有某种形式的全局匹配(*或regex),则除非实现chaining ,否则它会吞下对其后定义的路由的请求。 我们的示例最初显示了3条路线。

@PostConstruct
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.get("/v1/customer/:id")
                .produces("application/json")
                .blockingHandler(this::getCustomerById);
        router.put("/v1/customer")
                .consumes("application/json")
                .produces("application/json")
                .blockingHandler(this::addCustomer);
        router.get("/v1/customer")
                .produces("application/json")
                .blockingHandler(this::getAllCustomers);
        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

请注意,定义了HTTP方法,定义了“ Accept”标头(通过消耗),定义了“ Content-Type”标头(通过生产)。 我们还看到我们正在通过对blockingHandler方法的调用传递对请求的处理。 Vert.x路由的阻塞处理程序接受RoutingContext对象,因为它是唯一的参数。 RoutingContext包含Vert.x请求对象,响应对象以及任何参数/ POST主体数据(例如“:id”)。 您还将看到,我使用了方法引用而不是lambda来将逻辑插入blockingHandler(我发现它更具可读性)。 这3条请求路由的每个处理程序都在该类中一个单独的方法中定义。 这些方法基本上只是调用DAO上的方法,根据需要进行序列化或反序列化,设置一些响应头,并通过发送响应来结束请求。 总体而言,非常简单明了。

private void addCustomer(RoutingContext rc) {
        try {
            String body = rc.getBodyAsString();
            Customer customer = mapper.readValue(body, Customer.class);
            Customer saved = dao.save(customer);
            if (saved!=null) {
                rc.response().setStatusMessage("Accepted").setStatusCode(202).end(mapper.writeValueAsString(saved));
            } else {
                rc.response().setStatusMessage("Bad Request").setStatusCode(400).end("Bad Request");
            }
        } catch (IOException e) {
            rc.response().setStatusMessage("Server Error").setStatusCode(500).end("Server Error");
            log.error("Server error", e);
        }
    }

    private void getCustomerById(RoutingContext rc) {
        log.info("Request for single customer");
        Long id = Long.parseLong(rc.request().getParam("id"));
        try {
            Customer customer = dao.findOne(id);
            if (customer==null) {
                rc.response().setStatusMessage("Not Found").setStatusCode(404).end("Not Found");
            } else {
                rc.response().setStatusMessage("OK").setStatusCode(200).end(mapper.writeValueAsString(dao.findOne(id)));
            }
        } catch (JsonProcessingException jpe) {
            rc.response().setStatusMessage("Server Error").setStatusCode(500).end("Server Error");
            log.error("Server error", jpe);
        }
    }

    private void getAllCustomers(RoutingContext rc) {
        log.info("Request for all customers");
        List customers = StreamSupport.stream(dao.findAll().spliterator(), false).collect(Collectors.toList());
        try {
            rc.response().setStatusMessage("OK").setStatusCode(200).end(mapper.writeValueAsString(customers));
        } catch (JsonProcessingException jpe) {
            rc.response().setStatusMessage("Server Error").setStatusCode(500).end("Server Error");
            log.error("Server error", jpe);
        }
    }

您可能会说:“但是,这比我的Spring注释和类还要更多的代码和混乱”。 这可能是正确的,但实际上取决于您如何实现代码。 这只是一个介绍性的示例,因此我使代码非常简单易懂。 我可以使用Vert.x的注释库以类似于JAX-RS的方式实现端点。 此外,我们还获得了可扩展性的巨大改进。 在幕后,Vert.x Web使用Netty进行低级异步I / O操作,从而使我们能够处理更多并发请求(受数据库连接池的大小限制)。

通过使用Vert.x Web库,我们已经对该应用程序的可伸缩性和并发性进行了一些改进,但是我们可以通过实现Vert.x EventBus进行一些改进。 通过将数据库操作分为Worker Verticles而不是使用blockingHandler,我们可以更有效地处理请求处理。 这显示在“ 转换为工作人员垂直”分支中。 应用程序类保持不变,但是我们更改了CustomerEndpoints类,并添加了一个名为CustomerWorker的新类。 此外,我们添加了一个名为Spring Vert.x Extension的新库,该库为Vert.x Verticles提供了Spring Dependency Injections支持。 首先查看新的CustomerEndpoints类。

@PostConstruct
    public void start() throws Exception {
        log.info("Successfully create CustomerVerticle");
        DeploymentOptions deployOpts = new DeploymentOptions().setWorker(true).setMultiThreaded(true).setInstances(4);
        vertx.deployVerticle("java-spring:com.zanclus.verticles.CustomerWorker", deployOpts, res -> {
            if (res.succeeded()) {
                Router router = Router.router(vertx);
                router.route().handler(BodyHandler.create());
                final DeliveryOptions opts = new DeliveryOptions()
                        .setSendTimeout(2000);
                router.get("/v1/customer/:id")
                        .produces("application/json")
                        .handler(rc -> {
                            opts.addHeader("method", "getCustomer")
                                    .addHeader("id", rc.request().getParam("id"));
                            vertx.eventBus().send("com.zanclus.customer", null, opts, reply -> handleReply(reply, rc));
                        });
                router.put("/v1/customer")
                        .consumes("application/json")
                        .produces("application/json")
                        .handler(rc -> {
                            opts.addHeader("method", "addCustomer");
                            vertx.eventBus().send("com.zanclus.customer", rc.getBodyAsJson(), opts, reply -> handleReply(reply, rc));
                        });
                router.get("/v1/customer")
                        .produces("application/json")
                        .handler(rc -> {
                            opts.addHeader("method", "getAllCustomers");
                            vertx.eventBus().send("com.zanclus.customer", null, opts, reply -> handleReply(reply, rc));
                        });
                vertx.createHttpServer().requestHandler(router::accept).listen(8080);
            } else {
                log.error("Failed to deploy worker verticles.", res.cause());
            }
        });
    }

路由相同,但实现代码不同。 现在,我们不再使用对blockingHandler的调用,而是实现了适当的异步处理程序,该处理程序在事件总线上发送事件。 此Verticle中不再进行任何数据库处理。 我们已将数据库处理移至一个工作线程,该线程具有多个实例,以线程安全的方式并行处理多个请求。 我们还为这些事件的回复时间注册了一个回调,以便我们可以向发出请求的客户端发送适当的响应。 现在,在CustomerWorker Verticle中,我们已经实现了数据库逻辑和错误处理。

@Override
public void start() throws Exception {
    vertx.eventBus().consumer("com.zanclus.customer").handler(this::handleDatabaseRequest);
}

public void handleDatabaseRequest(Message<Object> msg) {
    String method = msg.headers().get("method");

    DeliveryOptions opts = new DeliveryOptions();
    try {
        String retVal;
        switch (method) {
            case "getAllCustomers":
                retVal = mapper.writeValueAsString(dao.findAll());
                msg.reply(retVal, opts);
                break;
            case "getCustomer":
                Long id = Long.parseLong(msg.headers().get("id"));
                retVal = mapper.writeValueAsString(dao.findOne(id));
                msg.reply(retVal);
                break;
            case "addCustomer":
                retVal = mapper.writeValueAsString(
                                    dao.save(
                                            mapper.readValue(
                                                    ((JsonObject)msg.body()).encode(), Customer.class)));
                msg.reply(retVal);
                break;
            default:
                log.error("Invalid method '" + method + "'");
                opts.addHeader("error", "Invalid method '" + method + "'");
                msg.fail(1, "Invalid method");
        }
    } catch (IOException | NullPointerException e) {
        log.error("Problem parsing JSON data.", e);
        msg.fail(2, e.getLocalizedMessage());
    }
}

CustomerWorker工人垂直服务器在事件总线上注册消费者以获取消息。 代表事件总线上的地址的字符串是任意的,但是建议使用反向tld样式的命名结构,以确保地址唯一(“ com.zanclus.customer”)很简单。 每当有新消息发送到该地址时,它将被发送到一个,只有一个工作层。 然后,工作层将调用handleDatabaseRequest来完成数据库工作,JSON序列化和错误处理。

你有它。 您已经看到,可以将Vert.x集成到旧版应用程序中,以提高并发性和效率,而不必重写整个应用程序。 我们可以使用现有的Google Guice或JavaEE CDI应用程序完成类似的操作。 当我们在Vert.x中尝试添加响应功能时,所有业务逻辑都可能保持相对不变。 下一步由您决定。 接下来的一些想法包括ClusteringWebSockets和ReactiveX sugar的VertxRx

翻译自: https://www.javacodegeeks.com/2015/12/reactive-development-using-vert-x.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Vert.x 是一个基于 JVM 的开源、高性能、响应式应用程序框架,用于构建事件驱动的应用程序。阿里巴巴使用 Vert.x 构建了很多应用程序,如阿里云数据库的 Hologres 等。 Vert.x 的主要特点包括: 1. 高性能:Vert.x 可以处理大量并发连接,而不会出现阻塞情况,从而提供高性能的响应式应用程序。 2. 响应式Vert.x 支持异步编程模型,使得开发响应式应用程序更加容易。 3. 多语言支持:Vert.x 可以使用多种语言进行开发,包括 Java、Kotlin、Groovy、JavaScript 等。 4. 模块化:Vert.x 拥有一个丰富的模块库,使得开发人员可以方便地集成第三方组件和库。 5. 部署简单:Vert.x 的应用程序可以轻松地部署到云、容器和传统服务器上。 阿里巴巴使用 Vert.x 来构建高性能、高可用性的微服务应用程序。例如,阿里云数据库的 Hologres 就是使用 Vert.x 开发的。Vert.x 可以帮助阿里巴巴构建高性能、响应式、可扩展的应用程序,从而提高用户的体验。 ### 回答2: 阿里巴巴是一家中国的互联网巨头公司,它在多个领域进行了项目开发,其中就包括了使用Vert.xVert.x是一个开源的、事件驱动的应用程序框架,它可以用于构建高性能、可伸缩、分布式的应用程序。阿里巴巴在使用Vert.x时,主要应用于以下几个项目中: 1. 分布式应用程序:阿里巴巴在架构设计上广泛采用微服务架构,而Vert.x提供的分布式事件总线和消息传递机制能够帮助阿里巴巴实现不同微服务之间的通信和协作。 2. 实时数据处理:阿里巴巴的许多业务都需要实时处理大量的数据,比如电商平台的实时订单处理、物流跟踪等。Vert.x的高性能和低延迟的特点能够满足这些实时处理的需求。 3. 规模化应用程序:随着阿里巴巴业务的快速发展,应用程序需要能够处理更大规模的请求和并发用户。Vert.x提供的事件驱动和非阻塞IO模型,可以帮助阿里巴巴构建高性能、可伸缩的应用程序。 4. 弹性和容错性:Vert.x框架提供了容错和弹性的机制,这对于阿里巴巴的系统来说是非常重要的。当系统出现故障或部分节点失效时,Vert.x可以自动进行故障转移,保证服务的可用性和稳定性。 综上所述,阿里巴巴在使用Vert.x时,主要应用于分布式应用程序、实时数据处理、规模化应用程序和弹性与容错性的需求上。通过利用Vert.x的优势,阿里巴巴的项目能够更好地满足业务需求,并提供高性能和可靠性的服务。 ### 回答3: 阿里巴巴在使用Vert.x进行了一些项目开发,其中一个主要的应用是实现分布式系统的高性能异步通信。Vert.x是一个基于事件驱动的开发工具包,适用于构建高性能的网络应用程序。 阿里巴巴的部门和团队使用Vert.x来构建分布式系统的核心组件,以提高系统的性能和可伸缩性。通过使用Vert.x,可以将系统的各个部分拆分为独立的微服务,并使用事件驱动的方式进行通信和协作。Vert.x的异步非阻塞特性可以有效地处理高并发的网络请求,提高系统的响应速度。 除了构建核心组件,阿里巴巴还使用Vert.x开发一些实时的监控和日志分析工具。这些工具可以在系统运行时实时收集和分析各种指标和日志数据,以帮助开发人员快速定位和解决问题。Vert.x的事件驱动模型和高性能的特性使得这些工具可以在高并发的情况下保持稳定和高效。 此外,阿里巴巴还使用Vert.x来构建一些服务网关和API网关。服务网关用于将不同服务之间的通信进行统一管理和路由,API网关则用于对外提供统一的API接口。通过使用Vert.x,这些网关可以高效地处理大量的请求,并提供稳定和可靠的服务。 综上所述,阿里巴巴使用Vert.x进行了一些项目开发,包括分布式系统的高性能异步通信、实时监控和日志分析工具、服务网关和API网关等。这些项目的应用范围广泛,并能够满足阿里巴巴在高并发和高性能方面的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值