Elasticsearch Transcript的Java客户端

Elasticsearch Transcript的Java客户端

2016年11月9日

这是我在2016年11月9日在新加坡Java用户组发表演讲的成绩单。它也可以看作是我在2014年在Found博客上发布的同名文章的更新版本。

在本次演讲中,我将介绍弹性搜索的三个不同客户端以及Spring Data Elasticsearch。但是要开始使用,我们来看看弹性搜索的一些基础知识。

elasticsearch

为了介绍elasticsearch我使用的是一个直接来自弹性网站的定义。

Elasticsearch是一种基于JSON的分布式搜索和分析引擎,专为水平可扩展性,最高可靠性和易管理性而设计。

让我们先看看基于JSON的搜索和分析引擎的含义。

要了解elasticsearch的作用,最好查看搜索页面的示例。这是每个人都熟悉的东西,Github上的代码搜索。

Github截图

关键字可以在单个搜索输入中输入,下面是结果列表。搜索引擎和其他数据库之间的区别特征之一是存在相关概念。我们可以看到我们的搜索词elasticsearch搜索引擎的项目是第一位的。在搜索这个术语时,人们很可能正在寻找项目。用于确定结果是否比另一个更具相关性的因素可能因应用程序而异 - 我不知道Github正在做什么,但我可以想象他们正在使用除了经典文本相关性功能之外的流行度等因素。像elasitcsearch这样的经典搜索引擎支持网站上有很多功能:突出显示结果中的出现,对列表进行分页并使用不同的标准进行排序。在左侧,您可以看到所谓的构面,可用于使用找到的文档中的条件进一步细化结果列表。这类似于ebay和亚马逊等电子商务网站上的功能。对于这样的事情,elasticsearch中的聚合功能也是其分析功能的基础。使用elasticsearch也可以完成更多这样的工作。在这种情况下,这更加明显--Github实际上使用elasticsearch搜索他们正在存储的大量数据。

如果要构建这样的搜索应用程序,则必须先安装引擎。幸运的是,弹性搜索很容易上手。除了最近的Java运行时之外没有特殊要求。您可以从弹性网站下载elasticsearch存档,解压缩并使用脚本启动elasticsearch。

# download archive
wget https://artifacts.elastic.co/downloads/
    elasticsearch/elasticsearch-5.0.0.zip

unzip elasticsearch-5.0.0.zip

# on windows: elasticsearch.bat
elasticsearch-5.0.0/bin/elasticsearch

对于生产用途,还有用于不同Linux发行版的软件包。您可以通过在标准端口上执行HTTP GET请求来查看elasticsearch。在示例中,我使用curl,这是用于执行HTTP请求的命令行客户端,可用于许多环境。

curl -XGET "http://localhost:9200"

elasticsearch将使用包含有关安装的一些信息的JSON文档来回答此请求。

{
  "name" : "LI8ZN-t",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "UvbMAoJ8TieUqugCGw7Xrw",
  "version" : {
    "number" : "5.0.0",
    "build_hash" : "253032b",
    "build_date" : "2016-10-26T04:37:51.531Z",
    "build_snapshot" : false,
    "lucene_version" : "6.2.0"
  },
  "tagline" : "You Know, for Search"
} 

对我们来说最重要的事实是我们可以看到服务器已启动。但是还有关于elasticsearch和Lucene的版本信息,Lucene是用于大多数搜索功能的底层库。

如果我们现在想要在elasticsearch中存储数据,我们也将它作为JSON文档发送,这次使用POST请求。因为我非常喜欢新加坡的食物,我想建立一个应用程序,让我可以搜索我最喜欢的食物。让我们索引第一道菜。

curl -XPOST "http://localhost:9200/food/dish" -d'
{
  "food": "Hainanese Chicken Rice",
  "tags": ["chicken", "rice"],
  "favorite": {
    "location": "Tian Tian",
    "price": 5.00
  }
}'

我们使用的是之前使用的相同端口,这次我们只需要在url中添加两个片段:fooddish。第一个是索引的名称,一个逻辑的文档集合。第二种是类型。它决定了我们正在保存的文档的结构,即所谓的映射。

