UNIXwork

Fulltext Search Engines Teil 2: Apache Lucene Howto

22. Dezember 2019

Um Apache Lucene mal auszuprobieren, habe ich meine Blog-Software, die auch in Java geschrieben ist, um eine Volltextsuche erweitert. Dies war mit recht wenig Handgriffen erledigt. Daher gibts jetzt hier ein paar Code-Snippets, um eine minimale Volltextsuche mit Apache Lucene zu implementieren.

Maven-Dependencies:

<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-core</artifactId>
	<version>8.3.1</version>
</dependency>
<dependency>
	<groupId>org.apache.lucene</groupId>
	<artifactId>lucene-queryparser</artifactId>
	<version>8.3.1</version>
</dependency>

Zunächst benötigt Lucene ein Directory. Dies ist eine abstrakte Klasse für den Zugriff auf den Datenspeicher, den Lucene benutzt. Hier kann man sich dann für eine Implementierung entscheiden, z.B. FSDirectory, wenn Lucene seine Indexdaten im Dateisystem speichern soll.

Directory fsIndex;

...

fsIndex = FSDirectory.open(new File("/var/blogindex/").toPath());

Benötigt wird auch ein Analyzer, der benutzt wird, um Text zu analysieren und in einzelne Tokens zu teilen. Hier habe ich den StandardAnalyzer benutzt.

StandardAnalyzer analyzer;

...

analyzer = new StandardAnalyzer();

Jetzt ist die grundlegende Initialisierung auch schon abgeschlossen und wir können Dokumente zum Index hinzufügen. Meine Blog-Software hat eine Klasse Article mit den Gettern getTitle(), getTags(), getContent() und getID(). Diese drei Attribute möchten wir auch als Felder in einem Lucene-Document speichern. Hierfür benötigen wir zunächst einen IndexWriter.

IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter writer = new IndexWriter(fsIndex, indexWriterConfig);

Dann erstellen wir ein Lucene Document.

Document document = new Document();
document.add(new TextField("title", article.getTitle(), Field.Store.YES));
document.add(new TextField("tags", article.getTags(), Field.Store.YES));
document.add(new TextField("content", article.getContent(), Field.Store.NO));
document.add(new StringField("id", Integer.toString(article.getID()), Field.Store.YES));

Mit Field.Store.YES kann man angeben, dass der Originalwert im Index gespeichert werden soll. Bei Field.Store.NO wird dieser nicht gespeichert. Des Weiteren nutze ich hier auch verschiedene Feld-Typen. Der Unterschied zwischen TextField und StringField ist, dass beim TextField der Inhalt analysiert, also tokenized wird. Beim Inhalt des Blog-Artikels ist dies natürlich wichtig, bei der ID hingegen will man das Gegenteil. Die ID brauchen wir zum Einen, um aus dem Lucene-Index dann den passenden Artikel in der SQL-Datenbank zu finden, zum anderen muss die ID aber auch in Lucene indiziert sein, damit wir Documents aus dem Lucene-Index entfernen oder updaten können.

Es gibt noch jede Menge andere Feld-Typen, z.B. für numerische Werte, oder Werte, die nicht indiziert, aber gespeichert werden sollen. Siehe Field.

Jetzt wo wir das Document erstellt haben, kann dies zum Index hinzugefügt werden. Hierfür gibt es die IndexWriter-Methode addDocument(). Ich benutze hingegen updateDocument(), die das Dokument vorher löscht, falls es vorhanden ist. Dies ist wichtig, da beim Update von Blog-Artikeln somit im Lucene-Index nicht mehrere Documents für den Artikel gespeichert werden.

writer.updateDocument(new Term("id", Integer.toString(article.getID())), document);

Abschließend ist es wichtig, die Methode commit() des IndexWriter aufzurufen. Ebenfalls können wir die close() Methode aufrufen, da wir mit dem Indizieren eines Artikels fertig sind.

writer.commit();
writer.close();

