Статьи

Индексирование существующих данных с помощью SolrJ

Два популярных метода индексации существующих данных — это обработчик импорта данных (DIH) и Tika (ячейка Solr) / ExtractingRequestHandler. Их можно использовать для индексации данных из базы данных или структурированных документов (например, документов Word, PDF или …). Это отличные инструменты для быстрого запуска и запуска, и я видел производственные сайты, которые хорошо работают с одним или обоими этими инструментами.

Хорошо, тогда зачем говорить о SolrJ?

Ну, где-то в архитектурном документе есть два поля, которые имеют такие метки, соединенные стрелкой:

Solr Server ——- чудесное соединение ——-> источник данных

О хорошо Редко соединитель между сервером и индексатором Solr и данными, которые он будет индексировать, помечен как «чудесное соединение», но я иногда хотел бы, чтобы люди были более откровенны. Здесь могут мешать всевозможные вещи, я упомяну 0,01% из них:

  • Сотрудники службы безопасности НЕ «просто откроют базу данных для IP-адреса индексатора Solr, пожалуйста».
  • На самом деле, это не один источник данных. Это три. Как минимум. И, кстати, вам действительно нужно кешировать данные из базы данных 1, иначе производительность умрет. Не забывайте, что для каждого из них требуются разные учетные данные.
  • Я упоминал, что «чудесная связь» — это то, где живут наши бизнес-правила, которые должны быть закодированы в документах Solr?
  • Привет! Я думал, что мы могли бы провести документы через категоризацию на этом этапе!
  • На самом деле метаданные живут в БД, и вы берете эту информацию и затем запрашиваете файловый сервер, который доставит вам документ.
  • Мы индексируем фильмы. Правильно, вам нужны только метаданные. Какая? Solr собирается выбросить 99,9999% данных после того, как вы передали, сколько  данных по моей сети?
  • <вставьте вашу любимую проблему здесь>

И это даже не говорит о том, что DIH и Tika работают на сервере. То есть индексатор Solr выполняет всю работу, а бедняжка может идти только так быстро (хорошо, это здорово, но анализ большого количества документов PDF / Word / Excel — это большая нагрузка для одной машины).

Моя точка зрения заключается в том, что часто существуют веские причины, по которым использование DIH и / или Tika не является оптимальным, от обеспечения безопасности до усиления контроля над тем, как обрабатываются «плохие» документы, чтобы передать то, какие инструменты наиболее удобны для вашей организации. Для таких ситуаций SolrJ может быть наиболее подходящим. Далее следует скелетная программа, которая:

  1. Соединяется из Java-программы с сервером Solr, в данном случае с индексатором.
  2. Запрашивает базу данных через JDBC и выбирает информацию из таблицы, помещая ее в подходящую форму для индексации.
  3. Обходит файловую систему и индексирует все документы, которые Tika может анализировать по заданному каталогу.
  4. Добавляет эти документы на сервер Solr.

 

Пожалуйста, обратите внимание, что пример очень прост, ничего сделать здесь невозможно с помощью DIH и Solr Cell. Цель здесь является обеспечение отправной точки для вас ,  чтобы приспособиться к вашей  конкретной ситуации , в которой DIH и Solr Ce не будет работать прямо из коробки.

Много наращивания, не так много кода

Несмотря на все вышесказанное, сама программа довольно короткая. Я выделю некоторые основные моменты, и полный список приведен в конце этой статьи.

Настройте соединение Solr

Это просто код для настройки соединения с сервером Solr. URL, передаваемый этому методу, является тем же URL, который вы использовали бы для запроса Solr, например, http: // localhost: 8983 / solr. Единственный дополнительный бит находится в конце, где мы настраиваем парсер Tika.

private SqlTikaExample(String url) throws IOException, SolrServerException {
      // Create a multi-threaded communications channel to the Solr server.
      // Could use CommonsHttpSolrServer instead.
   _server = new StreamingUpdateSolrServer(url, 10, 4);
   _server.setSoTimeout(1000); // socket read timeout
   _server.setConnectionTimeout(1000);
   _server.setMaxRetries(1); // defaults to 0. > 1 not recommended.
     // binary parser is used by default for responses
   _server.setParser(new XMLResponseParser());
     // One of the ways Tika can be used to attempt to parse arbitrary files.
   _autoParser = new AutoDetectParser();
 }