菜肴本身被建模为文件。elasticsearch支持不同的数据类型,如字符串,用于food属性,列表,如tags文档,甚至嵌入favorite文档。除此之外,还有更多原始类型,如数字,布尔值和地理坐标等特殊类型。

我们现在可以索引另一个文档执行另一个POST请求。

curl -XPOST "http://localhost:9200/food/dish" -d'
{
  "food": "Ayam Penyet",
  "tags": ["chicken", "indonesian"],
  "spicy": true
}'

这份文件的结构有点不同。它不包含favorite子文档,而是具有另一个属性spicy。同类文档可能非常不同 - 但请记住,您需要解释应用程序中的某些部分。通常你会有类似的文件。

通过索引这些文档,可以自动搜索它们。一种选择是执行GET请求/_search并将查询词添加为参数。

curl -XGET "http://localhost:9200/food/dish/_search?q=chicken"

在两个文件中搜索鸡肉也会返回它们。这是结果的摘录。

...
{"total":2,"max_score":0.3666863,"hits":[{"_index":"food","_type":"dish","_id":"AVg9cMwARrBlrY9tYBqX","_score":0.3666863,"_source":
{
  "food": "Hainanese Chicken Rice",
  "tags": ["chicken", "rice"],
  "favorite": {
    "location": "Tian Tian",
    "price": 5.00
  }
}},
...

有一些全球信息,如找到的文件数量。但最重要的属性是hits包含索引菜肴原始来源的数组。

这样开始很容易,但大多数情况下查询会更复杂。这就是为什么elasticsearch提供查询DSL,一种描述查询的JSON结构以及所请求的任何其他搜索功能。

curl -XPOST "http://localhost:9200/food/dish/_search" -d'
{
  "query": {
    "bool": {
      "must": {
      "match": {
        "_all": "rice"
      }
      },
      "filter": {
    "term": {
      "tags.keyword": "chicken"
    }
      }
    }
  }
}'

我们正在寻找包含术语的所有文件rice,也有chickentags。使用该字段访问字段.keyword允许进行精确搜索,并且是elasticsearch 5.0中的新功能。

除了搜索本身,您还可以使用查询DSL从elasticsearch请求更多信息,无论是突出显示还是自动完成,还是可以用于构建分面功能的聚合。

让我们继续讨论定义的另一部分。

Elasticsearch是[...]分布式的,专为水平可扩展性和最高可靠性而设计

到目前为止,我们只访问了一个弹性搜索实例。

访问单个节点

我们的应用程序将直接与该节点对话。现在,由于elasticsearch是为水平可伸缩性而设计的,我们还可以添加更多节点。

访问群集

节点形成一个集群。我们仍然可以与第一个节点通信,它会将所有请求分发到集群的必要节点。这对我们来说完全透明。

使用elasticsearch构建集群在开始时非常简单,但当然维护生产集群可能更具挑战性。

现在我们对elasticsearch的作用有了基本的了解,让我们看看如何从Java应用程序访问它。

运输客户

传输客户端从一开始就可用,并且是最常选择的客户端。从elasticsearch 5.0开始,它有自己的工件,可以集成到您的构建中,例如使用Gradle。

dependencies {
    compile group: 'org.elasticsearch.client',
        name: 'transport',
        version: '5.0.0'
}

Elasticsearch的所有功能都可以使用Client接口,具体实例是TransportClient,可以使用Settings对象实例化,并且可以具有一个或多个elasticsearch节点的地址。

TransportAddress address =
    new InetSocketTransportAddress(
        InetAddress.getByName("localhost"), 9300);

Client client = new PreBuiltTransportClient(Settings.EMPTY)
    addTransportAddress(address);

client随之提供了用于elasticsearch的不同特征的方法。首先,让我们再次搜索。回想一下上面发出的查询的结构。

curl -XPOST "http://localhost:9200/food/dish/_search" -d'
{
  "query": {
    "bool": {
      "must": {
      "match": {
        "_all": "rice"
      }
      },
      "filter": {
    "term": {
      "tags.keyword": "chicken"
    }
      }
    }
  }
}'

一个bool具有查询match在查询must部分和term在其查询filter部分。

幸运的是,一旦你有这样的查询,你可以很容易地将它转换为Java等价物。

SearchResponse searchResponse = client
   .prepareSearch("food")
   .setQuery(
    boolQuery().
      must(matchQuery("_all", "rice")).
      filter(termQuery("tags.keyword", "chicken")))
   .execute().actionGet();

assertEquals(1, searchResponse.getHits().getTotalHits());

SearchHit hit = searchResponse.getHits().getAt(0);
String food = hit.getSource().get("food").toString();

我们请求SearchSourceBuilder调用prepareSearchclient。我们可以使用静态帮助器方法设置查询。再次,这是一个bool具有查询match在查询must部分和term在其查询filter部分。

调用execute返回Future对象,actionGet是调用的阻塞部分。该SearchResponse代表相同的JSON结构做使用HTTP接口搜索时,我们可以看到。然后菜肴的来源可以作为地图获得。

索引数据时,可以使用不同的选项。一种是使用它jsonBuilder来创建JSON表示。

XContentBuilder builder = jsonBuilder()
    .startObject()
        .field("food", "Roti Prata")
        .array("tags", new String [] {"curry"})
        .startObject("favorite")
        .field("location", "Tiong Bahru")
        .field("price", 2.00)
        .endObject()
    .endObject();

它提供了可用于创建JSON文档结构的不同方法。然后,可以将其用作IndexRequest的源。

IndexResponse resp = client.prepareIndex("food","dish")
        .setSource(builder)
        .execute()
        .actionGet();

除了使用之外,jsonBuilder还有其他几种选择。

索引的方法不同

一个常见的选择是使用Map,接受简单结构的字段名称和值的便捷方法或传递String的选项,通常与像Jackson这样的库一起用于序列化。

我们在上面已经看到,传输客户端接受一个或多个elasticsearch节点的地址。您可能已经注意到端口与用于http,9300而不是9200的端口不同。这是因为客户端不通过http进行通信 - 它使用传输协议连接到现有群集,传输协议也是用于集群中的节点间通信。

传输客户端使用二进制协议

您可能也注意到,到目前为止,我们只与群集的一个节点进行通信。一旦此节点出现故障,我们可能无法再访问我们的数据。如果您需要高可用性,则可以启用嗅探选项,以使您的客户端与群集中的多个节点通信。

嗅探可实现高可用性

现在,当其中一个节点出现故障时,我们仍然可以使用其他节点访问数据。可以通过设置client.transport.snifftrue创建客户端时启用该功能。

TransportAddress address =
    new InetSocketTransportAddress(
        InetAddress.getByName("localhost"), 9300);

Settings settings = Settings.builder()
            .put("client.transport.sniff", true)
            .build();

Client client = new PreBuiltTransportClient(settings)
    addTransportAddress(address);

此功能的工作原理是使用elasticsearch的一个管理API从已知节点请求集群的当前状态。配置时,这是在启动期间和定期间隔内完成的,默认情况下每5秒完成一次。

嗅探是一项重要功能,可确保您的应用程序即使在节点故障期间也能保持正常运行。

使用传输客户端时,您有一些明显的好处:由于客户端随服务器一起提供(甚至包括对服务器的依赖性),因此您可以确保所有当前API都可用于您的客户端代码。通信比基于HTTP的JSON更有效,并且支持客户端负载平衡。

另一方面也存在一些缺点:由于传输协议是内部协议,因此您需要在服务器和客户端上使用兼容的elasticsearch版本。此外,相当意外,这也意味着需要使用类似的JDK版本。此外,您需要在应用程序中包含elasticsearch的所有依赖项。这可能是一个大问题,尤其是对于较大的现有应用程序。例如,CMS可能已经发布了某些版本的Lucene。通常不可能像这样解决依赖性冲突。

幸运的是,有一个解决方案。

RESTClient实现

elasticsearch 5.0引入了一个新客户端,它使用elasticsearch的HTTP API而不是内部协议。这需要更少的依赖性。此外,您不需要太在意版本 - 当前客户端也可以与elasticsearch 2.x一起使用。

RestClient使用HTTP与群集通信

但也有一个缺点 - 它还没有很多功能。

客户端也可以作为Maven工件使用。

dependencies {
    compile group: 'org.elasticsearch.client',
        name: 'rest',
        version: '5.0.0'
}

客户端仅依赖于apache httpclient及其依赖项。这是所有依赖项的Gradle列表。

+--- org.apache.httpcomponents:httpclient:4.5.2
+--- org.apache.httpcomponents:httpcore:4.4.5
+--- org.apache.httpcomponents:httpasyncclient:4.1.2
+--- org.apache.httpcomponents:httpcore-nio:4.4.5
+--- commons-codec:commons-codec:1.10
\--- commons-logging:commons-logging:1.1.3

它可以通过传递一个或多个来实现HttpHost

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200, "http"),
    new HttpHost("localhost", 9201, "http"))
    .build();

