Cliente java REST de alto nivel de elasticsearch RestHighLevelClient

0
8579

 

  1. Introducción
  2. Entorno
  3. Inicialización de elasticsearch
  4. Creación del cliente
  5. Creación de índices
  6. Inserción de datos
  7. Búsqueda de datos
  8. Conclusiones
  9. Bibliografía

Introducción

En la versión 6 de elasticsearch se puede leer en sus cambios «This Java High Level REST Client is designed to replace the TransportClient in a near future.» y en los cambios de la versión 7 se puede leer «the transport client will be removed in the future», por lo tanto, es importante empezar a migrar el cliente de transporte al nuevo cliente oficial de elasticsearch y no tener problemas cuando queramos subir la versión de nuestro cluster.

Hay que saber que con el nuevo cliente podemos hacer todas las peticiones disponibles en la API de elasticsearch y, por lo tanto, nos abstrae de todo el tedioso trabajo de hacer peticiones usando un cliente REST java hecho por nosotros.

Entorno

  • Hardware: Portátil MacBook Pro 15′ (2.5 GHz Intel Core i7, 16GB DDR3)
  • Sistema Operativo: MacOS Mojave 10.14.2
  • Docker Desktop 2.0.0.2 para generar un nodo de elasticsearch con la versión 6.5.4
  • Jdk 11.0.1

Inicialización de elasticsearch

Es necesario tener levantado un elasticsearch al que hacer peticiones para probar el cliente, en mi caso, lo levantaré con docker-compose, aunque podremos tenerlo instalado de cualquier otra manera.

Si queremos usar docker-compose, tendremos que tener instalado docker en nuestra máquina y ejecutar el comando:

docker-compose -p=tutorial-client-elasticsearch up

Para que funcione este comando debemos tener un archivo con el nombre docker-compose.yml en el directorio que lo lancemos o usar la opción -f para especificar el nombre del fichero. La opción -p se usa para elegir el nombre del proyecto y así identificar fácilmente las redes, volumenes y contenedores que genera docker-compose.

Nuestro docker-compose.yml tendrá la siguiente forma:

version: '3'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:6.5.4
    environment:
      - cluster.name=tutorial-elasticsearch-cluster
      - node.name=tutorial-elasticsearch-nodo
      - xpack.security.enabled=false
    ports:
      - '9200:9200/tcp'
    container_name: tutorial-client-elasticsearch

Para comprobar que el cluster está levantado podemos lanzar una petición con cualquier navegador o postman para ver las estadísticas del cluster. Teniendo en cuenta que tenemos elasticsearch levantado el localhost exponiendo el puerto 9200.

GET http://localhost:9200/_cluster/stats

La respuesta tendrá que ser del tipo:

