Elasticsearch è un motore di ricerca con json rest api, che utilizza Lucene e scritto in Java. Una descrizione di tutti i vantaggi di questo motore è disponibile su . Nel seguito faremo riferimento ad Elasticsearch come ES.
Motori simili vengono utilizzati per ricerche complesse in un database di documenti. Ad esempio, la ricerca tenendo conto della morfologia della lingua o la ricerca per coordinate geografiche.
In questo articolo parlerò delle basi di ES utilizzando l'esempio dell'indicizzazione dei post del blog. Ti mostrerò come filtrare, ordinare e cercare documenti.
Per non dipendere dal sistema operativo, farò tutte le richieste a ES utilizzando CURL. Esiste anche un plugin per Google Chrome chiamato .
Il testo contiene collegamenti a documentazione e altre fonti. Alla fine sono presenti i link per un rapido accesso alla documentazione. Le definizioni dei termini non familiari si trovano in .
Installazione dell'ES
Per fare ciò, abbiamo prima bisogno di Java. Sviluppatori installare le versioni Java più recenti di Java 8 aggiornamento 20 o Java 7 aggiornamento 55.
La distribuzione ES è disponibile su . Dopo aver decompresso l'archivio è necessario eseguire bin/elasticsearch. Anche disponibile . C'è . .
Dopo l'installazione e l'avvio, controlliamo la funzionalità:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLRiceveremo qualcosa del genere:
{
"name" : "Heimdall",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.2.1",
"build_hash" : "d045fc29d1932bce18b2e65ab8b297fbf6cd41a1",
"build_timestamp" : "2016-03-09T09:38:54Z",
"build_snapshot" : false,
"lucene_version" : "5.4.1"
},
"tagline" : "You Know, for Search"
}indicizzazione
Aggiungiamo un post a ES:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Смешная история про котят<p>",
"tags": [
"котята",
"смешная история"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
risposta del server:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES creato automaticamente blog e inviare. Possiamo tracciare un'analogia condizionale: un indice è un database e un tipo è una tabella in questo database. Ogni tipo ha il proprio schema − , proprio come una tabella relazionale. La mappatura viene generata automaticamente quando il documento viene indicizzato:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"Nella risposta del server ho aggiunto nei commenti i valori dei campi del documento indicizzato:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Смешная история про котят<p>", */
"content" : {
"type" : "string"
},
/* "published_at": "2014-09-12T20:44:42+00:00" */
"published_at" : {
"type" : "date",
"format" : "strict_date_optional_time||epoch_millis"
},
/* "tags": ["котята", "смешная история"] */
"tags" : {
"type" : "string"
},
/* "title": "Веселые котята" */
"title" : {
"type" : "string"
}
}
}
}
}
}Vale la pena notare che ES non distingue tra un singolo valore e un array di valori. Ad esempio, il campo title contiene semplicemente un titolo e il campo tags contiene un array di stringhe, sebbene siano rappresentate allo stesso modo nella mappatura.
Parleremo più approfonditamente della mappatura in seguito.
richieste
Recupero di un documento tramite il suo ID:
# извлечем документ с id 1 типа post из индекса blog
curl -XGET "$ES_URL/blog/post/1?pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
}Nella risposta sono apparse nuove chiavi: _version и _source. In generale, tutte le chiavi iniziano con _ sono classificati come ufficiali.
Chiave _version mostra la versione del documento. È necessario affinché il meccanismo di blocco ottimistico funzioni. Ad esempio, vogliamo modificare un documento che ha la versione 1. Inviamo il documento modificato e indichiamo che si tratta di una modifica di un documento con la versione 1. Se anche qualcun altro ha modificato un documento con la versione 1 e ha inviato le modifiche prima di noi, allora ES non accetterà le nostre modifiche, perché memorizza il documento con la versione 2.
Chiave _source contiene il documento che abbiamo indicizzato. ES non utilizza questo valore per le operazioni di ricerca perché Gli indici vengono utilizzati per la ricerca. Per risparmiare spazio, ES memorizza un documento sorgente compresso. Se abbiamo bisogno solo dell'ID e non dell'intero documento sorgente, possiamo disabilitare l'archiviazione del sorgente.
Se non abbiamo bisogno di informazioni aggiuntive, possiamo ottenere solo il contenuto di _source:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
Puoi anche selezionare solo alcuni campi:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}Indicizziamo qualche altro post ed eseguiamo query più complesse.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Веселые щенки",
"content": "<p>Смешная история про щенков<p>",
"tags": [
"щенки",
"смешная история"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Как у меня появился котенок",
"content": "<p>Душераздирающая история про бедного котенка с улицы<p>",
"tags": [
"котята"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Ordinamento
# найдем последний пост по дате публикации и извлечем поля title и published_at
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"size": 1,
"_source": ["title", "published_at"],
"sort": [{"published_at": "desc"}]
}'{
"took" : 8,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : null,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : null,
"_source" : {
"title" : "Веселые котята",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}Abbiamo scelto l'ultimo post. size limita il numero di documenti da rilasciare. total mostra il numero totale di documenti corrispondenti alla richiesta. sort nell'output contiene un array di numeri interi in base al quale viene eseguito l'ordinamento. Quelli. la data è stata convertita in un numero intero. Maggiori informazioni sull'ordinamento possono essere trovate in .
Filtri e query
ES a partire dalla versione 2 non distingue invece tra filtri e query .
Un contesto di query differisce da un contesto di filtro in quanto la query genera un _score e non viene memorizzata nella cache. Ti mostrerò cos'è _score più tardi.
Filtra per data
Usiamo la richiesta nel contesto del filtro:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtra per tag
Noi usiamo per cercare gli ID dei documenti contenenti una determinata parola:
# найдем все документы, в поле tags которых есть элемент 'котята'
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": [
"title",
"tags"
],
"filter": {
"term": {
"tags": "котята"
}
}
}'{
"took" : 9,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 1.0,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"title" : "Веселые котята",
"tags" : [ "котята", "смешная история" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Как у меня появился котенок",
"tags" : [ "котята" ]
}
} ]
}
}Ricerca a testo integrale
Tre dei nostri documenti contengono quanto segue nel campo del contenuto:
<p>Смешная история про котят<p><p>Смешная история про щенков<p><p>Душераздирающая история про бедного котенка с улицы<p>
Noi usiamo per cercare gli ID dei documenti contenenti una determinata parola:
# source: false означает, что не нужно извлекать _source найденных документов
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"_source": false,
"query": {
"match": {
"content": "история"
}
}
}'{
"took" : 13,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.11506981,
"hits" : [ {
"_index" : "blog",
"_type" : "post",
"_id" : "2",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_score" : 0.11506981
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 0.095891505
} ]
}
}Se però cerchiamo “storie” nel campo del contenuto, non troveremo nulla, perché L'indice contiene solo le parole originali, non le loro radici. Per effettuare una ricerca di alta qualità, è necessario configurare l'analizzatore.
Campo _score spettacoli . Se la richiesta viene eseguita in un contesto di filtro, il valore _score sarà sempre uguale a 1, il che significa una corrispondenza completa con il filtro.
Gli analizzatori
sono necessari per convertire il testo sorgente in un insieme di token.
Gli analizzatori sono costituiti da uno e diversi facoltativi . Tokenizer può essere preceduto da diversi . I tokenizzatori suddividono la stringa di origine in token, ad esempio spazi e caratteri di punteggiatura. TokenFilter può modificare token, eliminarne o aggiungerne di nuovi, ad esempio lasciare solo la radice della parola, rimuovere preposizioni, aggiungere sinonimi. CharFilter: modifica l'intera stringa sorgente, ad esempio, elimina i tag html.
ES ne ha diversi . Ad esempio, un analizzatore .
Usiamo e vediamo come gli analizzatori standard e russi trasformano la stringa “Storie divertenti sui gattini”:
# используем анализатор standard
# обязательно нужно перекодировать не ASCII символы
curl -XGET "$ES_URL/_analyze?pretty&analyzer=standard&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"{
"tokens" : [ {
"token" : "веселые",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истории",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "про",
"start_offset" : 16,
"end_offset" : 19,
"type" : "<ALPHANUM>",
"position" : 2
}, {
"token" : "котят",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}# используем анализатор russian
curl -XGET "$ES_URL/_analyze?pretty&analyzer=russian&text=%D0%92%D0%B5%D1%81%D0%B5%D0%BB%D1%8B%D0%B5%20%D0%B8%D1%81%D1%82%D0%BE%D1%80%D0%B8%D0%B8%20%D0%BF%D1%80%D0%BE%20%D0%BA%D0%BE%D1%82%D1%8F%D1%82"{
"tokens" : [ {
"token" : "весел",
"start_offset" : 0,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 0
}, {
"token" : "истор",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 1
}, {
"token" : "кот",
"start_offset" : 20,
"end_offset" : 25,
"type" : "<ALPHANUM>",
"position" : 3
} ]
}L'analizzatore standard ha diviso la stringa per spazi e ha convertito tutto in minuscolo, l'analizzatore russo ha rimosso le parole non importanti, le ha convertite in minuscolo e ha lasciato la radice delle parole.
Vediamo quali Tokenizer, TokenFilters, CharFilters utilizza l'analizzatore russo:
{
"filter": {
"russian_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"russian_keywords": {
"type": "keyword_marker",
"keywords": []
},
"russian_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"russian": {
"tokenizer": "standard",
/* TokenFilters */
"filter": [
"lowercase",
"russian_stop",
"russian_keywords",
"russian_stemmer"
]
/* CharFilters отсутствуют */
}
}
}Descriviamo il nostro analizzatore basato sul russo, che taglierà i tag html. Chiamiamolo predefinito, perché per impostazione predefinita verrà utilizzato un analizzatore con questo nome.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
/* добавляем удаление html тегов */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}Innanzitutto, tutti i tag HTML verranno rimossi dalla stringa sorgente, quindi lo standard tokenizzatore la dividerà in token, i token risultanti verranno spostati in minuscolo, le parole insignificanti verranno rimosse e i token rimanenti rimarranno la radice della parola.
Creazione di un indice
Sopra abbiamo descritto l'analizzatore predefinito. Si applicherà a tutti i campi stringa. Il nostro post contiene una serie di tag, quindi anche i tag verranno elaborati dall'analizzatore. Perché Cerchiamo i post in base alla corrispondenza esatta con un tag, quindi dobbiamo disabilitare l'analisi per il campo dei tag.
Creiamo un indice blog2 con analizzatore e mappatura, in cui l'analisi del campo tag è disabilitata:
curl -XPOST "$ES_URL/blog2" -d'
{
"settings": {
"analysis": {
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_russian_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "russian"
}
},
"analyzer": {
"default": {
"char_filter": [
"html_strip"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}
},
"mappings": {
"post": {
"properties": {
"content": {
"type": "string"
},
"published_at": {
"type": "date"
},
"tags": {
"type": "string",
"index": "not_analyzed"
},
"title": {
"type": "string"
}
}
}
}
}'Aggiungiamo gli stessi 3 post a questo indice (blog2). Ometterò questo processo perché... è simile all'aggiunta di documenti all'indice del blog.
Ricerca di testo completo con supporto delle espressioni
Diamo un'occhiata ad un altro tipo di richiesta:
# найдем документы, в которых встречается слово 'истории'
# query -> simple_query_string -> query содержит поисковый запрос
# поле title имеет приоритет 3
# поле tags имеет приоритет 2
# поле content имеет приоритет 1
# приоритет используется при ранжировании результатов
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "истории",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'Perché Stiamo utilizzando un analizzatore con radice russa, quindi questa richiesta restituirà tutti i documenti, sebbene contengano solo la parola "storia".
La richiesta può contenere caratteri speciali, ad esempio:
""fried eggs" +(eggplant | potato) -frittata"Richiedi sintassi:
+ signifies AND operation
| signifies OR operation
- negates a single token
" wraps a number of tokens to signify a phrase for searching
* at the end of a term signifies a prefix query
( and ) signify precedence
~N after a word signifies edit distance (fuzziness)
~N after a phrase signifies slop amount# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковriferimenti
PS
Se siete interessati a tali articoli-lezioni, avete idee per nuovi articoli o proposte di collaborazione, sarò lieto di ricevere un messaggio personale o via e-mail all'indirizzo m.kuzmin+habr@darkleaf.ru.
Fonte: habr.com
