2016-08-13 8 views
1

Пожалуйста, посмотрите на этот Скотти приложение (это взято непосредственно из this old answer from 2014):соединения с базой данных Haskell

import Web.Scotty 
import Database.MongoDB 
import qualified Data.Text.Lazy as T 
import Control.Monad.IO.Class 

runQuery :: Pipe -> Query -> IO [Document] 
runQuery pipe query = access pipe master "nutrition" (find query >>= rest) 

main = do 
    pipe <- connect $ host "127.0.0.1" 
    scotty 3000 $ do 
    get "/" $ do 
     res <- liftIO $ runQuery pipe (select [] "stock_foods") 
     text $ T.pack $ show res 

Вы видите, как подключение базы данных (pipe) создается только один раз, когда запуски веб-приложений. Впоследствии тысячи, если не миллионы посетителей, будут одновременно бить по маршруту «/» и читать из базы данных с использованием того же соединения (pipe).

У меня есть вопросы о том, как правильно использовать Database.MongoDB:

  1. Является ли это правильный способ установки вещи? В отличие от создания соединения с базой данных для каждого посещения «/». В этом последнем случае мы могли бы иметь миллионы соединений одновременно. Это обескураживает? Каковы преимущества и недостатки такого подхода?
  2. В приведенном выше приложении, что произойдет, если соединение по какой-либо причине потеряно по какой-либо причине и должно быть создано снова? Как бы вы оправились от этого?
  3. Как насчет аутентификации с помощью auth function? Должна ли функция auth вызываться один раз после создания pipe или ее следует называть при каждом попадании в «/»?
  4. Некоторые говорят, что я должен использовать бассейн (Data.Pool). Похоже, что это только ограничит количество посетителей, использующих одно и то же соединение с базой данных одновременно. Но зачем мне это делать? Разве соединение MongoDB не имеет встроенной поддержки одновременного использования?
+0

Этот комментарий к конструкции привода API может помочь: https://github.com/mongodb-haskell/mongodb/blob/master/doc/Article1.md#pipelining – ErikR

ответ

2
  1. Даже если вы создаете соединение на одного клиента, вы не сможете создать слишком много из них. Вы попадете в ulimit. Как только вы нажмете на это ulimit, клиент, попавший в этот ulimit, получит ошибку времени выполнения. Причина, по которой это не имеет смысла, заключается в том, что сервер mongodb будет тратить слишком много времени на опрос всех этих соединений, и у него будет только столько значимых рабочих, сколько у многих процессоров вашего сервера db. Одно соединение - неплохая идея, потому что mongodb предназначен для отправки нескольких запросов и ожидания ответов. Таким образом, он будет использовать столько ресурсов, сколько ваш mongodb может иметь только одно ограничение - у вас есть только одна трубка для записи, и если она случайно закрывается, вам нужно будет заново создать этот канал самостоятельно. Итак, имеет смысл иметь пул соединений. Он не должен быть большим. У меня было приложение, которое проверяет подлинность пользователей и дает им токены. С 2500 одновременных пользователей в секунду он имел только 3-4 одновременных подключения к базе данных.

Вот бассейн преимущества подключения дает:

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

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

    1. Если соединение с базой данных закрыто, тогда: прослушиватель mongodb в этом соединении завершит печать сообщения об ошибке на вашем терминале, ваше приложение получит ошибку ввода-вывода. Чтобы справиться с этой ошибкой, вам нужно будет создать другое соединение и повторите попытку. Когда дело доходит до обработки этой ситуации, вы понимаете, что проще использовать пул db. Потому что в конечном итоге вы решите, что это очень похоже на пул соединений.

    2. Я делаю auth один раз как часть открытия соединения. Если вам нужно будет авторизовать другого пользователя, вы всегда сможете это сделать.

    3. Да, mongodb обрабатывает одновременное использование, но, как я уже сказал, он дает только одну трубу для записи, и вскоре она становится шеей бутылки. Если вы создаете по крайней мере столько соединений, сколько ваш сервер mongodb может позволить потокам для их обработки (количество CPU), то они будут идти на полной скорости.

