Elasticsearch ist eine Suchmaschine mit JSON-REST-API, die Lucene verwendet und in Java geschrieben ist. Eine Beschreibung aller Vorteile dieses Motors finden Sie unter . Von nun an werden wir Elasticsearch als ES bezeichnen.
Solche Engines werden für komplexe Suchvorgänge in einer Dokumentdatenbank verwendet. Beispielsweise die Suche unter Berücksichtigung der Morphologie der Sprache oder die Suche nach Geokoordinaten.
In diesem Artikel werde ich die Grundlagen von ES am Beispiel der Indizierung von Blogbeiträgen behandeln. Ich zeige Ihnen, wie Sie Dokumente filtern, sortieren und suchen.
Um vom Betriebssystem unabhängig zu sein, werde ich alle Anfragen an ES mittels CURL stellen. Es gibt auch ein Plugin für Google Chrome namens .
Im gesamten Text finden Sie Links zu Dokumentationen und anderen Quellen. Am Ende befinden sich Links für den schnellen Zugriff auf die Dokumentation. Definitionen unbekannter Begriffe finden Sie in .
ES installieren
Dazu benötigen wir zunächst Java. Entwickler Installieren Sie Java-Versionen, die neuer sind als Java 8 Update 20 oder Java 7 Update 55.
Die ES-Verteilung ist verfügbar unter . Nach dem Entpacken des Archivs müssen Sie ausführen bin/elasticsearch. Auch verfügbar . Gibt es . .
Lassen Sie uns nach der Installation und dem Start die Funktionalität überprüfen:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLWir erhalten eine Antwort, die ungefähr so aussieht:
{
"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"
}Indizierung
Fügen wir einen Beitrag zu ES hinzu:
# Добавим документ 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"
}'
Serverantwort:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES automatisch erstellt Blog und Post. Wir können eine bedingte Analogie ziehen: Der Index ist eine Datenbank und der Typ ist eine Tabelle in dieser Datenbank. Jeder Typ hat sein eigenes Schema - , genau wie eine relationale Tabelle. Die Zuordnung wird automatisch generiert, wenn das Dokument indiziert wird:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"In der Serverantwort habe ich in den Kommentaren die Werte der indizierten Dokumentfelder hinzugefügt:
{
"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"
}
}
}
}
}
}Es ist erwähnenswert, dass ES nicht zwischen einem einzelnen Wert und einem Wertearray unterscheidet. Beispielsweise enthält das Titelfeld nur einen Titel und das Tag-Feld ein Array von Zeichenfolgen, obwohl sie in der Zuordnung auf die gleiche Weise dargestellt werden.
Wir werden später ausführlicher über die Zuordnung sprechen.
Anfragen
Extrahieren eines Dokuments anhand seiner 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"
}
}In der Antwort sind neue Schlüssel aufgetaucht: _version и _source. Im Allgemeinen alle Schlüssel, die mit beginnen _ werden als Serviceartikel klassifiziert.
Schlüssel _version zeigt die Dokumentversion an. Es ist erforderlich, damit der optimistische Sperrmechanismus funktioniert. Beispiel: Wir möchten ein Dokument mit der Version 1 ändern. Wir senden das geänderte Dokument und geben an, dass es sich um eine Bearbeitung eines Dokuments mit der Version 1 handelt. Wenn jemand anderes das Dokument mit der Version 1 ebenfalls bearbeitet und die Änderungen vor uns gesendet hat, akzeptiert ES unsere Änderungen nicht, da es das Dokument mit der Version 2 speichert.
Schlüssel _source enthält das von uns indizierte Dokument. ES verwendet diesen Wert nicht für Suchvorgänge, da für die Suche Indizes verwendet werden. Um Platz zu sparen, speichert ES das Originaldokument komprimiert. Wenn wir nur die ID und nicht das gesamte Quelldokument benötigen, können wir die Speicherung der Quelle deaktivieren.
Wenn wir keine zusätzlichen Informationen benötigen, können wir nur den Inhalt von _source abrufen:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
Sie können auch nur bestimmte Felder auswählen:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}Lassen Sie uns noch ein paar weitere Beiträge indizieren und komplexere Abfragen ausführen.
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"
}'Sortierung
# найдем последний пост по дате публикации и извлечем поля 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 ]
} ]
}
}Wir haben den letzten Beitrag ausgewählt. size begrenzt die Anzahl der ausgestellten Dokumente. total zeigt die Gesamtzahl der Dokumente an, die der Abfrage entsprechen. sort Die Ausgabe enthält ein Array von Ganzzahlen, nach denen die Sortierung erfolgt. Diese. Datum in Ganzzahl konvertiert. Mehr zum Thema Sortieren erfahren Sie in .
Filter und Abfragen
ES unterscheidet seit Version 2 nicht mehr zwischen Filtern und Abfragen, sondern .
Der Abfragekontext unterscheidet sich vom Filterkontext darin, dass die Abfrage einen _score generiert und nicht zwischengespeichert wird. Ich werde Ihnen später zeigen, was _score ist.
Filtern nach Datum
Wir verwenden die Abfrage im Kontext des Filters:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Filtern nach Tags
Wir gebrauchen So suchen Sie nach Dokument-IDs, die ein bestimmtes Wort enthalten:
# найдем все документы, в поле 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" : [ "котята" ]
}
} ]
}
}Volltextsuche
Unsere drei Dokumente enthalten im Inhaltsfeld Folgendes:
<p>Смешная история про котят<p><p>Смешная история про щенков<p><p>Душераздирающая история про бедного котенка с улицы<p>
Wir gebrauchen So suchen Sie nach Dokument-IDs, die ein bestimmtes Wort enthalten:
# 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
} ]
}
}Wenn wir jedoch im Inhaltsfeld nach „Geschichten“ suchen, werden wir nichts finden, da der Index nur Originalwörter enthält, nicht deren Wurzeln. Um eine qualitativ hochwertige Suche durchzuführen, müssen Sie den Analysator konfigurieren.
Feld _score zeigt . Wenn die Abfrage in einem Filterkontext ausgeführt wird, ist der _score-Wert immer 1, was bedeutet, dass der Filter vollständig übereinstimmt.
Analysatoren
werden benötigt, um den Quelltext in einen Satz von Token umzuwandeln.
Die Analysatoren bestehen aus einem und mehrere optionale . Dem Tokenizer können mehrere vorangestellt werden . Tokenizer zerlegen eine Quellzeichenfolge in Token, beispielsweise durch Leerzeichen und Satzzeichen. TokenFilter kann Token ändern, entfernen oder neue hinzufügen, zum Beispiel nur den Wortstamm belassen, Präpositionen entfernen, Synonyme hinzufügen. CharFilter – ändert die ursprüngliche Zeichenfolge vollständig, schneidet beispielsweise HTML-Tags aus.
Es gibt mehrere in ES . Beispielsweise ein Analysator .
Lassen Sie uns verwenden und sehen wir uns an, wie die Standard- und russischen Analysatoren die Zeichenfolge „Lustige Geschichten über Kätzchen“ umwandeln:
# используем анализатор 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
} ]
}Der Standardanalysator teilte die Zeile durch Leerzeichen und konvertierte alles in Kleinbuchstaben, der russische Analysator entfernte unwichtige Wörter, konvertierte alles in Kleinbuchstaben und ließ die Wortstämme stehen.
Sehen wir uns an, welche Tokenizer, TokenFilter und CharFilter der russische Analysator verwendet:
{
"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 отсутствуют */
}
}
}Beschreiben wir unseren auf Russisch basierenden Analysator, der HTML-Tags ausschneidet. Nennen wir es „Standard“, da standardmäßig ein Analysator mit diesem Namen verwendet wird.
{
"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"
]
}
}
}Zuerst werden alle HTML-Tags aus der Originalzeichenfolge entfernt, dann wird sie nach dem Tokenizer-Standard in Token aufgeteilt, die resultierenden Token werden in Kleinbuchstaben umgewandelt, unbedeutende Wörter werden entfernt und die verbleibenden Token bilden den Wortstamm.
Erstellen eines Indexes
Oben haben wir den Standardanalysator beschrieben. Es gilt für alle Zeichenfolgenfelder. Unser Beitrag enthält eine Reihe von Tags, daher werden die Tags auch vom Analysator verarbeitet. Da wir nach Beiträgen suchen, die genau einem Tag entsprechen, müssen wir die Analyse für das Tag-Feld deaktivieren.
Erstellen wir einen Blog2-Index mit einem Analysator und einer Zuordnung, in dem die Analyse des Tag-Felds deaktiviert ist:
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"
}
}
}
}
}'Fügen wir diesem Index (Blog3) dieselben 2 Beiträge hinzu. Ich werde diesen Vorgang überspringen, da er dem Hinzufügen von Dokumenten zum Blog-Index ähnelt.
Volltextsuche mit Ausdrucksunterstützung
Machen wir uns mit einer anderen Art von Anfragen vertraut:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Da wir einen Analysator mit russischer Wortstammerkennung verwenden, gibt diese Abfrage alle Dokumente zurück, obwohl sie nur das Wort „History“ enthalten.
Die Anfrage kann Sonderzeichen enthalten, zum Beispiel:
""fried eggs" +(eggplant | potato) -frittata"Abfragesyntax:
+ 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 поста про котиковReferenzen
PS
Wenn Sie an solchen Artikel-Lektionen interessiert sind, Ideen für neue Artikel haben oder Vorschläge für eine Zusammenarbeit haben, freue ich mich über eine Nachricht in einer persönlichen Nachricht oder per E-Mail an m.kuzmin+habr@darkleaf.ru.
Source: habr.com