Nun können wir uns noch Anschauen, wie man den Index durchsuchen kann. Hierfür benötigen wir einen IndexReader und IndexSearcher.

IndexReader indexReader = DirectoryReader.open(fsIndex);
IndexSearcher searcher = new IndexSearcher(indexReader);

Außerdem wird ein Query für die Suche benötigt. Hierfür gibt es unzählige Möglichkeiten, diesen zu erstellen. Ich habe mich für MultiFieldQueryParser entschieden, da dieser einen Query erstellen kann, der mehrere Felder durchsuchen kann. Diese Klasse gehört nicht mehr zur Lucene Core Lib, sondern zur Queryparser Lib.

String[] fields = {"title", "tags", "content"};
Query query = new MultiFieldQueryParser(fields, analyzer).parse(queryString);

Jetzt können wir mit dem Searcher und dem Query Dokumente suchen:

TopDocs topDocs = searcher.search(query, numResults);
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
	Document doc = searcher.doc(scoreDoc.doc);
	
	// TODO: get fields from doc
}
indexReader.close();

Aus dem Document kann man dann die gespeicherten Werte der Fields wieder rausholen.

Die Suchfunktion lässt sich hier im Blog jetzt nutzen, in dem ein Parameter q=query an die URL angehängt wird. Ein Beispiel. Ist noch nicht besonders schön, aber das war jetzt auch nur schnell ran gefrickelt und ich habe eh vor, die Blogsoftware demnächst komplett neu zu schreiben.

Autor: Olaf | 0 Kommentare | Tags: apache, lucene, java, blog

Fulltext Search Engines Teil 1: Lucene, Solr und Elasticsearch

21. Dezember 2019

Lucene

Apache Lucene ist ein Fulltext Search Engine, die als Java-Bibliothek in eigene Programme eingebunden werden kann.

Lucene arbeitet mit Dokumenten, die verschiedene Felder besitzen können. Felder haben einen Namen und einen Wert. Diese Dokumente können dann zu einem Index hinzugefügt werden. Die Suche nach Dokumenten kann sich dabei auf mehrere Felder beziehen.

Lucene bietet dabei sehr umfangreiche Suchmöglichkeiten. Unterstützt wird außerdem auch ein Ranking der Suchergebnisse.

Im Prinzip lässt sich sagen, dass Apache Lucene die Feature-Messlatte für Open-Source-Volltextsuchen angibt.

Apache Solr

Apache Solr ist ein sehr verbreiteter Suchserver, der auf Apache Lucene aufbaut und den Funktionsumfang davon weitgehend über eine REST-Schnittstelle zur Verfügung stellt. Apache Solr kann man auch als Cluster betreiben und erhält damit ein skalierbares und hochverfügbares System.

Während Lucene nur einfachen Text indizieren kann, ist es mit Apache Solr möglich, verschiedene Dokumententypen zu verarbeiten. Hierfür gibt es verschiedene Content Handler, die Text aus unterschiedlichen Dateitypen extrahieren können.

Der Zugriff auf die REST-Schnittstelle kann man mit Tools wie curl oder anderen HTTP-Clients erfolgen. Es gibt allerdings auch diverse Client-Libs für alle möglichen Programmiersprachen.

Elasticsearch

Genau wie Apache Solr ist Elasticsearch ein Suchserver auf Lucene-Basis. Allerdings handelt es sich um eine kommerzielle Software, wobei ein Teil auch unter einer Open Source Lizenz steht.

Elasticsearch kann ebenfalls als verteilte Searchengine betrieben werden. Zusätzlich bietet es auch diverse Analysewerkzeuge.

Fazit

Mit Apache Lucene steht einem eine sehr mächtige Volltextsuche zur Verfügung, die sich auch recht leicht verwenden lässt (dazu mehr in einem weiteren Artikel). Die selbe Funktionalität steht einem dann auch in größerer Dimension mit Apache Solr und Elasticsearch zur Verfügung.

Autor: Olaf | 0 Kommentare | Tags: apache, lucene, solr, java, db