由于目前没有很多功能,因此大多数JSON只能作为String使用。这是执行match_all查询并使用帮助器方法将响应转换为String 的示例。

  HttpEntity entity = new NStringEntity(
      "{ \"query\": { \"match_all\": {}}}",
      ContentType.APPLICATION_JSON);
  // alternative: performRequestAsync
  Response response = restClient.performRequest("POST",                                     "/_search", emptyMap(), entity);
  String json = toString(response.getEntity());
  // ...

索引数据也是低级别的。您只需将包含JSON文档的String发送到端点。客户端支持使用单独的库进行嗅探。除了依赖性较少且弹性搜索版本不再那么重要之外,操作还有另一个好处:现在可以将群集与应用程序分开,HTTP是与群集通信的唯一协议。

大多数功能直接依赖于Apache http客户端。支持设置超时,使用基本身份验证,自定义标头和错误处理。

目前还没有查询支持。如果您能够将elasticsearch依赖项添加到您的应用程序(这当然会使某些优势无效),您可以使用SearchSourceBuilder相关功能为查询创建字符串。

除了新的RestClient之外,还有另一个可用的HTTP客户端具有更多功能:社区构建的客户端Jest。

笑话

Jest已经有很长一段时间了,是标准客户的可行替代品。它也可以通过Maven中心获得。