Индексируйте структурированный документ с Tika

Примечание здесь. Можно либо скачать Tika из проекта Apache, посмотреть  Apache Tika  и поместить jar-файлы в classpath, либо просто скопировать jar-файл, поставляемый с Solr, который должен находиться в <solr_home> / solr / contrib / extract / lib / *. Jar. , Хотя вы можете обойтись без использования выбранных файлов из этого каталога, вероятно, лучше начать со всего набора и удалить те, которые вам не нужны, чем испытать разочарование от пропуска только одного из них. Вот как выглядит этот код. Полный исходный код в конце обрабатывает файловую систему и т. Д.

ContentHandler textHandler = new BodyContentHandler();
 Metadata metadata = new Metadata();
 ParseContext context = new ParseContext();
 InputStream input = new FileInputStream(file);
   // Try parsing the file. Note we haven't checked at all to
   // see whether this file is a good candidate.
 try {
   _autoParser.parse(input, textHandler, metadata, context);
 } catch (Exception e) {
      // Needs better logging of what went wrong in
      // order to track down "bad" documents.
   log(String.format("File %s failed", file.getCanonicalPath()));
   e.printStackTrace();
   continue;
 }
    // Just dump ALL the meta-data, remove this
    // in any production environment of course.
 dumpMetadata(file.getCanonicalPath(), metadata);
    // Index just a couple of the meta-data fields.
 SolrInputDocument doc = new SolrInputDocument();
 doc.addField("id", file.getCanonicalPath());
    // Crude way to get known meta-data fields. Also
    // possible to write a simple loop to examine all the
    // metadata returned and selectively index it and/or just
    // get a list of them.
    // One can also use the LucidWorks field mapping to
    // accomplish much the same thing.
 String author = metadata.get("Author");
 if (author != null) {
   doc.addField("author", author);
 }
 doc.addField("text", textHandler.toString());
 _docs.add(doc);

Есть внешний цикл, который отвечает за обход файловой системы, которую я не показывал. Все, что здесь происходит, это то, что Тике разрешено делать свое дело. Если Тика не может проанализировать документ (обратите внимание, что я не позаботился о том, чтобы определить, являются ли файлы разумными, например, jar-файл или exe-файл или что-то еще, что может быть проанализировано), мы регистрируем ошибку и продолжаем.

Если документ анализируется, мы извлекаем пару полей и добавляем их в документ Solr. Этот документ затем добавляется в список Java, и в конце концов, когда в списке появляется 1000 документов, все это передается Solr для индексации, как вы можете видеть в полном списке. Кстати, пример индекса, который поставляется с распределением Solr, уже определит эти поля.

Но обратите внимание на тонкость здесь, даже в тривиальном случае. Мы предполагаем,  что поле метаданных для автора — «Автор». Для этого не существует кросс-форматных стандартов, он может называться «document_author» или, может быть, вам нужен «last_editor». Вы можете контролировать все это здесь либо с помощью разумной конфигурации Tika, либо программно.

Вперед к биту Sql

Далее мы рассмотрим код, который подключается через JDBC к базе данных MySql. Опять же, это самая простая из таблиц базы данных и самая простая из экстракций. Вы, вероятно, не будете использовать решение SolrJ, если ваша ситуация не будет более сложной, чем эта, но это поможет вам начать.

    Class.forName("com.mysql.jdbc.Driver").newInstance();
    log("Driver Loaded......");

    con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?" + "user=test&password=test");

    Statement st = con.createStatement();
    ResultSet rs = st.executeQuery("select id,title,text from test");
    while (rs.next()) {
         // DO NOT move this outside the while loop
         // or be sure to call doc.clear()
      SolrInputDocument doc = new SolrInputDocument(); 

      doc.addField("id", rs.getString("id"));
      doc.addField("title", rs.getString("title"));
      doc.addField("text", rs.getString("text"));

      _docs.add(doc);
   }

