Где / Как вписать Solr в приложение ASP.net MVC (используя nHibernate / Repository Pattern) - PullRequest
11 голосов
/ 09 сентября 2010

В настоящее время я нахожусь в середине довольно крупного приложения, основанного на вопросах / ответах (например, stackoverflow / answerbag.com). Мы используем SQL (Azure) и nHibernate для доступа к данным и MVC для приложения пользовательского интерфейса.

Пока что схема примерно совпадает с db stackoverflow в том смысле, что у нас есть одна таблица Post (содержит оба вопроса / ответа)

Вероятно, будетиспользовать что-то вроде следующего интерфейса репозитория:

public interface IPostRepository
{
    void PutPost(Post post);
    void PutPosts(IEnumerable<Post> posts);

    void ChangePostStatus(string postID, PostStatus status);

    void DeleteArtefact(string postId, string artefactKey);
    void AddArtefact(string postId, string artefactKey);

    void AddTag(string postId, string tagValue);
    void RemoveTag(string postId, string tagValue);

    void MarkPostAsAccepted(string id);
    void UnmarkPostAsAccepted(string id);

    IQueryable<Post> FindAll();
    IQueryable<Post> FindPostsByStatus(PostStatus postStatus);
    IQueryable<Post> FindPostsByPostType(PostType postType);
    IQueryable<Post> FindPostsByStatusAndPostType(PostStatus postStatus, PostType postType);
    IQueryable<Post> FindPostsByNumberOfReplies(int numberOfReplies);
    IQueryable<Post> FindPostsByTag(string tag);
}

Мой вопрос: где / как бы я поместил solr в это для лучшего запроса этих «сообщений» (я буду использовать solrnet дляфактическое взаимодействие с Solr)

В идеале я бы использовал базу данных SQL как просто постоянное хранилище. Большая часть вышеупомянутых операций IQueryable переместится в какой-то класс SolrFinder (или что-то в этом роде)

Свойство Body в настоящее время вызывает проблемы - оно довольно большое и замедляет запросы на sql.

My maпроблема, например, в том, что если кто-то «обновляет» сообщение - например, добавляет новый тег, то весь этот пост будет нуждаться в повторной индексации.Очевидно, что для этого потребуется запрос, подобный следующему:

"SELECT * FROM POST WHERE ID = xyz"

Это, конечно, будет очень медленно.У Solrnet есть объект nHibernate, но я верю, что это будет тот же результат, что и выше?

Я подумал, как обойти это, и я хотел бы, чтобы ваши взгляды:

  • Добавление идентификатора в очередь (amazon sqs или что-то в этом роде - мне нравится простота использования)
  • Наличие службы (или набора служб) где-нибудь, выполняющего вышеупомянутый запрос, создаст документ и повторно-добавить к решению.

Еще одна проблема, с которой я столкнулся с моим дизайном: Откуда должны вызываться методы повторной индексации?Контроллер MVC?или у меня должен быть класс типа «PostService», который оборачивает экземпляр IPostRepository?

Любые указатели на этот получаются очень хорошо!

Ответы [ 3 ]

27 голосов
/ 09 сентября 2010

На сайте электронной коммерции, на котором я работаю, мы используем Solr для быстрого поиска и поиска в каталоге товаров.(В терминах, не связанных с Solr, это означает навигационные ссылки в стиле «ATI Cards (34), NVIDIA (23), Intel (5)»), которые можно использовать для перехода по каталогам продуктов на таких сайтах, как Zappos, Amazon,NewEgg и Lowe's.)

Это потому, что Solr разработан, чтобы делать такие вещи быстро и хорошо, и попытки делать такие вещи эффективно в традиционной реляционной базе данных, ну, в общем, не произойдет, если только вы не хотите начинать добавлять и удалять индексы на лету и переходить на полную EAV, что просто кашель Magento кашель глупо.Таким образом, наша база данных SQL Server является «авторитетным» хранилищем данных, а индексы Solr являются «проекциями» этих данных только для чтения.

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

  • Как устаревшие слишком устарели?
  • Когда я оцениваю скорость или запрашиваю функции по устареванию?

Например, у меня есть то, что я называю «Рабочий», - это служба Windows, которая использует Quartz.NET для периодического выполнения реализаций C # IJob.Каждые 3 часа одно из выполняемых заданий - это RefreshSolrIndexesJob, и все, что выполняет это задание, - пинг HttpWebRequest до http://solr.example.com/dataimport?command=full-import.Это потому, что мы используем встроенный в Solr DataImportHandler для фактического всасывания данных из базы данных SQL;задание просто периодически «трогает» этот URL, чтобы обеспечить синхронизацию.Поскольку DataImportHandler периодически фиксирует изменения, все это эффективно работает в фоновом режиме, прозрачно для пользователей веб-сайта.