dependencies {
    compile group: 'io.searchbox',
        name: 'jest',
        version: '2.0.0'
}

JestClient是允许向e​​lasticsearch发送请求的中央接口。它可以使用工厂创建。

JestClientFactory factory = new JestClientFactory();
factory.setHttpClientConfig(new HttpClientConfig
            .Builder("http://localhost:9200")
            .multiThreaded(true)
            .build());

JestClient client = factory.getObject();

与RestClient一样,Jest不支持生成查询。您可以使用String模板创建它们,也可以重用elasticsearch构建器(缺点是必须再次管理所有依赖项)。

构建器可用于创建搜索请求。

String query = jsonStringThatMagicallyAppears;

Search search = new Search.Builder(query)
    .addIndex("library")
    .build();

SearchResult result = client.execute(search);
assertEquals(Integer.valueOf(1), result.getTotal());

可以通过遍历可能变得相当复杂的Gson对象结构来处理结果。

JsonObject jsonObject = result.getJsonObject();
JsonObject hitsObj = jsonObject.getAsJsonObject("hits");
JsonArray hits = hitsObj.getAsJsonArray("hits");
JsonObject hit = hits.get(0).getAsJsonObject();

// ... more boring code

但这并不是你通常与Jest合作的方式。Jest的好处是它直接支持索引和搜索Java bean。例如,我们可以代表我们的菜肴文件。

public class Dish {

    private String food;
    private List<String> tags;
    private Favorite favorite;

    @JestId
    private String id;

     // ... getters and setters
}

然后可以从搜索结果中自动填充此类。

Dish dish = result.getFirstHit(Dish.class).source;

assertEquals("Roti Prata", dish.getFood());

当然,bean支持也可用于索引数据。

当通过http访问elasticsearch时,Jest可以是一个很好的选择。它具有许多有用的功能,例如索引和搜索时的bean支持以及称为节点发现的嗅探功能。不幸的是,您必须自己创建搜索查询,但RestClient也是如此。

现在我们已经看了三个客户端,现在是时候看到更高层次的抽象了。

Spring Data Elasticsearch