Опять же, это тот же процесс, что и в прошлом, но теперь вместо синтаксического анализа структурированного документа мы выбираем строки из таблицы и добавляем выбранные значения из этих строк в каждый документ Solr. И снова мы собираем эти документы в список для последующей отправки в Solr.

Вывод

Как видите, использование Tika и / или SQL / JDBC из клиента SolrJ не очень сложно. Я предполагаю, что этот блог вызван количеством запросов в списке пользователей Solr, которые запрашивают примеры того, как использовать SolrJ для индексации документов. Довольно сложно столкнуться со всей документацией по Solr API и не иметь понятия, с чего начать, я надеюсь, что этот пример немного обеззараживает процесс.

Среда

Я скомпилировал и протестировал этот код на текущей версии Solr для ствола (4.0), но он должен работать при компиляции с версией 3.x. Если нет, изменения должны быть минимальными.

Полный исходный код и отказ от ответственности

Одна из прелестей написания примеров заключается в том, что можно исключить всю уродливую обработку ошибок, ведение журналов и т. Д. Этот код необходимо значительно улучшить для производственных целей, и ваша ситуация почти наверняка будет гораздо более сложной, или вы не захотите Во-первых, не нужно беспокоиться о SolrJ! Так что не стесняйтесь использовать это как основу для продвижения вперед с SolrJ, но это всего лишь пример.

Также обратите внимание, что я использовал SolrJ в качестве примера, но есть и другие реализации, C #, PHP и т. Д. Версия Java — та, с которой мне удобнее всего, и она, как правило, самая современная. Тем не менее, другие клиенты в порядке, если они лучше вписываются в вашу зону комфорта / среду.

И мне придется попросить у вас прощения за уродливое форматирование, но вставьте его в свою любимую среду IDE, и оно будет выглядеть намного лучше!

package SolrJExample;

import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.StreamingUpdateSolrServer;
import org.apache.solr.client.solrj.impl.XMLResponseParser;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrInputDocument;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collection;

/* Example class showing the skeleton of using Tika and
   Sql on the client to index documents from
   both structured documents and a SQL database.

   NOTE: The SQL example and the Tika example are entirely orthogonal.
   Both are included here to make a
   more interesting example, but you can omit either of them.

 */
public class SqlTikaExample {
  private StreamingUpdateSolrServer _server;
  private long _start = System.currentTimeMillis();
  private AutoDetectParser _autoParser;
  private int _totalTika = 0;
  private int _totalSql = 0;

  private Collection _docs = new ArrayList();

