Elasticsearch — egy JSON REST API-val rendelkező keresőmotor, amely a Lucene-re épül és Java nyelven íródott. Ennek a motorának minden előnyét a szövegben Elasticsearch-ot ES-nek fogjuk nevezni.
Hasonló motorokat használnak összetett keresések végrehajtására dokumentum adatbázisokban. Például nyelvi morfológia figyelembevételével vagy geo koordináták alapján történő kereséshez.
Ebben a cikkben az ES alapjairól fogok beszélni egy blogbejegyzések indexelésének példáján keresztül. Megmutatom, hogyan lehet szűrni, rendezni és keresni a dokumentumokat.
Annak érdekében, hogy ne függjek az operációs rendszertől, minden kérést az ES-hez CURL segítségével fogok végrehajtani. Emellett elérhető egy Google Chrome bővítmény, amelynek neve .
. A szövegben találhatók hivatkozások a dokumentációra és más forrásokra. A cikk végén gyorslinkek találhatók a dokumentáció eléréséhez. Az ismeretlen kifejezések definícióit a .
Az ES telepítése
Ehhez először is szükségünk lesz Java-ra. A fejlesztők ajánlják, hogy telepített Java verziója újabb legyen, mint a Java 8 update 20 vagy a Java 7 update 55.
Az ES disztribúciója elérhető a . A csomag kicsomagolása után indítsuk el a bin/elasticsearchfájlt. Emellett elérhetők . Létezik . .
A telepítés és indítás után ellenőrizzük a működést:
# для удобства запомним адрес в переменную
#export ES_URL=$(docker-machine ip dev):9200
export ES_URL=localhost:9200
curl -X GET $ES_URLHasonló válasz érkezik majd:
{
"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
Adjunk hozzá egy bejegyzést az ES-hez:
# Добавим документ c id 1 типа post в индекс blog.
# ?pretty указывает, что вывод должен быть человеко-читаемым.
curl -XPUT "$ES_URL/blog/post/1?pretty" -d'
{
"title": "Веселые котята",
"content": "<p>Vicces történet a kiscicákról<p>",
"tags": [
"kiscicák",
"vicces történet"
],
"published_at": "2014-09-12T20:44:42+00:00"
}'
a szerver válasza:
{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : false
}
Az ES automatikusan létrehozta a blog-ot és post-ot. Ezeket a fogalmakat egy hasonlat alapján is érthetjük: az index egy adatbázis, míg a típus egy tábla ebben az adatbázisban. Minden típusnak saját sémája van — , akárcsak egy relációs táblának. A mapping automatikusan generálódik a dokumentum indexelésekor:
# Получим mapping всех типов индекса blog
curl -XGET "$ES_URL/blog/_mapping?pretty"A szerver válaszában megjegyzésben hozzáadtam a megindexelt dokumentum mezőinek értékeit:
{
"blog" : {
"mappings" : {
"post" : {
"properties" : {
/* "content": "<p>Vicces történet a kiscicákról<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": ["kiscicák", "vicces történet"] */
"tags" : {
"type" : "string"
},
/* "title": "Vidám kiscicák" */
"title" : {
"type" : "string"
}
}
}
}
}
}Érdemes megjegyezni, hogy az ES nem különbözteti meg az egyedi értéket és az értékek tömbjét. Például a title mező egyszerűen a címet tartalmazza, míg a tags mező sztringek tömbje, bár a mappingben ugyanolyan módon van bemutatva.
Később a mappingről részletesebben fogunk beszélni.
Kérések
Dokumentum lekérése az id-ja 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": "\u0412\u0435\u0441\u0435\u043b\u044b\u0435 \u043a\u043e\u0442\u044f\u0442\u0430",
"content": "<p>Vicces t\u00f6rt\u00e9net a kiscic\u00e1kr\u00f3l<p>",
"tags": [
"\u043a\u043e\u0442\u044f\u0442\u0430",
"\u0441\u043c\u0435\u0448\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f"
],
"published_at": "2014-09-12T20:44:42+00:00"
}
}A válaszban új kulcsok jelentek meg: _version és _source. Általánosságban elmondható, hogy minden kulcs, amely a _ elemmel kezdődik, rendszerkulcsokhoz tartozik.
Kulcs _version megmutatja a dokumentum verzióját. Ez szükséges az optimista zárak mechanizmusának működéséhez. Például, ha módosítani szeretnénk egy dokumentumot, amelynek verziója 1, akkor elküldjük a módosított dokumentumot, és jelezzük, hogy ez a dokumentum 1-es verziójának módosítása. Ha valaki más is szerkesztette az 1-es verziójú dokumentumot, és korábban küldte el a változtatásokat, akkor az ES nem fogadja el a mi módosításainkat, mivel ő már a 2-es verziójú dokumentumot tárolja.
Kulcs _source tartalmazza azt a dokumentumot, amelyet indexeltünk. Az ES nem használja ezt az értéket keresési műveletekhez, mivel a kereséshez indexek alkalmazandók. A helytakarékosság érdekében az ES a kiindulási dokumentumot tömörítve tárolja. Ha csak az azonosítóra van szükség, nem pedig az egész kiindulási dokumentumra, akkor le lehet tiltani a kiindulási dokumentum tárolását.
Ha nincs szükségünk kiegészítő információkra, akkor csak a _source tartalmát kaphatjuk meg:
curl -XGET "$ES_URL/blog/post/1/_source?pretty"{
"title": "\u0412\u0435\u0441\u0435\u043b\u044b\u0435 \u043a\u043e\u0442\u044f\u0442\u0430",
"content": "<p>Vicces t\u00f6rt\u00e9net a kiscic\u00e1kr\u00f3l<p>",
"tags": [
"\u043a\u043e\u0442\u044f\u0442\u0430",
"\u0441\u043c\u0435\u0448\u043d\u0430\u044f \u0438\u0441\u0442\u043e\u0440\u0438\u044f"
],
"published_at": "2014-09-12T20:44:42+00:00"
}
Továbbá, csak bizonyos mezők is választhatók:
# извлечем только поле title
curl -XGET "$ES_URL/blog/post/1?_source=title&pretty"{
"_index" : "blog",
"_type" : "post",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"title" : "Vidám kiscicák"
}
}Indexáljunk be még néhány bejegyzést, és végezzünk el összetettebb lekérdezéseket.
curl -XPUT "$ES_URL/blog/post/2" -d'
{
"title": "Vidám kiskutyák",
"content": "<p>Vicces történet a kiskutyákról<p>"
"tags": [
"kiskutyák",
"vicces történet"
],
"published_at": "2014-08-12T20:44:42+00:00"
}'curl -XPUT "$ES_URL/blog/post/3" -d'
{
"title": "Hogyan lett egy kiscicám",
"content": "<p>Szívszorító történet egy szegény utcai kiscicáról<p>",
"tags": [
"kiscicák"
],
"published_at": "2014-07-21T20:44:42+00:00"
}'Rendezé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" : "Vidám kiscicák",
"published_at" : "2014-09-12T20:44:42+00:00"
},
"sort" : [ 1410554682000 ]
} ]
}
}A legutolsó bejegyzést választottuk ki. méret korlátozza a dokumentumok számát a kimenetben. összesen megmutatja az összes olyan dokumentum számát, amely megfelel a lekérdezésnek. sort a kimenet tartalmaz egy egész számokból álló tömböt, amely alapján a rendezés történik. Azaz, a dátum egy egész számmá alakult. További információk a rendezésről olvashatók itt: .
Szűrők és lekérdezések
Az ES 2-es verziójától kezdve nem különíti el a szűrőket és a lekérdezéseket, ehelyett .
A lekérdezések kontextusa eltér a szűrők kontextusától, mivel a lekérdezés _score-t generál, és nem kerül tárolásra. A _score-ról később fogok beszélni.
Dáta szerinti szűrés
Használjuk a lekérdezést a filter kontextusában:
# получим посты, опубликованные 1ого сентября или позже
curl -XGET "$ES_URL/blog/post/_search?pretty" -d'
{
"filter": {
"range": {
"published_at": { "gte": "2014-09-01" }
}
}
}'Címkékre történő szűrés
Használjuk azoknak a dokumentumoknak az azonosítójának keresésére, amelyek tartalmazzák a megadott szót:
# найдем все документы, в поле 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" : "Vidám kiscicák",
"tags" : [ "kiscicák", "vicces történet" ]
}
}, {
"_index" : "blog",
"_type" : "post",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"title" : "Hogyan lett kiscicám",
"tags" : [ "kiscicák" ]
}
} ]
}
}Teljes szöveg keresés
Három dokumentumunk tartalmazza a következőket a content mezőben:
<p>Vicces történet a kiscicákról<p><p>Vicces történet a kiskutyákról<p><p>Szívszorító történet egy szegény utcai kiscicáról<p>
Használjuk azoknak a dokumentumoknak az azonosítójának keresésére, amelyek tartalmazzák a megadott szót:
# 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
} ]
}
}Ellenben, ha a 'történetek' kifejezésre keresünk a content mezőben, akkor semmit sem találunk, mivel az indexben csak az eredeti szavak találhatóak, nem pedig azok alapformái. A minőségi keresés érdekében be kell állítani az elemzőt.
Mező _score megmutatja . Ha a lekérdezés filter context-ben valósul meg, akkor a _score értéke mindig 1 lesz, ami a szűrő teljes megfelelődését jelenti.
Elemzők
szükségesek az eredeti szöveg tokenekre bontásához.
Az elemzők egy és néhány opcionális . A Tokenizáló előzheti meg a több A Tokenizáló a bemeneti szöveget tokenekre bontja, például szóközök és írásjelek szerint. A TokenFilter módosíthatja a tokeneket, eltávolíthat vagy újakat adhat hozzá, például megtartva a szó alapját, eltávolítva a névelőt, szinonimákat hozzáadva. A CharFilter teljes egészében módosítja a bemeneti szöveget, például eltávolítja a html tageket.
Az ES-ben több Például az .
Használjuk ki és nézzük meg, hogy a standard és a russian elemző hogyan alakítja a 'Vidám történetek a kiscicákról' szöveget:
# используем анализатор 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" : "vidám",
"start_offset" : 0,
"end_offset" : 5,
"type" : "",
"position" : 0
}, {
"token" : "történetek",
"start_offset" : 6,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "a",
"start_offset" : 16,
"end_offset" : 17,
"type" : "",
"position" : 2
}, {
"token" : "kiscicákról",
"start_offset" : 18,
"end_offset" : 30,
"type" : "",
"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" : "vidám",
"start_offset" : 0,
"end_offset" : 7,
"type" : "",
"position" : 0
}, {
"token" : "történet",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 1
}, {
"token" : "macska",
"start_offset" : 20,
"end_offset" : 25,
"type" : "",
"position" : 3
} ]
}A standard elemző szóközök mentén bontotta fel a szöveget, és az összes karaktert kisbetűsre alakította. Az orosz elemző eltávolította a jelentéktelen szavakat, átkonvertálta az összeset kisbetűsre, és megőrizte a szavak gyökerét.
Nézzük meg, hogy az orosz elemző milyen Tokenizer, TokenFilters, CharFilters elemeket használ:
{
"filter": {
"orosz_stop": {
"type": "stop",
"stopwords": "_orosz_"
},
"orosz_keywords": {
"type": "keyword_marker",
"keywords": []
},
"orosz_stemmer": {
"type": "stemmer",
"language": "orosz"
}
},
"analyzer": {
"orosz": {
"tokenizer": "standard",
/* TokenFilters */
"filter": [
"lowercase",
"orosz_stop",
"orosz_keywords",
"orosz_stemmer"
]
/* CharFilters hiányoznak */
}
}
}Írjuk le az orosz alapján felépített elemzőt, amely eltávolítja a HTML tageket. Nevezzük el defaultnak, mivel az így elnevezett elemző lesz az alapértelmezett.
{
"filter": {
"ru_stop": {
"type": "stop",
"stopwords": "_orosz_"
},
"ru_stemmer": {
"type": "stemmer",
"language": "orosz"
}
},
"analyzer": {
"default": {
/* HTML tagek eltávolítása */
"char_filter": ["html_strip"],
"tokenizer": "standard",
"filter": [
"lowercase",
"ru_stop",
"ru_stemmer"
]
}
}
}Először az eredeti szövegből eltávolítják az összes HTML taget, majd az a standard tokenizáló segítségével tokenekre bontódik, az így kapott tokenek kisbetűsre alakulnak, eltávolítják a jelentéktelen szavakat, és végül a megmaradt tokenekből a szavak gyökerét kapjuk.
Index létrehozása
Fent leírtuk a default elemzőt. Ez alkalmazásra kerül minden szöveges mezőnél. A bejegyzésünk egy címke tömböt tartalmaz, így a címkék is feldolgozásra kerülnek az elemző által. Mivel pontos egyezés alapján keresünk a címkék között, szükséges letiltani az elemzést a tags mezőnél.
Hozzuk létre a blog2 indexet az elemzővel és a mappinggel, amelyben le van tiltva a tags mező elemzése:
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"
}
}
}
}
}'Hozzáadjuk ugyanazokat a 3 bejegyzést ehhez az indexhez (blog2). Ezt a folyamatot kihagyom, mivel hasonló a dokumentumok indexbe való hozzáadásához.
Teljes szöveges keresés kifejezés támogatással
Ismerkedjünk meg egy másik lekérdezéstípussal:
# найдем документы, в которых встречается слово 'истории'
# 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 orosz stemming analizátort használunk, ez a lekérdezés minden dokumentumot visszaad, bár csak az 'történet' szó szerepel bennük.
A lekérdezés speciális karaktereket tartalmazhat, például:
""sült tojás" +(padlizsán | burgonya) -frittata"A lekérdezés szintaxisa:
+ jelzi az ÉS műveletet
| jelzi a VAGY műveletet
- egyetlen tokent tagad
" körülvesz egy csoportnyi tokent, hogy kifejezést keresni tudjunk
* a kifejezés végén prefix lekérdezést jelez
( és ) a precedenciát jelzik
~N egy szó után a szerkesztési távolságot (fuzziness) jelenti
~N egy kifejezés után a slop mennyiségét jelzi# найдем документы без слова 'щенки'
curl -XPOST "$ES_URL/blog2/post/_search?pretty" -d'
{
"query": {
"simple_query_string": {
"query": "-щенки",
"fields": [
"title^3",
"tags^2",
"content"
]
}
}
}'
# получим 2 поста про котиковHivatkozások
PS
Ha érdeklik az ilyen cikkek-oktatóanyagok, van ötlete új cikkekre, vagy javaslatai vannak együttműködésre, örömmel várom az üzenetét személyesen vagy e-mailben a m.kuzmin+habr@darkleaf.ru címen.
Forrás: habr.com