Spring Data项目系列使用通用编程模型提供对不同数据存储的访问。它并不试图在所有商店提供抽象,每个商店的特色仍然可用。最令人印象深刻的功能是动态存储库,允许您使用接口定义查询。流行的模块是Spring Data JPA,用于访问关系数据库和Spring Data MongoDB。

与所有Spring模块一样,工件在Maven中心可用。

dependencies {
    compile group: 'org.springframework.data',
    name: 'spring-data-elasticsearch',
    version: '2.0.4.RELEASE'
}

要编制索引的文档使用自定义注释表示为Java bean。

@Document(indexName = "spring_dish")
public class Dish {

    @Id
    private String id;
    private String food;
    private List<String> tags;
    private Favorite favorite;

     // more code

}

可以使用不同的注释来定义文档在elasticsearch中的存储方式。在这种情况下,我们只定义在持久化文档时使用的索引名称以及用于存储elasticsearch生成的id的属性。

为了访问文档,可以定义键入菜类的接口。有不同的接口可用于扩展,ElasticsearchCrudRepository提供通用索引和搜索操作。

public interface DishRepository 
  extends ElasticsearchCrudRepository<Dish, String> {

}

该模块为XML配置提供命名空间。

<elasticsearch:transport-client id="client" />

<bean name="elasticsearchTemplate" 
  class="o.s.d.elasticsearch.core.ElasticsearchTemplate">
    <constructor-arg name="client" ref="client"/>
</bean>

<elasticsearch:repositories 
  base-package="de.fhopf.elasticsearch.springdata" />

transport-client元素instanciates传输客户端,ElasticsearchTemplate提供elasticsearch的常用操作。最后,该repositories元素指示Spring Data扫描扩展其中一个Spring Data接口的接口。它会自动为这些创建实例。

然后,您可以将存储库连接到应用程序中,并将其用于存储和查找实例Dish

Dish mie = new Dish();
mie.setId("hokkien-prawn-mie");
mie.setFood("Hokkien Prawn Mie");
mie.setTags(Arrays.asList("noodles", "prawn"));

repository.save(Arrays.asList(hokkienPrawnMie));

// one line ommited

Iterable<Dish> dishes = repository.findAll();

Dish dish = repository.findOne("hokkien-prawn-mie");

通过id检索文档对于搜索引擎来说不是很有趣。要真正查询文档,您可以向接口添加更多遵循特定命名约定的方法。

public interface DishRepository 
  extends ElasticsearchCrudRepository<Dish, String> {

    List<Dish> findByFood(String food);

    List<Dish> findByTagsAndFavoriteLocation(String tag, String location);

    List<Dish> findByFavoritePriceLessThan(Double price);

    @Query("{\"query\": {\"match_all\": {}}}")
    List<Dish> customFindAll();
}

大多数方法都findBy以一个或多个属性开头。例如,findByFoodfood使用给定参数查询字段。结构化查询也是可能的,在这种情况下通过添加lessThan。这将返回所有价格低于给定价格的菜肴。最后一种方法使用不同的方法。它不遵循命名约定,而是使用Query注释。当然,此查询也可以包含参数的占位符。

总而言之,Spring Data Elasticsearch是标准客户端之上的一个有趣的抽象。它与某个弹性搜索版本有些联系,当前版本使用2.2版本。有计划使其与5.x兼容,但这可能还需要一些时间。有一个使用Jest进行通信的pull请求,但不清楚是否以及何时将其合并。不幸的是,项目中没有很多活动。

结论

我们已经研究了三个Java客户端和更高级别的抽象Spring Data Elasticsearch。每个都有其优点和缺点,并没有建议在所有情况下使用一个。传输客户端具有完整的API支持,但与弹性搜索依赖关联。RestClient是未来,有一天会取代传输客户端。功能明智,它目前是非常低的水平。Jest有一个更丰富的API但是在外部开发,并且它背后的公司似乎不再存在,尽管项目中的委员会有活动。另一方面,Spring Data Elasticsearch更适合已经使用Spring Data的开发人员,并且不希望直接与elasticsearch API联系。它目前与标准客户端的版本相关联,开发活动相当低。

 

 

注:转载自:http://blog.florian-hopf.de/2016/11/java-clients-elasticsearch.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值