Az Elasticsearch egy JSON REST API-val rendelkező keresőmotor, amely Lucene-t használ és Java nyelven íródott. A motor összes előnyének leírása itt érhető el: Mostantól az Elasticsearch-et ES-ként fogjuk emlegetni.
Az ilyen motorokat összetett dokumentum-adatbázis-keresésekhez használják, például nyelvi morfológia vagy földrajzi koordináták szerinti kereséshez.
Ebben a cikkben az ES alapjait fogom ismertetni a blogbejegyzések indexelésének példáján keresztül. Megmutatom, hogyan szűrheted, rendezheted és keresheted a dokumentumokat.
Az operációs rendszertől való függetlenség érdekében az összes ES-kérésemet CURL-lel fogom intézni. Van egy Google Chrome-hoz készült bővítmény is, melynek neve .
A szövegben számos hivatkozás található a dokumentációra és más forrásokra. A végén gyors elérésű linkek találhatók a dokumentációhoz. Az ismeretlen kifejezések definíciói a következő helyen találhatók: .
ES telepítése
Ehhez először Java-ra van szükségünk. Fejlesztők Telepítsen a Java 8 20-as vagy a Java 7 55-ös frissítésénél újabb Java verziókat.
Az ES disztribúció elérhető a következő címen: Az archívum kicsomagolása után futtatni kell a bin/elasticsearchSzintén elérhető . Van . .
A telepítés és indítás után ellenőrizzük a funkcionalitást:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLEgy ilyen kinézetű választ fogunk kapni:
{
"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"
}indexelés
Tegyünk fel egy bejegyzést az ES-re:
# Добавим документ 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"
}'
szerver válasza:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
ES automatikusan létrehozva blog és bejegyzés. Egy durva analógia vonható le: az index egy adatbázis, a típus pedig egy tábla az adatbázison belül. Minden típusnak megvan a saját sémája— , akárcsak egy relációs tábla. A megfeleltetés automatikusan generálódik egy dokumentum indexelésekor:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"A szerver válaszában hozzáadtam az indexelt dokumentum mezőértékeit a megjegyzésekhez:
{
"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"
}
}
}
}
}
}Érdemes megjegyezni, hogy az ES nem tesz különbséget egyetlen érték és értéktömb között. Például a title mező csak egy title-t tartalmaz, míg a tags mező karakterláncok tömbjét, annak ellenére, hogy a leképezésben azonosan jelennek meg.
A térképezésről később részletesebben is beszélünk.
kérelmek
Dokumentum kinyerése azonosító alapján:
# извлечем документ с 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"
}
}Új kulcsok jelentek meg a válaszban: _version и _sourceÁltalánosságban elmondható, hogy minden kulcs, amely ezzel kezdődik: _ szolgáltatási cikkekként vannak besorolva.
kulcs _version Megjeleníti a dokumentum verzióját. Ez szükséges ahhoz, hogy az optimista zárolási mechanizmus működjön. Például módosítani szeretnénk egy 1-es verziójú dokumentumot. Beküldjük a módosított dokumentumot, és jelezzük, hogy ez az 1-es verziójú dokumentum revíziója. Ha valaki más is szerkesztette az 1-es verziójú dokumentumot, és beküldte a módosításokat előttünk, az ES nem fogadja el a módosításainkat, mivel a 2-es verziójú dokumentumot tárolja.
kulcs _source Tartalmazza az indexelt dokumentumot. Az ES ezt az értéket nem használja keresési műveletekhez, mivel az indexeket keresésre használják. Helytakarékosság érdekében az ES az eredeti dokumentum tömörített verzióját tárolja. Ha csak az azonosítóra van szükségünk, és nem a teljes eredeti dokumentumra, akkor letilthatjuk az eredeti tárolását.
Ha nincs szükségünk további információkra, akkor csak a _source tartalmát kaphatjuk meg:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title" : "Веселые котята",
"content" : "<p>Смешная история про котят<p>",
"tags" : [ "котята", "смешная история" ],
"published_at" : "2014-09-12T20:44:42+00:00"
}
Csak bizonyos mezőket is kiválaszthat:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Веселые котята"
}
}Indexeljünk még néhány bejegyzést, és futtassunk le összetettebb lekérdezéseket.
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"
}'osztályozás
# найдем последний пост по дате публикации и извлечем поля 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 ]
} ]
}
}Az utolsó bejegyzést választottuk. size korlátozza a kiállított dokumentumok számát. total a lekérdezésnek megfelelő dokumentumok teljes számát mutatja. sort A kimenet egy egész számokból álló tömböt tartalmaz, amelyek alapján a rendezés történik. Vagyis a dátum egész számmá lett konvertálva. A rendezésről bővebben itt olvashat. .
Szűrők és lekérdezések
Az ES a 2. verzió óta nem tesz különbséget a szűrők és a lekérdezések között, ehelyett .
A lekérdezési kontextus abban különbözik a szűrő kontextusától, hogy a lekérdezés egy _score értéket generál, és nem kerül gyorsítótárba. Később elmagyarázom, hogy mi is az a _score.
Szűrés dátum szerint
A lekérdezés használata a szűrő kontextusában:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Szűrés címkék szerint
Használjuk Egy adott szót tartalmazó dokumentumazonosítók keresése:
# найдем все документы, в поле 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" : [ "котята" ]
}
} ]
}
}Teljes szöveges keresés
A három dokumentumunk tartalommezője a következőket tartalmazza:
<p>Смешная история про котят<p><p>Смешная история про щенков<p><p>Душераздирающая история про бедного котенка с улицы<p>
Használjuk Egy adott szót tartalmazó dokumentumazonosítók keresése:
# 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
} ]
}
}Ha azonban a tartalom mezőben a „történetek” kifejezésre keresünk, akkor semmit sem fogunk találni, mivel az index csak az eredeti szavakat tartalmazza, a szótövüket nem. A minőségi keresés végrehajtásához konfigurálnunk kell az analizátort.
Mező _score mutatja Ha a lekérdezés szűrőkörnyezetben fut, a _score értéke mindig 1 lesz, ami azt jelenti, hogy a szűrő teljes mértékben egyezik.
Analizátorok
szükségesek ahhoz, hogy a forrásszöveget tokenek halmazává alakítsuk.
Az analizátorok egyből állnak és számos opcionális A Tokenizer több előzhet meg A tokenizerek egy forráskarakterláncot tokenekre bontanak, például szóközök és írásjelek segítségével. A TokenFilterek módosíthatják a tokeneket, eltávolíthatják azokat, vagy újakat adhatnak hozzá, például csak a szótő meghagyásával, elöljárószavak eltávolításával vagy szinonimák hozzáadásával. A CharFilterek teljesen módosítják a forráskarakterláncot, például HTML-címkék eltávolításával.
Több is van ES-ben Például egy analizátor .
Használjuk ki és nézzük meg, hogyan alakítják át a standard és az orosz elemzők a "Vicces történetek kiscicákról" karakterláncot:
# используем анализатор 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
} ]
}A standard analizátor a szóközöknél kettévágta a karakterláncot, és mindent kisbetűsre alakított, az orosz analizátor eltávolította a lényegtelen szavakat, kisbetűsre alakította őket, és meghagyta a szótöveket.
Lássuk, hogy az orosz analizátor mely Tokenizer, TokenFilterek és CharFilterek közül választ:
{
"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 отсутствуют */
}
}
}Írjuk le a saját orosz alapú analizátorunkat, amely HTML-címkéket távolít el. Nevezzük alapértelmezettnek, mivel ez lesz az alapértelmezett analizátor.
{
"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"
]
}
}
}Először az összes HTML-címkét eltávolítjuk a forrásszövegből, majd a tokenizer szabvány tokenekre osztja azt, a kapott tokeneket kisbetűssé alakítjuk, a jelentéktelen szavakat eltávolítjuk, és a fennmaradó tokenek a szó gyökerét alkotják.
Index létrehozása
Fentebb leírtuk az alapértelmezett analizátort. Ez minden karakterláncmezőre érvényes lesz. A bejegyzésünk címkék tömbjét tartalmazza, így a címkéket is feldolgozza az analizátor. Mivel olyan bejegyzéseket keresünk, amelyek pontosan egyeznek a címkével, le kell tiltanunk a "címkék" mező elemzését.
Hozzunk létre egy blog2 indexet egy analizátorral és leképezéssel, amelyben a tags mező elemzése le van tiltva:
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"
}
}
}
}
}'Adjuk hozzá ugyanezt a három bejegyzést ehhez az indexhez (blog2). Ezt a folyamatot kihagyom, mivel hasonló a dokumentumok blogindexhez való hozzáadásához.
Teljes szöveges keresés kifejezéstámogatással
Ismerkedjünk meg egy másik típusú lekérdezéssel:
# найдем документы, в которых встречается слово 'истории'
# 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"
]
}
}
}'Mivel egy orosz eredetű szóelemzőt használunk, ez a lekérdezés az összes dokumentumot visszaadja, még akkor is, ha azok csak a „history” szót tartalmazzák.
A lekérdezés tartalmazhat speciális karaktereket, például:
""fried eggs" +(eggplant | potato) -frittata"Lekérdezés szintaxisa:
+ 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 поста про котиковreferenciák
PS
Ha érdekelnek hasonló oktatóanyag cikkek, vannak ötleteid új cikkekhez, vagy bármilyen együttműködési javaslatod, szívesen fogadom válaszod privát üzenetben vagy e-mailben az m.kuzmin+habr@darkleaf.ru címen.
Forrás: will.com