  public static void main(String[] args) {
    try {
      SqlTikaExample idxer = new SqlTikaExample("http://localhost:8983/solr");

      idxer.doTikaDocuments(new File("/Users/Erick/testdocs"));
      idxer.doSqlDocuments();

      idxer.endIndexing();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private SqlTikaExample(String url) throws IOException, SolrServerException {
      // Create a multi-threaded communications channel to the Solr server.
      // Could be CommonsHttpSolrServer as well.
      //
    _server = new StreamingUpdateSolrServer(url, 10, 4);

    _server.setSoTimeout(1000);  // socket read timeout
    _server.setConnectionTimeout(1000);
    _server.setMaxRetries(1); // defaults to 0.  > 1 not recommended.
         // binary parser is used by default for responses
    _server.setParser(new XMLResponseParser()); 

      // One of the ways Tika can be used to attempt to parse arbitrary files.
    _autoParser = new AutoDetectParser();
  }

    // Just a convenient place to wrap things up.
  private void endIndexing() throws IOException, SolrServerException {
    if (_docs.size() > 0) { // Are there any documents left over?
      _server.add(_docs, 300000); // Commit within 5 minutes
    }
    _server.commit(); // Only needs to be done at the end,
                      // commitWithin should do the rest.
                      // Could even be omitted
                      // assuming commitWithin was specified.
    long endTime = System.currentTimeMillis();
    log("Total Time Taken: " + (endTime - _start) +
         " milliseconds to index " + _totalSql +
        " SQL rows and " + _totalTika + " documents");
  }

  // I hate writing System.out.println() everyplace,
  // besides this gives a central place to convert to true logging
  // in a production system.
  private static void log(String msg) {
    System.out.println(msg);
  }

  /**
   * ***************************Tika processing here
   */
  // Recursively traverse the filesystem, parsing everything found.
  private void doTikaDocuments(File root) throws IOException, SolrServerException {

    // Simple loop for recursively indexing all the files
    // in the root directory passed in.
    for (File file : root.listFiles()) {
      if (file.isDirectory()) {
        doTikaDocuments(file);
        continue;
      }
        // Get ready to parse the file.
      ContentHandler textHandler = new BodyContentHandler();
      Metadata metadata = new Metadata();
      ParseContext context = new ParseContext();

      InputStream input = new FileInputStream(file);

        // Try parsing the file. Note we haven't checked at all to
        // see whether this file is a good candidate.
      try {
        _autoParser.parse(input, textHandler, metadata, context);
      } catch (Exception e) {
          // Needs better logging of what went wrong in order to
          // track down "bad" documents.
        log(String.format("File %s failed", file.getCanonicalPath()));
        e.printStackTrace();
        continue;
      }
      // Just to show how much meta-data and what form it's in.
      dumpMetadata(file.getCanonicalPath(), metadata);

      // Index just a couple of the meta-data fields.
      SolrInputDocument doc = new SolrInputDocument();

      doc.addField("id", file.getCanonicalPath());

      // Crude way to get known meta-data fields.
      // Also possible to write a simple loop to examine all the
      // metadata returned and selectively index it and/or
      // just get a list of them.
      // One can also use the LucidWorks field mapping to
      // accomplish much the same thing.
      String author = metadata.get("Author");

      if (author != null) {
        doc.addField("author", author);
      }

      doc.addField("text", textHandler.toString());

      _docs.add(doc);
      ++_totalTika;

      // Completely arbitrary, just batch up more than one document
      // for throughput!
      if (_docs.size() >= 1000) {
          // Commit within 5 minutes.
        UpdateResponse resp = _server.add(_docs, 300000);
        if (resp.getStatus() != 0) {
          log("Some horrible error has occurred, status is: " +
                  resp.getStatus());
        }
        _docs.clear();
      }
    }
  }

    // Just to show all the metadata that's available.
  private void dumpMetadata(String fileName, Metadata metadata) {
    log("Dumping metadata for file: " + fileName);
    for (String name : metadata.names()) {
      log(name + ":" + metadata.get(name));
    }
    log("\n\n");
  }

  /**
   * ***************************SQL processing here
   */
  private void doSqlDocuments() throws SQLException {
    Connection con = null;
    try {
      Class.forName("com.mysql.jdbc.Driver").newInstance();
      log("Driver Loaded......");

      con = DriverManager.getConnection("jdbc:mysql://192.168.1.103:3306/test?"
                + "user=testuser&password=test123");

      Statement st = con.createStatement();
      ResultSet rs = st.executeQuery("select id,title,text from test");

      while (rs.next()) {
        // DO NOT move this outside the while loop
        // or be sure to call doc.clear()
        SolrInputDocument doc = new SolrInputDocument(); 
        String id = rs.getString("id");
        String title = rs.getString("title");
        String text = rs.getString("text");

        doc.addField("id", id);
        doc.addField("title", title);
        doc.addField("text", text);

        _docs.add(doc);
        ++_totalSql;

        // Completely arbitrary, just batch up more than one
        // document for throughput!
        if (_docs.size() > 1000) {
             // Commit within 5 minutes.
          UpdateResponse resp = _server.add(_docs, 300000);
          if (resp.getStatus() != 0) {
            log("Some horrible error has occurred, status is: " +
                  resp.getStatus());
          }
          _docs.clear();
        }
      }
    } catch (Exception ex) {
      ex.printStackTrace();
    } finally {
      if (con != null) {
        con.close();
      }
    }
  }
}