Если я что-то пропустил, не стесняйтесь обратиться за разъяснениями. Спасибо за ваш вопрос.

+0

Спасибо, это полезно. У меня есть вопрос о 2. Документ для 'withResource' говорит:« Если действие выдает исключение любого типа, ресурс уничтожается и не возвращается в пул ». Означает ли это, что я должен выбросить исключение при получении IO-ошибки из библиотеки MongoDB? Похоже, что это разрушит ресурс пула, убедившись, что соединение не будет повторно использовано, и новое соединение будет получено в следующий раз. – haskellHQ

+1

Вы правы. Если вы используете withResource, вам просто нужно сделать исключение в конце, чтобы убедиться, что ресурс не возвращается в пул. Бассейн предназначен для отказа от сломанного соединения. И единственный способ определить это - получить исключение из действия, использующего это соединение. Пул не проверяет работоспособность соединения. –

1

Что вы действительно хотите - это пул соединений с базой данных. Взгляните на код от this other answer.

Вместо auth вы можете использовать withMongoDBPool, если ваш сервер MongoDB находится в защищенном режиме.

+0

Привет, спасибо. Но я действительно не думаю, что это отвечает на мои 3 вопроса. Фактически, это заставило меня добавить новый 4-й вопрос в микс :) ​​ – haskellHQ

+0

Здесь вы должны задать только один вопрос на вопрос. Тем не менее, я ответил на вопросы 1 и 2. Я обновлю ответ на 3. –

+0

См. Это для политики по нескольким вопросам. Http://meta.stackexchange.com/questions/39223/one-post-with- multiple-questions-or-multiple-posts –

1

Является ли это правильным способом настройки? В отличие от создания соединения с базой данных для каждого посещения «/». В этом последнем случае мы могли бы иметь миллионы соединений одновременно. Это обескураживает? Каковы преимущества и недостатки такого подхода?

Вы не хотите открывать одно соединение, а затем использовать его. HTTP-сервер, который вы используете, который лежит в основе Scotty, называется Warp. У Warp есть multi-core, multi-green-thread design. Вы разрешили для совместного использования одинакового соединения по всем темам, так как Database.MongoDB прямо говорит, что соединения потокобезопасны, но произойдет то, что когда один поток заблокирован в ожидании ответа (the MongoDB protocol follows a simple request-response design) все темы в вашей сети служба блокирует. Это несчастливо.

Мы можем вместо этого создать соединение на каждый запрос. Это тривиально решает проблему блокировки одного потока, но приводит к собственной доле проблем. Накладные расходы на настройку TCP-соединения, хотя и не существенные, также не равны нулю. Напомним, что каждый раз, когда мы хотим открыть или закрыть сокет, мы должны перейти от пользователя к ядру, дождаться, когда ядро ​​обновит его внутренние структуры данных, а затем вернется (контекстный переключатель). Мы также должны иметь дело с рукопожатием TCP и прощанием. Мы также под высокой нагрузкой закончили файловые дескрипторы или память.

Было бы неплохо, если бы у нас было решение где-то посередине.Решение должно быть

  • поточно-
  • Давайте Макс переплете число соединений, поэтому мы не исчерпывают ограниченные ресурсы операционной системы
  • Быстрый
  • Делиться между потоками в рамках нормальная нагрузка
  • Создайте новые соединения по мере увеличения нагрузки
  • Позвольте нам очистить ресурсы (например, закрыть ручку), поскольку соединения удаляются при уменьшенной нагрузке
  • Надеюсь, уже написана и боевые испытания других систем производства

Именно это именно проблема, что resource-pool тали.

Некоторые говорят, что я должен использовать пул (Data.Pool). Похоже, что это только ограничит количество посетителей, использующих одно и то же соединение с базой данных одновременно. Но зачем мне это делать? Разве соединение MongoDB не имеет встроенной поддержки одновременного использования?

Непонятно, что вы подразумеваете под одновременным использованием. Есть одна интерпретация, на которую я могу догадаться: вы имеете в виду что-то вроде HTTP/2, который имеет конвейерную обработку, встроенную в протокол.

