1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-think-dast-zh

Клонировать/Скачать
14.md 26 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 27.11.2024 20:24 3a5df95

Глава 14. Персистентность

В следующих нескольких упражнениях мы вернёмся к построению веб-поискового движка. Для обзора, компонентами поискового движка являются:

  • Сбор: нам нужна программа, которая может загрузить веб-страницу, проанализировать её и извлечь текст и любые другие ссылки на странице.
  • Индексирование: нам нужен индекс, который может искать элементы поиска и находить страницу, содержащую их.
  • Поиск: нам нужен способ собрать результаты из индекса и определить страницу, наиболее релевантную элементу поиска.

Если вы выполнили упражнение 8.3, вы реализовали индекс с помощью Java-отображения. В этом упражнении мы пересмотрим индексатор и создадим новую версию, которая будет хранить результаты в базе данных.

Если вы сделали упражнение 7.4, вы создали паука, который отслеживает найденную им первую ссылку. В следующем упражнении мы создадим более универсальную версию, которая сохранит каждую найденную ссылку в очереди и отсортирует их.

Затем, наконец, вы будете работать над проблемой поиска.

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

Теперь давайте начнём писать новую версию индексатора.

14.1 Redis

Предыдущая версия индексатора хранила индекс в двух структурах данных: TermCounter сопоставлял поисковые слова с количеством их появления на веб-странице, а Index сопоставлял поисковые слова со списком страниц, на которых они появились.

Эти структуры данных хранятся в памяти работающей программы Java, что означает, что индекс теряется, когда программа завершает работу. Данные, хранящиеся только в памяти запущенной программы, называются «эфемерными», потому что они исчезают, когда программа завершается.

Данные, которые всё ещё существуют после завершения работы программы, называются «персистентными». Обычно данные, хранящиеся в файловой системе или в базе данных, являются персистентными.