Это означает, что информация в каталоге продуктов может устареть до 3 часов.Пользователь может щелкнуть ссылку «Medium In Stock (3)» на странице каталога (так как этот вид граненых данных генерируется путем запроса SOLR), но затем увидеть на странице сведений о продукте, что на складе нет носителей (так как на этомстраница, информация о количестве - это одна из немногих вещей, которые не кэшируются и запрашиваются непосредственно в базе данных).Это раздражает, но, как правило, редко встречается в нашем конкретном сценарии (мы - достаточно малый бизнес, а не с большим трафиком), и в любом случае это будет исправлено через 3 часа, когда мы снова перестроим весь индекс с нуляТаким образом, мы приняли это как разумный компромисс.

Если вы можете принять эту степень «устаревания», то этот фоновый рабочий процесс - хороший путь.Вы можете использовать подход «перестраивать все каждые несколько часов», или ваш репозиторий может вставить идентификатор в таблицу, скажем, dbo.IdentitiesOfStuffThatNeedsUpdatingInSolr, и тогда фоновый процесс может периодически сканировать эту таблицу и обновлять только те документы в Solr.если периодически восстанавливать весь индекс с нуля нецелесообразно, учитывая размер или сложность вашего набора данных.

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

class MyRepository
{
    void Save(Post post)
    {
         // the following method runs on the current thread
         SaveThePostInTheSqlDatabaseSynchronously(post);

         // the following method spawns a new thread, task,
         // queueuserworkitem, whatevever floats our boat this week,
         // and so returns immediately
         UpdateTheDocumentInTheSolrIndexAsynchronously(post);
    }
}

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

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

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

Но при запросе данных я хорошо знаю, использую ли я их для целей каталога (меня интересуют скорость и запросы ) или для отображения в таблица на административном административном приложении (меня волнует валюта ). Для запросов на веб-сайте у меня есть интерфейс под названием ICatalogSearchQuery. Он имеет метод Search(), который принимает SearchRequest, где я определяю некоторые параметры - выбранные фасеты, условия поиска, номер страницы, количество элементов на странице и т. Д. - и возвращает SearchResult - оставшиеся фасеты , количество результатов, результаты на этой странице и т. д. Довольно скучные вещи.

Интересно, что реализация этого ICatalogSearchQuery использует список ICatalogSearchStrategy внизу. Стратегия по умолчанию, SolrCatalogSearchStrategy, напрямую обращается к SOLR через простой старомодный HttpWebRequest и анализирует XML в HttpWebResponse (который намного проще в использовании, IMHO, чем некоторые клиентские библиотеки SOLR, хотя они возможно, стало лучше, так как я последний раз смотрел на них более года назад). Если эта стратегия по какой-то причине вызывает исключение или вызывает рвоту, то DatabaseCatalogSearchStrategy напрямую попадает в базу данных SQL - хотя она игнорирует некоторые параметры SearchRequest, такие как фасетирование или расширенный текстовый поиск, поскольку это неэффективно, и это единственная причина, по которой мы используем Solr. Идея состоит в том, что обычно SOLR отвечает на мои поисковые запросы быстро и полнофункционально, но если что-то взорвется и SOLR выйдет из строя, то страницы каталога сайта все еще могут функционировать в «режиме ограниченной функциональности», попав в базу данных с помощью ограниченный набор функций напрямую. (Поскольку в коде мы четко указали, что это поиск, эта стратегия может позволить себе игнорировать некоторые параметры поиска, не беспокоясь о слишком серьезном влиянии на клиентов.)

Вывод ключа: Важно то, что было принято решение выполнить запрос к возможно устаревшему хранилищу данных относительно авторитетного хранилища данных явно - если я хочу быстрые, возможно устаревшие данные с расширенными функциями поиска, я использую ICatalogSearchQuery. Если я хочу медленные, свежие данные с возможностью вставки / обновления / удаления, я использую именованные запросы NHibernate (или хранилище в вашем случае). И если я внесу изменения в базу данных SQL, я знаю, что внешняя служба Worker в конечном итоге обновит Solr, в результате чего все станет согласованно. (И если что-то действительно важно, я мог бы транслировать событие или напрямую пинговать хранилище SOLR, предлагая обновить его, возможно, в фоновом потоке, если потребуется).

Надеюсь, что это даст вам некоторое представление.

8 голосов
/ 15 сентября 2010

Мы используем solr для запроса большой базы данных продуктов.Около 1 миллиона продуктов и 30 магазинов.

Мы использовали триггеры в таблицах продуктов и таблицах запасов на нашем сервере Sql.

Каждый раз при изменении строки она помечает продуктбыть переиндексирован.И у нас есть служба Windows, которая захватывает эти продукты и публикует их в Solr каждые 10 секунд.(С лимитом в 100 товаров на партию).

Это супер эффективная информация о запасе в реальном времени.

2 голосов
/ 09 сентября 2010

Если у вас большое текстовое поле (ваше поле 'body'), тогда да, переиндексируйте в фоновом режиме. Решения, которые вы упомянули (очередь или периодический фоновый сервис), подойдут.

Контроллеры MVC должны не замечать этого процесса.

Я заметил, что у вас есть IQueryables в вашем интерфейсе репозитория. SolrNet в настоящее время не имеет поставщика LINQ . В любом случае, если эти операции - это все, что вы собираетесь делать с Solr (то есть без фасетирования), вы можете рассмотреть возможность использования Lucene.Net, который действительно имеет поставщика LINQ.

...