standard picture of pipelining http://research.worksap.com/wp-content/uploads/2015/08/pipeline.png

Выше мы видим клиент делает несколько запросов на сервер, не дожидаясь ответа, а затем клиент может получить ответы обратно в некотором порядке. (Время течет сверху вниз). У этого MongoDB нет. Это довольно сложный дизайн протокола, который не намного лучше, чем просто попросить ваших клиентов использовать пулы соединений. И MongoDB здесь не одинок: простой дизайн запроса и ответа - это то, на что рассчитывали Postgres, MySQL, SQL Server и большинство других баз данных.

И: правда, пул соединений ограничивает загрузку, которую вы можете использовать в качестве веб-службы, прежде чем все потоки будут заблокированы, и ваш пользователь просто увидит панель загрузки. Но эта проблема будет существовать в любом из трех сценариев (пул соединений, одно совместное соединение, одно соединение на запрос)! Компьютер имеет конечные ресурсы, и в какой-то момент что-то рухнет при достаточной нагрузке. Преимущество пула соединений заключается в том, что он масштабируется грациозно вплоть до момента, когда он не может. Правильное решение для обработки большего трафика - увеличить количество компьютеров; Из-за этой проблемы мы не должны избегать пула.

В приведенном выше приложении, что происходит, если соединение по какой-либо причине потеряно по какой-либо причине и должно быть создано снова? Как бы вы оправились от этого?

Я считаю, что такие виды чего-то не входят в сферу переполнения стека и не имеют лучшего ответа, чем «попробуйте и посмотрите». Buuuuuuut, учитывая, что сервер завершает соединение, я могу взять удар в том, что может случиться: предположив, что Warp развивает зеленый поток для каждого запроса (что, я думаю, это так), каждый поток будет испытывать непроверенный IOException, когда он пытается записать в закрытое TCP-соединение. Warp поймал бы это исключение и подал бы его как HTTP 500, мы надеемся написать что-то полезное для журналов. Предполагая, что у вас есть модель с одним соединением, вы можете сделать что-то умное (но высоко в строках кода), где вы «перезагрузите» свою функцию main и настроите второе соединение.Что-то, что я делаю для проектов хобби: если что-то странное произойдет, например, отброшенное соединение, я попрошу мой процесс диспетчера (например, systemd) посмотреть журналы и перезапустить веб-службу. Хотя это явно не идеальное решение для производства, веб-сайт money-makin, он работает достаточно хорошо для небольших приложений.

Как насчет аутентификации с помощью функции auth? Должна ли функция auth вызываться только один раз после создания трубы или она должна быть вызвана при каждом ударе на «/»?

Его следует вызывать один раз после создания соединения. Аутентификация MongoDB - для каждого соединения. Вы можете увидеть an example here of how the db.auth() command mutates the MongoDB server's data structures corresponding to the current client connection.

+0

Два последующих вопроса: 1. Все ли вы сказали, что вы держите за фреймворк Snap? (Просто интересно, если Snap не использует Warp, а что-то еще.) 2. Что касается ошибки соединения, вы в основном говорите, что я не должен беспокоиться об этом, потому что, если ошибка соединения происходит в одном экземпляре пула, этот экземпляр пула в любом случае скоро исчезнут, и скоро будет создан новый с новым соединением? Это идея? – haskellHQ

+0

1. Snap использует warp по умолчанию Я думаю // 2. Вы должны волноваться: производственная система должна, по крайней мере, регистрировать ошибки базы данных и контролировать их. Возможно, база данных повреждена или отсутствует на диске или в вашей сети есть раздел - все сценарии нового соединения не будут исправлены. – hao

+0

Эй, еще один вопрос, если у вас есть время. Документ для 'withResource' говорит:« Если действие вызывает исключение любого типа, ресурс уничтожается и не возвращается в пул ». В свете этого, что вы думаете о следующей идее: когда операция базы данных выходит из строя, я регистрирую сбой в файле и вручную выдаю исключение. Это позволит убедиться, что ресурс пула будет уничтожен. – haskellHQ