ElasticSearch Java客户端介绍

952次阅读  |  发布于5年以前

这篇文章主要介绍ElasticSearch客户端,包括Transport客户端、Jest客户端和Spring Data ElasticSearch,首先来看几个基本概念。

ElasticSearch

官方的介绍:

ElasticSearch是一个分布式的、基于Json的检索引擎,具有水平扩展、高可靠性和易于管理等优点。

为了更好的理解ealsticsearch的作用,我们可以看下Github的搜索页面:

我们在输入框输入一个单词,会列出许多查询结果,搜索引擎和数据库的区别就是相关性,
我们可以看到elasticsearch的项目排在第一位,很有可能别人在搜索这个关键词时想要访问这个项目。对于不同的应用影响排名的因素可能各不一样。

如果你想建立一个像这样的搜索引擎,首先你的安装elasticsearch,elasticsearch很容易安装,你只需要先安装好Java虚拟机就可以了。

安装和使用ElasticSearch

ElasticSearch默认绑定9200端口,你可以通过http://localhost:9200来访问,你也可以用命令行客户端来执行HTTP请求,这里我用curl来执行

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.0.0.zip  
    unzip elasticsearch-5.0.0.zip  

    elasticsearch-5.0.0/bin/elasticsearch  

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

你会收到一个Json Document响应,这个响应包含ElasticSearch的安装信息,如下所示:

{  
      "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后面,food和dish,第一个是索引(index)的名称,这是所有文档的集合,第二个是类型(type),相当于关系型数据库里的表。

Dish用文档来建模,Elasticsearch支持多种数据类型,比如string,boolean,数值类型numerics以及嵌套的数据类型,比如上面的favorite。

接下来我们再通过一个POST请求添加一个文档:

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

这一次文档的结构有啥不同,它没有包含faorite元素,但添加了另一个spicy属性。同个类型的文档可能差异很大。

建立索引之后,我们就可以来搜索关键字了。我们可以在链接的后面加一个_search,然后添加一个查询参数:

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

这个请求在dish类型里搜索包含chicken字段的文档,结果如下:

{"took":57,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max_score":0.3666863,"hits":[{"_index":"food","_type":"dish","_id":"AVq9cnkMZAUVR2HS07Sa","_score":0.3666863,"_source":  
    {  
      "food": "Hainanese Chicken Rice",  
      "tags": ["chicken", "rice"],  
      "favorite": {  
        "location": "Tian Tian",  
        "price": 5.00  
      }  
    }},{"_index":"food","_type":"dish","_id":"AVq9cqoiZAUVR2HS07Sb","_score":0.2876821,"_source":  
    {  
      "food": "Ayam Penyet",  
      "tags": ["chicken", "indonesian"],  
      "spicy": true  
    }}]}}  

搜索结果包含了找到文档的数量,最重要的属性是hits数组,这里面包含索引到的source原始数据。Elasticsearch提供基于JSON结构的查询DSL,如下所示:

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

上面这个JSON是在搜索包含rice字段以及tags字段里有chicken的文档,elasticsearch 5.0提供了.keyword字段来精确查询。

到目前为止我们只访问了单个ElasticSearch实例,ElasticSearch具有水平伸缩的特性,我们可以添加更多的node,我们仍然可以连接第一个node,它可以将请求转发到集群中的其他节点。

ElasticSearch客户端介绍

Transport客户端

Transport客户端从ElasticSearch第一个版本就有了,也是使用最广泛的客户端,使用它需要在你的构建工具中添加依赖,我这里用的Gradle:

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

通过客户端你可以使用ElasticSearch的所有功能,你可以通过Settings对象来初始化一个TransportClient实例,你可以绑定多个节点的地址:

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

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

查询接口

对于之前的那条查询:

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

我们将它转换成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();  

我这里调用prepareSearch来请求SearchSourceBuilder,然后我们可以通过静态帮助方法来设置一个查询,上面这个例子用的是bool查询,这个查询的must节点有个match查询,filter节点有个term查询。

调用execute方法会返回一个future对象,actionGet是个阻塞调用,SearchResponse的结果和我们用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();  

上面的表达式使用不同的method来创建JSON文档,这个文档可以作为IndexRequest的source:

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

除了使用jsonBuilder我们还可以用其他的选项,如下所示:

对于比较简单的数据结构你可以使用Map来构造数据,可以结合序列化插件入jackson来完成对象的序列化。

从前面的例子我们可以看到Transport客户端接收一个或多个节点的地址,你可能注意到这里用的是9300而不是9200端口,因为Transport客户端不使用HTTP来通信,它内部通过传输层协议来通信。

前面只连接了一个节点,一旦这个节点挂了我们就不能访问数据了,如果你需要高可用性你可以启用sniffing选项来允许你的客户端连接集群中的其他节点,只需要把client.transport.sniff选项为true即可。

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);  

更多关于sniffing特性可以参考elasticsearch的官方文档:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html

Jest客户端

jest客户端能够发送请求给ElasticSearch,首先添加依赖:

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

我们可以用过工厂方法来创建一个JestClient:

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

    JestClient client = factory.getObject();  

和普通的Rest客户端一样,Jest不支持生成查询,你可以使用string模板或者复用ElasticSearch builders,builder可以用来创建搜索请求:

String query = jsonStringThatMagicallyAppears;  

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

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

获取查询结果:

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,比如说我用下面这个Java Bean来表示Dish类:

public class Dish {  

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

        @JestId  
        private String id;  

         // ... getters and setters  
    }  

可以自动将查询结果转换成Dish对象:

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

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

当通过HTTP来访问ElasticSearch时Jest是个不错的选择。

Spring Data ElasticSearch

Spring Data项目提供了通用的编程模型来访问不同的数据源,吸引人的特性是Spring Data允许你使用接口来定义查询,比如比较流行的用来访问关系型数据库的Spring Data JPA以及Spring Data MongoDB。

首先添加依赖:

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

使用自定义注解来表示要索引的文档:

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

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

         // more code  

    }  

我们可以定义一个接口来访问文档,这里使用ElasticsearchCrudRepository,它提供通用的索引和查询操作:

public interface DishRepository   
      extends ElasticsearchCrudRepository<Dish, String> {  

    }  

Spring Data ElasticSearch支持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="ezlippi.elasticsearch.springdata" />  

transport-client用于实例化transport客户端,elasticsearchTemplate提供访问ElasticSearch的通用操作,最后repositories元素告诉Spring Data扫描继承自Spring Data接口的接口,Spring会自动为这些接口创建实例。

接下来就可以在Java代码中使用repository来执行索引操作:

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+属性名开头,比如findByFood会查询food属性,结构化查询也是支持的,比如上面的lessThan,上面这个接口会查询比给定价格少的dishes,最后这个查询使用不同的方式,可以使用Query注解来添加查询。

更多关于Spring Data ElasticSearch的知识可以参考Spring的文档:http://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#project

总结

上面介绍了几种常用的ElasticSearch Java客户端,可以针对实际使用场景来选择。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8