Spring、Reactor 和 ElasticSearch使用虚假测试数据进行基准测试

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(71)   2024-06-06 16:21:07

上一篇文章中,我们创建了一个从 ElasticSearch 的 API 到 Reactor 的 Mono 的简单适配器,如下所示:

import reactor.core.publisher.Mono;
 
private Mono indexDoc(Doc doc) {
    //...
}

现在我们想在受控的并发级别上运行此方法数百万次。基本上,我们想看看我们的索引代码在负载下的表现如何,对其进行基准测试。

使用 jFairy 伪造数据

首先,我们需要一些好看的测试数据。为此,我们将使用方便的 jFairy 库。我们要索引的文档是一个简单的 POJO:

@Value
class Doc {
    private final String username;
    private final String json;
}

生成逻辑包装在一个 Java 类中:

import io.codearte.jfairy.Fairy;
import io.codearte.jfairy.producer.person.Address;
import io.codearte.jfairy.producer.person.Person;
import org.apache.commons.lang3.RandomUtils;
 
 
@Component
class PersonGenerator {
 
    private final ObjectMapper objectMapper;
    private final Fairy fairy;
 
    private Doc generate() {
        Person person = fairy.person();
        final String username = person.getUsername() + RandomUtils.nextInt(1_000_000, 9_000_000);
        final ImmutableMap<String, Object> map = ImmutableMap.<String, Object>builder()
                .put("address", toMap(person.getAddress()))
                .put("firstName", person.getFirstName())
                .put("middleName", person.getMiddleName())
                .put("lastName", person.getLastName())
                .put("email", person.getEmail())
                .put("companyEmail", person.getCompanyEmail())
                .put("username", username)
                .put("password", person.getPassword())
                .put("sex", person.getSex())
                .put("telephoneNumber", person.getTelephoneNumber())
                .put("dateOfBirth", person.getDateOfBirth())
                .put("company", person.getCompany())
                .put("nationalIdentityCardNumber", person.getNationalIdentityCardNumber())
                .put("nationalIdentificationNumber", person.getNationalIdentificationNumber())
                .put("passportNumber", person.getPassportNumber())
                .build();
        final String json = objectMapper.writeValueAsString(map);
        return new Doc(username, json);
    }
 
    private ImmutableMap<String, Object> toMap(Address address) {
        return ImmutableMap.<String, Object>builder()
                .put("street", address.getStreet())
                .put("streetNumber", address.getStreetNumber())
                .put("apartmentNumber", address.getApartmentNumber())
                .put("postalCode", address.getPostalCode())
                .put("city", address.getCity())
                .put("lines", Arrays.asList(address.getAddressLine1(), address.getAddressLine2()))
                .build();
    }
 
}

相当无聊的代码实际上做了一些很酷的事情。每次我们运行它时,它都会生成随机但合理的 JSON,如下所示:

{
  "address": {
    "street": "Ford Street",
    "streetNumber": "32",
    "apartmentNumber": "",
    "postalCode": "63913",
    "city": "San Francisco",
    "lines": [
      "32 Ford Street",
      "San Francisco 63913"
    ]
  },
  "firstName": "Evelyn",
  "middleName": "",
  "lastName": "Pittman",
  "email": "pittman@mail.com",
  "companyEmail": "evelyn.pittman@woodsllc.eu",
  "username": "epittman5795354",
  "password": "VpEfFmzG",
  "sex": "FEMALE",
  "telephoneNumber": "368-005-109",
  "dateOfBirth": "1917-05-14T16:47:06.273Z",
  "company": {
    "name": "Woods LLC",
    "domain": "woodsllc.eu",
    "email": "contact@woodsllc.eu",
    "vatIdentificationNumber": "30-0005081",
    "url": "http://www.woodsllc.eu"
  },
  "nationalIdentityCardNumber": "713-79-5185",
  "nationalIdentificationNumber": "",
  "passportNumber": "jVeyZLSt3"
}

整洁的!不幸的是,没有记录jFairy 是否是线程安全的,所以为了以防万一在实际代码中,我使用了ThreadLocal。好吧,我们只有一份文件,但我们需要数百万份!使用 for-loop 太过时了。关于无穷无尽的随机人群,您会说些什么?

import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
 
private final Scheduler scheduler = Schedulers.newParallel(PersonGenerator.class.getSimpleName());
 
Mono<Doc> generateOne() {
    return Mono
            .fromCallable(this::generate)
            .subscribeOn(scheduler);
}
 
Flux<Doc> infinite() {
    return generateOne().repeat();
}

generateOne() 将阻塞的 generate() 方法包装在 Mono<Doc> 中。此外,generate()parallelScheduler 上运行。为什么?事实证明,jFairy 在单核上不够快(大量随机数生成、表查找等),所以我不得不并行化数据生成。通常应该不是问题。但是当生成假数据比你接触外部服务器的反应式应用程序慢时——它告诉你一些关于基于 Netty 的 Spring web-flux 的性能(!)

并发调用 ElasticSearch

好吧,我们现在想要在 ElasticSearch 中为它建立索引,拥有无穷无尽的好看的假测试数据流。

@PostConstruct
void startIndexing() {
    index(1_000_000, 1_000);
}
 
private void index(int count, int maxConcurrency) {
    personGenerator
            .infinite()
            .take(count)
            .flatMap(this::indexDocSwallowErrors, maxConcurrency)
            .window(Duration.ofSeconds(1))
            .flatMap(Flux::count)
            .subscribe(winSize -> log.debug("Got {} responses in last second", winSize));
}
 
private Mono<IndexResponse> indexDocSwallowErrors(Doc doc) {
    return indexDoc(doc)
            .doOnError(e -> log.error("Unable to index {}", doc, e))
            .onErrorResume(e -> Mono.empty());
}

当应用程序启动时,它会启动对 100 万个文档的索引。请注意,告诉 Reactor(对于 RxJava 也是如此)它应该向 ElasticSearch 调用多达一千个并发请求是多么容易。我们每秒计算一次我们收到了多少响应:

Got 2925 responses in last second
Got 2415 responses in last second
Got 3336 responses in last second
Got 2199 responses in last second
Got 1861 responses in last second

不错!尤其是考虑到有多达 1000 个并发 HTTP 请求,而我们的应用程序开始时峰值只有 30 个线程(!)好吧,它是 localhost <-> localhost,有罪!但是我们怎么知道所有这些呢?伐木很好,但现在是二十一世纪,我们可以做得更好!监控将是下一部分的主题。

源代码在 reactive-elastic-search 分支中可用 github.com/nurkiewicz/elastic-flux

标签2: Java教程
地址:https://www.cundage.com/article/jcg-spring-reactor-elasticsearch-bechmarking-fake-test-data.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...