{
    "_nodes": {
        "total": 1,
        "successful": 1,
        "failed": 0
    },
    "cluster_name": "tutorial-elasticsearch-cluster",
    "cluster_uuid": "EE4PFmBDRpiEhFZrUZrB6A",
    "timestamp": 1548171423308,
    "status": "green",
    "indices": {
        "count": 0,
        "shards": {},
        "docs": {
            "count": 0,
            "deleted": 0
        },
        "store": {
            "size_in_bytes": 0
        },
        "fielddata": {
            "memory_size_in_bytes": 0,
            "evictions": 0
        },
        "query_cache": {
            "memory_size_in_bytes": 0,
            "total_count": 0,
            "hit_count": 0,
            "miss_count": 0,
            "cache_size": 0,
            "cache_count": 0,
            "evictions": 0
        },
        "completion": {
            "size_in_bytes": 0
        },
        "segments": {
            "count": 0,
            "memory_in_bytes": 0,
            "terms_memory_in_bytes": 0,
            "stored_fields_memory_in_bytes": 0,
            "term_vectors_memory_in_bytes": 0,
            "norms_memory_in_bytes": 0,
            "points_memory_in_bytes": 0,
            "doc_values_memory_in_bytes": 0,
            "index_writer_memory_in_bytes": 0,
            "version_map_memory_in_bytes": 0,
            "fixed_bit_set_memory_in_bytes": 0,
            "max_unsafe_auto_id_timestamp": -9223372036854775808,
            "file_sizes": {}
        }
    },
    "nodes": {
        "count": {
            "total": 1,
            "data": 1,
            "coordinating_only": 0,
            "master": 1,
            "ingest": 1
        },
        "versions": [
            "6.5.4"
        ],
        "os": {
            "available_processors": 8,
            "allocated_processors": 8,
            "names": [
                {
                    "name": "Linux",
                    "count": 1
                }
            ],
            "mem": {
                "total_in_bytes": 16803217408,
                "free_in_bytes": 13421002752,
                "used_in_bytes": 3382214656,
                "free_percent": 80,
                "used_percent": 20
            }
        },
        "process": {
            "cpu": {
                "percent": 1
            },
            "open_file_descriptors": {
                "min": 312,
                "max": 312,
                "avg": 312
            }
        },
        "jvm": {
            "max_uptime_in_millis": 107908,
            "versions": [
                {
                    "version": "11.0.1",
                    "vm_name": "OpenJDK 64-Bit Server VM",
                    "vm_version": "11.0.1+13",
                    "vm_vendor": "Oracle Corporation",
                    "count": 1
                }
            ],
            "mem": {
                "heap_used_in_bytes": 171236144,
                "heap_max_in_bytes": 1037959168
            },
            "threads": 39
        },
        "fs": {
            "total_in_bytes": 94221574144,
            "free_in_bytes": 62299418624,
            "available_in_bytes": 57482735616
        },
        "plugins": [
            {
                "name": "ingest-user-agent",
                "version": "6.5.4",
                "elasticsearch_version": "6.5.4",
                "java_version": "1.8",
                "description": "Ingest processor that extracts information from a user agent",
                "classname": "org.elasticsearch.ingest.useragent.IngestUserAgentPlugin",
                "extended_plugins": [],
                "has_native_controller": false
            },
            {
                "name": "ingest-geoip",
                "version": "6.5.4",
                "elasticsearch_version": "6.5.4",
                "java_version": "1.8",
                "description": "Ingest processor that uses looksup geo data based on ip adresses using the Maxmind geo database",
                "classname": "org.elasticsearch.ingest.geoip.IngestGeoIpPlugin",
                "extended_plugins": [],
                "has_native_controller": false
            }
        ],
        "network_types": {
            "transport_types": {
                "netty4": 1
            },
            "http_types": {
                "netty4": 1
            }
        }
    }
}

Llegado a este punto, empezamos con el objetivo de este tutorial, ver cómo funciona el cliente de elasticsearch.

Creación del cliente

En primer lugar, necesitamos importar la dependencia de maven o gradle al proyecto.

Para maven, necesitamos importar la dependencia en el pom.xml

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.5.4</version>
</dependency>

En caso de gradle, tendremos que añadir nuestra dependencia en el fichero build.gradle

dependencies {
    compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:6.5.4'
}

Una vez importada la dependencia, ya podemos usar el cliente dentro de nuestro proyecto y podremos inicializarlo. Con el cluster que tengo inicializado en docker, podría crearlo con la siguiente línea:

RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")));

De esta forma ya podemos empezar a hacer peticiones con este cliente. Siempre hay que tener en cuenta que el cliente de elasticsearch va a tener una respuesta diferente para cada petición y, por lo tanto, la petición como la respuesta, van a estar definidas por una clase java concreta.

Vamos con nuestro primer código java, consultar la información básica del cluster y obtener una respuesta del tipo «MainResponse».

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http")));
LOGGER.info("Cliente conectado. ");

MainResponse response = client.info(RequestOptions.DEFAULT);
ClusterName clusterName = response.getClusterName();
String clusterUuid = response.getClusterUuid();
String nodeName = response.getNodeName();
Version version = response.getVersion();