Один из простых способов сделать данные персистентными — сохранить их в файле. Перед завершением работы он может преобразовать свои структуры данных в формат JSON (http://thinkdast.com/json) и записать их в файл. Когда он снова запустится, он сможет прочитать файл и восстановить структуру данных.

Но у этого решения есть несколько проблем:

  • Чтение и запись больших структур данных (например, веб-индекса) происходит медленно.
  • Вся структура данных может не поместиться в память одной работающей программы.
  • Любые изменения, сделанные с момента последнего запуска программы, будут потеряны, если программа неожиданно завершится (например, из-за сбоя питания).

Лучшим выбором является предоставление базы данных для постоянного хранения и возможность читать и записывать части базы данных без необходимости чтения и записи всего набора данных.

Существует множество систем управления базами данных (СУБД), которые предоставляют различные функции. Вы можете прочитать обзор на http://thinkdast.com/database.

Я рекомендую базу данных Redis для этого упражнения, которая предоставляет структуры данных, подобные структурам данных Java, для персистентного хранения. Конкретно, она предоставляет:

  • Строковые списки, похожие на List в Java.
  • Хеши, похожие на Map в Java.
  • Наборы строк, похожие на Set в Java.

Переводчик: Также есть упорядоченные наборы, похожие на LinkedHashSet в Java.

Redis — это «база данных ключ-значение», что означает, что данные (значение), содержащиеся в ней, идентифицируются уникальным строковым ключом (ключом). Ключи в Redis похожи на ссылки в Java: они идентифицируют объект. Мы увидим несколько примеров позже.

14.2 Redis клиент и сервер

Обычно Redis работает как удалённый сервис; фактически его название представляет собой аббревиатуру от «REmote DIctionary Server» (удаленный словарный сервер, словарь — это, по сути, отображение). Чтобы использовать Redis, вам необходимо запустить сервер Redis где-нибудь и использовать клиент Redis для подключения к серверу Redis. Существует множество способов настроить сервер, и существует множество клиентов, которые можно использовать. Для этого упражнения я предлагаю:

  • Не устанавливайте и не запускайте сервер самостоятельно, рассмотрите возможность использования такой службы, как RedisToGo (http://thinkdast.com/redistogo), которая запускает сервер Redis на облачном хосте. Они предлагают бесплатный план (конфигурацию), который имеет достаточно ресурсов для упражнений.
  • Для клиента я рекомендую Jedis, библиотеку Java, которая предоставляет классы и методы для использования Redis.

Вот более подробные инструкции, чтобы помочь вам начать:

  • Создайте учётную запись на RedisToGo по адресу http://thinkdast.com/redissign и выберите необходимый план (возможно, бесплатный начальный план).
  • Создайте «экземпляр», который является виртуальной машиной, на которой работает сервер Redis. Если вы щёлкнете вкладку «Экземпляры», вы увидите свой новый экземпляр, идентифицированный именем хоста и номером порта. Например, у меня есть экземпляр под названием dory-10534.
  • Щёлкните имя экземпляра, чтобы перейти на страницу конфигурации. Запишите URL-адрес в верхней части страницы рядом с ним, например:
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534

Этот URL содержит имя хоста сервера dory.redistogo.com, номер порта 10534 и пароль, необходимый для подключения к серверу, который представляет собой длинную строку букв и цифр посередине. Вам понадобится эта информация для следующего шага.

14.3 Создание индекса на основе Redis

Вы найдёте исходный код для этого упражнения в репозитории книги:

  • JedisMaker.java содержит пример кода для подключения к серверу Redis и выполнения нескольких методов Jedis.
  • JedisIndex.java содержит стартовый код для этого упражнения.
  • JedisIndexTest.java содержит тестовый код для JedisIndex.
  • WikiFetcher.java содержит код, который мы видели в предыдущих упражнениях, для чтения веб-страницы и анализа её с помощью jsoup.

Вам также понадобятся эти файлы, с которыми вы столкнулись в предыдущих упражнениях:

  • Index.java использует структуры данных Java для реализации индекса.
  • TermCounter.java представляет собой отображение от поисковых слов до их частоты.
  • WikiNodeIterable.java выполняет итерацию по узлам DOM-дерева, созданного jsoup.

Если у вас есть рабочие версии этих файлов, вы можете использовать их для этого упражнения. Если у вас нет опыта работы с предыдущими упражнениями или вы не уверены в своём решении, вы можете скопировать моё решение из папки solutions.

Первый шаг — использовать Jedis для подключения к вашему серверу Redis. JedisMaker показывает, как это сделать. Он считывает информацию о вашем сервере Redis из файла, подключается к нему и входит в систему с вашим паролем, а затем возвращает объект Jedis, который можно использовать для выполнения операций Redis.

Если вы откроете JedisMaker.java, вы увидите класс JedisMaker, который является вспомогательным классом, предоставляющим статический метод make, который создаёт объект Jedis. Как только объект аутентифицирован, вы можете использовать его для связи с вашей базой данных Redis.

JedisMaker считывает информацию вашего сервера Redis из файла с именем redis_url.txt, который вы должны поместить в каталог src/resources:

  • Используйте текстовый редактор для создания и редактирования ThinkDataStructures/code/src/resources/redis_url.txt.
  • Скопируйте URL-адрес сервера. Если вы используете RedisToGo, URL-адрес будет выглядеть следующим образом:
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534

Поскольку этот файл содержит пароль вашего сервера Redis, вы не должны помещать этот файл в общедоступный репозиторий. Чтобы помочь вам избежать случайного размещения, репозиторий содержит файл .gitignore, который затрудняет (но не делает невозможным) размещение файла в вашем репозитории.

Теперь запустите ant build для компиляции исходных файлов и ant JedisMaker для запуска примера кода в main:

public static void main(String[] args) {

    Jedis jedis = make();
    
    // String
    jedis.set("mykey", "myvalue");
    String value = jedis.get("mykey");
    System.out.println("Got value: " + value);
    
    // Set
    jedis.sadd("myset", "element1", "element2", "element3");
    System.out.println("element2 is member: " + 
                       jedis.sismember("myset", "element2"));
    
    // List
    jedis.rpush("mylist", "element1", "element2", "element3");
    System.out.println("element at index 1: " +
``` **Вот перевод текста на русский язык:**

Этот пример демонстрирует типы данных и методы, которые вы, скорее всего, будете использовать в этом упражнении. При его выполнении вывод должен быть следующим:

Получил значение: myvalue element2 является членом: true элемент с индексом 1: element2 частота слова1: 2 частота слова2: 1


В следующем разделе я объясню принцип работы кода.

## 14.4 Типы данных Redis

Redis по сути представляет собой отображение от ключа к значению, где ключ — это строка, а значение может быть строкой или одним из нескольких типов данных. Самый базовый тип данных в Redis — это строка. Я буду использовать курсив для обозначения типов Redis, чтобы отличать их от типов Java.

Чтобы добавить строку в базу данных, используйте `jedis.set`, аналогично `Map.put`; параметры — это новый ключ и соответствующее значение. Чтобы найти ключ и получить его значение, используйте `jedis.get`:

```java
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");

Здесь ключ — "mykey", а значение — "myvalue".

Redis предоставляет структуру коллекции, аналогичную Set<String> в Java. Чтобы добавить элементы в коллекцию Redis, вы можете выбрать ключ для идентификации коллекции и затем использовать jedis.sadd:

        jedis.sadd("myset", "element1", "element2", "element3");
        boolean flag = jedis.sismember("myset", "element2");

Вам не нужно выполнять отдельные шаги для создания коллекции. Если она не существует, Redis создаст её. В этом случае он создаст набор с именем myset, содержащий три элемента.

Метод jedis.sismember проверяет, является ли элемент членом коллекции. Добавление элементов и проверка членов являются операциями с постоянным временем.

Redis также предоставляет структуру списка, аналогичную List<String> в Java. Метод jedis.rpush добавляет элементы в конец (правый конец) списка:

        jedis.rpush("mylist", "element1", "element2", "element3");
        String element = jedis.lindex("mylist", 1);

Опять же, вам не нужно создавать структуру перед добавлением элементов. Этот пример создаёт список с именем mylist, содержащий три элемента.

Метод jedis.lindex использует целочисленный индекс и возвращает указанный элемент списка. Добавление и доступ к элементам являются операциями с постоянным временем.

Наконец, Redis предоставляет структуру хэша, аналогичную Map<String, String> в Java. Метод jedis.hset добавляет новую запись в хеш-таблицу:

        jedis.hset("myhash", "word1", Integer.toString(2));
        String value = jedis.hget("myhash", "word1");

Этот пример создаёт хеш-таблицу с именем myhash, содержащую одну запись, которая сопоставляет ключ word1 со значением "2".

И ключи, и значения являются строками, поэтому если мы хотим сохранить Integer, нам необходимо преобразовать его в String перед вызовом hset. Когда мы используем hget для поиска значения, результатом будет String, поэтому нам, возможно, придётся преобразовать его обратно в Integer.

Использование хеш-таблицы Redis может сбивать с толку, поскольку мы используем один ключ для идентификации нужной хеш-таблицы, а затем другой ключ для идентификации значения в хеш-таблице. В контексте Redis второй ключ называется «поле», что может помочь сохранить ясность. Таким образом, ключ, подобный myhash, обозначает конкретную хеш-таблицу, а поле, подобное word1, идентифицирует значение в хеш-таблице.

Для многих приложений значения в хэш-таблицах Redis являются целыми числами, поэтому Redis предоставляет несколько специальных методов, таких как hincrby, которые обрабатывают значения как числа:

        jedis.hincrBy("myhash", "word2", 1);

Этот метод обращается к myhash, получает текущее значение word2 (если оно не существует, то равно 0), увеличивает его на 1 и записывает результат обратно в хеш-таблицу.

Установка, получение и увеличение записей в хеш-таблице являются операциями с постоянным временем.

Вы можете прочитать больше о типах данных Redis на сайте http://thinkdast.com/redistypes.

14.5 Упражнение 11

На данный момент вы можете получить некоторую информацию, которую вам нужно использовать для создания индекса поисковой системы, который будет хранить результаты в базе данных Redis.

Теперь запустите ant JedisIndexTest. Он должен завершиться ошибкой, потому что у вас есть работа!

JedisIndexTest тестирует следующие методы:

  • JedisIndex, который является конструктором, принимающим объект Jedis в качестве параметра.
  • indexPage, который добавляет веб-страницу в индекс; ему требуется StringURL и объект jsoup Elements, содержащий элементы страницы, для которых должен быть создан индекс.
  • getCounts, который принимает поисковый термин и возвращает Map<String, Integer>, содержащее отображение поискового термина на количество его появлений на странице.

Вот пример использования этих методов:

        WikiFetcher wf = new WikiFetcher();
        String url1 = 
            "http://en.wikipedia.org/wiki/Java_(programming_language)";
        Elements paragraphs = wf.readWikipedia(url1);

        Jedis jedis = JedisMaker.make();
        JedisIndex index = new JedisIndex(jedis);
        index.indexPage(url1, paragraphs);
        Map<String, Integer> map = index.getCounts("the");

Если мы посмотрим на url1 в результате map, мы должны получить 339, что является количеством появлений the на странице Java Википедии (то есть сохранённой нами версии).

Повторное индексирование той же страницы приведёт к замене старого результата новым.

Одним из советов по переводу структуры данных из Java в Redis является помнить, что каждый объект в базе данных Redis идентифицируется уникальным ключом, который является строкой. Если в одной базе данных есть два объекта, может потребоваться добавить префикс к ключу, чтобы различать их. Например, в нашем решении у нас есть два типа объектов:

  • Мы определяем URLSet как набор Redis, содержащий URL, где URL содержит заданный поисковый термин. Каждый ключ URLSet начинается с "URLSet:", поэтому для получения набора, содержащего слово the, мы используем ключ "URLSet:the" для доступа к этому набору.
  • Мы определяем TermCounter как хеш-таблицу Redis, которая отображает каждое слово, появляющееся на странице, на его количество появлений. Каждый ключ TermCounter начинается с "TermCounter:", за которым следует URL страницы, которую мы ищем.

В моей реализации каждый поисковый термин имеет свой собственный URLSet, и каждая проиндексированная страница имеет свой собственный TermCounter. Я предоставляю два вспомогательных метода, urlSetKey и termCounterKey, для сборки этих ключей.

14.6 Дополнительные советы (если они вам нужны)

К этому моменту у вас есть вся информация, необходимая для завершения упражнения, так что вы можете начать, если готовы. Однако у меня есть несколько советов, которые вы, возможно, захотите прочитать:

  • Для этого упражнения я предоставляю меньше инструкций, чем для предыдущих упражнений. Вы должны принять некоторые дизайнерские решения; в частности, вам придётся выяснить, как разбить проблему на части, которые можно протестировать за один раз, а затем объединить эти части в полное решение. Если вы попытаетесь написать весь проект сразу, не тестируя более мелкие части, отладка может занять много времени.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-think-dast-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-think-dast-zh.git
oschina-mirror
wizardforcel-think-dast-zh
wizardforcel-think-dast-zh
master