LOGGER.info("Información del cluster: ");

LOGGER.info("Nombre del cluster: {}", clusterName.value());
LOGGER.info("Identificador del cluster: {}", clusterUuid);
LOGGER.info("Nombre de los nodos del cluster: {}", nodeName);
LOGGER.info("Versión de elasticsearch del cluster: {}", version.toString());

client.close();
LOGGER.info("Cliente desconectado.");

si ejecutamos el código anterior y vemos el resultado por pantalla, obtendremos:

Cliente conectado. 
Información del cluster: 
Nombre del cluster: tutorial-elasticsearch-cluster
Identificador del cluster: EE4PFmBDRpiEhFZrUZrB6A
Nombre de los nodos del cluster: tutorial-elasticsearch-nodo
Versión de elasticsearch del cluster: 6.5.4
Cliente desconectado.

En esta sección hemos visto cómo crear el cliente de elasticsearch y una petición para consultar la información básica del cluster. A continuación, veremos una serie de ejemplos sencillos que nos puede ilustrar la facilidad de uso de este cliente.

Creación de índices

Para crear un índice, usaremos una petición «CreateIndexRequest» y obtendremos una respuesta del tipo «CreateIndexResponse», he elegido crear un índice con un «mapping» sencillo y sin «settings» para que quede un ejemplo más fácil y claro, pero se puede ver en la documentación de elasticsearch cómo añadir estas opciones a la petición.

Vamos a crear el índice «temas» con el tipo «tema». Este tipo tendrá un solo campo «message» de tipo texto.

Para este ejemplo usaremos el siguiente código:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http")));
LOGGER.info("Cliente conectado. ");

CreateIndexRequest request = new CreateIndexRequest("temas");
request.mapping("tema", "message", "type=text");
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);

boolean acknowledged = createIndexResponse.isAcknowledged();
LOGGER.info("Indice creado: {}", acknowledged);

client.close();
LOGGER.info(" Cliente desconectado.");

Y veremos que por consola se imprime:

Cliente conectado.
WARNING: request [PUT http://localhost:9200/temas?master_timeout=30s&timeout=30s] returned 1 warnings: [299 Elasticsearch-6.5.4-d2ef93d "the default number of shards will change from [5] to [1] in 7.0.0; if you wish to continue using the default of [5] shards, you must manage this on the create index request or with an index template" "xxx, xx xxx xxxx xx:xx:xx GMT"]
Indice creado: true
Cliente desconectado.

Es posible que en este punto veamos un WARNING del cliente, es algo normal, nos avisa de los futuros cambios en nuevas versiones. Una funcionalidad que puede ayudar a los programadores a anticiparse a futuros fallos cuando subamos de versión.

Pero una vez creado el índice, nos interesaría rellenarlo con una serie de datos, para ello usaremos la Bulk API de elasticsearch con la que podemos crear, editar o eliminar documentos de cualquier índice de forma masiva.

Inserción de datos

Aunque se pueden añadir documentos uno a uno a elasticsearch con peticiones PUT sobre un índice usando el cliente, para este ejemplo, usaremos la Bulk API. Usando la Bulk API con una sola petición http podemos añadir miles de documentos a un índice.

En el tutorial añadiremos 8 temas con diferentes mensajes.

Para atacar la Bulk API usaremos el siguiente código:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http")));
LOGGER.info("Cliente conectado. ");

BulkRequest request = new BulkRequest();
request.add(new IndexRequest("temas", "tema", "1").source(XContentType.JSON,"message", "Introducción"));
request.add(new IndexRequest("temas", "tema", "2").source(XContentType.JSON,"message", "Entorno"));
request.add(new IndexRequest("temas", "tema", "3").source(XContentType.JSON,"message", "Inicialización de elasticsearch"));
request.add(new IndexRequest("temas", "tema", "4").source(XContentType.JSON,"message", "Creación del cliente"));
request.add(new IndexRequest("temas", "tema", "5").source(XContentType.JSON,"message", "Creación de índices"));
request.add(new IndexRequest("temas", "tema", "6").source(XContentType.JSON,"message", "Inserción de datos"));
request.add(new IndexRequest("temas", "tema", "7").source(XContentType.JSON,"message", "Búsqueda de datos"));
request.add(new IndexRequest("temas", "tema", "8").source(XContentType.JSON,"message", "Conclusiones"));

BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
LOGGER.info("Bulk con errores: {}", bulkResponse.hasFailures());

client.close();
LOGGER.info("Cliente desconectado.");

el resultado obtenido de esta parte es:

Cliente conectado. 
Bulk con errores: false
Cliente desconectado.

Vemos que el bulk no ha fallado y que todos los documentos deberían estar insertados en nuestro índice «temas». Hay que recordar que aunque la Bulk API de fallos, esto no significa que no se ha insertado ningún documento, con que solo un documento de fallo, la Bulk API contiene errores, pero el resto de documentos se insertarían. En el siguiente punto vamos a ver cómo comprobar esto.

Búsqueda de datos

Como ocurre en elasticsearch cada cosa la podemos hacer de varias maneras, en este tutorial, usaremos la Search API y haremos una query para encontrar todos los documentos del índice «temas», aunque, de forma parecida, usando QueryBuilders del cliente, se pueden realizar todo tipo de búsquedas.

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http")));
LOGGER.info("Cliente conectado.");

SearchRequest searchRequest = new SearchRequest("temas");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

for (SearchHit hit: searchResponse.getHits().getHits()){
    LOGGER.info("Documento con id {}: {}", hit.getId(), hit.getSourceAsString());
}

client.close();
LOGGER.info("Cliente desconectado.");

ejecutando este código anterior veremos por pantalla:

Cliente conectado.
Documento con id 5: {"message":"Creación de índices"}
Documento con id 8: {"message":"Conclusiones"}
Documento con id 2: {"message":"Entorno"}
Documento con id 4: {"message":"Creación del cliente"}
Documento con id 6: {"message":"Inserción de datos"}
Documento con id 1: {"message":"Introducción"}
Documento con id 7: {"message":"Búsqueda de datos"}
Documento con id 3: {"message":"Inicialización de elasticsearch"}
Cliente desconectado.

Con esta llamada hemos podido comprobar que el código de la Bulk API funcionaba como esperábamos y además hemos aprendido una de las formas más sencillas de realizar una petición a elasticsearch.

En realidad, las líneas:

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);

no son necesarias, ya que por defecto haremos una búsqueda que encuentre todos los documentos de un índice, pero con este pequeño ejemplo hemos visto la facilidad de añadir una query a la petición de búsqueda.

Conclusiones

En este tutorial hemos visto la facilidad de usar el cliente de elasticsearch. Con estos ejemplos también vemos que es muy fácil leer el código y entender lo que hace el cliente, pero esto no sucede al desarrollar.

Cuando estamos desarrollando deberíamos apoyarnos constantemente de la documentación oficial del cliente ya que tiene muchísimas clases y cada una sirve para hacer una petición concreta.

Además de esto, hay que estar atentos a los WARNING que saca el cliente de elasticsearch ya que puede hacer que nos anticipemos a los futuros problemas que puedan surgir cuando subamos la versión de elasticsearch. Puede darnos información sobre funcionalidad deprecada y cambios de configuración de futuras versiones.

Por último, hay que tener en cuenta que si estamos usando el cliente Java de Transporte hay que prever que debemos cambiarlo por el cliente REST de alto nivel porque éste lo sustituirá y en un futuro no podremos usar el cliente de Transporte para comunicarnos con futuras versiones de elasticsearch.

Bibliografía

Cliente Java High REST Client versión 6.5.4

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad